知 識(shí) 點(diǎn) / 超 人
目錄
- 背景
- UNNotificationServiceExtension 與 UNNotificationContentExtension的關(guān)系
- UNNotificationServiceExtension
- UNNotificationContentExtension
- 擴(kuò)展知識(shí)點(diǎn)
- 示例代碼
背景
iOS 10之前,iPhone手機(jī)中,通知欄僅能展示 標(biāo)題和內(nèi)容文本
iOS 10 之前的通知欄iOS 10 開(kāi)始,蘋果新增了UserNotifications.framework庫(kù)用于對(duì)通知的擴(kuò)展。通過(guò)UNNotificationService 與 UNNotificationContent來(lái)進(jìn)行通知的 攔截 與 通知界面的自定義。讓通知欄變得豐富多彩,既可以展示圖文內(nèi)容,也可以展示音視頻。使得用戶在不打開(kāi)App的情況下也能進(jìn)行App內(nèi)容的交互。
圖文推送長(zhǎng)按推送推送后帶有圖文的自定義推送長(zhǎng)按推送后帶有交互的自定義推送長(zhǎng)按推送后的帶有聊天回復(fù)功能的自定義推送
iOS 12開(kāi)始,新增了通知欄的分組,默認(rèn)會(huì)根據(jù)Bundle Id自動(dòng)區(qū)分不同應(yīng)用的通知,也可以通過(guò)Thread identifier 精細(xì)化控制同一個(gè)應(yīng)用里不同類型的通知。
設(shè)置App分組
自動(dòng):按照Thread identifier進(jìn)行區(qū)分不同的消息,App如果未設(shè)置Thread identifier,則按照Bundle Id區(qū)分。
按App:按照App的 Bundle Id 區(qū)分通知消息
關(guān):關(guān)閉App的通知分組
UNNotificationServiceExtension 與 UNNotificationContentExtension的關(guān)系
UNNotificationServiceExtension負(fù)責(zé)攔截通知,對(duì)通知內(nèi)容做中間處理,而UNNotificationContentExtension負(fù)責(zé)自定義通知界面的。
如果只是想對(duì)通知內(nèi)容進(jìn)行解析或單純的系統(tǒng)通知中有小圖片,那么使用UNNotificationServiceExtension即可
如果想顯示用戶長(zhǎng)按通知自定義后顯示的通知界面,則需要使用UNNotificationContentExtension自定義通知界面
一般都是UNNotificationServiceExtension與UNNotificationContentExtension結(jié)合使用,由UNNotificationServiceExtension解密通知,將通知內(nèi)容進(jìn)行轉(zhuǎn)換,并下載相關(guān)的通知資源文件。然后在UNNotificationContentExtension中拿到UNNotificationServiceExtension處理后的通知內(nèi)容,將內(nèi)容賦值在界面上進(jìn)行展示。
UNNotificationServiceExtension
蘋果官方關(guān)于UNNotificationServiceExtension的說(shuō)明文檔
UNNotificationServiceExtension是用于攔截遠(yuǎn)程通知的,在手機(jī)收到對(duì)應(yīng)App的通知時(shí),會(huì)觸發(fā)UNNotificationServiceExtension,可以在UNNotificationServiceExtension中對(duì)通知內(nèi)容進(jìn)行修改,收到可以對(duì)通知添加附件,例如圖片、音頻、視頻。
1、創(chuàng)建UNNotificationServiceExtension
在工程中的TARGETS中選擇加號(hào),然后在 iOS 模板中選擇 Notification Service Extension


NotificationService是通知攔截響應(yīng)的類,Info.plist是NotificationsServiceExtension的配置信息。
在NotificationService.m中,自動(dòng)生成了contentHandler與bestAttemptContent兩個(gè)屬性。didReceiveNotificationRequest:withContentHandler與serviceExtensionTimeWillExpire方法。
bestAttemptContent
//通知消息的內(nèi)容對(duì)象,里面包含了通知相關(guān)的所有信息。一般有title、body、subTitle。
//如果是服務(wù)端封裝的擴(kuò)展參數(shù),則一般都在userInfo中。
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
contentHandler
//用于告知系統(tǒng)已經(jīng)處理完成,可以將通知內(nèi)容傳給App的回調(diào)對(duì)象。
//該對(duì)象需要返回一個(gè)UNMutableNotificationContent對(duì)象。一般都是返回bestAttemptContent
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
didReceiveNotificationRequest:withContentHandler
//可以通過(guò)重寫此方法來(lái)實(shí)現(xiàn)自定義推送通知修改。
//如果要使用修改后的通知內(nèi)容,則需要在該方法中調(diào)用contentHandler傳遞修改后的通知內(nèi)容。
//如果在服務(wù)時(shí)間(30秒)到期之前未調(diào)用處理程序contentHandler,則將傳遞未修改的通知。
//@param request 通知內(nèi)容
//@param contentHandler 處理結(jié)果,需要返回一個(gè)UNNotificationContent的通知內(nèi)容
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
//接收回調(diào)對(duì)象
self.contentHandler = contentHandler;
//copy通知內(nèi)容
self.bestAttemptContent = [request.content mutableCopy];
//回調(diào)通知結(jié)果
self.contentHandler(self.bestAttemptContent);
}
serviceExtensionTimeWillExpire
//當(dāng)didReceiveNotificationRequest的方法執(zhí)行超過(guò)30秒未調(diào)用contentHandler時(shí)
//系統(tǒng)會(huì)自動(dòng)調(diào)用serviceExtensionTimeWillExpire方法,給我們最后一次彌補(bǔ)處理的機(jī)會(huì)
//可以在serviceExtensionTimeWillExpire方法中設(shè)置didReceiveNotificationRequest方法中未完成數(shù)據(jù)的默認(rèn)值
- (void)serviceExtensionTimeWillExpire {
self.contentHandler(self.bestAttemptContent);
}
UNNotificationRequest
遠(yuǎn)程通知發(fā)送給App的通知請(qǐng)求,其中包括通知的內(nèi)容和交互的觸發(fā)條件。
@interface UNNotificationRequest : NSObject <NSCopying, NSSecureCoding>
// 該屬性為通知請(qǐng)求的唯一標(biāo)識(shí)符??梢杂盟鼇?lái)替換或刪除掛起的通知請(qǐng)求或已傳遞的通知。
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *identifier;
// 該屬性是用于顯示的通知內(nèi)容
@property (NS_NONATOMIC_IOSONLY, readonly, copy) UNNotificationContent *content;
// 通知交互的觸發(fā)器
@property (NS_NONATOMIC_IOSONLY, readonly, copy, nullable) UNNotificationTrigger *trigger;
UNNotificationTrigger
抽象類,用于表示觸發(fā)通知傳遞的事件。不能直接創(chuàng)建此類的實(shí)例。具體的觸發(fā)子類看下面的類
//抽象類,用于表示觸發(fā)通知傳遞的事件。不能直接創(chuàng)建此類的實(shí)例。具體的觸發(fā)子類看下面的類
@interface UNNotificationTrigger : NSObject <NSCopying, NSSecureCoding>
/// 通知的觸發(fā)是否循環(huán)執(zhí)行
@property (NS_NONATOMIC_IOSONLY, readonly) BOOL repeats;
- (instancetype)init NS_UNAVAILABLE;
@end
//遠(yuǎn)程推送
// 蘋果遠(yuǎn)程推送服務(wù)時(shí)的推送對(duì)象.
API_AVAILABLE(macos(10.14), ios(10.0), watchos(3.0), tvos(10.0))
@interface UNPushNotificationTrigger : UNNotificationTrigger
@end
// 本地推送
// 基于時(shí)間間隔去觸發(fā)的通知.
API_AVAILABLE(macos(10.14), ios(10.0), watchos(3.0), tvos(10.0))
@interface UNTimeIntervalNotificationTrigger : UNNotificationTrigger
/// 通知延遲觸發(fā)的時(shí)間
@property (NS_NONATOMIC_IOSONLY, readonly) NSTimeInterval timeInterval;
/// 創(chuàng)建延遲觸發(fā)的通知
/// @param timeInterval 延遲的時(shí)間
/// @param repeats 是否重復(fù)執(zhí)行
+ (instancetype)triggerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats;
/// 獲取通知下一次觸發(fā)的時(shí)間
- (nullable NSDate *)nextTriggerDate;
@end
// 本地推送
// 根據(jù)日期和時(shí)間觸發(fā)的通知
API_AVAILABLE(macos(10.14), ios(10.0), watchos(3.0), tvos(10.0))
@interface UNCalendarNotificationTrigger : UNNotificationTrigger
/// 通知觸發(fā)的時(shí)間
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSDateComponents *dateComponents;
// The next date is calculated using matching date components.
/// 創(chuàng)建基于時(shí)間觸發(fā)的通知
/// @param dateComponents 日期
/// @param repeats 是否重復(fù)執(zhí)行
+ (instancetype)triggerWithDateMatchingComponents:(NSDateComponents *)dateComponents repeats:(BOOL)repeats;
/// 獲取通知下一次觸發(fā)的時(shí)間
- (nullable NSDate *)nextTriggerDate;
@end
// 根據(jù)用戶手機(jī)定位,當(dāng)進(jìn)入某個(gè)區(qū)域或者離開(kāi)某個(gè)區(qū)域時(shí)觸發(fā)通知
API_AVAILABLE(ios(10.0), watchos(3.0)) API_UNAVAILABLE(macos, tvos, macCatalyst)
@interface UNLocationNotificationTrigger : UNNotificationTrigger
/// 觸發(fā)通知的地理位置信息
@property (NS_NONATOMIC_IOSONLY, readonly, copy) CLRegion *region;
/// 創(chuàng)建基于地理位置觸發(fā)的通知
/// @param region 地理位置
/// @param repeats 是否重復(fù)觸發(fā)
+ (instancetype)triggerWithRegion:(CLRegion *)region repeats:(BOOL)repeats API_AVAILABLE(watchos(8.0));
@end
UNNotificationContent
通知的具體內(nèi)容,包括通知類型、自定義參數(shù)、附件信息等。不能直接創(chuàng)建該類,如果需要?jiǎng)?chuàng)建自定義的通知內(nèi)容,應(yīng)該創(chuàng)建UNMutableNotificationContent,并配置內(nèi)容
// 附件數(shù)組,必須是UNNotificationAttachment對(duì)象
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSArray <UNNotificationAttachment *> *attachments API_UNAVAILABLE(tvos);
// 應(yīng)用的認(rèn)證號(hào)碼,圖標(biāo)右上角的數(shù)字
@property (NS_NONATOMIC_IOSONLY, readonly, copy, nullable) NSNumber *badge;
// 通知的內(nèi)容
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *body API_UNAVAILABLE(tvos);
// 已注冊(cè)的UNNotificationCategory的標(biāo)識(shí)符,用于確定顯示哪一個(gè)自定義通知的UI。
//該標(biāo)識(shí)是在UNNotificationContentExtension的info.plist中注冊(cè)的
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *categoryIdentifier API_UNAVAILABLE(tvos);
// 通知欄中應(yīng)用程序顯示App圖片,在App中通設(shè)置該屬性來(lái)修改通知欄中的App Icon
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *launchImageName API_UNAVAILABLE(macos, tvos);
// 通知將播放的音頻,在App中通過(guò)設(shè)置該屬性來(lái)修改App的通知聲音
@property (NS_NONATOMIC_IOSONLY, readonly, copy, nullable) UNNotificationSound *sound API_UNAVAILABLE(tvos);
// 通知的副標(biāo)題
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *subtitle API_UNAVAILABLE(tvos);
// 與當(dāng)前通知請(qǐng)求相關(guān)的線程或?qū)υ挼奈ㄒ粯?biāo)識(shí)符。它是通知分組的標(biāo)識(shí)
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *threadIdentifier API_UNAVAILABLE(tvos);
// 通知標(biāo)題
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *title API_UNAVAILABLE(tvos);
// 通知的詳細(xì)信息
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSDictionary *userInfo API_UNAVAILABLE(tvos);
// 通知摘要參數(shù)
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *summaryArgument API_DEPRECATED("summaryArgument is ignored", ios(12.0, 15.0), watchos(5.0, 8.0), tvos(12.0, 15.0));
// 摘要的數(shù)量
@property (NS_NONATOMIC_IOSONLY, readonly, assign) NSUInteger summaryArgumentCount API_DEPRECATED("summaryArgumentCount is ignored", ios(12.0, 15.0), watchos(5.0, 8.0), tvos(12.0, 15.0));
// 點(diǎn)擊自定義通知是激活的場(chǎng)景唯一標(biāo)識(shí),默認(rèn)為空
@property (NS_NONATOMIC_IOSONLY, readonly, copy, nullable) NSString *targetContentIdentifier API_AVAILABLE(ios(13.0));
// 通知的級(jí)別
//UNNotificationInterruptionLevelPassive,添加到通知列表中;不會(huì)點(diǎn)亮屏幕或播放聲音
//UNNotificationInterruptionLevelActive,立即執(zhí)行,點(diǎn)亮屏幕并可能播放聲音
//UNNotificationInterruptionLevelTimeSensitive,立即執(zhí)行,點(diǎn)亮屏幕并可能播放聲音;在請(qǐng)勿打擾期間都會(huì)出現(xiàn)
// Presented immediately; Lights up screen and plays sound; Always presented during Do Not Disturb; Bypasses mute switch; Includes default critical alert sound if no sound provided
//立即執(zhí)行,點(diǎn)亮屏幕并播放聲音,如果處于“請(qǐng)勿打擾”狀態(tài)會(huì)一直顯示,不收靜音開(kāi)關(guān)影響,如果沒(méi)有設(shè)置附件聲音,則會(huì)使用默認(rèn)的嚴(yán)重警報(bào)聲音
//UNNotificationInterruptionLevelCritical,
@property (NS_NONATOMIC_IOSONLY, readonly, assign) UNNotificationInterruptionLevel interruptionLevel API_AVAILABLE(macos(12.0), ios(15.0), watchos(8.0), tvos(15.0));
// 關(guān)聯(lián)系數(shù),決定了通知在應(yīng)用程序通知中的排序。其范圍在0.0f和1.0f之間。
@property (NS_NONATOMIC_IOSONLY, readonly, assign) double relevanceScore API_AVAILABLE(macos(12.0), ios(15.0), watchos(8.0), tvos(15.0));
UNNotificationSound
通知發(fā)出時(shí),通知的聲音。如果想手機(jī)收到通知時(shí)播放特定聲音,需要?jiǎng)?chuàng)建UNNotificationSound對(duì)象來(lái)設(shè)置特定的音頻文件。UNNotificationSound對(duì)象僅讀取以下位置文件:
應(yīng)用程序容器目錄的/Library/Sounds目錄。
應(yīng)用程序的共享組容器目錄之一的/Library/Sounds目錄。播放自定義聲音,必須采用以下音頻數(shù)據(jù)格式之一:Linear PCM、MA4 (IMA/ADPCM)、μLaw、aLaw
可以將音頻數(shù)據(jù)打包為aiff、wav或caf文件。聲音文件的長(zhǎng)度必須小于30秒。如果聲音文件超過(guò)30秒,系統(tǒng)將播放默認(rèn)聲音
可以使用afconvert命令行工具轉(zhuǎn)換聲音。例如,將系統(tǒng)聲音轉(zhuǎn)換為Submarine.aiff。aiff到IMA4音頻在CAF文件中,在終端中使用以下命令:
afconvert /System/Library/Sounds/Submarine.aiff ~/Desktop/sub.caf -d ima4 -f caff -
//通知發(fā)出時(shí),通知的聲音。如果想手機(jī)收到通知時(shí)播放特定聲音,需要?jiǎng)?chuàng)建UNNotificationSound對(duì)象來(lái)設(shè)置特定的音頻文件。
//UNNotificationSound對(duì)象僅在以下位置顯示:
//應(yīng)用程序容器目錄的/Library/Sounds目錄。
//應(yīng)用程序的共享組容器目錄之一的/Library/Sounds目錄。
//播放自定義聲音,必須采用以下音頻數(shù)據(jù)格式之一:Linear PCM、MA4 (IMA/ADPCM)、μLaw、aLaw
//可以將音頻數(shù)據(jù)打包為aiff、wav或caf文件。聲音文件的長(zhǎng)度必須小于30秒。如果聲音文件超過(guò)30秒,系統(tǒng)將播放默認(rèn)聲音
//可以使用afconvert命令行工具轉(zhuǎn)換聲音。例如,將系統(tǒng)聲音轉(zhuǎn)換為Submarine.aiff。aiff到IMA4音頻在CAF文件中,在終端中使用以下命令:
//afconvert /System/Library/Sounds/Submarine.aiff ~/Desktop/sub.caf -d ima4 -f caff -
@interface UNNotificationSound : NSObject <NSCopying, NSSecureCoding>
// 默認(rèn)的通知聲音
@property(class, NS_NONATOMIC_IOSONLY, copy, readonly) UNNotificationSound *defaultSound;
// 用于來(lái)電通知的默認(rèn)聲音。播放設(shè)置中指定的鈴聲和觸覺(jué),持續(xù)30秒。
// 父UNNotificationContent對(duì)象必須通過(guò)-[UnnotificationContentByUpdateingWithProvider:error:]在通知服務(wù)擴(kuò)展中創(chuàng)建
// 其中提供程序是InstartCallContent,其destinationType為INCallDestinationTypeNormal。
// 如果此用例可用,請(qǐng)使用CallKit而不是UserNotifications。
@property(class, NS_NONATOMIC_IOSONLY, copy, readonly) UNNotificationSound *defaultRingtoneSound API_AVAILABLE(ios(15.2)) API_UNAVAILABLE(macos, watchos, tvos, macCatalyst);
// 用于關(guān)鍵警報(bào)的默認(rèn)聲音。嚴(yán)重警報(bào)將繞過(guò)靜音開(kāi)關(guān),且不會(huì)干擾
@property(class, NS_NONATOMIC_IOSONLY, copy, readonly) UNNotificationSound *defaultCriticalSound API_AVAILABLE(ios(12.0), watchos(5.0)) API_UNAVAILABLE(tvos);
// The default sound used for critical alerts with a custom audio volume level. Critical alerts will bypass the mute switch and Do Not Disturb. The audio volume is expected to be between 0.0f and 1.0f.
/// 嚴(yán)重警報(bào)將繞過(guò)靜音開(kāi)關(guān),且不會(huì)干擾。
/// @param volume 音頻音量預(yù)計(jì)在0.0f到1.0f之間。
+ (instancetype)defaultCriticalSoundWithAudioVolume:(float)volume API_AVAILABLE(ios(12.0), watchos(5.0)) API_UNAVAILABLE(tvos);
// 為通知播放的聲音文件。聲音必須位于應(yīng)用程序數(shù)據(jù)容器的Library/Sounds文件夾或應(yīng)用程序組數(shù)據(jù)容器的Library/Sounds文件夾中。
// 如果在容器中找不到該文件,系統(tǒng)將在應(yīng)用程序包中查找。
+ (instancetype)soundNamed:(UNNotificationSoundName)name API_UNAVAILABLE(watchos, tvos);
+ (instancetype)ringtoneSoundNamed:(UNNotificationSoundName)name API_AVAILABLE(ios(15.2)) API_UNAVAILABLE(macos, watchos, tvos, macCatalyst);
+ (instancetype)criticalSoundNamed:(UNNotificationSoundName)name API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos, tvos);
+ (instancetype)criticalSoundNamed:(UNNotificationSoundName)name withAudioVolume:(float)volume API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos, tvos);
- (instancetype)init NS_UNAVAILABLE;
@end
UNNotificationAttachment
通知的附件,可以存放音頻、圖像或視頻內(nèi)容,創(chuàng)建UNNotificationAttachment對(duì)象時(shí),指定的文件必須在磁盤上,并且文件格式必須是受支持的類型之一。不然會(huì)創(chuàng)建失敗,返回nil。
系統(tǒng)會(huì)在顯示相關(guān)通知之前驗(yàn)證附件。如果是本地通知,請(qǐng)求附加的文件已損壞、無(wú)效或文件類型不受支持,則系統(tǒng)不會(huì)執(zhí)行請(qǐng)求。如果是遠(yuǎn)程通知,系統(tǒng)會(huì)在通知服務(wù)應(yīng)用程序擴(kuò)展完成后驗(yàn)證附件。驗(yàn)證后,系統(tǒng)會(huì)將附件移動(dòng)到附件數(shù)據(jù)存儲(chǔ)中,以便適當(dāng)?shù)牧鞒炭梢栽L問(wèn)這些文件。系統(tǒng)會(huì)復(fù)制應(yīng)用包中的附件。
| 附件類型 | 支持的文件類型 | 文件的最大容量 |
|---|---|---|
| Audio | kUTTypeAudioInterchangeFileFormat kUTTypeWaveformAudio kUTTypeMP3 kUTTypeMPEG4Audio |
5MB |
| Image | kUTTypeJPEG kUTTypeGIF kUTTypePNG |
10MB |
| Movie | kUTTypeMPEG kUTTypeMPEG2Video kUTTypeMPEG4 kUTTypeAVIMovie |
50MB |
@interface UNNotificationAttachment : NSObject <NSCopying, NSSecureCoding>
// 附件的唯一標(biāo)識(shí)
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *identifier;
// 附件文件的url
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSURL *URL;
// 附件的類型
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *type;
// 創(chuàng)建一個(gè)附件,附件的url必須是有效的文件路徑,否則會(huì)返回nil。
/// @param identifier 附件唯一標(biāo)識(shí)
/// @param URL 附件url
/// @param options 附件類型詳情設(shè)置
/// UNNotificationAttachmentOptionsTypeHintKey:附件類型,傳入一個(gè)string值,如果未設(shè)置,則會(huì)根據(jù)文件的擴(kuò)展名設(shè)置文件類型
/// UNNotificationAttachmentOptionsThumbnailHiddenKey:是否隱藏此附件的縮略圖,值是BOOL類型(NSNumber),默認(rèn)為NO
/// UNNotificationAttachmentOptionsThumbnailClippingRectKey:指定用于附件縮略圖的標(biāo)準(zhǔn)化剪裁矩形
/// (上)該值必須是使用CGRectCreateDictionaryRepresentation編碼的CGRect
/// UNNotificationAttachmentOptionsThumbnailTimeKey:指定要用作縮略圖的動(dòng)畫圖像幀數(shù)或電影時(shí)間。
/// (上)該值動(dòng)畫圖像幀編號(hào)必須是NSNumber類型
/// 該值電影時(shí)間必須是以秒為單位的NSNumber,或者是使用CMTimeCopyAsDictionary編碼的CMTime。
/// @param error 創(chuàng)建附件的報(bào)錯(cuò)信息
+ (nullable instancetype)attachmentWithIdentifier:(NSString *)identifier URL:(NSURL *)URL options:(nullable NSDictionary *)options error:(NSError *__nullable *__nullable)error;
UNNotificationContentExtension
該擴(kuò)展是為應(yīng)用創(chuàng)建自定義的通知界面的??梢栽赨IViewController里設(shè)置界面,在didReceiveNotification方法中獲得通知數(shù)據(jù)賦值界面。

UNNotificationContentExtension 的info.plist配置參數(shù)說(shuō)明
| key | value |
|---|---|
| UNNotificationExtensionCategory(必填) | string值或Array值,自定義通知界面的標(biāo)識(shí)符,系統(tǒng)會(huì)根據(jù)通知中category 的名稱自動(dòng)與該值匹配,匹配后會(huì)使用該自定義界面。設(shè)置為string則表示只有一個(gè),設(shè)置為Array則表示有多個(gè) |
| UNNotificationExtensionInitialContentSizeRatio(必填) | 浮點(diǎn)值,表示視圖控制器視圖的初始大小,表示為其高度與寬度的比率。加載自定義通知視圖時(shí),系統(tǒng)使用此值設(shè)置視圖控制器的初始大小,以寬度為基數(shù)。例如,值為0.5時(shí),表示 高度 = 寬度 * 0.5。值為2,表示 高度 = 寬度 * 2 |
| UNNotificationExtensionDefaultContentHidden | BOOL值,表示打開(kāi)自定義通知界面的時(shí)候,是否隱藏默認(rèn)通知的導(dǎo)航標(biāo)題和內(nèi)容,設(shè)置為YES時(shí)只顯示自定義的內(nèi)容,默認(rèn)為NO |
| UNNotificationExtensionOverridesDefaultTitle | BOOL值,設(shè)置為YES時(shí),系統(tǒng)將使用視圖控制器的title屬性作為通知的標(biāo)題。設(shè)置為NO時(shí),系統(tǒng)會(huì)將通知的標(biāo)題設(shè)置為應(yīng)用程序的名稱。默認(rèn)為NO |
| NSExtensionMainStoryboard | 自定義界面對(duì)應(yīng)的SB文件名 |
| NSExtensionPointIdentifier | 是擴(kuò)展的唯一標(biāo)識(shí),設(shè)置為com.apple.usernotifications.content-extension后會(huì)被系統(tǒng)識(shí)別為蘋果通知的Content的擴(kuò)展 |
| UNNotificationExtensionUserInteractionEnabledYES | BOOL類型,設(shè)置為YES后,自定義界面運(yùn)行有交互行為,設(shè)置為NO的話點(diǎn)擊自定義界面會(huì)直接打開(kāi)App |
UNNotificationContentExtension
@protocol UNNotificationContentExtension <NSObject>
//接收即將顯示的通知,在這里獲取通知內(nèi)容,然后根據(jù)通知信息處理顯示通知界面
- (void)didReceiveNotification:(UNNotification *)notification;
@optional
/// 如果設(shè)置Action并實(shí)現(xiàn)了該方法,當(dāng)用戶點(diǎn)擊了按鈕的時(shí)候就會(huì)調(diào)用該方法,可以在該方法中處理點(diǎn)擊事件
/// @param response 觸發(fā)的事件響應(yīng)體
/// @param completion 事件的處理結(jié)果,返回指示對(duì)通知的首選響應(yīng)的常量。UNNotificationContentExtensionResponseOption枚舉類型
/// UNNotificationContentExtensionResponseOptionDoNotDismiss:不要關(guān)閉通知界面
/// UNNotificationContentExtensionResponseOptionDismiss:關(guān)閉通知界面
/// UNNotificationContentExtensionResponseOptionDismissAndForwardAction:關(guān)閉通知界面,并將通知內(nèi)容傳遞給App
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion;
// Implementing this method and returning a button type other that "None" will
// make the notification attempt to draw a play/pause button correctly styled
// for that type.
//媒體播放按鈕的類型
//UNNotificationContentExtensionMediaPlayPauseButtonTypeNone:沒(méi)有播放按鈕
//UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault:播放和暫停的按鈕
//部分透明的播放/暫停按鈕,位于內(nèi)容上方。該屬性會(huì)導(dǎo)致mediaPlayPauseButtonTintColor屬性無(wú)效。
//UNNotificationContentExtensionMediaPlayPauseButtonTypeOverlay
@property (nonatomic, readonly, assign) UNNotificationContentExtensionMediaPlayPauseButtonType mediaPlayPauseButtonType;
//設(shè)置媒體播放按鈕的Frame,相對(duì)于媒體的尺寸
@property (nonatomic, readonly, assign) CGRect mediaPlayPauseButtonFrame;
// 設(shè)置媒體按鈕的顏色
#if TARGET_OS_OSX
@property (nonatomic, readonly, copy) NSColor *mediaPlayPauseButtonTintColor;
#else
@property (nonatomic, readonly, copy) UIColor *mediaPlayPauseButtonTintColor;
#endif
// 播放
- (void)mediaPlay;
// 暫停
- (void)mediaPause;
@end
注意:因?yàn)閁NNotificationServiceExtension與UNNotificationContentExtension是兩個(gè)不同的target,沙盒路徑不同。一般都是在UNNotificationServiceExtension中下載附件資源并保存在UNNotificationServiceExtension的沙盒路徑,然后在UNNotificationContentExtension中接收附件的路徑。因此要訪問(wèn)UNNotificationServiceExtension的路徑需要使用startAccessingSecurityScopedResource與stopAccessingSecurityScopedResource
/* 例 */
- (void)didReceiveNotification:(UNNotification *)notification {
if (notification.request.content.attachments && notification.request.content.attachments.count > 0) {
UNNotificationAttachment *attachment = [notification.request.content.attachments firstObject];
//通過(guò)使用安全作用域解析創(chuàng)建的attachment數(shù)據(jù)而創(chuàng)建的NSURL,使url引用的資源可供進(jìn)程訪問(wèn)。
//startAccessingSecurityScopedResource與stopAccessingSecurityScopedResource需成對(duì)出現(xiàn)
//當(dāng)不再需要訪問(wèn)此資源時(shí),客戶端必須調(diào)用stopAccessingSecurityScopedResource
if ([attachment.URL startAccessingSecurityScopedResource]) {
NSData *imageData = [NSData dataWithContentsOfURL:attachment.URL];
self.imageView.image = [UIImage imageWithData:imageData];
[attachment.URL stopAccessingSecurityScopedResource];
}
}
}
UNNotificationAction
使用UNNotificationAction對(duì)象,可以定義在響應(yīng)已發(fā)送通知時(shí)可以執(zhí)行的操作。例如,會(huì)議App可能會(huì)定義會(huì)議邀請(qǐng)的接受或拒絕的操作,就可以用UNNotificationAction來(lái)設(shè)置接受和拒絕的按鈕,直接在通知界面完成操作,而不用打開(kāi)App進(jìn)行操作,UNNotificationAction最多設(shè)置4個(gè)。
@interface UNNotificationAction : NSObject <NSCopying, NSSecureCoding>
// 事件的唯一標(biāo)識(shí)
@property (NS_NONATOMIC_IOSONLY, copy, readonly) NSString *identifier;
// 事件的標(biāo)題文本
@property (NS_NONATOMIC_IOSONLY, copy, readonly) NSString *title;
// 操作的配置
//UNNotificationActionOptionAuthenticationRequired:執(zhí)行此操作前是否需要解鎖
//UNNotificationActionOptionDestructive:該行為是否應(yīng)被視為具有破壞性,文本會(huì)是紅色
//UNNotificationActionOptionForeground:此操作是否應(yīng)導(dǎo)致應(yīng)用程序在前臺(tái)啟動(dòng)
@property (NS_NONATOMIC_IOSONLY, readonly) UNNotificationActionOptions options;
// 事件的icon圖片對(duì)象
@property (NS_NONATOMIC_IOSONLY, readonly, copy, nullable) UNNotificationActionIcon *icon API_AVAILABLE(macos(12.0), ios(15.0), watchos(8.0), tvos(15.0));
// 創(chuàng)建事件
+ (instancetype)actionWithIdentifier:(NSString *)identifier title:(NSString *)title options:(UNNotificationActionOptions)options;
+ (instancetype)actionWithIdentifier:(NSString *)identifier title:(NSString *)title options:(UNNotificationActionOptions)options icon:(nullable UNNotificationActionIcon *)icon API_AVAILABLE(macos(12.0), ios(15.0), watchos(8.0), tvos(15.0));
- (instancetype)init NS_UNAVAILABLE;
@end
UNNotificationActionIcon
//事件的關(guān)聯(lián)圖片
@interface UNNotificationActionIcon : NSObject <NSCopying, NSSecureCoding>
//使用應(yīng)用里的圖片作為事件的icon,圖片需要放在asset中
+ (instancetype)iconWithTemplateImageName:(NSString *)templateImageName;
//使用系統(tǒng)圖片作為icon
+ (instancetype)iconWithSystemImageName:(NSString *)systemImageName;
- (instancetype)init NS_UNAVAILABLE;
UNTextInputNotificationAction
//文本輸入框
@interface UNTextInputNotificationAction : UNNotificationAction
// 事件中顯示的文本輸入按鈕標(biāo)題文本
@property (NS_NONATOMIC_IOSONLY, copy, readonly) NSString *textInputButtonTitle;
// 事件的文本輸入框中的提示文本
@property (NS_NONATOMIC_IOSONLY, copy, readonly) NSString *textInputPlaceholder;
//創(chuàng)建文本輸入框
+ (instancetype)actionWithIdentifier:(NSString *)identifier title:(NSString *)title options:(UNNotificationActionOptions)options textInputButtonTitle:(NSString *)textInputButtonTitle textInputPlaceholder:(NSString *)textInputPlaceholder;
+ (instancetype)actionWithIdentifier:(NSString *)identifier title:(NSString *)title options:(UNNotificationActionOptions)options icon:(nullable UNNotificationActionIcon *)icon textInputButtonTitle:(NSString *)textInputButtonTitle textInputPlaceholder:(NSString *)textInputPlaceholder API_AVAILABLE(macos(12.0), ios(15.0), watchos(8.0), tvos(15.0));
@end
UNNotificationCategory
@interface UNNotificationCategory : NSObject <NSCopying, NSSecureCoding>
// 當(dāng)前category的唯一標(biāo)識(shí)符。當(dāng)UNNotificationCategory的標(biāo)識(shí)符與UNNotificationRequest的categoryIdentifier匹配時(shí),
//UNNotificationCategory的操作將顯示在通知上。
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *identifier;
// 具體的操作數(shù)組,按數(shù)組里Action順序顯示操作
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSArray<UNNotificationAction *> *actions;
//支持的intents的類型
//詳情可以查看<Intents/INIntentIdentifiers.h>
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSArray<NSString *> *intentIdentifiers;
//分配的配置,枚舉
// 將關(guān)閉操作發(fā)送給通知代理
//UNNotificationCategoryOptionCustomDismissAction = (1 << 0),
// CarPlay是否支持此類通知,沒(méi)用過(guò)
//UNNotificationCategoryOptionAllowInCarPlay API_UNAVAILABLE(macos) = (1 << 1),
// 如果用戶已關(guān)閉預(yù)覽,應(yīng)顯示標(biāo)題
//UNNotificationCategoryOptionHiddenPreviewsShowTitle API_AVAILABLE(macos(10.14), ios(11.0)) API_UNAVAILABLE(watchos, tvos) = (1 << 2),
// 如果用戶已關(guān)閉預(yù)覽,應(yīng)顯示字幕
//UNNotificationCategoryOptionHiddenPreviewsShowSubtitle API_AVAILABLE(macos(10.14), ios(11.0)) API_UNAVAILABLE(watchos, tvos) = (1 << 3),
// 允許當(dāng)前通知發(fā)布通知
//UNNotificationCategoryOptionAllowAnnouncement API_DEPRECATED("Announcement option is ignored", ios(13.0, 15.0), watchos(6.0, 7.0)) API_UNAVAILABLE(macos, tvos) = (1 << 4),
@property (NS_NONATOMIC_IOSONLY, readonly) UNNotificationCategoryOptions options;
// 當(dāng)預(yù)覽被隱藏時(shí),會(huì)使用該字符串內(nèi)容替換通知body進(jìn)行顯示
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *hiddenPreviewsBodyPlaceholder API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos);
///該屬性是用于描述,來(lái)自此category的通知分組,它應(yīng)該包含描述性文本和格式參數(shù),這些參數(shù)將被替換為信息
///來(lái)自此分組的通知。將會(huì)把參數(shù)被替換為數(shù)字,以及通過(guò)在每個(gè)分組通知中加入?yún)?shù)而創(chuàng)建的列表。
///例如:“%u來(lái)自%@的新郵件”。
///參數(shù)列表是可選的,“%u條新消息”也被接受。
///格式化中的 %u和 %@,分別對(duì)應(yīng)著NotificationService中的summaryArgumentCount與summaryArgument。
//summaryArgumentCount:該類型的通知摘要條數(shù),一般由系統(tǒng)管理
//summaryArgument:通知摘要文本
@property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *categorySummaryFormat API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos, tvos);
// 創(chuàng)建通知分類
+ (instancetype)categoryWithIdentifier:(NSString *)identifier
actions:(NSArray<UNNotificationAction *> *)actions
intentIdentifiers:(NSArray<NSString *> *)intentIdentifiers
options:(UNNotificationCategoryOptions)options;
+ (instancetype)categoryWithIdentifier:(NSString *)identifier
actions:(NSArray<UNNotificationAction *> *)actions
intentIdentifiers:(NSArray<NSString *> *)intentIdentifiers
hiddenPreviewsBodyPlaceholder:(NSString *)hiddenPreviewsBodyPlaceholder
options:(UNNotificationCategoryOptions)options API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos);
+ (instancetype)categoryWithIdentifier:(NSString *)identifier
actions:(NSArray<UNNotificationAction *> *)actions
intentIdentifiers:(NSArray<NSString *> *)intentIdentifiers
hiddenPreviewsBodyPlaceholder:(nullable NSString *)hiddenPreviewsBodyPlaceholder
categorySummaryFormat:(nullable NSString *)categorySummaryFormat
options:(UNNotificationCategoryOptions)options API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos, tvos);
- (instancetype)init NS_UNAVAILABLE;
@end
擴(kuò)展知識(shí)點(diǎn)
UNUserNotificationCenter
@interface UNUserNotificationCenter : NSObject
// 當(dāng)前應(yīng)用的通知代理對(duì)象
@property (NS_NONATOMIC_IOSONLY, nullable, weak) id <UNUserNotificationCenterDelegate> delegate;
// 當(dāng)前設(shè)備是否支持內(nèi)容擴(kuò)展,YES表示支持
@property (NS_NONATOMIC_IOSONLY, readonly) BOOL supportsContentExtensions;
// 當(dāng)前應(yīng)用的通知
+ (UNUserNotificationCenter *)currentNotificationCenter;
- (instancetype)init NS_UNAVAILABLE;
// 應(yīng)用需要用戶授權(quán)才能通過(guò)本地和遠(yuǎn)程通知使用UNUserNotificationCenter通知用戶
/// @param options 該值是用于想用戶請(qǐng)求交互授權(quán)的配置
/// UNAuthorizationOptionBadge:授權(quán)更新App icon的能力,加角標(biāo)
/// UNAuthorizationOptionSound:授權(quán)播放聲音的能力
/// UNAuthorizationOptionAlert:授權(quán)顯示報(bào)警的能力
/// UNAuthorizationOptionCarPlay:授權(quán)能在CarPlay中顯示通知的能力
/// UNAuthorizationOptionCriticalAlert:授權(quán)能夠播放警報(bào)聲音的能力
/// UNAuthorizationOptionProvidesAppNotificationSettings:授權(quán)系統(tǒng)顯示App通知設(shè)置按鈕的能力
/// UNAuthorizationOptionProvisional:授權(quán)能夠?qū)o(wú)中斷通知臨時(shí)發(fā)布到通知中心的能力
/// UNAuthorizationOptionAnnouncement:不建議使用
/// UNAuthorizationOptionTimeSensitive :不建議使用
/// @param completionHandler granted 表示用戶是否授權(quán),error表示授權(quán)是發(fā)生的錯(cuò)誤
- (void)requestAuthorizationWithOptions:(UNAuthorizationOptions)options completionHandler:(void (^)(BOOL granted, NSError *__nullable error))completionHandler;
// 設(shè)置當(dāng)前通知的類別,用于顯示哪些操作
- (void)setNotificationCategories:(NSSet<UNNotificationCategory *> *)categories API_UNAVAILABLE(tvos);
//獲取通知類別信息
- (void)getNotificationCategoriesWithCompletionHandler:(void(^)(NSSet<UNNotificationCategory *> *categories))completionHandler API_UNAVAILABLE(tvos);
// 獲取App通知設(shè)置
- (void)getNotificationSettingsWithCompletionHandler:(void(^)(UNNotificationSettings *settings))completionHandler;
// 添加一個(gè)通知請(qǐng)求對(duì)象,將用相同的標(biāo)識(shí)符的通知請(qǐng)求替換未當(dāng)前添加的通知請(qǐng)求。
// 如果標(biāo)識(shí)符為現(xiàn)有已送達(dá)的通知,通知請(qǐng)求將針對(duì)新通知請(qǐng)求發(fā)出警報(bào),并在觸發(fā)時(shí)替換現(xiàn)有已送達(dá)通知
// app中未送達(dá)通知請(qǐng)求的數(shù)量受系統(tǒng)限制,具體的限制還未測(cè)試過(guò)
- (void)addNotificationRequest:(UNNotificationRequest *)request withCompletionHandler:(nullable void(^)(NSError *__nullable error))completionHandler;
// 獲取未送達(dá)的通知請(qǐng)求
- (void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray<UNNotificationRequest *> *requests))completionHandler;
// 根據(jù)通知請(qǐng)求的唯一標(biāo)識(shí)移除未送達(dá)的通知
- (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers;
// 移除所有未送達(dá)的通知
- (void)removeAllPendingNotificationRequests;
// 獲取已送達(dá)并保留在通知中心的通知
- (void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray<UNNotification *> *notifications))completionHandler API_UNAVAILABLE(tvos);
// 根據(jù)通知的唯一標(biāo)識(shí)移除已送達(dá)的通知
- (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers API_UNAVAILABLE(tvos);
// 移除所有已送達(dá)的通知
- (void)removeAllDeliveredNotifications API_UNAVAILABLE(tvos);
@end
//當(dāng)前app的通知代理
@protocol UNUserNotificationCenterDelegate <NSObject>
@optional
/// 只有App處于前臺(tái)的時(shí)候才會(huì)調(diào)用該方法,如果為實(shí)現(xiàn)該方法或者為及時(shí)執(zhí)行completionHandler,則不會(huì)顯示該條通知。
/// App可以通過(guò)返回的options決定通知顯示的方式。
/// @param center 當(dāng)前App的通知管理對(duì)象
/// @param notification 通知
/// @param completionHandler options:指示如何在前臺(tái)應(yīng)用程序中顯示通知的常量
/// UNNotificationPresentationOptionBadge:將通知的角標(biāo)值應(yīng)用于App的icon
/// UNNotificationPresentationOptionSound:播放與通知相關(guān)的聲音
/// UNNotificationPresentationOptionAlert:不建議使用
/// UNNotificationPresentationOptionList:在通知中心顯示通知
/// UNNotificationPresentationOptionBanner:以橫幅形式顯示通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(macos(10.14), ios(10.0), watchos(3.0), tvos(10.0));
// 當(dāng)用戶通過(guò)打開(kāi)App、拒絕通知或選擇不通知的操作來(lái)響應(yīng)通知時(shí)
// 將對(duì)委托調(diào)用該方法。通知的代理必須在didFinishLaunchingWithOptions:之前設(shè)置
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler API_AVAILABLE(macos(10.14), ios(10.0), watchos(3.0)) API_UNAVAILABLE(tvos);
// 當(dāng)App響應(yīng)用戶查看應(yīng)用內(nèi)通知設(shè)置的請(qǐng)求而啟動(dòng)時(shí),代理將調(diào)用該方法
// 添加未授權(quán)選項(xiàng)將AppNotificationSettings作為requestAuthorizationWithOptions:completionHandler中的選項(xiàng)提供
// 將按鈕添加到“設(shè)置”中的“內(nèi)聯(lián)通知設(shè)置”視圖和“通知設(shè)置”視圖中。從設(shè)置打開(kāi)時(shí),通知將為零。
- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(nullable UNNotification *)notification API_AVAILABLE(macos(10.14), ios(12.0)) API_UNAVAILABLE(watchos, tvos);
@end
通知消息體說(shuō)明
{
aps = {//消息推送的主題內(nèi)容
alert = {//推送的彈窗顯示內(nèi)容
body = "\U5b87\U4f73\U7684\U5185\U5bb9”;//顯示的內(nèi)容文本
title = "\U5b87\U4f73\U7684\U6807\U9898”;//顯示的標(biāo)題文本
};
sound = default;//通知的聲音
category = myImageNotificationCategory;//通知對(duì)應(yīng)的自定義Content標(biāo)識(shí)
thread-id = 10923;//通知分組的id
mutable-content = 1;//通知是否走自定義內(nèi)容的標(biāo)記,只有該值設(shè)置為1的時(shí)候才會(huì)走擴(kuò)展內(nèi)容,否則走系統(tǒng)通知
};
}
后續(xù)會(huì)補(bǔ)充一下 如何在Service和Content里引用其他工程資源
示例代碼
#import "NotificationService.h"
//語(yǔ)音播放需要
#import <AVFoundation/AVFoundation.h>
@interface NotificationService () <AVSpeechSynthesizerDelegate>
//用于告知系統(tǒng)已經(jīng)處理完成,可以將通知內(nèi)容傳給App的回調(diào)對(duì)象。
//該對(duì)象需要返回一個(gè)UNMutableNotificationContent對(duì)象。一般都是返回bestAttemptContent
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
//通知消息的內(nèi)容對(duì)象,里面包含了通知相關(guān)的所有信息。一般有title、body、subTitle。
//如果是服務(wù)端封裝的擴(kuò)展參數(shù),則一般都在userInfo中。
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@property (nonatomic, strong) AVSpeechSynthesisVoice *synthesisVoice;
@property (nonatomic, strong) AVSpeechSynthesizer *synthesizer;
//資源文件
@property (nonatomic, strong) NSMutableArray<UNNotificationAttachment *> *attachments;
@end
@implementation NotificationService
/// 可以通過(guò)重寫此方法來(lái)實(shí)現(xiàn)自定義推送通知修改。
///如果要使用修改后的通知內(nèi)容,則需要在該方法中調(diào)用contentHandler傳遞修改后的通知內(nèi)容。如果在服務(wù)時(shí)間(30秒)到期之前未調(diào)用處理程序contentHandler,則將傳遞未修改的通知。
/// @param request 通知內(nèi)容
/// @param contentHandler 處理結(jié)果,需要返回一個(gè)UNNotificationContent的通知內(nèi)容
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
//接收回調(diào)對(duì)象
self.contentHandler = contentHandler;
//將收到的通知內(nèi)容 copy并賦值到對(duì)象屬性中,向下傳遞
self.bestAttemptContent = [request.content mutableCopy];
//獲取通知信息
NSDictionary *userInfo = request.content.userInfo;
if (userInfo) {
//獲取通知中aps信息
NSDictionary *aps = [userInfo objectForKey:@"aps"];
NSDictionary *params = [userInfo objectForKey:@"params"];
if (aps && params) {
NSString *type = [params objectForKey:@"myNotificationType"];
if (type) {
BOOL isActionContentHandler = YES;
if ([type isEqualToString:@"HYJFileTypeImage"]) {//下載圖片
[self downFileWithUrl:[aps objectForKey:@"imageUrl"] withFileType:@"HYJFileTypeImage"];
} else if ([type isEqualToString:@"HYJFileTypeSound"]) {//下載音頻文件
[self downFileWithUrl:[aps objectForKey:@"imageUrl"] withFileType:@"HYJFileTypeImage"];
[self downFileWithUrl:[params objectForKey:@"soundUrl"] withFileType:@"HYJFileTypeSound"];
} else if ([type isEqualToString:@"HYJFileTypeVideo"]) {//下載視頻
[self downFileWithUrl:[params objectForKey:@"videoUrl"] withFileType:@"HYJFileTypeVideo"];
} else if ([type isEqualToString:@"pay"]) {//支付播報(bào)
if (@available(iOS 12.1,*)) {
//背景:12.1以后蘋果不允許在Service中合成語(yǔ)音或文字轉(zhuǎn)語(yǔ)音
//方案1,使用VOIP,喚醒App,由App完成語(yǔ)音的播報(bào)
//方案2,收到遠(yuǎn)程通知后,循環(huán)發(fā)送本地通知,通知中播放本地拆分開(kāi)的音頻文件,這樣可以減少音頻文件的數(shù)量
//方案3,本地預(yù)置大量的音頻文件,例如:“支付寶收款100元.mp3”。包的體積會(huì)很大
//方案4,服務(wù)端生成tts文件,客戶端在Service里下載,然后設(shè)置通知的聲音為tts文件 (目前采用的方案)
NSURL *saveUrl = [self downFile:[params objectForKey:@"soundUrl"] withFileType:@"HYJFileTypeSound"];
UNNotificationSound *sound = [UNNotificationSound soundNamed:saveUrl.absoluteString];
self.bestAttemptContent.sound = sound;
} else {
isActionContentHandler = NO;
[self playPaySound:[params objectForKey:@"pay"] isPayments:NO];
}
} else if ([type isEqualToString:@"payments"]) {//收款播報(bào)
isActionContentHandler = NO;
[self playPaySound:[params objectForKey:@"payments"] isPayments:NO];
}
if (self.attachments.count > 0) {
self.bestAttemptContent.attachments = self.attachments;
}
if (isActionContentHandler) {
self.contentHandler(self.bestAttemptContent);
}
} else {
self.contentHandler(self.bestAttemptContent);
}
} else {
self.contentHandler(self.bestAttemptContent);
}
} else {
self.contentHandler(self.bestAttemptContent);
}
}
//當(dāng)didReceiveNotificationRequest的方法執(zhí)行超過(guò)30秒未調(diào)用contentHandler時(shí)
//系統(tǒng)會(huì)自動(dòng)調(diào)用serviceExtensionTimeWillExpire方法,給我們最后一次彌補(bǔ)處理的機(jī)會(huì)
//可以在serviceExtensionTimeWillExpire方法中設(shè)置didReceiveNotificationRequest方法中未完成數(shù)據(jù)的默認(rèn)值
- (void)serviceExtensionTimeWillExpire {
self.contentHandler(self.bestAttemptContent);
}
#pragma mark - Private Method
/// 下載文件
/// @param urlString 文件的url
/// @param type 文件的類型
- (void)downFileWithUrl:(NSString *)urlString withFileType:(NSString *)type
{
if (!urlString || urlString.length <= 0) {
return;
}
//傳給自定義通知欄的URL
NSURL *saveUrl = [self downFile:urlString withFileType:type];
if (!saveUrl) {
return;
}
UNNotificationAttachment *attachment;
if ([type isEqualToString:@"HYJFileTypeVideo"]) {
NSDictionary *options = @{@"UNNotificationAttachmentOptionsTypeHintKey":@"kUTTypeMPEG",@"UNNotificationAttachmentOptionsThumbnailHiddenKey":[NSNumber numberWithBool:NO],@"UNNotificationAttachmentOptionsThumbnailTimeKey":[NSNumber numberWithInt:2]};
attachment = [UNNotificationAttachment attachmentWithIdentifier:@"attachment" URL:saveUrl options:options error:nil];
} else if ([type isEqualToString:@"HYJFileTypeSound"]) {
NSDictionary *options = @{@"UNNotificationAttachmentOptionsTypeHintKey":@"kUTTypeMP3"};
attachment = [UNNotificationAttachment attachmentWithIdentifier:@"attachment" URL:saveUrl options:options error:nil];
} else {//暫時(shí)未考慮動(dòng)圖
attachment = [UNNotificationAttachment attachmentWithIdentifier:@"attachment" URL:saveUrl options:nil error:nil];
}
if (attachment) {
[self.attachments addObject:attachment];
}
// });
}
/// 播放支付聲音
/// @param money 價(jià)格
- (void)playPaySound:(NSString *)money isPayments:(BOOL)isPayments
{
if (!money || money.length <=0) {
self.contentHandler(self.bestAttemptContent);
return;
}
NSString *payStr = @"";
if (isPayments) {
payStr = [NSString stringWithFormat:@"您在App中收到了 %@ 元",money];
} else {
payStr = [NSString stringWithFormat:@"您在App中支付了 %@ 元",money];
}
[self playSoundText:payStr];
}
- (void)playSoundText:(NSString *)text
{
//文本內(nèi)容不宜過(guò)長(zhǎng),超過(guò)30秒會(huì)播報(bào)不完整,具體的播報(bào)字?jǐn)?shù)與播放速度需要自己計(jì)算
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:text];
[self.synthesizer stopSpeakingAtBoundary:(AVSpeechBoundaryImmediate)];
utterance.rate = 1;
utterance.voice = self.synthesisVoice;
[self.synthesizer speakUtterance:utterance];
}
/// 下載文件
/// @param urlString 文件的url路徑
/// @param type 文件的類型
- (NSURL *)downFile:(NSString *)urlString withFileType:(NSString *)type
{
//1. 下載
// dispatch_queue_t queue = dispatch_queue_create("yujia_notification_queue", DISPATCH_QUEUE_SERIAL);
// dispatch_async(queue, ^{
//下載圖片數(shù)據(jù)
NSURL *url = [NSURL URLWithString:urlString];
NSError *error;
/**
注:
dataWithContentsOfURL方法是同步方法,一般不建議用來(lái)請(qǐng)求基于網(wǎng)絡(luò)的URL。對(duì)于基于網(wǎng)絡(luò)的URL,此方法可以在慢速網(wǎng)絡(luò)上阻止當(dāng)前線程數(shù)十秒,會(huì)導(dǎo)致用戶體驗(yàn)不佳,并且可能會(huì)導(dǎo)致應(yīng)用程序終止。
*/
NSData *fileData = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&error];
if (error) {
return nil;
}
//確定文件保存路徑,這里要注意文件是保存在NotificationService這個(gè)應(yīng)用沙盒中,并不是保存在主應(yīng)用中
NSString *userDocument = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//附件保存的路徑
NSString *path = @"";
if ([type isEqualToString:@"HYJFileTypeImage"]) {
path = [NSString stringWithFormat:@"%@/notification.jpg", userDocument];
} else if ([type isEqualToString:@"HYJFileTypeSound"])
{
path = [NSString stringWithFormat:@"%@/notification.mp3", userDocument];
} else if ([type isEqualToString:@"pay"]){
path = [self getFilePath];
} else {
path = [NSString stringWithFormat:@"%@/notification.mp4", userDocument];
}
//先刪除老的文件
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
//再保存新文件
[fileData writeToFile:path atomically:YES];
//傳給自定義通知欄的URL
NSURL *saveUrl = [NSURL fileURLWithPath:path];
return saveUrl;
}
- (NSString *)getFilePath
{
NSString *filePath = @"";
//通過(guò)App組,獲取主App沙盒路徑l
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.yujia.mpaas.demo"];
NSString *groupPath = [groupURL path];
//獲取的文件路徑
filePath = [groupPath stringByAppendingPathComponent:@"Library/Sounds"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:filePath])
{
NSError *error;
[fileManager createDirectoryAtPath:filePath withIntermediateDirectories:NO attributes:nil error:&error];
if (error) {
NSLog(@"error:%@",error);
}
}
NSString *pathFile = [NSString stringWithFormat:@"%@/%@",filePath,@"pay.wav"];
return pathFile;
}
#pragma mark - AVSpeechSynthesizerDelegate
// 新增語(yǔ)音播放代理函數(shù),在語(yǔ)音播報(bào)完成的代理函數(shù)中,我們添加下面的一行代碼
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {
// 語(yǔ)音播放完成后調(diào)用
self.contentHandler(self.bestAttemptContent);
}
#pragma mark - LazyLoad
- (AVSpeechSynthesisVoice *)synthesisVoice {
if (!_synthesisVoice) {
_synthesisVoice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
}
return _synthesisVoice;
}
- (AVSpeechSynthesizer *)synthesizer {
if (!_synthesizer) {
_synthesizer = [[AVSpeechSynthesizer alloc] init];
_synthesizer.delegate = self;
}
return _synthesizer;
}
- (NSMutableArray<UNNotificationAttachment *> *)attachments
{
if (!_attachments) {
_attachments = [NSMutableArray new];
}
return _attachments;
}
@end
#import "NotificationViewController.h"
#import <UserNotifications/UserNotifications.h>
#import <UserNotificationsUI/UserNotificationsUI.h>
#import "HYJImageCell.h"
#import "HYJSoundCell.h"
#import "HYJVideoCell.h"
#import <Masonry/Masonry.h>
#import <AVFoundation/AVFoundation.h>
#define PRAISE @"myImageNotificationCategory_action_praise"
#define STARTAPP @"myImageNotificationCategory_action_startApp"
#define CANCEL @"myImageNotificationCategory_action_cancel"
#define InputText @"myImageNotificationCategory_action_inputText1"
@interface NotificationViewController () <UNNotificationContentExtension, UITableViewDelegate, UITableViewDataSource>
/** tableView */
@property (nonatomic, strong) UITableView *tableView;
/** 數(shù)據(jù)源 */
@property (nonatomic, strong) NSMutableArray *dataArray;
/** 當(dāng)前數(shù)據(jù)類型 */
@property (nonatomic, copy) NSString *type;
//Action的回調(diào)
@property (nonatomic, strong) void (^completion)(UNNotificationContentExtensionResponseOption option);
//播放器
@property (nonatomic , strong) AVPlayer *avPlayer;
//
@property (nonatomic , strong) AVAudioSession *audioSession;
//播放器視圖
@property (nonatomic , strong) AVPlayerLayer *playerLayer;
@end
@implementation NotificationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any required interface initialization here.
/** tableView */
self.tableView = [UITableView new];
[self.view addSubview:self.tableView];
// [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
// make.edges.equalTo(self.view);
// }];
self.tableView.frame = self.view.frame;
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.tableView registerClass:[HYJImageCell class] forCellReuseIdentifier:@"HYJImageCell"];
[self.tableView registerClass:[HYJSoundCell class] forCellReuseIdentifier:@"HYJSoundCell"];
[self.tableView registerClass:[HYJVideoCell class] forCellReuseIdentifier:@"HYJVideoCell"];
}
#pragma mark - UNNotificationContentExtension
//當(dāng)通知中category與當(dāng)前推送擴(kuò)展的UNNotificationExtensionCategory相同時(shí)
//該方法將被調(diào)用,用于接收通知的內(nèi)容進(jìn)行展示
- (void)didReceiveNotification:(UNNotification *)notification {
if (notification.request.content.attachments && notification.request.content.attachments.count > 0) {
//獲取通知信息
NSDictionary *userInfo = notification.request.content.userInfo;
if (userInfo) {
//獲取通知中aps信息
NSDictionary *aps = [userInfo objectForKey:@"aps"];
NSDictionary *params = [userInfo objectForKey:@"params"];
if (aps && params) {
UNNotificationAttachment *attachment = [notification.request.content.attachments firstObject];
NSString *type = [params objectForKey:@"myNotificationType"];
if (type) {
self.type = type;
if([type isEqualToString:@"HYJFileTypeImage"]) {
//通過(guò)使用安全作用域解析創(chuàng)建的attachment數(shù)據(jù)而創(chuàng)建的NSURL,使url引用的資源可供進(jìn)程訪問(wèn)。
//startAccessingSecurityScopedResource與stopAccessingSecurityScopedResource需成對(duì)出現(xiàn)
//當(dāng)不再需要訪問(wèn)此資源時(shí),客戶端必須調(diào)用stopAccessingSecurityScopedResource
[self setImageCategory];
if ([attachment.URL startAccessingSecurityScopedResource]) {
NSData *imageData = [NSData dataWithContentsOfURL:attachment.URL];
[self.dataArray removeAllObjects];
[self.dataArray addObject:[UIImage imageWithData:imageData]];
[attachment.URL stopAccessingSecurityScopedResource];
}
} else if ([type isEqualToString:@"HYJFileTypeSound"]) {
if (notification.request.content.attachments.count >= 2) {
[self setSoundCategory];
if ([attachment.URL startAccessingSecurityScopedResource]) {
NSData *imageData = [NSData dataWithContentsOfURL:attachment.URL];
[self.dataArray removeAllObjects];
[self.dataArray addObject:[UIImage imageWithData:imageData]];
[attachment.URL stopAccessingSecurityScopedResource];
}
UNNotificationAttachment *soundAttachment = [notification.request.content.attachments lastObject];
[self createrMediaPlay:YES withUlr:soundAttachment.URL];
}
} else if ([type isEqualToString:@"HYJFileTypeVideo"]) {
[self setSoundCategory];
[self createrMediaPlay:NO withUlr:attachment.URL];
[self.dataArray removeAllObjects];
[self.dataArray addObject:attachment.URL];
}
[self.tableView reloadData];
}
}
}
}
}
/// 當(dāng)實(shí)現(xiàn)了Actions點(diǎn)擊事件時(shí),用戶點(diǎn)擊后將會(huì)回調(diào)該方法
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion
{
self.completion = completion;
//獲取通知信息
NSDictionary *userInfo = response.notification.request.content.userInfo;
if (userInfo) {
//獲取通知中aps信息
NSDictionary *aps = [userInfo objectForKey:@"aps"];
if (aps && self.type) {
// NSString *type = [aps objectForKey:@"myNotificationType"];
NSString *identifier = response.actionIdentifier;
if ([self.type isEqualToString:@"HYJFileTypeImage"]) {
[self actionImageWithIdentifier:identifier];
} else if ([self.type isEqualToString:@"HYJFileTypeSound"]) {
[self actionSoundWithResponse:response];
} else if ([self.type isEqualToString:@"HYJFileTypeVideo"]) {
[self actionVideoWithIdentifier:identifier];
}
}
}
}
- (UNNotificationContentExtensionMediaPlayPauseButtonType)mediaPlayPauseButtonType
{
// return UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault;
//該屬性會(huì)導(dǎo)致mediaPlayPauseButtonTintColor無(wú)效
return UNNotificationContentExtensionMediaPlayPauseButtonTypeOverlay;
}
- (CGRect)mediaPlayPauseButtonFrame
{
CGFloat width = self.view.frame.size.width;
if (self.view.frame.size.width > self.view.frame.size.height) {
width = self.view.frame.size.height;
}
return CGRectMake((self.view.frame.size.width-width/2)/2, (self.view.frame.size.height-width/2)/2, width/2, width/2);
}
- (UIColor *)mediaPlayPauseButtonTintColor
{
return [UIColor orangeColor];
}
- (void)mediaPlay
{
if (self.avPlayer) {
[self.avPlayer play];
}
}
- (void)mediaPause
{
if (self.avPlayer) {
[self.avPlayer pause];
}
}
#pragma mark - UITableViewDelegate, UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.type isEqualToString:@"HYJFileTypeImage"])
{
HYJImageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"HYJImageCell"];
if (!cell) {
cell = [[HYJImageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"HYJImageCell"];
}
[cell setCellImage:[self.dataArray firstObject]];
return cell;
} else if ([self.type isEqualToString:@"HYJFileTypeSound"]) {
HYJSoundCell *cell = [tableView dequeueReusableCellWithIdentifier:@"HYJSoundCell"];
if (!cell) {
cell = [[HYJSoundCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"HYJSoundCell"];
}
// [cell setSoundUrl:[self.dataArray firstObject]];
cell.coverImageView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[cell setCellImage:[self.dataArray firstObject]];
return cell;
} else {
HYJVideoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"HYJVideoCell"];
if (!cell) {
cell = [[HYJVideoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"HYJVideoCell"];
}
if (self.playerLayer) {
[cell.layer addSublayer:self.playerLayer];
}
return cell;
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataArray.count;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.type isEqualToString:@"HYJFileTypeImage"]) {
UIImage *image = [self.dataArray firstObject];
if (image) {
CGSize imageSize = image.size;
if (imageSize.width > viewW) {
imageSize.height = imageSize.width/viewW*viewH;
}
return imageSize.height+10;
} else {
return 0;
}
} else if ([self.type isEqualToString:@"HYJFileTypeSound"]) {
return self.view.frame.size.height;
} else {
return 250;
}
}
#pragma mark - NSNotificationCenter
- (void)playDidEnd:(NSNotification*)notification
{
//重置播放
AVPlayerItem *item = [notification object];
//設(shè)置從0開(kāi)始
[item seekToTime:kCMTimeZero];
//播放往后,狀態(tài)設(shè)置為暫停
[self.extensionContext mediaPlayingPaused];
}
#pragma mark - Private Method
- (void)setImageCategory
{
UNNotificationAction *praiseAction = [UNNotificationAction actionWithIdentifier:PRAISE title:@"點(diǎn)贊" options:UNNotificationActionOptionAuthenticationRequired];
UNNotificationAction *startAppAction = [UNNotificationAction actionWithIdentifier:STARTAPP title:@"查看詳情" options:UNNotificationActionOptionForeground];
UNNotificationAction *cancelAction = [UNNotificationAction actionWithIdentifier:CANCEL title:@"取消" options:UNNotificationActionOptionDestructive];
NSArray *actionArray = @[praiseAction,startAppAction,cancelAction];
UNNotificationCategory *category;
if (@available(iOS 12.0,*)) {
//myImageNotificationCategory_01
category = [UNNotificationCategory categoryWithIdentifier:@"myImageNotificationCategory" actions:actionArray intentIdentifiers:@[] hiddenPreviewsBodyPlaceholder:nil categorySummaryFormat:@"宇佳測(cè)試,您還有%u條來(lái)自%@的消息" options:UNNotificationCategoryOptionCustomDismissAction];
} else {
category = [UNNotificationCategory categoryWithIdentifier:@"myImageNotificationCategory_01" actions:actionArray intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
}
NSSet *sets = [NSSet setWithObject:category];
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:sets];
}
- (void)setSoundCategory
{
UNTextInputNotificationAction *inputTextAction = [UNTextInputNotificationAction actionWithIdentifier:InputText title:@"評(píng)論" options:UNNotificationActionOptionAuthenticationRequired textInputButtonTitle:@"發(fā)送" textInputPlaceholder:@"請(qǐng)輸入評(píng)論內(nèi)容"];
UNNotificationAction *playAction = [UNNotificationAction actionWithIdentifier:@"play" title:@"播放" options:UNNotificationActionOptionAuthenticationRequired];
UNNotificationAction *pausedAction = [UNNotificationAction actionWithIdentifier:@"paused" title:@"暫停" options:UNNotificationActionOptionForeground];
UNNotificationAction *cancelAction = [UNNotificationAction actionWithIdentifier:CANCEL title:@"取消" options:UNNotificationActionOptionDestructive];
NSArray *actionArray = @[inputTextAction,playAction,pausedAction,cancelAction];
UNNotificationCategory *category;
if (@available(iOS 12.0,*)) {
category = [UNNotificationCategory categoryWithIdentifier:@"myImageNotificationCategory" actions:actionArray intentIdentifiers:@[] hiddenPreviewsBodyPlaceholder:nil categorySummaryFormat:@"宇佳測(cè)試,您還有%u條來(lái)自%@的消息" options:UNNotificationCategoryOptionCustomDismissAction];
} else {
category = [UNNotificationCategory categoryWithIdentifier:@"myImageNotificationCategory_01" actions:actionArray intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
}
NSSet *sets = [NSSet setWithObject:category];
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:sets];
}
- (void)actionImageWithIdentifier:(NSString *)identifier
{
if ([identifier isEqualToString:PRAISE]) {
self.completion(UNNotificationContentExtensionResponseOptionDoNotDismiss);
//dosomething
} else if ([identifier isEqualToString:STARTAPP]) {
self.completion(UNNotificationContentExtensionResponseOptionDismissAndForwardAction);
if (@available(iOS 12.0,*)) {
[self.extensionContext performNotificationDefaultAction];
}
} else if ([identifier isEqualToString:CANCEL]) {
self.completion(UNNotificationContentExtensionResponseOptionDismiss);
if (@available(iOS 12.0,*)) {
[self.extensionContext dismissNotificationContentExtension];
}
}
}
- (void)actionSoundWithResponse:(UNNotificationResponse *)response
{
NSString *identifier = response.actionIdentifier;
if ([identifier isEqualToString:InputText]) {
UNTextInputNotificationResponse *inputAction = (UNTextInputNotificationResponse *)response;
NSLog(@"輸入內(nèi)容:%@",inputAction.userText);
self.completion(UNNotificationContentExtensionResponseOptionDismiss);
} else if ([identifier isEqualToString:@"play"]) {
self.completion(UNNotificationContentExtensionResponseOptionDoNotDismiss);
} else if ([identifier isEqualToString:@"paused"]) {
self.completion(UNNotificationContentExtensionResponseOptionDoNotDismiss);
} else if ([identifier isEqualToString:CANCEL]) {
self.completion(UNNotificationContentExtensionResponseOptionDismiss);
if (@available(iOS 12.0,*)) {
[self.extensionContext dismissNotificationContentExtension];
}
}
}
- (void)actionVideoWithIdentifier:(NSString *)identifier
{
self.completion(UNNotificationContentExtensionResponseOptionDismiss);
}
/*!
計(jì)算文本size
@param text 文本
@param size 最大size
@param font 字體大小
@return 文本size
*/
- (CGSize)autoLabelSize:(NSString *)text maxSize:(CGSize)size font:(CGFloat)font
{
CGRect rect = [text boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:font]} context:nil];
CGSize lastSize = CGSizeMake(ceilf(rect.size.width), ceilf(rect.size.height));
return lastSize;
}
/// 創(chuàng)建媒體播放對(duì)象
/// @param isSound 是否創(chuàng)建音頻播放 YES表示僅音頻 NO表示播放音視頻
/// @param url 資源文件的本地url
- (void)createrMediaPlay:(BOOL)isSound withUlr:(NSURL *)url
{
if ([url startAccessingSecurityScopedResource]) {
AVAsset *asset = [AVAsset assetWithURL:url];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:asset];
if (!self.avPlayer) {
self.avPlayer = [[AVPlayer alloc] initWithPlayerItem:playerItem];
} else {
[self.avPlayer replaceCurrentItemWithPlayerItem:playerItem];
}
self.audioSession = [AVAudioSession sharedInstance];
[self.audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:(AVAudioSessionCategoryOptionDuckOthers) error:nil];
[self.audioSession setActive:YES error:nil];
if (!isSound) {
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
self.playerLayer.frame = self.view.bounds;//放置播放器的視圖
}
//監(jiān)聽(tīng)音頻播放完成
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.avPlayer.currentItem];
[url stopAccessingSecurityScopedResource];
}
}
#pragma mark - LazyLoad
- (NSMutableArray *)dataArray
{
if (!_dataArray) {
_dataArray = [NSMutableArray new];
}
return _dataArray;
}
@end





