I built an alarm app. Not because the world needed another alarm app, but because every alarm app I used did the same thing: ring, snooze, repeat. Snooze If You Can is different - it makes you earn your snooze.
The Idea
The concept is simple: when your alarm goes off, you can't just tap a button to snooze. You have to complete a challenge first. Solve a math problem. Shake your phone 30 times. Scan a barcode in your kitchen. By the time you've completed the challenge, you're awake.
I'd been using Android apps with similar concepts for years. When I switched to iOS, nothing matched the experience I wanted. So I built it.
Why Swift and SwiftUI
This was my first iOS application, and I chose to go fully native with Swift and SwiftUI. No React Native, no Flutter, no cross-platform compromise.
The reasoning:
-
Alarm functionality requires deep OS integration. Push notifications, background audio, scheduling wake alarms - these APIs are iOS-native. Cross-platform frameworks add a layer of abstraction that makes debugging harder.
-
SwiftUI's learning curve is worth it. Declarative UI in Swift feels natural coming from React. The mental model transfers: state drives UI, components compose into screens.
-
Performance matters for alarms. An alarm that lags or crashes doesn't wake you up. Native performance means the app launches instantly and the alarm sounds immediately.
struct ChallengeView: View {
@State private var answer = ""
let challenge: MathChallenge
var body: some View {
VStack(spacing: 24) {
Text(challenge.question)
.font(.system(size: 48, weight: .bold, design: .rounded))
TextField("Answer", text: $answer)
.keyboardType(.numberPad)
.textFieldStyle(.roundedBorder)
.font(.title)
Button("Submit") {
if answer == challenge.correctAnswer {
dismissAlarm()
}
}
.buttonStyle(.borderedProminent)
}
}
}SwiftUI's declarative approach made the challenge screens straightforward to build. Each challenge type (math, shake, barcode) is a separate view with its own state management.
The AlarmKit Architecture
Under the hood, the app is structured around what I call AlarmKit - the core scheduling and playback engine:
AlarmKit
├── AlarmScheduler (manages UNNotification scheduling)
├── AudioEngine (plays alarm tones, handles interruptions)
├── ChallengeEngine (selects and validates challenges)
└── SettingsStore (persists user preferences)
AlarmScheduler wraps UNUserNotificationCenter. Each alarm is a scheduled local notification with a custom sound. When the notification fires, the app launches into the challenge screen.
AudioEngine handles alarm audio playback, including edge cases: What happens when the phone is in silent mode? What about Do Not Disturb? What if another app is playing audio? Each case requires specific AVAudioSession configuration.
class AudioEngine {
private var audioSession: AVAudioSession
func configureForAlarm() throws {
try audioSession.setCategory(
.playback,
mode: .default,
options: [.mixWithOthers]
)
try audioSession.setActive(true)
}
}The Challenges System
Each challenge type implements a protocol:
protocol AlarmChallenge {
var type: ChallengeType { get }
var difficulty: ChallengeDifficulty { get }
func generateChallenge() -> ChallengeContent
func validateAnswer(_ answer: String) -> Bool
}Current challenge types:
- Math Problems - Addition, subtraction, multiplication at configurable difficulty. Easy: single digits. Hard: three-digit multiplication.
- Shake Challenge - Accelerometer-based. Shake the phone N times (configurable from 10 to 50).
- Barcode Scan - Scan a specific barcode you've registered. Forces you to physically move to the location.
- Memory Pattern - Repeat a sequence of colors/numbers that flashes on screen.
The protocol-based design means adding new challenge types is straightforward: implement the protocol, register the type, done. Community contributors have already proposed step-counter and photo challenges.
Going Open Source
Snooze If You Can is fully open source. The decision wasn't difficult - it's a personal utility app, not a revenue-generating product. Open-sourcing it means:
- Community contributions - better challenges, more features, bug fixes from real users
- Learning resource - the codebase demonstrates iOS patterns: SwiftUI architecture, local notifications, audio handling, sensor integration
- Trust - users can verify exactly what the app does with their permissions
The licensing decisions I've made with other projects (see Open Source Licensing: What I Learned the Hard Way) informed this choice. For a utility app, MIT licensing makes sense - maximum adoption, no restrictions.
Lessons from iOS Development
Coming from web development (TypeScript, React, Node.js), iOS development was surprisingly different:
- Xcode is... Xcode. The IDE is powerful but opinionated. Coming from VS Code, the adjustment was significant.
- Apple's review process adds friction. Web deployments are instant. iOS updates take 24-48 hours for review. This changes how you think about release cadence.
- The iOS ecosystem is smaller but deeper. Fewer packages than npm, but the quality bar is generally higher. You write more code yourself, but you understand it better.
- SwiftUI is not React. Despite surface similarities (declarative, component-based), the state management model is different.
@State,@Binding,@ObservedObject- each has specific semantics that don't map directly to React hooks.
What's Next
The next feature is charitable donations - every snooze can optionally trigger a small donation to Darüşşafaka, a Turkish educational foundation. I'm writing about the StoreKit integration and the philosophy behind it in Building Apps With a Purpose.
Want to build your first iOS app? Start with SwiftUI and a problem you personally have. The best apps come from scratching your own itch.