你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 在iOS8創建一個交互性強的本地通知

在iOS8創建一個交互性強的本地通知

編輯:IOS開發基礎

(原文:Creating Interactive Local Notifications in iOS 8 作者:Gabriel Theodoropoulos 譯者:ibenjamin)

通知(Notifications),是App用來和用戶交流的一種方式,特別是當App並沒有在前台運行的時候。通知,正如它的名稱所強調的,被用作向用戶‘通知’一個事件,或者僅僅向用戶提示一條重要信息。總而言之,通知在提示類型的App當中非常有用,甚至在一些別的類型的App當中也是如此。比如,當用戶進入一個指定區域(這是iOS8的新特性),一個下載任務完成,或者當朋友給你發送一條信息的時候,一條通知就可以被顯示出來。無論如何,通知的目的就是獲得用戶的關注,然後他們就能處理通知了。

從編程的角度來說,通知有著一套相當標准的API,可以非常簡單地被實現。不需要太多的腦力,開發者可以根據文檔輕松的在App中加入通知功能。也 就是說,詳細規定由系統轉發的通知的內容,在App啟動時處理通知,最後,從iOS8開始,處理任何由通知指定的動作(actions)。每一個App唯 一改變的只有業務邏輯而已。

圖片1

從iOS8開始,本質上來說有兩種通知:

  1. 本地通知(local notifications):由開發者定義,App觸發。觸發的時間是被事先安排好的。

  2. 遠程通知(remote notifications):這種情況下,通知可以被分成兩個類別:(a)推送通知(The push notifications),被服務器初始化,然後通過APNS,最終到達用戶設備。(b)靜默通知(The silent notifications),其實也是推送通知,但是他們並沒有被展示給用戶,而是立即被App處理以發起某項任務,最後當一切都完成時,一個本地通知 被顯示以提示用戶。

除了以上的2種以外,iOS8引入了地點通知(location notifications)。它其實也是本地通知(local notifications),但是他們只會在用戶一個特定的地理或者iBeacon區域時,才會被觸發。雖然我們看不到什麼細節,地點通知 (location notifications)實現起來也很容易。

這個好消息昭示著一個重要的信息:從iOS8開始,通知被加入了新的特性。簡單地說,從現在開始,當一個通知被展示時,開發者可以指定用戶可觸發的具體的動作(actions),而且甚至不用啟動App也可以處理這個通知。

隨著新特性的引入,通知變得越來越引人注目。用戶被給予一個選擇清單,然後可以命令App立即執行特定的命令。用戶再也不用浪費時間在啟動App、處理通知上了。動作(Actions)使得通知和App越來越強大,當然也極大的提升了用戶體驗。

動作(Actions)可以被歸類到一個類目(categories)下。特別是當App安排了不少通知顯示的時候相當方便。用類目 (categories),一個通知所有相關的動作(actions)都可以一次性的被捆綁和指定。反之,處理動作(actions)也非常簡單,只需要實現其代理方法即可。每一個動作(actions)都有一個特殊的屬性標示符(identifier),被App用來辨別收到的動作(actions)然後適當地處理它。

如你所見,本篇文章的目的就是讓你了解以上所有細節。盡管動作(actions)和類目(categories)是新的技術,你最後會發現,他們其實實現起來並不困難。然而,在我們進入到下一個部分之前,我需要說明我們只會就本地通知(local notifications)進行詳盡說明。我認為,如果想要在本篇文章中將通知的每個種類都詳盡說明,那麼這篇文章就會變得很泛泛,除此之外,我們也會將文章的重點放在對iOS8新引入的功能進行說明。

跟往常一樣,我強烈地推薦你去看看官方文檔。不僅僅是蘋果開發者中心,還去看看 WWDC 2014 #713 session video。將本篇文章和以上結合起來學習,你將會獲得所有通知新特性的知識。而且,我們在這裡並不會討論遠程和地點通知,官方文檔是你學習他們的一個好地方。

關於本地通知

本地通知被安排在指定的日期和時間被App觸發。時刻記在心中,盡管在和用戶通信時通知十分有用,你也應該小心,過分的使用可能會導致較差的用戶體驗。

有幾種方式來提示用戶一個通知,接下來會展示所有支持的通知類型。正如你已經了解的,你可以指定通知類型為他們中的幾個或者所有。

  1. Alert or Banner:通知可以用alert或者banner來顯示,這取決於用戶在設置中得選擇。他們都應當包含通知的消息(當然是可以本地化的)。

  2. 聲音(Sound):當一個通知被送達時,你可以‘告訴’iOS播放一段自定義或者系統默認的聲音。因為用戶不會一直看著設備,被展示的通知有可能會被忽略,所以聲音顯得很有用。但是,對於不重要的通知,聲音不應該被使用。

  3. Badge:當通知到達時,一個badge數字會在App的圖標上顯示。當一個通知到達時,badge數字必增加1,當通知被處理後badge數字減1。當badge數字不為0或者為0,iOS會顯示或者隱藏badge。

在iOS7時,用戶只能點擊通知(或者在鎖屏時滑動)然後關聯的App會啟動,最後處理相關動作。現在開發者可以用戶提供具體的預先定義的動作了。 相關聯的App可以在不被啟動的情況下,處理不關鍵或者重要的任務了,而且也可以根據用戶的選擇來執行不同的代碼。馬上我們就能通過這篇文章來學習如何實 現它。

除了上面所說的,一個本地通知還可以包含附加的數據,此數據可以被App處理。此數據可以被包含在一個用戶信息字典中,App可以通過通知來訪問此數據,在啟動時或者未啟動時。

可以被安排的本地通知的數量並不是無限的,最多有64個本地通知可以被安排和展示。如果多余這個數字,所有超過這個數字的通知都會被廢棄。盡管如此,無論通知是以什麼樣的形式被安排的,最早的那個會被最先展示。

接下來,讓我們看看我們今天學習會使用的到得示例App。

示例App概覽

通過開發一個示例App,我們會學習到所有通知的功能。實際上我們將會開發一款購物清單應用,用戶通過此應用發送的通知會得到他需要購買的物品清單。完成這個,我們需要下面兩個功能:

  1. 添加和刪除一個物品

  2. 選擇一個日期和時間通知用戶。

如你所想,我們只會實現本地通知。通過它,演示iOS8通知的新特性已經足夠了。

為了添加一個新物品,我們需要用到textfield控件。添加的物品會在一個tableview中展示出來,在tableview已經存在的物品 中,可以通過在物品cell左滑動來刪除物品。然後,date picker控件可以被用來設置通知展示的日期和時間。此date picker會在一個按鈕被點擊了之後展示出來,當選擇好了日期之後,這個按鈕會被用來安排通知和重新顯示tableivew。我們會以動畫的形式來顯示 和隱藏date picker,所以我們的App就會顯得更加吸引人啦,值得注意的是這裡也演示的如何在Swift中使用UIView來實現簡單地動畫效果。

對於我們將要安排的本地通知,我們會定義3種不同的動作(除了默認通知以外,所有本地通知都支持讓App啟動)。這些動作會給用戶如下的選擇(我寫下了動作標題和它將要做的事情):

  1. “好,買到了”(OK, got it):這個動作其實並不會做什麼,除了讓通知消失以外。在App中,沒有任何任務將會執行。

  2. “編輯清單”(Edit list):這個動作將會啟動App,然後textfield會獲得焦點,然後用戶可以直接寫下一個新的物品。

  3. “刪除清單”(delete list):這個動作不會啟動App。現存的物品清單將會被刪除,但是App並不會被啟動。下次用戶啟動App時,物品清單就會不見啦。

下面幾張圖演示了這個App的功能。正如我所說的,它很簡單,但是用來達到本篇文章的目的已經足夠啦:

圖片2圖片3

圖片4圖片5

最後再說幾句,我會在實現動作的時候具體講到他們。所以,目前將通知動作當做是根據通知用戶可觸發的方法就行了,所有與他們相關的內容你馬上就會看到了。

基本項目

我們的目標是了解所有和通知相關的新東西而不是從頭創建一個項目,你可以從這裡獲得基本項目。下載它,解壓它,打開它,這個項目將會作為我們實現通知的模板。

這個項目已經設計好了基本界面,在你閱讀下一章之前,請概覽一下此項目。打開Main.storyboard,看看那些subviews和已經連接好的IBoutle和IBAction。

除此之外,你會看到所有ViewController應該實現的協議也已經聲明好了。tableview的datasouce和delegate也已經寫好了,但是裡面並沒有邏輯相關的代碼。這僅僅是為了避免Xcode報錯而已。最後,在viewDidLoad方法中,你可以看到哪些對象被設置了代 理。

這個基本項目很容易理解,所以你沒有必要花太多時間來看它。但是它還是值得你快速的閱覽一下的。

設計一個購物清單

首先,讓我們來實現這個購物清單。本部分我們的目標是通過textfield添加一個物品和在tableivew中展示所有的物品。當然,我們也會 實現刪除已經存在物品的功能。顯然,我們需要一個數據結構來保存我們的數據,並且作為tableview的數據源。接下來,讓我們給 ViewController類添加一個NSmutableArray屬性。確保你選擇的是ViewController.swift文件。在此文件中, 找到IBOutlet屬性聲明然後添加下面的實例變量:

var shoppintList: NSMutableArray!

好了,我們已經完成了第一步。注意到我們並不會在viewDidLoad方法中對它進行初始化,而是在我們會添加新的物品到它之中時初始化它。

現在保存物品的數據結構已經聲明好了,接下來讓我們允許用戶通過textfied添加物品。實際上,我們希望新添加的物品在添加到了數組之 後,tableivew馬上更新顯示它。要實現此功能,我們必須實現textFieldShouldReturn(textField:) 代理方法。正如你在基本項目中所見到的,UITextFieldDelegate協議已經被遵從了,ViewController也設置成為了 txtAddItem textfield的代理類。

在這個代理方法中,我們希望做以下事情:

  • 如果shoppintList為nil,初始化它

  • 將textfield的txt添加為一個新的物品到數組

  • 讓tableview展示新的物品(我們稍後就會實現此功能)

  • 添加新物品後清空textfield的內容

  • 移除textfield的焦點,所以鍵盤就會隱藏了

上面的事情是不是看起來很多?其實不多,以可以通過下面的代碼片段來做這些事情:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    if shoppingList == nil{
        shoppingList = NSMutableArray()
    }
    shoppingList.addObject(textField.text)

    tblShoppingList.reloadData()

    txtAddItem.text = ""
    txtAddItem.resignFirstResponder()

    return true
}

上面的代碼所實現的功能已經非常清楚了,所以我認為不需要更多地討論了。

接下來讓我們在tableview中展示shoppingList的內容。在ViewController類中,tableview的 datasource和delegate方法我們已經加上去了,但是現在我們必須加上合適的代碼以讓它工作。讓我們從簡單的開始,tableview中得 section和row的數量,每個cell的高度。你可以用下面的代碼整體替換項目中的活著只是簡單的替換方法的內容。

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}


func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    var rows = 0

    if let list = shoppingList{
        rows = list.count
    }

    return rows
}


func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return 50.0
}

注意到我們對shoppingList數組進行了空指針判斷。

接下來,讓我們將數組中的每個元素賦值給cell的label,然後他就會在tableview中展示出來了。在此之前,我需要強調一下,在 Interface Builder中有一個cell原型,標示符(identifier)為idCellItem。首先,讓我們重用(dequeue)這個cell,然後將 數組中每個元素賦值給這個cell的label的text屬性。代碼如下:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCellWithIdentifier("idCellItem") as UITableViewCell

    cell.textLabel?.text = shoppingList.objectAtIndex(indexPath.row) as NSString

    return cell
}

下面代碼的類型轉換部分:

cell.textLabel?.text = shoppingList.objectAtIndex(indexPath.row) as NSString

非常重要(as NSString部分),因為我們想讓編譯器清楚我們是想將一個string賦值給label。

現在,我們終於可以添加一個新物品和展示所有的物品在tableview中啦。但其實還差一點點,這裡還差最後一個功能:刪除以存在的物品。

這實現起來很簡單,讓我們定義一個超級簡單的新方法:

func removeItemAtIndex(index: Int) {
    shoppingList.removeObjectAtIndex(index)

    tblShoppingList.reloadData()
}

此方法只接收一個參數,待刪除物品的數組下標。我們使用這個參數,刪除shoppingList數組中對應的物品,然後刷新tableview的數據。

現在讓我們在左滑cell後顯示的刪除按鈕上調用上面的方法。要實現此功能,我們需要實現 tableView(tableView:commitEditingStyle:forRowAtIndexPath:) 代理。在此代理方法中我們調用上面的刪除方法。下面是代碼:

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == UITableViewCellEditingStyle.Delete {
        removeItemAtIndex(indexPath.row)
    }
}

這裡有兩件事情需要注意一下:1.這個if判斷語句是非常必要的,有了這個if判斷,刪除方法就只會在用戶點擊刪除按鈕後被觸發。2.代理方法中的indexPath.row的row就是我們想要刪除的物品的數組下標。

你可能會想,為什麼我們要定義一個新的removeItemAtIndex(index:) 方法呢?畢竟我們只需要2行代碼就能在代理方法中實現刪除了。嗯,現在我不會回答此問題;去搜索思考吧。

最後,我需要強調的是我們並沒有必要向這個簡單的App中加入編輯物品功能。畢竟這也沒什麼難度。我們現在做的已經夠了。

保存和加載清單

盡管前面我們已經實現了這個App的基本功能,但是我們還需要加入另外兩個重要的功能來讓這個App正常的工作。我們需要將清單保存到磁盤,然後在程序啟動時從磁盤讀取清單。

NSMutableArray提供了將數據寫入磁盤和從磁盤讀取數據的方法,我們可以方便使用他們。接下來我們將會定義兩個方法,來保存和讀取數據。首先讓我們實現保存方法,保存到的文件名字為shopping_list:

func saveShoppingList() {
    let pathsArray = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
    let documentsDirectory = pathsArray[0] as String
    let savePath = documentsDirectory.stringByAppendingPathComponent("shopping_list")
    shoppingList.writeToFile(savePath, atomically: true)
}

方法的第一二行代碼,返回了App的Ducument類目。然後我們構建保存的文件的路徑。最後,調用NSMutableArray方法writeToFile(_:atomically:)。這個方法將數據保存到了磁盤。

實現了保存方法後我們就能調用他了。如果你有對前面的App基本功能進行思考,應該能想到我們會在兩個地方調用到保存方法:1.當一個新的物品被添加時,2.刪除一個已經存在的物品時。

首先,在 textFieldShouldReturn(textField:) 代理方法中的return之前添加保存方法:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    ...

    saveShoppingList()

    return true
}

非常好,現在我們可以將新添加的物品保存到磁盤啦。接下來,讓我們到removeItemAtIndex(index:)方法中也加入保存方法:

func removeItemAtIndex(index: Int) {
    ...

    saveShoppingList()
}

接下來任何可能對我們的數據產生影響的操作,都會被保存到磁盤啦。

現在我們可以實現讀取數據的方法啦。首先讓我們來看看方法定義:

func loadShoppingList() {
    let pathsArray = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
    let documentsDirectory = pathsArray[0] as String
    let shoppingListPath = documentsDirectory.stringByAppendingPathComponent("shopping_list")

    if NSFileManager.defaultManager().fileExistsAtPath(shoppingListPath){
        shoppingList = NSMutableArray(contentsOfFile: shoppingListPath)
        tblShoppingList.reloadData()
    }
}

這裡需要提醒一下,在讀取磁盤文件之前我們總是會檢查一下這個文件存不存在。在iOS當中,我們通過NSFileManager來檢查文件存不存 在。如果文件不存在那麼當然什麼事情都不會發生啦。如果文件存在的話,我們就會用文件的內容來初始化shoppingList數組,然後讓 tableview重新加載數據。

最後,我們當然得調用此方法。我們希望App在啟動完畢後馬上加載數據,在viewDidLoad方法中調用它就是一個很好的選擇。

override func viewDidLoad() {
    ...

    loadShoppingList()
}

當我們不啟動App處理通知時,這兩個方法將會非常有用。

選擇一個提醒時間

現在textfield和tableview在我們的修改之後工作的非常好了。對物品清單的管理也幾乎要完成了。我們現在可以將重心移到date picker控件上來了。在這個部分,我們將會做一些非常簡單有趣的事情。我們會以動畫的形式展現date picker,然後我們就能為一個通知選擇一個日期和時間啦。

如果你去看看ViewController的viewDidLoad方法,你會發現一行隱藏date picker控件的代碼:

datePicker.hidden = true

接下來我們將會使Schedule Reminder按鈕像一個開關一樣工作:當點擊它時,date picker將變得可見,tableview將會隱藏,再次點擊它時它又會做完全相反的事情。

如我所說,tableview和date picker之間將會以動畫的形式切換,為了實現這個動畫切換,我竟會定義一個新的方法。在方法中,我會使用UIView的 animateWithDuration(duration:animations:completionHandler:) 方法。這個方法幫助我們快速而方便的創建動畫,如果你曾經使用過它,你應該就會知道它的方便快捷之處。

我們先定義一個animateMyViews(viewToHide:viewToShow:)方法。從方法名就能看出,這個方法接收兩個參數,第 一個是需要隱藏的view,第二個是將要顯示的view。請記住,在此方法中我們需要同時隱藏或者顯示tableview和date picker,所以我們需要在調用此方法是依次傳入合適的參數。

讓我們挪到代碼部分,首先我們來看看方法的實現,然後我們會詳盡的對方法進行討論:

func animateMyViews(viewToHide: UIView, viewToShow: UIView) {
    let animationDuration = 0.35

    UIView.animateWithDuration(animationDuration, animations: { () -> Void in
        viewToHide.transform = CGAffineTransformScale(viewToHide.transform, 0.001, 0.001)
        }) { (completion) -> Void in

            viewToHide.hidden = true
            viewToShow.hidden = false

            viewToShow.transform = CGAffineTransformScale(viewToShow.transform, 0.001, 0.001)

            UIView.animateWithDuration(animationDuration, animations: { () -> Void in
                viewToShow.transform = CGAffineTransformIdentity
            })
    }
}

讓我們來看看這個方法都做了什麼:首先,我們以秒為單位定義了每個動畫的持續時間。注意到,這裡有兩個動畫將會先後順序執行。第一個動畫隱藏需要被隱藏的view,第二個動畫展示需要被展示的view。

首先,我們啟動第一個動畫,然後轉換viewToHide的transform屬性,這樣它的寬和高就會按比例縮小。這個view的實際frame 並不會變化,但是這個動畫會產生一種拉遠消失的漂亮效果。當第一個動畫完成時,在它的動畫完成回調閉包(closure)裡我們首先設置2個view是否 可見,第一個view被隱藏,第二個view變得可見了。然後我們對第二個view的transform屬性立即縮放。最後我們對第二個view的 transform逐步放大到正常值。第二個動畫的最終效果就是拉近。

注意到,如果你想改變動畫的持續時間,簡單的改變animationDuration值就行啦。上面的動畫持續時間是0.7秒鐘(0.35+0.35)。

你應該對這個動畫是如何實現的和最終的效果非常好奇吧?沒關系,馬上你就會知道了,現在我們還差一點點工作。我們現在需要實現唯一的一個IBAction方法。

這個方法將要做的事情非常簡單:首先檢查date picker最近的狀態,如果它是隱藏著的,我們會調用上面的動畫方法並以tableview為第一個參數,date picker為第二個參數。如果它沒有隱藏的話,我們就會給動畫方法傳入相反順序的參數。

@IBAction func scheduleReminder(sender: AnyObject) {
    if datePicker.hidden {
        animateMyViews(tblShoppingList, viewToShow: datePicker)
    }
    else{
        animateMyViews(datePicker, viewToShow: tblShoppingList)
    }

    txtAddItem.enabled = !txtAddItem.enabled
}

上面的if-else語句已經非常清楚了。在這個方法的最後,我們使用一個布爾值標志,來允許或者不允許添加物品,當tableview顯示時允許,反之不允許。

現在,Schedule Reminder按鈕可以被用來切換顯示tableview和date picker啦。稍後,我們會在上面的IBAction方法中加入更多代碼,然後我們就能安排顯示本地通知啦。

現在你可以稍微把玩一下這個App了,在模擬器或者iPhone上面跑跑它吧。試試添加刪除物品和動畫切換。如果你懶得跑它下面的圖片也展示了這個App目前的效果:

規定通知類型

到目前為止我們已經完成了一些有趣和cool的事情,我們的App也如我們所想的運行起來了。從此部分開始,我們將學習本地通知,並實踐每個新的功能。

在開始之前,先說明一下,在本部分和接下來的部分中我們都只會在同一個一個方法中編寫代碼。盡管如此,我們將會一步步的實現它,這樣我們就能討論每一個新功能了。

在本文章的開頭,我簡要的說明了一下本地通知。其中,我說明了通知的集中類型:在alert或者banner中顯示消息(稍後我會道速你如何在他們 之中切換),聲音和badge。本部分,我們會規定通知的類型。總之,我們只會實現在alert和banner中顯示消息,和播放一段聲音。我們不會實現 badge數字,如果你想你可以自己實現它。實現它的方法在本文章的末尾有。

在開始下一個部分之前,有一個非常重要的地方我需要強調一下。所有有關一個App的通知的設置都會在用戶設置中體現出來。如果一個App使用了通 知,那麼在第一次啟動程序時,App會詢問用戶是否允許App發送通知。不管用戶選擇了允許還是不允許,他都可以以後在用戶設置中改變他。所以,在程序啟 動時看到了下圖中的alert時別驚訝,選允許,否則我們就什麼通知都看不懂了。

接下來我們將創建一個叫setupNotificationSettings()的方法。除去定義方法部分,我們只會在這裡編寫一行代碼。也許你會 想,這很簡單,但是其實它做了一個非常重要的工作。通過這行代碼,我們告訴了App我們想要支持的通知類型。將此設置保存進一個變量,以後我們會用到它。

下面是目前的代碼:

func setupNotificationSettings() {
    // Specify the notification types.
    var notificationTypes: UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Sound

}

UIUserNotificationType是一個枚舉(enum)類型,它包含了通知所有可能的類型。如你所見,OR操作符(“|”)用來包括多種類型。在這裡你可以看到通知所有可能的類型。如果你想重溫一下Swift位操作符的話,這裡將會有你想要的。

在我們使用通知類型之前讓我們先看一點別的東西。

創建通知動作(Notification Actions)

前面我籠統的介紹了幾次通知動作,現在讓我們來詳細的了解一下他們。

一個動作就是一個UIMutableUserNotificationAction類的對象。UIMutableUserNotificationAction是iOS8新引入的類,有著許多有用的配置屬性:

  • 標示符(identifier):字符串,標示了一個對於整個App唯一的字符串。很明顯,你永遠不應該在同一個App中定義兩個同樣地標示符。通過此標示符,我們可以決定在用戶點擊不同的通知時,調用哪個動作。

  • 標題(title):用來在展示給用戶的動作按鈕上。可以是簡單地或者本地化的字符串。為了讓用戶能馬上理解動作的含義,一定要仔細考慮這個標題的值,最好是1到2個字符。

  • (destructive):布爾值。當設置為true時,通知中相應地按鈕的背景色會變成紅色。這只會在banner通知中出現。通常,當動作代表著刪除、移除或者其他關鍵的動作是都會被標記為destructive以獲得用戶的注意。

  • authenticationRequired:布爾值。當設置為true時,用戶在點擊動作之前必須確認自己的身份。當一個動作十分關鍵時這非常有用,因為為認證的操作有可能會破壞App的數據。

  • ActivationMode:枚舉。決定App在通知動作點擊後是應該被啟動還是不被啟動。此枚舉有兩個值: (a)UIUserNotificationActivationModeForeground, (b)UIUserNotificationActivationModeBackground。在background中,App被給予了幾秒中來運行 代碼。

當我描述此App時,我說過我們將會創建3中不同的動作:

  1. 一個簡單的通知,點擊後消失,不會做任何事情。

  2. 點擊通知動作後添加一個物品。

  3. 點擊通知動作後刪除整個清單。

讓我們用代碼來實現每個動作。對於每個動作,我都會使用到上面描述的每個屬性。

var justInformAction = UIMutableUserNotificationAction()
justInformAction.identifier = "justInform"
justInformAction.title = "OK, got it"
justInformAction.activationMode = UIUserNotificationActivationMode.Background
justInformAction.destructive = false
justInformAction.authenticationRequired = false

動作的標示符是“提示而已(justInform)”。動作只會在backgroun運行,不會產生任何安全問題,所以我們設置了destructive和authenticationRequired為false。

下一個動作:

var modifyListAction = UIMutableUserNotificationAction()
modifyListAction.identifier = "editList"
modifyListAction.title = "Edit list"
modifyListAction.activationMode = UIUserNotificationActivationMode.Foreground
modifyListAction.destructive = false
modifyListAction.authenticationRequired = true

很明顯,為了讓用戶能夠標記物品清單,我們需要App啟動。而且我們不希望用戶的物品清單被未驗明身份的人亂動,我們設置了authenticationRequired為true。

最後一個動作:

var trashAction = UIMutableUserNotificationAction()
trashAction.identifier = "trashAction"
trashAction.title = "Delete list"
trashAction.activationMode = UIUserNotificationActivationMode.Background
trashAction.destructive = true
trashAction.authenticationRequired = true

通過這個動作,我們允許用戶在App沒有啟動的情況下刪除整個物品清單。這個動作可能導致用戶丟失所有數據,所以我們設置了destructive和authenticationRequired為true。

通過上面的代碼,你應該了解到了配置動作其實很簡單。

現在讓我們把以上三個動作配置代碼片段加入到setupNotificationSettings方法中吧!

func setupNotificationSettings() {
    ...    

    // Specify the notification actions.
    var justInformAction = UIMutableUserNotificationAction()
    justInformAction.identifier = "justInform"
    justInformAction.title = "OK, got it"
    justInformAction.activationMode = UIUserNotificationActivationMode.Background
    justInformAction.destructive = false
    justInformAction.authenticationRequired = false

    var modifyListAction = UIMutableUserNotificationAction()
    modifyListAction.identifier = "editList"
    modifyListAction.title = "Edit list"
    modifyListAction.activationMode = UIUserNotificationActivationMode.Foreground
    modifyListAction.destructive = false
    modifyListAction.authenticationRequired = true

    var trashAction = UIMutableUserNotificationAction()
    trashAction.identifier = "trashAction"
    trashAction.title = "Delete list"
    trashAction.activationMode = UIUserNotificationActivationMode.Background
    trashAction.destructive = true
    trashAction.authenticationRequired = true

}

當一個通知的所有動作被配置好了之後,他們可以被包進一個類目(categories)裡。如果你的通知支持動作,那麼你就必須創建一個類目 (categories)。通常情況下一個類目(category)配對一個通知,假設一個App中得所有通知都支持動作,那麼這個App也會有和通知一 樣多的類目(categories)。

我們只會在這個示例App中創建一個通知,所以這裡也只會有一個類目(category)。從編程的角度來說,類目(category)就是一個 UIMutableUserNotificationCategory類的對象,這也是iOS8新引入的類。這個類只有一個屬性和一個方法。標示符屬性用 來表示一個唯一的類目(category),方法用來將多個動作包含進來。

讓我們來了解一下這個方法,先看看這個方法的聲明(來自蘋果官方文檔):

func setActions(_ actions: [AnyObject]!, forContext context: UIUserNotificationActionContext)

第一個參數指明了需要包含進來的動作。是一個包含所有動作的數組,他們在數組中的順序也代表著他們將會在一個通知中調用的先後順序。

第二個參數非常重要。context形參是一個枚舉類型,描述了通知alert顯示時的上下文,有兩個值:

  1. UIUserNotificationActionContextDefault:在屏幕的中央展示一個完整的alert。(未鎖屏時)

  2. UIUserNotificationActionContextMinimal:展示一個banner alert。

在默認上下文(default context)中,類目最多接受4個動作,會以預先定義好的順序依次在屏幕中央顯示。在minimal上下文中,最多可以在banner alert中設置2個動作。注意在第二個情況中,你必須選擇一個較為重要的動作以顯示到banner通知裡。接下來我們會將這兩種情況都用代碼實現。

如我所說,上述方法的第一個參數必須為一個數組。所以在我們的配置通知方法中我們首先為兩個上下文創建兩個數組:

func setupNotificationSettings() {
    ...

    let actionsArray = NSArray(objects: justInformAction, modifyListAction, trashAction)
    let actionsArrayMinimal = NSArray(objects: trashAction, modifyListAction)

}

然後讓我們來創建一個新的類目(category)吧,首先我們設置它的標示符(identifier),然後將上面的2個數組分別設置:

func setupNotificationSettings() {
    ...

    // Specify the category related to the above actions.
    var shoppingListReminderCategory = UIMutableUserNotificationCategory()
    shoppingListReminderCategory.identifier = "shoppingListReminderCategory"
    shoppingListReminderCategory.setActions(actionsArray, forContext: UIUserNotificationActionContext.Default)
    shoppingListReminderCategory.setActions(actionsArrayMinimal, forContext: UIUserNotificationActionContext.Minimal)

}

然後…這樣就行啦,為一個通知相關的動作創建一個類目就這樣完成了。

注冊通知設置

通過上面的3個部分,我們已經將本地通知的所有新功能已經實現了。現在我們需要將這些設定注冊到用戶設置中。為了完成這個目標,我們將會用到 UIUserNotificationSettings類(iOS8新引入),然後在下面的init方法中,我們會指定通知類型和類目 (category)。

convenience init(forTypes allowedUserNotificationTypes: UIUserNotificationType, categories actionSettings: NSSet?)

第一個參數是我們為通知設置的類型,第二個方法是一個集合(NSSet),在這個集合中必須包含一個App所有通知支持的類目。在本例中,我們只有一個類目,但是我們還是需要使用集合來傳遞它。

下面是代碼實現:

func setupNotificationSettings() {
    ...

    let categoriesForSettings = NSSet(objects: shoppingListReminderCategory)

}

現在,我們就可以創建一個UIUserNotificationSettings對象了,然後傳入相應的參數。

func setupNotificationSettings() {
    ...

    let newNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: categoriesForSettings)

}

最後,讓我們將它注冊一下吧!

func setupNotificationSettings() {
    ...
    UIApplication.sharedApplication().registerUserNotificationSettings(newNotificationSettings)
}

第一次啟動App時上述代碼就會執行,它會在用戶設置中創建一條我們的App記錄。

最後,在我展現一個完整的setupNotificationSettings(),還有一點需要注意。這個方法會在viewDidLoad方法中 被調用,這意味著每當App被啟動的時候它都會執行一次。很顯然一遍又一遍的設置同樣地值是在做無用功,這樣如果我們將上面的方法用一個if判斷執行一下 的話就好了。在這個判斷中,我們檢查通知的類型是否已經被設定了,如果沒有if塊中的代碼就會被執行。

func setupNotificationSettings() {
    let notificationSettings: UIUserNotificationSettings! = UIApplication.sharedApplication().currentUserNotificationSettings()

    if (notificationSettings.types == UIUserNotificationType.None){
        ...
    }
}

首先,我們通過UIApplication的類方法currentUserNotificationSettings()來獲取通知的類型。通過這 個方法返回的UIUserNotificationSettings類的對象,我們可以檢查它的types枚舉屬性。請記住這個屬性為枚舉類型。如果它的 值為None,那麼通知類型就還沒有被注冊,然後我們就運行上面的方法來注冊通知類型,否則什麼也不做。

通過上面的代碼我們避免了重復注冊通知類型。但是如果你想修改通知類型、添加動作或者類目的話,你可以將if開始和結束行注釋掉,然後運行一次App。新的設定會被添加,你就能測試一下他們了。然後移除注釋,避免重復注冊。

好了,設置通知的工作已經完成了。下面你可以看到setupNotificationSettings()方法的完整版本:

func setupNotificationSettings() {
    let notificationSettings: UIUserNotificationSettings! = UIApplication.sharedApplication().currentUserNotificationSettings()

    if (notificationSettings.types == UIUserNotificationType.None){
        // Specify the notification types.
        var notificationTypes: UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Sound


        // Specify the notification actions.
        var justInformAction = UIMutableUserNotificationAction()
        justInformAction.identifier = "justInform"
        justInformAction.title = "OK, got it"
        justInformAction.activationMode = UIUserNotificationActivationMode.Background
        justInformAction.destructive = false
        justInformAction.authenticationRequired = false

        var modifyListAction = UIMutableUserNotificationAction()
        modifyListAction.identifier = "editList"
        modifyListAction.title = "Edit list"
        modifyListAction.activationMode = UIUserNotificationActivationMode.Foreground
        modifyListAction.destructive = false
        modifyListAction.authenticationRequired = true

        var trashAction = UIMutableUserNotificationAction()
        trashAction.identifier = "trashAction"
        trashAction.title = "Delete list"
        trashAction.activationMode = UIUserNotificationActivationMode.Background
        trashAction.destructive = true
        trashAction.authenticationRequired = true

        let actionsArray = NSArray(objects: justInformAction, modifyListAction, trashAction)
        let actionsArrayMinimal = NSArray(objects: trashAction, modifyListAction)

        // Specify the category related to the above actions.
        var shoppingListReminderCategory = UIMutableUserNotificationCategory()
        shoppingListReminderCategory.identifier = "shoppingListReminderCategory"
        shoppingListReminderCategory.setActions(actionsArray, forContext: UIUserNotificationActionContext.Default)
        shoppingListReminderCategory.setActions(actionsArrayMinimal, forContext: UIUserNotificationActionContext.Minimal)


        let categoriesForSettings = NSSet(objects: shoppingListReminderCategory)


        // Register the notification settings.
        let newNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: categoriesForSettings)
        UIApplication.sharedApplication().registerUserNotificationSettings(newNotificationSettings)
    }
}

別忘了在viewDidLoad方法中調用它:

override func viewDidLoad() {
        ...

        setupNotificationSettings()
    }

安排本地通知

如果你在iOS之前的版本中使用過本地通知的話,你一定知道安排一個通知是很簡單地事情。在iOS8,安排一個通知並沒有什麼變化。事實上,所有的基本設置都是一模一樣的。唯一的新東西就是必須給一個通知設置一個類目,這樣通知就能知道當用戶點擊的時候該啟動哪些動作了。

你可能已經猜到了,我們會定義一個新的方法來配置和安排一個本地通知。在我們實現這個方法之前,我們先看看一個本地通知中得重要屬性:

  • fireDate:一個通知應當被顯示的日期和時間。NSDate對象。

  • alertBody:通知的內容。應當盡量的簡潔明了,這樣用戶才能馬上理解它。

  • alertAction:在默認情況下,點擊一個banner通知會導致App啟動。在以alert形式顯示的通知中,會創建一個和這個動作對應 的按鈕。在此屬性中,你必須指定這個按鈕的標題。比如,在這個示例App中,我們將View List設置為它的標題或者alert的動作。

牢記以上幾點,現在放我們定義這個方法配置這個通知。不用說先讓我們創建一個UILocalNotification對象:

func scheduleLocalNotification() {
    var localNotification = UILocalNotification()
    localNotification.fireDate = datePicker.date
    localNotification.alertBody = "Hey, you must go shopping, remember?"
    localNotification.alertAction = "View List"

}

還有一點,我們必須指定用戶點擊通知後對應的類目動作。回憶一下,我們前面已經定義了一個類目和類目標示符,我們在這裡就能使用到這個標示符了:

func scheduleLocalNotification() {
    ...

    localNotification.category = "shoppingListReminderCategory"
}

這很簡單。最後,我們需要使用UIApplication的scheduleLocalNotification(_:) 方法來真正的安排一個通知,不然這個通知永遠都不會“通知”到你啦。

func scheduleLocalNotification() {
    ...

    UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
}

接下來,讓我們調用這個方法吧。讓我們在scheduleReminder(sender:) 按鈕方法中加入新的代碼,在顯示tableview之前我們調用上面的代碼。注意,這裡有一點需要避免:如果我們在已經安排了一個本地通知以後在重新安排 一個,之前的通知仍然會有效。如果我們忽略了這一點,我們可能會創建許多個不需要的通知。為了避免這樣,我們簡單的在date picker顯示的時候移除所有已經安排的通知,這樣就沒事啦。下面是IBAction方法代碼:

@IBAction func scheduleReminder(sender: AnyObject) {
    if datePicker.hidden {
        animateMyViews(tblShoppingList, viewToShow: datePicker)

        UIApplication.sharedApplication().cancelAllLocalNotifications()
    }
    else{
        animateMyViews(datePicker, viewToShow: tblShoppingList)

        scheduleLocalNotification()
    }

    txtAddItem.enabled = !txtAddItem.enabled
}

這就是啦。在配置了類型、動作和其他的細節之後,安排他是一件很簡單的事情。有了上面的代碼,現在通知已經可以如我們期望的工作了。

修復安排通知的時間問題

目前為止我們都做的很好,每個功能都工作的很完美。但是,當你運行這個程序的時候你可能會發現一個關於通知送達時間的問題盡管它看起來是正常的,我 發現它存在一點問題。所以,在我們測試通知之前,讓我先深入的講一下這個問題。也許在你運行這個App的時候並不會察覺這個問題,但是在實時性強的App 中,通知推送的時間非常重要,而且不准確的時間可能會造成非常嚴重的問題。

那麼這個問題是什麼呢?嗯,接下來讓我以一個例子來說明它:如果你在10:23:14的時候安排了一個通知在14:00被推送(不用管日期,假設是 同一天),這個通知其實不會在14:00:00的時候被推送。而是在14:00:14的時候。我認為對於通知來說這是一個非常嚴重的問題。為什麼會這樣 呢?那是因為在date picker中,我們可以設置時間,但是卻不能設置到秒。然後系統就會將我們設置時的時間的秒賦值給通知的推送時間,而不是使用0。

那麼,我們該怎樣修復這個BUG呢?很簡單,通過代碼來修復。如果你以前沒有接觸過日期和時間的話也不用擔心。,這將是一個簡單而有趣的任務。

一個日期對象(NSDate對象)可以分為幾個部分,叫做date components。這些component是NSDateComponents類的屬性,可以可以同時讀和寫,所以當我們從date picker選擇的日期得到了這些components之後我們就可以修改其中的seconds屬性了。很明顯,通過這些components我們可以重 新得到一個NSDate對象,有了這個對象之後,我們就可以方便的將其設置為通知的推送日期了。

正如你在下面的實現中所看到的,將一個日期對象轉換成date components依賴於NSCalendar類。這個類提供讓我們完成這些工作的方法。

func fixNotificationDate(dateToFix: NSDate) -> NSDate {
    var dateComponets: NSDateComponents = NSCalendar.currentCalendar().components(NSCalendarUnit.DayCalendarUnit | NSCalendarUnit.MonthCalendarUnit | NSCalendarUnit.YearCalendarUnit | NSCalendarUnit.HourCalendarUnit | NSCalendarUnit.MinuteCalendarUnit, fromDate: dateToFix)

    dateComponets.second = 0

    var fixedDate: NSDate! = NSCalendar.currentCalendar().dateFromComponents(dateComponets)

    return fixedDate
}

詳細討論NSCalendar並不是我們的目的。但是這也將是一個非常有趣的話題,所以你可以通過這裡來了解更多有關NSCalendar的東西。

現在是時候選擇一個合適的地點來調用這個方法了,在設置通知推送時間的時候調用這個方法將是一個很好地選擇。返回 scheduleLocalNotification()方法,將date picker選擇的日期作為參數傳入此方法,然後將此方法返回的日期設置為通知的fireDate。下面是更新後的 scheduleLocalNotification()方法:

func scheduleLocalNotification() {
    var localNotification = UILocalNotification()
    localNotification.fireDate = fixNotificationDate(datePicker.date)
    localNotification.alertBody = "Hey, you must go shopping, remember?"
    localNotification.alertAction = "View List"
    localNotification.category = "shoppingListReminderCategory"

    UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
}

現在,我們的通知終於可以如願以償的按時被推送了!

處理通知動作

現在關於通知,我們只差最後一個部分了,那就是處理用戶點擊通知相關按鈕時候的各種動作。和往常一樣,這裡有幾個主要的委托方法我們需要實現。

在我們實現和處理動作之前,讓我給你介紹幾個代理方法,通過他們你可以方便的開發你的App。注意,在這個示例程序中,我們並不會真正的使用到他們,我們只會通過他們打印一些消息。現在,請打開AppDelegate.swift文件。

第一個代理方法是關於通知設置的。這個代理方法在程序啟動時被調用(不管是正常啟動還是通過一個本地通知),包含了所有App通知的設置選項。接下來你可以看到它的定義。我們所做的,僅僅是打印通知類型:

func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {

    println(notificationSettings.types.rawValue)
}

通過上述的方法,你可以得到所有UIUserNotificationSettings支持的類型。當你需要檢查你的App所支持的通知和動作的類型時,這個方法非常有用。別忘了,用戶可以通過用戶設置來改變通知類型,所以我們不能保證,初始的通知類型一直都有效。

當你安排了一個通知之後,無論你的App是否在運行,這個通知都將被推送。通常情況下,開發者設置通知如何在App沒有運行或者被掛起的時候被推 送,所有的代碼實現也聚焦在這兩個方面。但是,我們也應該處理當App在運行時通知如何被處理。感謝蘋果,iOS SDK讓這變得非常簡單,有一個代理方法正可以處理這種情況:

func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
    // Do something serious in a real app.
    println("Received Local Notification:")
    println(notification.alertBody)
}

當然在某些情況下在App運行時你並不需要處理通知。但是在另外一個情況下,上面的代理方法是處理通知動作的地方。

現在讓我們來看看當用戶點擊了一個通知動作按鈕後將會調用的代理方法。更具我們給動作設置的標示符(identifier),我們決定那個動作被調用,然後App就會執行對應的代碼了。我們會有三種動作:

  1. 簡單地讓通知消失(標示符:justInform)。

  2. 添加一個新的物品(標示符:editList)。

  3. 刪除整個物品清單(標示符:trashAction)。

從上面我們看出,我們不需要對第一個動作做任何事情。但是我們需要處理另外兩種動作。我們將根據identifier的值給每一種情況發送一個 NSNotification,在ViewController類中,我們監視這些NSNotification,然後我們處理他們。

讓我們從新的代理方法開始:

func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) {

    if identifier == "editList" {
        NSNotificationCenter.defaultCenter().postNotificationName("modifyListNotification", object: nil)
    }
    else if identifier == "trashAction" {
        NSNotificationCenter.defaultCenter().postNotificationName("deleteListNotification", object: nil)
    }

    completionHandler()
}

在上述幾種情況中我們根據動作的的標示符,發送不同名稱的NSNotification對象。注意到,我們在方法的結束調用了 completionHandler()方法,根據規定我們必須調用它,這樣系統才能知道我們已經處理完了通知動作。在處理本地通知時,這個代理方法非常 重要,在這裡你通過用戶的點擊執行相應地代碼。

接下來,讓我們打開ViewController.swift文件。首先,讓我們監視我們之前發送的NSNotification。在viewDidLoad中加入下面的代碼:

override func viewDidLoad() {
    ...

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleModifyListNotification", name: "modifyListNotification", object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleDeleteListNotification", name: "deleteListNotification", object: nil)
}

modifyListNotification()和deleteListNotification()方法都是我們自定義的方法,我們接下來會實現他們。

首先我們實現第一個方法。因為App是通過用戶點擊了編輯物品動作啟動的,所以我們需要將textfield控件設置為第一響應。完成此任務,只需加入一行代碼:

func handleModifyListNotification() {
    txtAddItem.becomeFirstResponder()
}

通過這行代碼,鍵盤會自動展現然後用戶就能立即添加一個新的物品了。

通過刪除清單按鈕,我們希望從物品對象數組移除所有的物品。接下來,我們首先移除shoppingList數組中得所有對象,然後將它(空數組)保存到磁盤。最後我們重新加載tableview的數據,這樣的話,當用戶啟動App時就什麼物品都看不到了。

func handleDeleteListNotification() {
    shoppingList.removeAllObjects()
    saveShoppingList()
    tblShoppingList.reloadData()
}

有了上面的代碼實現,我們這個示例程序現在終於完成啦!

啟動示例App

是時候測試一下我們的App了,首先在模擬器或者真機中啟動它。添加一些物品,然後安排一個本地通知。為了避免等待太久時間,安排他在1到2分鐘之後被推送,然後退出App。下面的圖片模擬了上述的幾個操作,和通知在不同的情況被推送的樣子。

添加一個新的物品到清單:

安排一個本地通知:

banner形式展現通知,查看動作(minimal context):

alert形式展現通知,所有動作(default context):

在通知中心展示通知(minimal context):

為了在banner和alert之間切換,在你的設備中打開設置App。找到Shopping Alert選項,點擊進入。

在下圖標記的地方,根據你希望通知展示的樣子選擇alert或者banner。

退出設置App,安排一個新的本地通知,這樣你就能你選擇的結果了。

總結

在iOS8當中,通知看起來更好用了,用戶現在甚至可以直接處理通知而不用啟動App。在本篇文章中我們提到了幾個新的概念,有新也有舊。重要的 是,現在設置通知的類型、動作和類目都十分簡單方便,如果你的App需要通知,那就使用他們吧!正如我在介紹中所說,還有另外幾種通知,如遠程和地點。盡 管我們沒有去實現他們,在知道了本地通知是如何工作了之後,你也會知道如何實現其他通知的大致路徑了。以後你只需要去搜索他們的實現具體細節。好了,上面 所有就是我敬獻給你的,靜下來好好思考一下。玩得開心!

你可以從這裡下載完整的項目以供參考。
(本文為CocoaChina組織翻譯,本譯文權利歸譯者所有,未經允許禁止轉載。)

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved