swift notification appicon image
위 사진과 같이 채팅 앱의 경우 기존에는 앱 아이콘이 있어야되는 위치에 상대방의 프로필 사진이 올라오고,
상대방이 올린 이미지가 우측에 나오는 경우가 있다.
이 경우를 구현하기로했다.
레퍼런스가 없어서 찾는데 오래걸렸다.
여러 테스트 결과
NotificationServiceExtension을 이용하여 구현 가능하다.
File - New - target... - Notification Service Extension 을 클릭한다.
이름을 NotificationServiceExtension로 만든다.
파일 내부에 NotificationService.swift 파일이 생성되는데 해당 내부 오버라이드된 함수 didReceive() 내부에 재정의하면 이미지를 설정할 수 있다.
didReceive()함수는 푸시를 받았을 때 발동 된다.
파라미터 중 escaping closer인 contentHandler클로저를 실행할 경우 푸시 알림이 동작된다.
파라미터 중 request는 payload내부에 포함된 정보를 가져올 수 있다.
{
"message": {
"token": "fLDngNy_fE2HvmHJ79Xm03:APA9...(푸시토큰)",
"notification": {
"title": "테스트!",
"body": "이미지가 갔니??"
},
"data": {
"image": "https://static01.nyt.com/athletic/uploads/wp/2024/10/01072609/diogo-jota-scaled-e1727782003173-1024x683.jpg",
"iconUrl": "https://imgnews.pstatic.net/image/311/2025/01/25/0001820500_001_20250125092306669.jpg"
},
"apns": {
"payload": {
"aps": {
"mutable-content": 1
}
}
}
}
}
위 payload 처럼 data 내부에 첨부할 이미지(image), 아이콘 이미지(iconUrl)을 같이 보낸다.
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
// 푸시가 오면 발동!
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler // contentHandler 실행 시 푸시 알림 발동!
// iconUrl key가 있다면 앱 아이콘 변경 실행
if let attachmentURLAsString = request.content.userInfo["iconUrl"] as? String {
changeAppIconToImage(request: request, iconUrl: attachmentURLAsString, contentHandler: contentHandler)
}
}
private func changeAppIconToImage(request: UNNotificationRequest, iconUrl: String, contentHandler: @escaping (UNNotificationContent) -> Void) {
fetchImageData(from: iconUrl) { data in
let avatar = INImage(imageData: data!) // 이미지(데이터) 설정
let senderPerson = INPerson(
personHandle: INPersonHandle(value: "unique-sender-id-2", type: .unknown),
nameComponents: nil,
displayName: request.content.title, // 타이틀을 이름으로 설정
image: avatar, // 이미지 아바타 설정
contactIdentifier: nil,
customIdentifier: nil,
isMe: false,
suggestionType: .none
)
let mePerson = INPerson(
personHandle: INPersonHandle(value: "unique-me-id-2", type: .unknown),
nameComponents: nil,
displayName: nil,
image: avatar,
contactIdentifier: nil,
customIdentifier: nil,
isMe: true,
suggestionType: .none
)
let intent = INSendMessageIntent(recipients: [mePerson],
outgoingMessageType: .outgoingMessageText,
content: "Message content",
speakableGroupName: nil,
conversationIdentifier: "unique-conversation-id-1",
serviceName: nil,
sender: senderPerson,
attachments: nil)
intent.setImage(avatar, forParameterNamed: \.sender)
let interaction = INInteraction(intent: intent, response: nil)
interaction.direction = .incoming
interaction.donate { error in
if let error = error {
print(error)
return
}
do {
// 이전 notification에 intent를 더해주고, 노티 띄우기
let updatedContent = try request.content.updating(from: intent)
contentHandler(updatedContent)
} catch {
print(error)
}
}
}
func fetchImageData(from urlString: String, completion: @escaping (Data?) -> Void) {
guard let url = URL(string: urlString) else {
completion(nil)
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, error == nil {
completion(data)
} else {
completion(nil)
}
}
task.resume()
}
}
INImage(imageData: ) 이용하여 이미지 설정
INPerson(...) 타입으로 sender, me 설정 (sender-displayname에 title 설정)
INSendMessageIntent에 내용 입력
intent와 interaction 인스턴스 생성
여기까지 하고 푸시를 보내면 아이콘이 바뀌기 시작한다.
여기서 문제 발생..
첨부 이미지와 아이콘 변경 이미지 두 개가 모두 첨부되지 않는 문제 발생
contentHandler를 한 번만 실행해야된다.
interaction.donate { error in
if let error = error {
print(error)
return
}
if let attachmentURLAsString = request.content.userInfo["image"] as? String,
let attachmentURL = URL(string: attachmentURLAsString) {
self.downloadImageFrom(url: attachmentURL) { (attachment) in
if let attachment = attachment {
// updatedContent에 intent 적용된 내용이 있으므로 bestAttemptContent로 반영
if let updated = try? request.content.updating(from: intent),
let updatedMutable = updated.mutableCopy() as? UNMutableNotificationContent {
updatedMutable.attachments = [attachment]
// 업데이트된 내용을 bestAttemptContent로 교체
self.bestAttemptContent = updatedMutable
FIRMessagingExtensionHelper().populateNotificationContent(updatedMutable, withContentHandler: contentHandler)
}
}
}
}
}
위 코드는 updatedMutable에 변경된 아이콘 intent를 업데이트하고,
업데이트된 updatedMutable을 FIRMessagingExtensionHelper을 이용하여 contentHandler를 실행시키는 코드이다. 이렇게 하면 이미지 두 개 모두 첨부 가능하다.