Local Notification(로컬 알림)
- 앱 내에서 자체적으로 만든 특정 메시지를 전달하는 알림
- 사용자의 관심을 끄는데에 기본적인 목적이 있다.
- 알림을 재생하거나, 앱 아이콘에 뱃지를 지정할 수 있다.
- 예를들어 백그라운드앱은 앱이 특정 작업을 완료할 때 경고를 표시하도록 시스템에 요청을 할 수 있다.
- 사용자가 원하는 중요한 정보를 전달하는 방법 중 하나이다.
UNNotificationRequest(UN은 User Notification의 약자)
Local Notification을 이용하기 위해 UNNotificationRequest를 작성해야한다.
UNNotificationRequest을 작성하려면 3가지 내용이 필수적으로 요구가 된다.
1. ID(identifier)
- 각각의 요청을 구분할 수 있는 id
- 중복되지 않는 고유한 값인 uid를 입력하는 것이 일반적이다.
2. UNMutableNotificationContent(Content)
- 알림에 나타날 내용들을 정의한다.
- 알림에 표시될 타이틀, 내용, 알림소리, 뱃지에 표시될 내용을 String으로 직접 입력하게됨.
3. 트리거
UNCalenderNotificationTrigger
- 달력, 날짜를 기준으로쓰는 트리거
UNTimeIntervalNotificationTrigger
- 10분마다, 1시간마다 등 시간 간격으로 쓰는 트리거
UNLocationNotificationTrigger
- 사용자의 위치에 따라 쓰는 트리거
이 알람이 어떤 상황에서 발생할 것인지를 선언하는 일종의 조건을 걸어주는 부분.
로컬알림의 성격을 가장 잘 구분하는 부분.
3가지 조건에 의해서만 로컬알림을 보낼 수 있게된다.
3가지 요소를 모두 작성하게 되면 Request객체를 생성할 조건이 모두 완료되었다고 할 수 있다.
잘 만들어진 Request를 UNNotificationCenter에 추가를 해주어야한다.
이후 이 Center에 쌓인 Request들은 보관되고 있다가 적정한 순간(트리거)가 되면 알림을 쏘게 된다.
만약 트리거가 Calender면 날짜기준, Timeinterval이면 시간 간격으로, Location이면 특정 장소 기준으로 알림이 발송됨.
Notification을 만드는 Content 설정
알림을 만들면 앱에서 어떠한 형태로 표현 할 것인지, 예를 들어 알림 센터 배너 벳지등을 설정할 수 있다.
이러한 내용을 설정하기 위해서 AppDelegate에서 설정한다.
AppDelegate에서 import NotificationCenter를 해준다.
NotificationCenter를 import해주어야 Notification을 보낼 수 있다.
NotificationCenter을 추가해준 뒤 Delegate설정을 해줘야 한다.
Appdelegate의 didFinishLanchingWithOptions에 추가해준다.
그리고 extension으로 Delegate를 구현해준다.
이 두 함수는 각각 NotificationCenter를 보내기 전에 어떤 핸들링을 해줄것인가를 설정한다.
여기서 우리는 각각의 알림을 banner, list, bedge, sound까지 표시하도록 설정해 줄 것이다.
// AppDelegate.swift
import UIKit
// Noti를 보내기 위해 설정
import NotificationCenter
// 사용자에게 허락을 받기위해 import
import UserNotifications
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// notificationCenter 생성
var userNotificationCenter = UNUserNotificationCenter.current()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 델리게이트 설정
UNUserNotificationCenter.current().delegate = self
// 요청할 권한들을 담아놓는다.
let authrizationOptions = UNAuthorizationOptions(arrayLiteral: [.alert, .badge, .sound])
// 사용자에게 권한을(알림에대한) 요청한다.
// completionHandler를 통해서 에러처리를 할 수 있다. 결과값은 무시하고 에러 처리정도만 해줌.
// 이를 통해 사용자에게 권한을 요청한다.
userNotificationCenter.requestAuthorization(options: authrizationOptions){ _, error in
if let error = error{
print("ERROR: notification authrization request \(error.localizedDescription)")
}
}
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
}
}
// UNUserNotificationCenterDelegate 설정
extension AppDelegate: UNUserNotificationCenterDelegate{
// Noti를 보내기 전에 어떤 핸들링을 해줄 것인지.
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// 알림을 배너. 리스트. 뱃지. 사운드까지 표시하도록 설정.
completionHandler([.banner, .list, .badge, .sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
completionHandler()
}
}
이렇게 하고 특정 앱에서 로컬 알림을 포함한 알림을 주려면 먼저 사용자의 승인을 받아야 한다.
우리들은 앱을 처음 설치하고 열었을 때 "이 앱에서 오는 알림을 받으시겠습니까?" 하는 별도에 Alert을 기억 할텐데 그런 식으로 만약에 사용자가 허용을 하지 않으면 우리가 잘 설정해 놓은 이런 알람도 사실상 앱에 표현되지 않는다.
어쨋든 이러한 허용을 받아야만 보낼 수 있는 구조로 되어있기 때문에, 사용자의 승인을 구하는 코드를 추가해야한다.
이제 샘플 앱에서 로컬 알림이 생성 되는 곳은 어디인가.
내가 만드는 앱은 AlertListViewController에서 Datepicker closer를 통해서 알람이 추가될 때이다.
따라서 AlertListViewController로 가서 설정을 한다. 근데 알람이 생성되는 곳이 하나 더 있다.
AlertListCell에서 알람 스위치가 켜지고, 꺼질 때, 이 중에서 스위치가 켜질때 알람이 생성되어야 한다.
로컬 알림은 Notification Center에 request가 add되는 형태이다.
그 Center가 trigger조건에 맞을 때 마다 Noti를 보내는 구조이다.
따라서 NotificationCenter의 extention에 범용함수를 추가해서 사용하려한다.
UNNotificationCenter.swift새 파일을 추가한다.
//UNNotificationCenter.swift
import Foundation
import UserNotifications
extension UNUserNotificationCenter{
}
여기에서 Alert객체를 받아서 request를 만들고 최종적으로 NotificationCenter에 추가하는 함수를 만든다.
import Foundation
import UserNotifications
// UNUserNotificationCenter 를 extension 해서 범용 함수를 추가.
extension UNUserNotificationCenter{
// Alert객체를 받아서 Noti를 만들고 NotificationCenter에 추가하는 함수
func addNotificationRequest(by alert: Alert){
// 알림의 내용이 될 컨텐츠를 생성
let content = UNMutableNotificationContent()
content.title = "물 마실 시간이에요."
content.body = "세계보건기구(WHO)가 권장하는 하루 물 섭취량은 1.5 ~ 2리터 입니다."
content.sound = .default
content.badge = 1
// 시간과 분 컴포넌트만 넣어서 생성한다. from?> alert의 date 에서
let component = Calendar.current.dateComponents([.hour, .minute], from: alert.date)
// 트리거를 설정, 어떤 조건으로 할 것인지, 반복 할 것인지. 이 앱은 시간, 분만 필요하므로 상단의 component 생성
// repeats = 반복여부, Switch가 켜져있는지 안켜져있는지.
let trigger = UNCalendarNotificationTrigger(dateMatching: component, repeats: alert.isOn)
// 실제로 request를 작성한다. identifier, content, trigger가 필요
let request = UNNotificationRequest(identifier: alert.id, content: content, trigger: trigger)
// UNUserNotificationCenter에 추가해준다.
self.add(request, withCompletionHandler: nil )
}
}
여기서 content에 설정한 대로 badge의 숫자를 1로 표시하는 알림이 발송 되었다고 생각을 해볼 경우, 자동적으로 사용자가 알림을 선택하거나 앱을 실행하면 뱃지가 사라질까.. 그렇지 않다.
코드로 따로 설정을 해주어야 한다. 어떠한 시점에 badge를 없애 줘야 하는데,
SceneDelegate.swift에 sceneDidBecomeActive라는 것이 있다.
scene이 Active상태가 되었을 경우를 의미 하는 것인데, 즉 사용자가 앱을 열어서 scene이 active상태가 되었을 때를 말한다.
이 때 badge를 없애주고 싶다면 이 곳에 추가한다.
//SceneDelegate.swift
func sceneDidBecomeActive(_ scene: UIScene) {
UIApplication.shared.applicationIconBadgeNumber = 0
}
이렇게 하면 앱이 실행되어서 scene이 active상태가 되었을 때 뱃지 넘버를 0 -> 즉 사라지게 해줄 수 있다.
LocalNotification을 활성화 하는, 발송시키는 조건이 되는 Trigger 설정
이 앱에선 datepicker로선택한 시간, AddAlertViewController의 pickedDate에 저장된 시간 데이터에 발송을 할 것인데, 특정 시간에 발송 하는것은 UNCalenderNotificationTrigger를 통해서 구현할 수 있다.
트리거를 선언한다.
이 전에 콘텐츠를 생성한 UNNotificationCenter.swift에서 트리거를 선언한다.
import Foundation
import UserNotifications
extension UNUserNotificationCenter{
func addNotificationRequest(by aler: Alert){
let content = UNMutableNotificationContent()
content.title = "물 마실 시간이에요."
content.body = "세계보건기구(WHO)가 권장하는 하루 물 섭취량은 1.5 ~ 2리터 입니다."
content.sound = .default
content.badge = 1
let trigger = UNCalendarNotificationTrigger(dateMatching: , repeats: )
}
}
트리거를 생성하고, 트리거를 UNCalendarNotificationTrigger로 선택 해준다.
dateMatching과 repeat을 설정 할 수 있는데, dateMatching은 어떤 데이트 조건으로 할 것인지 date컴포넌트를 집어 넣어야 하고, repeats 반복 할 것인지를 집어 넣어야 한다.
우리는 날짜는 필요 없고 어떤 날짜든 해당 시간과 분에 알림을 넣을 것이기 때문에 날짜는 Alert객체가 갖고있으니까 해당 date를 시간 분 형태의 date컴포넌트로 만들어 주면 된다. 위에 by alert: Alert로 받아오기 떄문!
Calendar의 current의 dateComnents가 있는데 여기서 hour과 minute을 넣겠다고 설정할 수 있다. 어떤 데이트에서 이런 시간과 분을 얻을거냐를 from에서 설정할 수 있는데, 함수에서 제공하는 파라미터인 Alert안에는 date가 있다 이 date를 통해서 넣어 준다.
이렇게 생성된 컴포넌트를 트리거의 컴포넌트로 넣어주면 된다.
import Foundation
import UserNotifications
extension UNUserNotificationCenter{
func addNotificationRequest(by aler: Alert){
let content = UNMutableNotificationContent()
content.title = "물 마실 시간이에요."
content.body = "세계보건기구(WHO)가 권장하는 하루 물 섭취량은 1.5 ~ 2리터 입니다."
content.sound = .default
content.badge = 1
//컴포넌트 생성
let component = Calendar.current.dateComponents([.hour, .minute], from: aler.date)
let trigger = UNCalendarNotificationTrigger(dateMatching: component, repeats: <#T##Bool#>)
}
}
이후 반복 여부는 스위치의 온오프 상태를 통해서 저장된 alert의 isOn으로 설정해주면 된다. 만약 스위치가 켜져있다면 계속 켜져있는동안 반복하면되고 스위치가 꺼져있다면 반복을 중단하면 된다. 따라서 데이트 컴포넌트의 repeat여부를 추가해서 트리거 구성을 완료한다.
트리거 구성 완료.
extension UNUserNotificationCenter{
func addNotificationRequest(by alert: Alert){
let content = UNMutableNotificationContent()
content.title = "물 마실 시간이에요."
content.body = "세계보건기구(WHO)가 권장하는 하루 물 섭취량은 1.5 ~ 2리터 입니다."
content.sound = .default
content.badge = 1
let component = Calendar.current.dateComponents([.hour, .minute], from: alert.date)
let trigger = UNCalendarNotificationTrigger(dateMatching: component, repeats: alert.isOn)
}
}
Request 설정하기
trigger와 content가 모두 생성되었으니 Request는 금방 만들 수 있다. 만들어둔 구성요소들을 하나씩 추가해주고 Center에 추가해주면 된다.
request는 UNNotificationRequest라고 검색하면 나온다. 여기에는 identifier, content, trigger세가지 요소가 있어야만 request를 만들 수 있다.
import Foundation
import UserNotifications
extension UNUserNotificationCenter{
func addNotificationRequest(by alert: Alert){
let content = UNMutableNotificationContent()
content.title = "물 마실 시간이에요."
content.body = "세계보건기구(WHO)가 권장하는 하루 물 섭취량은 1.5 ~ 2리터 입니다."
content.sound = .default
content.badge = 1
let component = Calendar.current.dateComponents([.hour, .minute], from: alert.date)
let trigger = UNCalendarNotificationTrigger(dateMatching: component, repeats: alert.isOn)
//requesst 생성.
let request = UNNotificationRequest(identifier: <#T##String#>, content: <#T##UNNotificationContent#>, trigger: <#T##UNNotificationTrigger?#>)
}
}
identifire에는 alert에있는 id값을 넣어주고, content에는 만든 content, trigger에는 만든 trigger를 넣어준다.
이렇게 만들어준 request를 self(여기선 UNUserNotificationCenter)에 add해주는데 request를 add 하고 completionHandler(완료된 다음의 별도의 행동)은 하지 않는다.
import Foundation
import UserNotifications
extension UNUserNotificationCenter{
func addNotificationRequest(by alert: Alert){
let content = UNMutableNotificationContent()
content.title = "물 마실 시간이에요."
content.body = "세계보건기구(WHO)가 권장하는 하루 물 섭취량은 1.5 ~ 2리터 입니다."
content.sound = .default
content.badge = 1
let component = Calendar.current.dateComponents([.hour, .minute], from: alert.date)
let trigger = UNCalendarNotificationTrigger(dateMatching: component, repeats: alert.isOn)
let request = UNNotificationRequest(identifier: alert.id, content: content, trigger: trigger)
//add
self.add(request, withCompletionHandler: nil )
}
}
identifire에는 alert에있는 id값을 넣어주고, content에는 만든 content, trigger에는 만든 trigger를 넣어준다.
이제 이렇게 만들어 놓은 로컬 알림 추가함수를 로컬 알림이 추가되는 두군데에 각각 추가를 해주면 된다.
먼저 AlertListController에 UserNotification을 추가한다.
switch가 on 되는곳과, 새로운 알림이 추가되는곳 두군데에 각각 추가해준다. 먼저 AlertListViewController로 가서 Notification을 사용 할 거니까 import UserNotifications를 해준다
//알림이 생성되는 위치
//여기선 AlertListViewController.swift
import UserNotifications
이 후에 UNUserNotificationCenter변수선언을 해준다.
let userNotificationCenter = UNUserNotificationCenter.current()
그리고 addalertButtonAction함수, 새로운 alert이 추가되는 함수에 noti를 추가하는 함수도 더해준다.
self.userNotificationCenter.addNotificationRequest(by: newAlert)
두 번째 AlertListCell에서 switch가 처음에는 on상태이고 추가된 직후에는 AlertListViewController에서 알림을 추가할 것이라 해당되지 않지만 껏다가 다시 켜는 경우에는 다시 추가를 해주어야 한다.
//AlertListCell.siwft
//alertSwitchValueCHanged메서드 내부
if sender.isOn{
userNotificationCenter.addNotificationRequest(by: alerts[sender.tag])
}
이제 알람이 삭제되는 경우인데 두군데 존재한다.
알람을 삭제 할 경우와 알림의 스위치를 끌 경우 두가지가 있다.
먼저 cell을 삭제할 때 알림도 삭제되어야 한다.
userNotificationCenter에 removePendingNotificationRequests가 있는데
이것은 center에서 center가 가지고 있는 request중에서 남아있는 notification요청중 해당하는 어떤것,
해당하는 Identifiers의 request만 삭제하겠다. 라는 것이다.
추가된 알림을 삭제하는 방법은 pending 말고도 여러가지가 있는데, 우리는 특정 id에 해당하는것 중에 아직 보내지 않은 pending되어있는 request를 삭제할 것이기 때문에 이렇게 작성을 한다.
여기서 identifire는 alert의 indexpath의 row의 id만 삭제 해주면 된다.
//AlertListViewController.swift의 알림이 삭제되는 함수
userNotificationCenter.removePendingNotificationRequests(withIdentifiers: [alertsList[indexPath.row].id])
AlertListCell.swift에도 똑같이 추가해준다.
//AlertListCell.siwft
//alertSwitchValueCHanged메서드 내부
if sender.isOn{
userNotificationCenter.addNotificationRequest(by: alerts[sender.tag])
}else{
userNotificationCenter.removePendingNotificationRequests(withIdentifiers: [alerts[sender.tag].id])
}