絶対に起きられるアラームアプリを作る


こんにちは。
この記事はクソアプリ [Advent Calendar 2019](https://qiita.com/advent-calendar/2019/kuso-app) の8日目の記事です。

# どうしても起きたい

この地球という星で活動していると、何度か **絶対に起きないといけない日** という日が来ます。
これが実に厄介でして、私のようなスロースターターの人間には非常に困るわけです。

地球には目覚まし時計というテクノロジーが存在し、休眠状態の人間を設定した時刻に起床させることが出来ます。
しかしこれには致命的な不具合が存在し、一旦起床させた後に再び休眠状態へ陥る、いわゆる **二度寝** が発生してしまうわけです。

今回の目的は **二度寝をさせないアラームアプリ** を作成します。


# アラーム
今回はiOSアプリとして制作します。やはりアラームは携帯で設定する人が多いですからね。
せっかくなので噂のSwiftUIを使いましょう。
こんな感じで、UIをSwiftのコードで記述できます。

<img width="1392" alt="スクリーンショット 2019-12-08 1.56.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297541/2e715e97-135a-cdb2-9730-8c773d0856e3.png">


以前よりもシンプルになりました。

## SwiftUI
```swift
struct ShakeTimeView: View {
    @ObservedObject var shakeTime: ShakeTime
    
    var body: some View {
        Toggle(isOn: $shakeTime.isOn) {
            Text(String(format: "%02d:%02d", shakeTime.hour, shakeTime.minute))
                .bold()
                .font(.largeTitle)
        }
        .padding(.horizontal)
        .frame(height: 80.0)
    }
}

struct ShakeTimeView_Previews: PreviewProvider {
    static var previews: some View {
        let shakeTime = ShakeTime()
        shakeTime.hour = 22
        return ShakeTimeView(shakeTime: shakeTime)
            .previewLayout(.fixed(width: 400, height: 80))
    }
}
```
↑のコードが↓になる
<img width="463" alt="スクリーンショット 2019-12-07 23.27.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297541/ef1de51a-9610-9f8d-807f-3cd1a3fcd6c4.png">

SwiftUIになってからApple標準のデータバインディングができました。
`@ObservedObject`がついたプロパティはViewとバインドすることができます。今回のケースで言うとToggleボタンの表示状態をShakeTimeに反映させることができます。つまりisOnが勝手に切り替わります。

```swift
class ShakeTime: ObservableObject, Identifiable {
    var hour = 0
    var minute = 0
    var isOn = false {
        didSet {
            if isOn {
                LocalPushCenter.sendLocalPush(hour: hour, minute: minute)
            }
        }
    }
}
```

isOnがtrueになった時にアラームを登録しましょう。

並べるとこのような画面に

![Screen Shot 2019-12-08 at 1.25.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297541/c46cf681-2fb5-e514-fc11-03aceb1d5ee9.png)


## ローカルプッシュ
iPhone使っていると時々上から「ピロリン！」と出てきますよね。あれはプッシュ通知と呼ばれています。通常はプッシュ通知用のサーバーを立てて、諸々面倒なことを行う必要がありますが、簡単な通知であれば端末内で完結することができます。

```swift
class LocalPushCenter {
    static func sendLocalPush(hour: Int, minute: Int) {
        
        let timeString = String(format: "%02d:%02d", hour, minute)
        let content = UNMutableNotificationContent()
        content.title = "起こします"
        content.subtitle = "\(timeString)のアラーム"
        content.body = "アラームの時間ですよ〜"
        content.sound = UNNotificationSound.default // UNNotificationSound(named:) で任意の音を設定可

        let component = DateComponents(hour: hour, minute: minute, second: 0, nanosecond: 0)
        let trigger = UNCalendarNotificationTrigger(dateMatching: component,
                                                    repeats: false)

        let request = UNNotificationRequest(identifier: "TIMER \(timeString)",
                                            content: content,
                                            trigger: trigger)
        
        let center = UNUserNotificationCenter.current()
        center.delegate = (UIApplication.shared.delegate as! AppDelegate)
        center.add(request) { (error) in
            if let error = error {
                print(error.localizedDescription)
            }
        }
    }
}
```
実はプッシュの音は変更することができます。
やかましい音を設定して叩き起こしてあげましょう。


# 二度寝防止

かくして、普通のアラームアプリが出来ました。
しかし、これでは快適な睡眠を邪魔するだけのアプリでしょう。
次は二度寝を防止していきましょう。

二度寝を防止するのに有効な方法は何でしょうか。
要するに寝ながら行うことが不可能なことを、行い続ければ良いのです。

**運動**させましょう。

CoreMotionを使用します。


## CoreMotion

CoreMotionを用いて加速度を取得することで、「どの程度スマホを振っているか」を取得できます。

```swift
let motionManager = CMMotionManager()
```

0.1秒間隔で端末の加速度を取得します。
今回はその加速度の類型を変数に記録することにしましょう。

今回は「加速度が1000溜まるまで」としましたが、これは私の検証端末の iPhone 6s で30秒程度かかりました。良い運動です。

```swift
guard motionManager.isDeviceMotionAvailable else { return }
motionManager.deviceMotionUpdateInterval = 0.1

motionManager.startDeviceMotionUpdates(to: OperationQueue.current!, withHandler: {(motion:CMDeviceMotion?, error:Error?) in
    self.power += abs(motion?.userAcceleration.y ?? 0)
    
    if self.power >= 1000 {
        self.isAwaked = true
    }
})
```

ちなみにCoreMotionを使用するには Info.plist に`NSMotionUsageDescription`の項目を記述する必要があります。要するに「あなたが二度寝しないように、運動するところを監視します。」と明記します。

```xml:Info.plist
<key>NSMotionUsageDescription</key>
<string>あなたが二度寝しないように、運動するところを監視します。</string>
```

この画面が表示されている間は永遠にアラームがなります。
よく考えたらQiitaの記事に音の出るものって伝わらないですね。

「起きた！」ボタンを押すことでアラーム一覧へ遷移します。

| 1P | 149P|
|---|---|
|![Screen Shot 2019-12-08 at 1.25.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297541/441ffd05-4ccf-ba97-5e18-1b39cda8d6bf.png)|![Screen Shot 2019-12-08 at 1.25.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297541/6c8a016d-1c7c-7df9-29c8-b90e76a6801a.png)
|

今回は加速度の類型値としましたが、一定時間振り続けるなどのルールにすることでさらなる効果を期待できそうです。

# まとめ
実はこれには色々と抜け道があります。iPhoneの音量をミュートにする。push通知に応答しない。途中でバックグラウンドへ送る。電源を切るなど。対策できるところもあれば、権限的に難しい点も存在します。
どうか寝起きの私がそのような抜け道を見つけ出さないことを祈ります。

右手が痛いのでもう寝ます。明日はきっと時間通り起きられるでしょう。


#### 2019年12月10日追記
おはようございます。ソースコードはこちらです。おやすみなさい。
https://github.com/hal-cha-n/ShakeUp