Help us understand the problem. What is going on with this article?

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

こんにちは。
この記事はクソアプリ Advent Calendar 2019 の8日目の記事です。

どうしても起きたい

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

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

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

アラーム

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

スクリーンショット 2019-12-08 1.56.53.png

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

SwiftUI

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))
    }
}

↑のコードが↓になる
スクリーンショット 2019-12-07 23.27.06.png

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

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

ローカルプッシュ

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

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を用いて加速度を取得することで、「どの程度スマホを振っているか」を取得できます。

let motionManager = CMMotionManager()

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

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

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の項目を記述する必要があります。要するに「あなたが二度寝しないように、運動するところを監視します。」と明記します。

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

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

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

1P 149P
Screen Shot 2019-12-08 at 1.25.36.png Screen Shot 2019-12-08 at 1.25.44.png

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

まとめ

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

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

2019年12月10日追記

おはようございます。ソースコードはこちらです。おやすみなさい。
https://github.com/hal-cha-n/ShakeUp

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away