360
14
8.
运行应用,然后轻点一个联系人附件。你会看到联系人的信息。
14.5
通知
移动设备最重要的功能之一是,需要你关注时会提醒你。需要关注的事情有很多:想与
你聊天的人发来的短信,来电,闹钟等。这些都是“通知”。
发给用户的通知有两种:本地通知和远程通知。
本地通知由应用发送,根据应用的需要在特定的时刻发出。
远程通知也叫推送通知,由
Apple
的推送通知服务器发出。
本书不讨论远程通知,因为这是一个涉及面很广的话题,需要服务器端基础设施的
支持。如果想自己实现,
Mattt Thompson
开发的
Houston
项目(
https://github.com/
nomad/houston
)很优秀,简化了发送测试推送通知的方法。
本地通知是这样运作的创建
UILocalNotification
类的实例,提供想让用户看到的文本,
然后提供一个发送日期(即发送通知的日期和时间)让系统调度,或者让系统立即显示。
如果用户有
Apple Watch
,还可以定制通知
UI
,自定义应用呈现通知的方式。
通知还可以关联动作。收到通知后,用户向左轻扫(在主屏幕)或者向下轻扫(正在使
用手机时收到)后会显示可以单击的按钮;用户轻点这些按钮后,应用会启动,告诉用
户做什么。
通知可能会让人厌烦。因此
iOS
要求先获得显示通知的权限,才能调度通知。本
节后面会处理这个问题;与其他用户权限问题一样,要知道用户可能会拒绝接收通
知,因此应用要优雅地处理这种情况。
在这个应用中,我们将使用本地通知为笔记添加闹钟。当闹钟响起时,用户可以选择“推
迟”闹钟,即稍后再响。
多媒体、联系人、位置和通知
361
通知不是真正的附件,因此不会存储为文件。我们将在导航栏中提供一个按钮,用
于访问通知“附件”。
1.
打开
Assets.xcassets
,把
Notification
图标添加到静态资源目录中。
2.
打开
Document.swift
3.
localNotification
属性添加到
Document
类中:
var localNotification: UILocalNotification? {
get {
if let allNotifications = UIApplication.sharedApplication()
.scheduledLocalNotifications {
return allNotifications.filter({
(item:UILocalNotification) -> Bool in
//
如果有“属主”,而且将在未来某个时刻出现……
if let owner = item.userInfo?["owner"] as? String where
item.fireDate?.timeIntervalSinceNow > 0
{
//
如果属主等于文档自身的
URL
,那就属于文档
return owner == self.fileURL.absoluteString
} else {
return false
}
}).first
} else {
return nil
}
}
set {
if let currentNotification = self.localNotification {
UIApplication.sharedApplication()
.cancelLocalNotification(currentNotification)
}
if let theNotification = newValue {
var userInfo = theNotification.userInfo ?? [:]
userInfo["owner"] = self.fileURL.absoluteString
theNotification.userInfo = userInfo
UIApplication.sharedApplication()
.scheduleLocalNotification(theNotification)
362
14
}
}
}
这是一个计算属性,因为获取文档的通知要遍历应用的全部本地通知,然后返回属
于文档的那个。设置属性的方式类似把通知对象标记为属于文档,然后提交给系统。
这意味着,在这个应用中,一个文档只能有一个通知。此外,还意味着,通知不会
通过
iCloud
同步。这种限制是一种简化,支持同步和多个通知不利于理解处理通知
的方式。
每个文档都能存储一个通知。通知不会以文件的形式存储在硬盘中,而是由系统调
度。读取
localNotification
属性时,查询应用的所有
UILocalNotification
象,
再检查各个通知的属主(
owner
)是否等于文档的
URL
。如果等于,返回那个通知。
设定这个属性的方式类似,只不过是反过来的收到
UILocalNotification
对象后,
owner
设为文档的
URL
,然后让系统调度。
4.
Document
类中添加
alertCategory
alertSnoozeAction
属性:
static let alertSnoozeAction = "snooze"
static let alertCategory = "notes-alert"
我们要知道用户同意还是拒绝接收通知。只有应用代理的
didRegisterUserNotificat
ionSettings
方法才知道这一信息;然而,我们想让管理通知的视图控制器也知道这
一信息。
为此,我们将创建并发送一个自定义的
NSNotification
。我们在前面用过
NSNotification
NSNotificationCenter
那时我们在应用广播消息时运行一些代
码。为了让应用的其他部分知道用户是同意还是拒绝接收通知,我们要广播一个自定
义的
NSNotification
说“通知”有点过分了。
NSNotification
是一种消息,在应用内部的对象之间发送;
UILocalNotification
是发送给用户的消息。
1.
打开
AppDelegate.swift
2.
在类的定义体外部添加下述属性:
let NotesApplicationDidRegisterUserNotificationSettings
多媒体、联系人、位置和通知
363
= "NotesApplicationDidRegisterUserNotificationSettings"
3.
AppDelegate
类中实现下述方法:
func application(application: UIApplication,
didRegisterUserNotificationSettings notificationSettings:
UIUserNotificationSettings) {
NSNotificationCenter.defaultCenter().postNotificationName(
NotesApplicationDidRegisterUserNotificationSettings,
object: self)
}
当应用代理收到
didRegisterUserNotificationSettings
消息时,使用
NSNotificationCenter
postNotificationName
法广播一个通知。后面将为管理通
知的视图控制器添加接收和处理这个消息的方法。
我们需要一种方式,访问稍后添加的通知视图控制器。我们不会把它添加到附件列表
中,因为那个列表是处理
Attachments
目录中的文件的。我们将在导航栏右边添加一个
UIBarButtonItem
,放在“
Edit
”按钮旁边;那个按钮的图像取决于文档有没有通知。
我们会经常处理导航栏,所以最好把相关的代码写入一个方法中。
1.
updateBarItems
方法添加到
DocumentViewController
类中:
func updateBarItems() {
var rightButtonItems : [UIBarButtonItem] = []
rightButtonItems.append(self.editButtonItem())
let notificationButtonImage : UIImage?
if self.document?.localNotification == nil {
notificationButtonImage = UIImage(named:"Notification-Off")
} else {
notificationButtonImage = UIImage(named:"Notification")
}
let notificationButton =
UIBarButtonItem(image: notificationButtonImage,
style: UIBarButtonItemStyle.Plain,
target: self,
action: "showNotification")
rightButtonItems.append(notificationButton)
self.navigationItem.rightBarButtonItems = rightButtonItems
}
2.
然后在
viewWillAppear
方法中添加下述代码,调用
updateBarItems
方法:
364
14
override func viewWillAppear(animated: Bool) {
//
确保有文档
guard let document = self.document else {
NSLog("No document to display!")
self.navigationController?.popViewControllerAnimated(true)
return
}
//
如果文档尚未打开,把它打开
if document.documentState.contains(UIDocumentState.Closed) {
document.openWithCompletionHandler { (success) -> Void in
if success == true {
self.textView?.attributedText = document.text
self.attachmentsCollectionView?.reloadData()
//
支持搜索这个文档
document.userActivity?.title = document.localizedName
let contentAttributeSet
= CSSearchableItemAttributeSet(
itemContentType: document.fileType!)
contentAttributeSet.title = document.localizedName
contentAttributeSet.contentDescription =
document.text.string
document.userActivity?.contentAttributeSet
= contentAttributeSet
document.userActivity?.eligibleForSearch = true
//
现在参与到这个活动中
document.userActivity?.becomeCurrent()
//
注册状态变化通知
self.stateChangedObserver = NSNotificationCenter
.defaultCenter().addObserverForName(
UIDocumentStateChangedNotification,
object: document,
queue: nil,
usingBlock: { (notification) -> Void in
self.documentStateChanged()
})
self.documentStateChanged()
> self.updateBarItems()
}
else
{
//
无法打开,显示一个提醒框
let alertTitle = "Error"
let alertMessage = "Failed to open document"
let alert = UIAlertController(title: alertTitle,

Get Swift学习手册 now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.