iOS 標準アプリの時計の様に指定時間・曜日にメッセージを通知する方法

iOS標準の時計アプリでは時間と曜日を指定して、その時間にアラームを通知する方法があります。

今回は同じような方法でメッセージを通知する方法がないかなぁ〜と思って調べました。(仕事で使う予定があるから)

やりたいことはこんな感じ

  • アプリでメッセージを通知したい曜日と時間を設定
  • アプリを終了(バックグラウンドでもOK)
  • 指定した曜日と時間になったらメッセージを通知
  • 「OK」ボタンを押下するとアプリが起動

で調べたけど、メッセージを通知するのはUIRemoteNotification、UILocalNotificationというのを使うらしい。

UIRemoteNotificationはサーバーを立ててそこからメッセージを一括送信する場合などに使うみたい。

UILocalNotificationは指定時間にメッセージを表示する方法で端末内で完結できる。

ということで、今回はUILocalNotificationを使う。

で今回最も悩んだのが、UILocalNotificationに指定曜日にメッセージを通知する方法。

repeatIntervalというプロパティがあるのですが、毎日とか毎時ならこれでOKなんだけど、指定曜日(毎週月・火・木)となると、できないみたい。(多分)

で色々と調べた結果、簡単に設定できるプロパティはないのではないかと。。。

Notification系のものは内部にカレンダー形式でローカル通知の予定を登録しているようなので、ぐるぐる回しながら自分で曜日を判断して登録するという方法を取りました。困ったときは回せば良い。

[[UIApplication sharedApplication] cancelAllLocalNotifications];
NSInteger repeartCount = 7;

for (NSInteger i = 0; i < repeartCount; i++) {
  date = [today dateByAddingTimeInterval:i * 24 * 60 * 60];
  dateComponents = [calendar components:NSWeekdayCalendarUnit fromDate:date];

  if ([selectedWeek containsObject:[NSNumber numberWithInteger:dateComponents.weekday – 1]]) {
    // Adding the local notification.
    dateString = [NSString stringWithFormat:@”%@ %@”, [dateFormat stringFromDate:date], [timeFormat stringFromDate:notificationTime]];
  }
  if ([today compare:[format dateFromString:dateString]] == NSOrderedAscending) {
    notification.fireDate = [format dateFromString:dateString];
    notification.timeZone = [NSTimeZone localTimeZone];
    notification.alertBody = [NSString stringWithFormat:@”Notify:%@”, dateString];
    notification.soundName = UILocalNotificationDefaultSoundName;
    notification.alertAction = @”Open”;
    notification.repeatCalendar
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
  }
}

1行目の「[[UIApplication sharedApplication] cancelAllLocalNotifications]」で現在登録されているメッセージ通知のカレンダーを全クリアします。これは後で何度もこの処理を実行するので重複してローカル通知が登録されないようにするためです。

3行目で繰り返しを行う日数を今日から含めて指定します。今回は今日から7日間に指定しました。最大で7日分のローカル通知の予定を登録します。UILocalNotificationは最大で60前後のカレンダーが登録できるみたいです。

5行目から指定回数分ループで回します。

6行目で日付を取得して、9行目でその日付の曜日を取得してその曜日が登録すべき曜日かを判断しています。selectedWeekには日曜日〜土曜日までを0〜6の数値で保持しているので、containsObjectで配列内の要素の存在確認を行っています。

登録すべき曜日であれば、11〜22行目の処理によってメッセージ通知の登録が行われていきます。 ちなみに15行目では登録する日付が過去日付ならば登録をスキップしています。今日が月曜日の12時でローカル通知を月曜日の6持で設定すると過去日付になるので1回目の通知が即座に表示されてしまうので。

でこの方法だと登録されているローカル通知を全部通知し終わってしまうと通知されなくなります。 そこで、UILocalNotificationのデリゲートを使って起動の度に上記の処理を実行します。 デリゲートはメッセージ通知のアクションボタンが押下されてアプリが起動される度に呼び出されるみたい。

– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  UILocalNotification *notification;
  notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
  if (notification) {
    [LocalNotificationManager addLocalNotification];
  }
  return YES;
}

でまずは、アプリが完全に終了(バックグラウンドで動作していない)している場合には、「application:didFinishLaunchingWithOptions:」から起動されます。

4〜8行目でこの起動がNotificationによるものなのか判断してもし、Notificationによるものだったら7行目でローカル通知の処理を実行しています。

– (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
  [LocalNotificationManager addLocalNotification];
}

でもう一個のデリゲートが「application:didReceiveLocalNotification:」でこちらはバックグラウンドでアプリが動作していた場合に呼ばれます。あとは先程と同様にローカル通知の処理を実行して完了です。

これでローカル通知が指定した曜日の時間に起動され、通知によってアプリが起動された場合には再度起動した日から7日間のスケジュールが再登録されるようになりました。

まあただこの方法だと、ローカル通知で呼び出しても起動されなかった場合には登録した通知を全て表示し終わった場合には通知されなくなりますね。

アプリ起動時に毎回ローカル通知の登録を行っても、やっぱり同じでアプリ起動もローカル通知による起動もまったく行われなかった場合にはいつか通知されなくなります。

永久的にローカル通知を表示することはできるのかな?(良い方法があればダレか教えて。)

ということで今回の確認に使ったサンプルをgithubに上げておきます。

LocalNotificationSample

もうちょっと使いやすい様にクラス化すればよかったんですけどね。。。それはオイオイということで。