UILocalNotificationでローカル通知を実装する【Swift】

ローカル通知実装できたので書いていきます。

今回実装したローカル通知は、アプリを閉じた(orバックグラウンド)状態の日時から曜日を指定して、その日の指定した時間に通知を送るというものです。

ちょっとややこしいですが、アプリを閉じた時が3月18日金曜日12:00(今日とします)だった場合に、次の火曜日の12:00に通知を送ろう!ということです。

この場合だと3/18(金)にアプリを閉じ、その後3/22(火)の12時に通知がくるよ!ということです。

説明が下手で申し訳ないです。。。

 

今回はこちらの本を参考に

UIKit&Swiftプログラミング 優れたiPhoneアプリ開発のためのUI実装ガイド

ローカル通知の基本

まずは適当にローカル通知をAppDelegateに実装していきます。

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        if #available(iOS 8.0, *) {
            // iOS8以上
            //forTypesは.Alertと.Soundと.Badgeがあります。
            let notiSettings = UIUserNotificationSettings(forTypes:[.Alert,.Sound,.Badge], categories:nil)
            application.registerUserNotificationSettings(notiSettings)
            application.registerForRemoteNotifications()
            
        } else{
            // iOS7以前
            application.registerForRemoteNotificationTypes( [.Alert,.Sound,.Badge] )
        }
        return true
    }

まずはユーザーに通知許可をもらうためのコードをかきます。

iOS8以上とiOS7以前で書き方が少しだけ違うので場合分けしておきます。

とりあえずはアラートとサウンドとバッジの全部盛りで

 

次にアプリがバックグラウンドにいったときに通知の設定をします

//アプリがバックグラウンドに行ったときによばれるやつ
func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
      //ローカル通知
      let notification = UILocalNotification()
      //ロック中にスライドで〜〜のところの文字
      notification.alertAction = "アプリを開く"
      //通知の本文
      notification.alertBody = "ごはんたべよう!"
      //通知される時間(とりあえず10秒後に設定)
      notification.fireDate = NSDate(timeIntervalSinceNow:10)
      //通知音
      notification.soundName = UILocalNotificationDefaultSoundName
      //アインコンバッジの数字
      notification.applicationIconBadgeNumber = 1
      //通知を識別するID
      notification.userInfo = ["notifyID":"gohan"]
      //通知をスケジューリング
      application.scheduleLocalNotification(notification)

    }

 

とりあえずこれだけで通知が来るようになります!意外と簡単!

ただしこれは10秒後に通知が来るというだけなのであまり実用性はないです。。。

ここを24時間後〜とかにすればこれだけでも活用できそう

 

アプリを開いたときにバッジを0に戻す

上記のままだと通知がきて、アプリを開いてもバッジは消えません。

今回はとりあえず、通知から起動したときとアプリを開いたときの両方の場合でバッジを0に戻します。

 

まずは通知からアプリに戻ってきたとき(アプリがバックグラウンドにあるとき)

func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
        //アプリがactive時に通知を発生させた時にも呼ばれる
        if application.applicationState != .Active{
            //バッジを0にする
            application.applicationIconBadgeNumber = 0
            //通知領域から削除する
            application.cancelLocalNotification(notification)
        }else{
      //active時に通知が来たときはそのままバッジを0に戻す
            if application.applicationIconBadgeNumber != 0{
                application.applicationIconBadgeNumber = 0
                application.cancelLocalNotification(notification)
            }
        }
    }

 

active時に通知が来たときもバッジが1に変わっていたので、とりあえずそこも0に戻すようにしてあります。

 

アプリがバックグラウンドになく、閉じられた状態から通知で復帰したとき

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        //復帰したかどうか
        if let notification = launchOptions?[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification,let userInfo = notification.userInfo{
            application.applicationIconBadgeNumber = 0
            application.cancelLocalNotification(notification)
        }
       ////////以下略(通知許可の)
       return true
}

 

通知からアプリの戻ってきた場合に何か処理を行ったり、アラートを出す場合には上記の2箇所に処理をかきます。

今回はアプリが開いた時点なのであまり復帰したかどうかは関係ない気もしますが・・・。

 

アプリを開いたときにバッジを0にする(バックグラウンドからフォアグラウンドへ)

    //アプリがバックグラウンドにある状態からフォアグラウンドになったとき
    func applicationWillEnterForeground(application: UIApplication) {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
        print("バックグラウンドからフォアグラウウンド")
        if application.applicationIconBadgeNumber != 0{
            application.applicationIconBadgeNumber = 0
            print("application\(application.applicationIconBadgeNumber)")
        }
    }

 

バッジが0じゃなかったら0にする〜って書いています。

あとは

   func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        //復帰したかどうか
        if let notification = launchOptions?[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification,let userInfo = notification.userInfo{
            application.applicationIconBadgeNumber = 0
            application.cancelLocalNotification(notification)
        }
    //復帰に関係なくバッジが0じゃなければ0にする
        if application.applicationIconBadgeNumber != 0{
            application.applicationIconBadgeNumber = 0
        }
       ////////以下略(通知許可の)
       return true
}

 

こんな感じでちょっと強引な気もしますが。

バックグラウンドにアプリがある状態とない状態で書く場所が違うので注意です。

 

新しく通知を追加するときに古い通知を削除する

このままだとアプリを何度も開いたり閉じたりすると、何回も何回も通知が来てしまうので、新しく通知を作成する場合はそれまでの通知を削除するようにします。

//アプリがバックグラウンドに行ったときによばれるやつ
func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
      //古い通知があれば削除する
   application.cancelAllLocalNotifications()
      //ローカル通知
      let notification = UILocalNotification()
      //ロック中にスライドで〜〜のところの文字
      notification.alertAction = "アプリを開く"
      //通知の本文
      notification.alertBody = "ごはんたべよう!"
      //通知される時間(とりあえず10秒後に設定)
      notification.fireDate = NSDate(timeIntervalSinceNow:10)
      //通知音
      notification.soundName = UILocalNotificationDefaultSoundName
      //アインコンバッジの数字
      notification.applicationIconBadgeNumber = 1
      //通知を識別するID
      notification.userInfo = ["notifyID":"gohan"]
      //通知をスケジューリング
      application.scheduleLocalNotification(notification)

}

cancelAllLocationNotifications()で全ての通知が削除されます。

IDを検索して指定したものだけ消すことも可能です。

 

これでとりあえず通知は完了です。

あとは通知される時間、のところをカスタマイズするだけです。

 

通知される時間を指定する(次の月曜日12:00)

現在の日時から次の月曜日12時に通知が行くようにします。

まずは適当にswiftファイルを作成して曜日をenumで数字でわかりやすくしておきます。

week.swiftを作成

enum Week :Int {
    case Sunday = 1     // 日曜日
    case Monday = 2     // 月曜日
    case Tuesday = 3    // 火曜日
    case Wednesday = 4  // 水曜日
    case Thursday = 5   // 木曜日
    case Friday = 6     // 金曜日
    case Saturday = 7   // 土曜日
}

 

そして次の月曜日12時を算出するクラスを作ります。

NextWeek.swiftを作成

import Foundation

class NextWeek {
    //次の月曜日
    class func MonDay() -> NSDate {
        //現在の日時を取得する
        let date = NSDate()
        let calender = NSCalendar.currentCalendar()
        let components = calender.components([.Year , .Month ,.Day , .Weekday], fromDate: date)
        let weekday = components.weekday  // 1が日曜日(week.swiftで設定)
        let hour = components.hour
        //通知する曜日を指定
        let fireWeekday = Week.Monday.rawValue
        let interval: NSTimeInterval
        //現在の日時から次の月曜日を算出
        if (weekday >= fireWeekday && hour >= 12) {
            interval = Double(60 * 60 * 24 * ((7 + fireWeekday) - weekday))
        } else {
            interval = Double(60 * 60 * 24 * (fireWeekday - weekday))
        }
        //通知する日時
        let nextDate = date.dateByAddingTimeInterval(interval)
        let fireDateComponents = calender.components([.Year , .Month ,.Day , .Weekday], fromDate: nextDate)
        //12時
        fireDateComponents.hour = 12
        //0分
        fireDateComponents.minute = 0
        //0秒
        fireDateComponents.second = 0
        
        return calender.dateFromComponents(fireDateComponents)!
    }
}

 

あとはローカル通知のところを

notification.fireDate = NextWeek.MonDay()

としてあげると次の月曜日に通知が行くはず。

確認は、設定アプリより一般>日付と時刻で自動設定をオフにして時刻を指定して確認してみましょう。

 

その他の曜日も必要に応じてコピペして下記のようにライン部分を変更すれば対応できます(例:火曜日)

    //次の火曜日
    class func TuesDay() -> NSDate {
        //現在の日時を取得する
        let date = NSDate()
        let calender = NSCalendar.currentCalendar()
        let components = calender.components([.Year , .Month ,.Day , .Weekday], fromDate: date)
        let weekday = components.weekday  // 1が日曜日(week.swiftで設定)
        let hour = components.hour
        //通知する曜日を指定
        let fireWeekday = Week.Tuesday.rawValue
        let interval: NSTimeInterval
        //現在の日時から次の火曜日を算出
        if (weekday >= fireWeekday && hour >= 12) {
            interval = Double(60 * 60 * 24 * ((7 + fireWeekday) - weekday))
        } else {
            interval = Double(60 * 60 * 24 * (fireWeekday - weekday))
        }
        //通知する日時
        let nextDate = date.dateByAddingTimeInterval(interval)
        let fireDateComponents = calender.components([.Year , .Month ,.Day , .Weekday], fromDate: nextDate)
        //12時
        fireDateComponents.hour = 12
        //0分
        fireDateComponents.minute = 0
        //0秒
        fireDateComponents.second = 0
        
        return calender.dateFromComponents(fireDateComponents)!
    }

 

あとは条件文で必要に応じてローカル通知を呼び出してあげればoKです。

 

曜日指定に関してはこちらの記事を参考にさせていただきました!

[swift]挫折しながら覚えるiOS開発その11 ローカルプッシュ通知(UILocalNotification)の追加

(というかむしろこちらのがわかりやすいと思うのでぜひ)

componentsの書き方がちょっと違うくらいです。

 

その他のローカル通知に関するその他のことなどは詳しくこちらの本に書かれています。データ保存のやり方とかもかいてあるのでなかなか活用させていただいてます!

UIKit&Swiftプログラミング 優れたiPhoneアプリ開発のためのUI実装ガイド

Swiftのバージョンが上がっているのちょっと書き方が変わっているところもありますが、それでも十分にお勧めできます〜〜

 

全体コードはちょっと割愛。ではでは

2018/04/30追記 Swift4で。(iOS9まで対応)
iOS10からはUILocalNotificationがdeprecatedになっちゃいましたが、iOS9に対応する場合は使うのでSwift4で書き直してみました。

実のところ、自分のアプリは全てiOS9にも対応しているのでdeprecatedになっていること自体にさっき気づいたんですけどね!!!!
iOS10からはこちら<Swift>iOS 10 User Notifications Framework実装まとめ - Qiitaを見れば良さそうです。

とりあえず今のところiOS11でもUILocalNotificationは問題なく動いています。


とりあえず現在の時刻から何日後何分後といった感じの実装で作ってみました。
この辺の知識はこの頃からあんまり変わってないですね・・・。ん〜

そろそろiOS9も切り頃かなーとも思うので、新しいのもちゃんと覚えないと。。。

Comments...

2016/12/13 11:12

参考になりました!ありがとうございます!

アバター
from.
    2016/12/13 07:12

    わわ!こちらこそありがとうございます〜

    アバター
    from.Reo(管理人)
コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS
more