iOS9-by-Tutorials-學習筆記二:App-Search
iOS9-by-Tutorials-學習筆記二:App-Search
本文為自己讀書的一個總結,可能與原書有一定出入
iOS 9推出了搜尋技術,能夠讓使用者在Spotlight中搜尋到APP內部的內容。蘋果提供了三個APP Search API:
* NSUserActivity
* Core Spotlight
* Web markup
下面簡單的說一下我對於這三個API的理解:
1. NSUserActivity:
NSUserActivity在iOS8就已經提出來了,只是那時候提出來是用作HandOff。在iOS9中它可以用來搜尋App中的內容。我們可以把一些想要在Spotlight中被搜到的東西,放到NSUserActivity中,然後就能在Spotlight中被搜到,但是這個有一點限制,就是只能搜尋使用者訪問過得內容。因為UIViewController的userActivity屬性繼承自UIResponser,只有在UIViewcontroller訪問的時候,才有機會設定userActivity屬性。
2. Core Spotlight:
這個是在iOS9新推出的技術,能夠將APP的內容在Spotlight中被搜尋到。這個技術我理解:蘋果給開發人員提供了一個全域的index資料庫,我們能夠把我們想要能夠在Spotlight中搜尋的內容,按照蘋果的要求放到資料庫中,然後蘋果就做了其他的事情,讓其能夠被搜尋到。同樣我們也可以刪除我們儲存到資料庫中的內容。
3. Web markup:
Web Markup在網頁上顯示App的內容並編入Spotlight索引,如此一來即便沒有安裝某個App,蘋果的索引器也能在網頁上搜尋特別的標記(markup),在Safari或Spotlight上顯示搜尋結果。具體會在下一篇文章中詳細介紹。
Getting started
下面開始實驗一下相關的技術,這裡還是利用書中的star工程。現在這個工程運行後,就兩個介面:
下面是這個工程的:
下面是圖中標註的幾個關鍵類的解釋:
1. AppDelegate
點擊搜尋結果跳跳轉到程式中,會先在這個類裡面做一定的處理
2. EmployeeViewController
人員的詳細介面,這個裡面主要設定NSUserActivity
3. EmployeeService
這個主要是寫CoreSpotlight中index相關的東西
4. EmployeeSearch
主要是擴充了Employee類,添加了與搜尋相關的屬性
另外工程中有員工相關的一些操作都封裝在了一個EmployeeKit的target,由於跟主target不在一個module,所以在主target中需要import。
在Iphone的Setting/Colleagues/Indexing中有如下三個選項:
* Disabled 不使用Search API,即不能在Spotlight中搜尋到APP中的內容
* ViewedRecords 只有開啟過的才能夠被搜尋到
* AllRecZ喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcmRzIMv509C1xNSxuaTQxc+itrzE3Lm7sbvL0cv3tb08L3A+DQo8aDQgaWQ9"搜尋我們已經開啟過的內容">搜尋我們已經開啟過的內容
使用NSUserActivity實現這個比較簡單,只要兩個步驟就可以了:
1. 建立NSUserActivity的一個執行個體,設定相關的屬性
2. 賦值給UIViewController的userActivity屬性
下面我們在EmployeeSearch中添加如下代碼:
如果沒有該檔案,需要手動建立一個,然後target選擇EmployeeKit
import Foundationimport CoreSpotlightextension Employee { // 這個用於區分Activity,會在點擊搜尋結果進入APP,相關處理的時候用到,同樣也可以在CoreSpotlight中使用到,對於添加、刪除index資料的時候都會用到 public static let domainIdentifier = "com.mengxiangyue.colleagues.employee" // 字典 在處理點擊的時候,可以根據該字典擷取我們想要的資料 public var userActivityUserInfo: [NSObject: AnyObject] { return ["id": objectId] } // 給Employee添加userActivity屬性,主要是方便我們擷取userActivity public var userActivity: NSUserActivity { let activity = NSUserActivity(activityType: Employee.domainIdentifier) activity.title = name // 顯示的名字 activity.userInfo = userActivityUserInfo // 與該Activity相關的資料 activity.keywords = [email, department] // 關鍵字 表示搜尋什麼關鍵字,能夠搜尋出來該條記錄,當然這個只是補充,這裡沒有添加name,同樣也是可以按照name搜尋 return activity }}
這裡擴充了Employee,然後添加了幾個屬性,屬性的意義見注釋。
這時候我們需要重新編譯一下EmployeeKit(因為與主target不是同一個target)。
下面開啟EmployeeViewController.swift,在viewDidLoad()中添加如下代碼:
let activity = employee.userActivityswitch Setting.searchIndexingPreference {case .Disabled: activity.eligibleForSearch = falsecase .ViewedRecords: activity.eligibleForSearch = true // relatedUniqueIdentifier 定義一個id 防止NSUserActivity和Core Spotlight重複索引,這裡設定為nil,顯示一下會重複 activity.contentAttributeSet?.relatedUniqueIdentifier = nilcase .AllRecords: activity.eligibleForSearch = true}userActivity = activity
下面在該類中添加如下的方法,用於在合適的時機更新Activity:
// 更新NSUserActivity關聯的資訊 override func updateUserActivityState(activity: NSUserActivity) { activity.addUserInfoEntriesFromDictionary(employee.userActivityUserInfo) }
下面在Iphone的Setting/Colleagues/Indexing中選擇ViewedRecords。然後啟動APP,在列表中點擊Brent Reid進入詳細頁面,然後使用Command+shift+H,計入Home頁面,下拉出現搜尋方塊,然後輸入brent出現如下介面:
看到這個搜尋結果介面,感覺太難看了,下面我們豐富一下這個搜尋結果,蘋果提供的搜尋結果可以設定如下的內容:
下面我們在EmployeeSearch.swift添加如下屬性:
public var attributeSet: CSSearchableItemAttributeSet { let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeContact as String) attributeSet.title = name // 不太清楚是幹啥的 attributeSet.contentDescription = "\(department), \(title)\n\(phone)" attributeSet.thumbnailData = UIImageJPEGRepresentation(loadPicture(), 0.9) attributeSet.supportsPhoneCall = true attributeSet.phoneNumbers = [phone] attributeSet.emailAddresses = [email] attributeSet.keywords = skills attributeSet.relatedUniqueIdentifier = objectId return attributeSet}
然後將給userActivity添加如下屬性:
public var userActivity: NSUserActivity { let activity = NSUserActivity(activityType: Employee.domainIdentifier) activity.title = name activity.userInfo = userActivityUserInfo activity.keywords = [email, department] activity.contentAttributeSet = attributeSet // 新添加的這一行 return activity}
然後運行程式,搜尋結果如下:
但是現在我們注意到,我們點擊搜尋結果,開啟APP並沒有按照我們預想的跳轉到該員工的詳細介面。這個因為我們在程式中沒有做對應的處理,下面我們在AppDelete中添加如下的方法:
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool { let objectId: String // 先判斷了一個type是不是我們自己定義的 然後擷取到對應的EmployeeId if userActivity.activityType == Employee.domainIdentifier, let activityObjectId = userActivity.userInfo?["id"] as? String { objectId = activityObjectId } // 擷取對應Employee執行個體 然後跳轉到對應的介面 if let nav = window?.rootViewController as? UINavigationController, listVC = nav.viewControllers.first as? EmployeeListViewController, employee = EmployeeService().employeeWithObjectId(objectId) { nav.popToRootViewControllerAnimated(false) let employeeViewController = listVC.storyboard?.instantiateViewControllerWithIdentifier("EmployeeView") as! EmployeeViewController employeeViewController.employee = employee nav.pushViewController(employeeViewController, animated: false) return true } return false}
這時候我們再點擊搜尋結果就能夠跳轉到對應的詳細介面了。
CoreSpotlight
下面我們開始使用CoreSpotlight添加這些搜尋內容。首先在EmployeeSearch.swift的attributeSet中設定如下屬性:
// 在前面的代碼中已經設定過了attributeSet.relatedUniqueIdentifier = objectId
這個屬性主要是將NSUserActivity與Core Spotlight indexed object進行一個關聯,防止出現重複的內容(如果出現重複內容,是因為開始的時候測試NSUserActivity的時候沒有設定id,還原一下模擬器就好了)
然後在EmployeeSearch.swift添加如下的代碼:
// CoreSpotlight需要將一個個item放入其索引資料庫中,這裡建立一個方便使用var searchableItem: CSSearchableItem { let item = CSSearchableItem(uniqueIdentifier: objectId, domainIdentifier: Employee.domainIdentifier, attributeSet: attributeSet) return item}
然後在EmployeeService.swift添加如下代碼:
import CoreSpotlight..............<省略一部分代碼>public func indexAllEmployees() { let employees = fetchEmployees() let searchableItems = employees.map{ $0.searchableItem } // 將我們需要被索引的item放入到defaultSearchableIndex中 CSSearchableIndex.defaultSearchableIndex().indexSearchableItems(searchableItems) { (error) -> Void in if let error = error { print("Error indexing employees: \(error)") } else { print("Employees indexed.") } }}
然後在設定中選擇AllRecords,這時候啟動APP,然後搜尋,看到的搜尋結果如下:
但是這時候我們點擊搜尋結果沒有反應,想想應該也能猜到,我們需要在AppDelete中添加代碼,最終代碼如下:
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool { let objectId: String if userActivity.activityType == Employee.domainIdentifier, let activityObjectId = userActivity.userInfo?["id"] as? String { objectId = activityObjectId } // 這部分else是新添加的 使用不一樣的type區分NSUserActivity和CoreSpotlight,然後擷取對應的objectId,其他的處理都一樣了 // CSSearchableItemActivityIdentifier這個是CoreSpotlight提供的一個key值 else if userActivity.activityType == CSSearchableItemActionType, let activityObjectId = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String { objectId = activityObjectId } else { return false } if let nav = window?.rootViewController as? UINavigationController, listVC = nav.viewControllers.first as? EmployeeListViewController, employee = EmployeeService().employeeWithObjectId(objectId) { nav.popToRootViewControllerAnimated(false) let employeeViewController = listVC.storyboard?.instantiateViewControllerWithIdentifier("EmployeeView") as! EmployeeViewController employeeViewController.employee = employee nav.pushViewController(employeeViewController, animated: false) return true } return false }
這時候我們點擊搜尋結果應該就能夠跳轉進入對應的人員詳情了。
刪除Item
最後在簡單的說下刪除已經索引的Item,修改EmployeeService.swift對應的方法如下:
public func destroyEmployeeIndexing() { CSSearchableIndex.defaultSearchableIndex().deleteAllSearchableItemsWithCompletionHandler { (error) -> Void in if let error = error { print("Error deleting searching employee items: \(error)") } else { print("Employees indexing deleted.") } }}
這個方法會在APP啟動並且Indxing設定為Disabled的時候調用。
另外對於CoreSpotlight中對於Item的操作方式還有好多種,這裡我就不一一寫出來了,有興趣的可以看看我翻譯的API注釋,當然文章可能有點老了,但是基本思想應該沒變。