---
title: "The AlarmKit Journey: Building Snooze If You Can"
description: "The story behind Snooze If You Can - an iOS alarm app built in Swift and SwiftUI, from concept to open-source release."
date: 2026-01-04T00:00:00.000Z
category: iOS
readingTime: "3 min read"
---


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:**

1. **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.

2. **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.

3. **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.

```swift
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.

```swift
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:

```swift
protocol AlarmChallenge {
    var type: ChallengeType { get }
    var difficulty: ChallengeDifficulty { get }
    func generateChallenge() -> ChallengeContent
    func validateAnswer(_ answer: String) -> Bool
}
```

Current challenge types:

1. **Math Problems** - Addition, subtraction, multiplication at configurable difficulty. Easy: single digits. Hard: three-digit multiplication.
2. **Shake Challenge** - Accelerometer-based. Shake the phone N times (configurable from 10 to 50).
3. **Barcode Scan** - Scan a specific barcode you've registered. Forces you to physically move to the location.
4. **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](/blog/open-source-licensing-lessons)) 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](/blog/building-apps-with-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.*
