23、打造功能丰富的 iOS 应用:分享、接力与搜索功能实现指南

打造功能丰富的 iOS 应用:分享、接力与搜索功能实现指南

在开发 iOS 应用时,为了提升用户体验和应用的实用性,我们可以为其添加分享、接力(Handoff)和搜索等功能。这些功能能够让应用更好地融入用户的设备生态系统,使应用不再孤立,而是成为用户日常使用的一部分。下面将详细介绍如何为应用添加这些功能。

1. 附件删除功能

在应用中,我们可能需要实现附件删除的功能。以下是相关代码示例:

self.attachmentsCollectionView?
    .deleteItems(at: [indexPath])
self.endEditMode()
} catch let error as NSError {
    NSLog("Failed to delete attachment: \(error)")
}

同时,在 collectionView(_, cellForItemAt indexPath:) 方法中添加代码,设置单元格的代理为 self ,并添加长按手势:

// Add a long-press gesture to it, if it doesn't
// already have it
let longPressGesture = UILongPressGestureRecognizer(target: self,
    action: #selector(DocumentViewController.beginEditMode))
attachmentCell.gestureRecognizers = [longPressGesture]
// Contact us when the user taps the delete button
attachmentCell.delegate = self

完成上述代码添加后,运行应用,就可以实现附件删除功能了。

2. 分享功能实现

分享功能可以让用户方便地将应用内的内容分享到社交媒体、邮件或短信等平台。在 iOS 中,分享功能由 UIActivityViewController 处理。以下是实现分享功能的具体步骤:
1. 打开 Main.storyboard ,进入图像附件视图控制器。
2. 从对象库中添加 UIToolBar 到视图,并将其放置在屏幕底部。该工具栏会包含一个 UIBarButtonItem ,类似于 UIButton ,但专门用于工具栏。
3. 调整工具栏大小,使其适应屏幕宽度。点击“Pin”菜单,固定视图的左、右和底部边缘,确保工具栏始终位于屏幕底部并填满屏幕宽度。
4. 选择按钮,将其 System Item 属性设置为 Action ,这样按钮图标将变为标准的 iOS 分享图标。
5. 在助理编辑器中打开 ImageAttachmentViewController.swift
6. 按住 Control 键,从刚添加的工具栏按钮拖动到 ImageAttachmentViewController ,创建一个名为 shareImage 的新动作。
7. 在 shareImage 方法中添加以下代码:

@IBAction func shareImage(_ sender: UIBarButtonItem) {
    // Ensure that we're actually showing an image
    guard let image = self.imageView?.image else {
        return
    }
    let activityController = UIActivityViewController(
        activityItems: [image], applicationActivities: nil)
    // If we are being presented in a window that's a Regular width,
    // show it in a popover (rather than the default modal)
    if UIApplication.shared.keyWindow?.traitCollection
        .horizontalSizeClass == UIUserInterfaceSizeClass.regular {
        activityController.modalPresentationStyle = .popover
        activityController.popoverPresentationController?
            .barButtonItem = sender
    }
    self.present(activityController, animated: true,
        completion: nil)
}

当用户点击分享按钮时,会弹出 UIActivityViewController ,用户可以选择将图像分享到不同的平台。如果应用在较大屏幕(如 iPhone 6 Plus 或 iPad)上运行,分享视图将以弹出框的形式显示。

3. 接力(Handoff)功能实现

接力功能允许用户在不同设备(如 iOS 设备和 macOS 应用)之间无缝切换工作。以下是实现接力功能的步骤:
1. 选择项目导航器顶部的项目。
2. 进入 Notes 目标设置(即 macOS 应用),滚动到“Document Types”部分。
3. 展开“Additional document type properties”,选择 CFBundleTypOSTypes 条目,点击出现的“+”按钮,添加一个新条目。
4. 将新条目命名为 NSUbiquitousDocumentUserActivityType ,类型设置为 String ,值设置为 au.com.secretlab.Notes.editing
5. 在 Notes-iOS 中进行相同的设置。如果使用了自定义的 bundleID ,请确保在此处使用该 bundleID 并在末尾添加 .editing ,否则接力功能将无法正常工作。
6. 打开属于 Notes-iOS 目标的 AppDelegate.swift 文件。
7. 实现以下方法:

func application(_ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
    // Return to the list of documents
    if let navigationController =
        self.window?.rootViewController as? UINavigationController {
        navigationController.popToRootViewController(animated: false)
        // We're now at the list of documents; tell the restoration
        // system that this view controller needs to be informed
        // that we're continuing the activity
        if let topViewController = navigationController.topViewController {
            restorationHandler([topViewController])
        }
        return true
    }
    return false
}
  1. 打开 DocumentListViewController.swift 文件。
  2. DocumentListViewController 类中添加以下方法:
override func restoreUserActivityState(_ activity: NSUserActivity) {
    // We're being told to open a document
    if let url = activity.userInfo?[NSUserActivityDocumentURLKey] as? URL {
        // Open the document
        self.performSegue(withIdentifier: "ShowDocument", sender: url)
    }
}
  1. 最后,在 DocumentViewController viewWillAppear 方法中添加以下代码,使活动变为当前活动:
// If this document is not already open, open it
if document.documentState.contains(UIDocumentState.closed) {
    document.open { (success) -> Void in
        if success == true {
            self.textView?.attributedText = document.text
            self.attachmentsCollectionView?.reloadData()
            // We are now engaged in this activity
            document.userActivity?.becomeCurrent()
            // Register for state change notifications
            self.stateChangedObserver = Notification.default
.addObserver(
                forName: NSNotification.Name.UIDocumentStateChanged,
                object: document,
                queue: nil,
                using: { (notification) -> Void in
                self.documentStateChanged()
            })
            self.documentStateChanged()
        }

完成上述步骤后,运行 iOS 应用和 macOS 应用,在一个设备上打开文档,在另一个设备上就可以通过接力功能继续编辑该文档。

4. 搜索功能实现

搜索功能可以让用户方便地在应用内搜索文档。在 iOS 中,有三种搜索技术可供使用: NSUserActivity 对象、Core Spotlight 和网页索引。这里主要介绍通过 NSUserActivity 实现搜索功能的方法。

4.1 索引活动

通过 NSUserActivity 为应用添加搜索索引支持的步骤如下:
1. 打开 DocumentViewController.swift 文件。
2. 在文件顶部导入 Core Spotlight 框架:

import CoreSpotlight
  1. 更新 viewWillAppear 方法,在文档打开时为文档的用户活动添加可搜索的元数据:
// If this document is not already open, open it
if document.documentState.contains(UIDocumentState.closed) {
    document.open { (success) -> Void in
        if success == true {
            self.textView?.attributedText = document.text
            self.attachmentsCollectionView?.reloadData()
            // Add support for searching for this document
            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?.isEligibleForSearch = true
            // We are now engaged in this activity
            document.userActivity?.becomeCurrent()
            // Register for state change notifications
            self.stateChangedObserver = Notification.default
.addObserver(
                forName: NSNotification.Name.UIDocumentStateChanged,
                object: document,
                queue: nil,
                using: { (notification) -> Void in
                self.documentStateChanged()
            })
            self.documentStateChanged()
        }

完成上述代码添加后,运行应用,打开文档并输入一些内容,关闭应用后,在主屏幕上向下滑动打开搜索框,输入文档中的内容,文档就会出现在搜索结果中。点击搜索结果,应用将启动并打开相应的文档。

4.2 Spotlight 扩展

为了让应用的所有文档内容都能在 Spotlight 中搜索到,我们可以添加一个 Spotlight 索引应用扩展。以下是添加扩展的步骤:
1. 打开文件菜单,选择“New” -> “Target”。
2. 选择 iOS -> Application Extension -> Spotlight Index Extension
3. 将新目标命名为 Notes-SpotlightIndexer
4. 点击“Finish”后,Xcode 会弹出一个窗口询问是否激活新创建的方案,点击“Activate”切换到新方案。
5. 为扩展授予访问 iCloud 容器的权限:
- 进入目标属性,点击“Capabilities”标签。
- 打开 iCloud 开关,等待加载完成。
- 打开 iCloud Documents 开关。
- 选择 Mac 和 iOS 应用使用的 iCloud 容器,确保不选择其他容器。将“Use default container” 改为 “Specify custom container”。
6. 确保 Notes-SpotlightIndexer 目标能够使用定义重要文件名称的枚举:
- 打开 DocumentCommon.swift 文件,选择“View” -> “Utilities” -> “Show File Inspector” 打开文件检查器。
- 确保“Notes-SpotlightIndexer” 旁边的复选框被选中。
7. 实现 Spotlight 索引器:
- 打开 IndexRequestHandler.swift 文件,该文件是添加目标时 Xcode 生成的模板代码的一部分。
- 在文件顶部添加以下代码:

import UIKit
- 在 `IndexRequestHandler` 类中添加以下计算属性,用于获取应用已知的所有文档的集合:
var availableFiles : [URL] {
    let fileManager = FileManager.default
    var allFiles : [URL] = []
    // Get the list of all local files
    if let localDocumentsFolder
        = fileManager.urls(for: .documentDirectory,
            in: .userDomainMask).first {
        do {
            let localFiles = try fileManager
                .contentsOfDirectory(atPath: localDocumentsFolder.path)
                .map({
                    localDocumentsFolder.appendingPathComponent($0,
                        isDirectory: false)
                })
            allFiles.append(contentsOf: localFiles)
        } catch {
            NSLog("Failed to get list of local files!")
        }
    }
    // Get the list of documents in iCloud
    if let documentsFolder = fileManager
        .url(forUbiquityContainerIdentifier: nil)?
        .appendingPathComponent("Documents", isDirectory: true) {
        do {
            // Get the list of files
            let iCloudFiles = try fileManager
                .contentsOfDirectory(atPath: documentsFolder.path)
                .map({
                    documentsFolder.appendingPathComponent($0,
                        isDirectory: false)
                })
            allFiles.append(contentsOf: iCloudFiles)
        } catch  {
            // Log an error and return the empty array
            NSLog("Failed to get contents of iCloud container")
            return []
        }
    }
    // Filter these to only those that end in ".note",
    // and return NSURLs of these
    return allFiles
        .filter({ $0.lastPathComponent.hasSuffix(".note") })
}
- 在 `IndexRequestHandler` 类中添加以下方法,用于为给定的 URL 生成 `CSSearchableItem`:
func itemForURL(_ url: URL) -> CSSearchableItem? {
    // If this URL doesn't exist, return nil
    if (url as NSURL).checkResourceIsReachableAndReturnError(nil) == false
{
        return nil
    }
    // Replace this with your own type identifier
    let attributeSet = CSSearchableItemAttributeSet(
        itemContentType: "au.com.secretlab.Note")
    attributeSet.title = url.lastPathComponent
    // Get the text in this file
    let textFileURL = url.appendingPathComponent(
        NoteDocumentFileNames.TextFile.rawValue)
    if let textData = try? Data(contentsOf: textFileURL),
       let text = try? NSAttributedString(data: textData,
           options:
[NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType],
           documentAttributes: nil) {
                attributeSet.contentDescription = text.string
    } else {
        attributeSet.contentDescription = ""
    }
    let item =
        CSSearchableItem(uniqueIdentifier: url.absoluteString,
        domainIdentifier: "au.com.secretlab.Notes",
        attributeSet: attributeSet)
    return item
}
- 删除 `searchableIndex(_, reindexAllSearchableItemsWithAcknowledgementHandler:)` 方法,并替换为以下代码:
override func searchableIndex(_ searchableIndex: CSSearchableIndex,
    reindexAllSearchableItemsWithAcknowledgementHandler
        acknowledgementHandler: @escaping () -> Void) {
    // Reindex all data with the provided index
    let files = availableFiles
    var allItems : [CSSearchableItem] = []
    for file in files {
        if let item = itemForURL(file) {
            allItems.append(item)
        }
    }
    searchableIndex.indexSearchableItems(allItems) { (error) -> Void in
        acknowledgementHandler()
    }
}
- 删除 `searchableIndex(_, reindexSearchableItemsWithIdentifiers:, acknowledgementHandler:)` 方法,并替换为以下代码:
override func searchableIndex(_ searchableIndex: CSSearchableIndex,
              reindexSearchableItemsWithIdentifiers identifiers: [String],
                                  acknowledgementHandler: @escaping () -> Void) {
    // Reindex any items with the given identifiers and the provided index
    var itemsToIndex : [CSSearchableItem] = []
    var itemsToRemove : [String] = []
    for identifier in identifiers {
        if let url = URL(string: identifier), let item = itemForURL(url) {
            itemsToIndex.append(item)
        } else {
            itemsToRemove.append(identifier)
        }
    }
    searchableIndex.indexSearchableItems(itemsToIndex) { (error) -> Void in
        searchableIndex
            .deleteSearchableItems(withIdentifiers: itemsToRemove) {
                (error) -> Void in
                acknowledgementHandler()
            }
    }
}
  1. 最后,在 DocumentListViewController.swift 文件中添加以下代码,使应用能够在用户选择搜索结果后打开相应的文档:
import CoreSpotlight
override func restoreUserActivityState(_ activity: NSUserActivity) {
    // We're being told to open a document
    if let url = activity.userInfo?[NSUserActivityDocumentURLKey] as? URL {
        // Open the document
        self.performSegue(withIdentifier: "ShowDocument", sender: url)
    }
    // We're coming from a search result
    if let searchableItemIdentifier = activity
            .userInfo?[CSSearchableItemActivityIdentifier] as? String,
        let url = URL(string: searchableItemIdentifier) {
        // Open the document
        self.performSegue(withIdentifier: "ShowDocument", sender: url)
    }
}

通过以上步骤,应用将能够定期对所有文档进行索引,并在搜索结果中显示这些文档。

总结

通过为应用添加分享、接力和搜索功能,我们可以提升应用的用户体验和实用性,使应用更好地融入用户的设备生态系统。这些功能的实现不仅可以让用户更方便地使用应用,还可以提高应用在用户设备中的可见性和使用率。希望以上内容对你有所帮助,祝你在应用开发中取得成功!

整个功能实现的流程可以用以下 mermaid 流程图表示:

graph LR
    A[附件删除功能] --> B[分享功能实现]
    B --> C[接力功能实现]
    C --> D[搜索功能实现]
    D --> D1[索引活动]
    D --> D2[Spotlight 扩展]

同时,为了更清晰地展示不同功能的实现步骤,我们可以用表格进行总结:
| 功能 | 实现步骤 |
| — | — |
| 附件删除功能 | 1. 添加删除代码
2. 设置单元格代理和长按手势 |
| 分享功能 | 1. 打开 Main.storyboard 添加工具栏和按钮
2. 创建 shareImage 动作
3. 添加分享代码 |
| 接力功能 | 1. 设置文档类型属性
2. 实现 continueUserActivity 方法
3. 实现 restoreUserActivityState 方法
4. 使活动变为当前活动 |
| 搜索功能 - 索引活动 | 1. 导入 Core Spotlight 框架
2. 更新 viewWillAppear 方法添加元数据 |
| 搜索功能 - Spotlight 扩展 | 1. 添加扩展目标
2. 授予 iCloud 访问权限
3. 实现索引器方法 |

打造功能丰富的 iOS 应用:分享、接力与搜索功能实现指南

功能实现的关键要点回顾与深入分析

在前面的内容中,我们详细介绍了如何为 iOS 应用添加附件删除、分享、接力和搜索等功能。接下来,我们将对这些功能实现过程中的一些关键要点进行深入分析。

附件删除功能

附件删除功能的实现主要涉及两个关键步骤:删除集合视图中的项目和结束编辑模式。代码如下:

self.attachmentsCollectionView?
    .deleteItems(at: [indexPath])
self.endEditMode()
} catch let error as NSError {
    NSLog("Failed to delete attachment: \(error)")
}

在这个代码中, attachmentsCollectionView 是用于显示附件的集合视图,通过 deleteItems(at:) 方法删除指定索引路径的项目。同时,为了确保用户体验的一致性,我们需要调用 endEditMode() 方法结束编辑模式。

另外,为了让用户能够触发删除操作,我们在 collectionView(_, cellForItemAt indexPath:) 方法中添加了长按手势,并设置单元格的代理为 self

// Add a long-press gesture to it, if it doesn't
// already have it
let longPressGesture = UILongPressGestureRecognizer(target: self,
    action: #selector(DocumentViewController.beginEditMode))
attachmentCell.gestureRecognizers = [longPressGesture]
// Contact us when the user taps the delete button
attachmentCell.delegate = self

这样,当用户长按单元格时,就会触发 beginEditMode() 方法,从而进入编辑模式,进行删除操作。

分享功能

分享功能的核心是使用 UIActivityViewController 来提供系统服务,让用户可以将图像分享到不同的平台。实现步骤如下:
1. 在故事板中添加工具栏和分享按钮。
2. 创建 shareImage 动作,并在其中添加分享代码:

@IBAction func shareImage(_ sender: UIBarButtonItem) {
    // Ensure that we're actually showing an image
    guard let image = self.imageView?.image else {
        return
    }
    let activityController = UIActivityViewController(
        activityItems: [image], applicationActivities: nil)
    // If we are being presented in a window that's a Regular width,
    // show it in a popover (rather than the default modal)
    if UIApplication.shared.keyWindow?.traitCollection
        .horizontalSizeClass == UIUserInterfaceSizeClass.regular {
        activityController.modalPresentationStyle = .popover
        activityController.popoverPresentationController?
            .barButtonItem = sender
    }
    self.present(activityController, animated: true,
        completion: nil)
}

在这个代码中,我们首先检查是否有图像可供分享,然后创建 UIActivityViewController 并传入图像作为活动项目。如果应用在较大屏幕上运行,我们将分享视图以弹出框的形式显示,这样可以提供更好的用户体验。

接力(Handoff)功能

接力功能允许用户在不同设备之间无缝切换工作,实现步骤较为复杂,主要包括以下几个方面:
1. 设置文档类型属性,确保不同设备之间的活动类型一致:

1. 选择项目导航器顶部的项目。
2. 进入 `Notes` 目标设置(即 macOS 应用),滚动到“Document Types”部分。
3. 展开“Additional document type properties”,选择 `CFBundleTypOSTypes` 条目,点击“+”按钮添加新条目。
4. 将新条目命名为 `NSUbiquitousDocumentUserActivityType`,类型设置为 `String`,值设置为 `au.com.secretlab.Notes.editing`。
5. 在 `Notes-iOS` 中进行相同的设置。
  1. 实现 continueUserActivity 方法,处理用户从一个设备接力到另一个设备的活动:
func application(_ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
    // Return to the list of documents
    if let navigationController =
        self.window?.rootViewController as? UINavigationController {
        navigationController.popToRootViewController(animated: false)
        // We're now at the list of documents; tell the restoration
        // system that this view controller needs to be informed
        // that we're continuing the activity
        if let topViewController = navigationController.topViewController {
            restorationHandler([topViewController])
        }
        return true
    }
    return false
}
  1. 实现 restoreUserActivityState 方法,根据用户活动信息打开相应的文档:
override func restoreUserActivityState(_ activity: NSUserActivity) {
    // We're being told to open a document
    if let url = activity.userInfo?[NSUserActivityDocumentURLKey] as? URL {
        // Open the document
        self.performSegue(withIdentifier: "ShowDocument", sender: url)
    }
}
  1. 使活动变为当前活动,让系统知道用户正在进行的活动:
// If this document is not already open, open it
if document.documentState.contains(UIDocumentState.closed) {
    document.open { (success) -> Void in
        if success == true {
            self.textView?.attributedText = document.text
            self.attachmentsCollectionView?.reloadData()
            // We are now engaged in this activity
            document.userActivity?.becomeCurrent()
            // Register for state change notifications
            self.stateChangedObserver = Notification.default
.addObserver(
                forName: NSNotification.Name.UIDocumentStateChanged,
                object: document,
                queue: nil,
                using: { (notification) -> Void in
                self.documentStateChanged()
            })
            self.documentStateChanged()
        }

通过以上步骤,我们可以确保用户在不同设备之间能够无缝切换工作,提高工作效率。

搜索功能

搜索功能的实现分为索引活动和 Spotlight 扩展两个部分。

索引活动 :通过 NSUserActivity 为应用添加搜索索引支持,主要步骤如下:
1. 导入 Core Spotlight 框架:

import CoreSpotlight
  1. 更新 viewWillAppear 方法,为文档的用户活动添加可搜索的元数据:
// If this document is not already open, open it
if document.documentState.contains(UIDocumentState.closed) {
    document.open { (success) -> Void in
        if success == true {
            self.textView?.attributedText = document.text
            self.attachmentsCollectionView?.reloadData()
            // Add support for searching for this document
            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?.isEligibleForSearch = true
            // We are now engaged in this activity
            document.userActivity?.becomeCurrent()
            // Register for state change notifications
            self.stateChangedObserver = Notification.default
.addObserver(
                forName: NSNotification.Name.UIDocumentStateChanged,
                object: document,
                queue: nil,
                using: { (notification) -> Void in
                self.documentStateChanged()
            })
            self.documentStateChanged()
        }

Spotlight 扩展 :为了让应用的所有文档内容都能在 Spotlight 中搜索到,我们需要添加一个 Spotlight 索引应用扩展,步骤如下:
1. 添加扩展目标:

1. 打开文件菜单,选择“New” -> “Target”。
2. 选择 `iOS` -> `Application Extension` -> `Spotlight Index Extension`。
3. 将新目标命名为 `Notes-SpotlightIndexer`。
4. 点击“Finish”后,激活新方案。
  1. 授予 iCloud 访问权限:
1. 进入目标属性,点击“Capabilities”标签。
2. 打开 iCloud 开关,等待加载完成。
3. 打开 iCloud Documents 开关。
4. 选择 Mac 和 iOS 应用使用的 iCloud 容器,确保不选择其他容器。将“Use default container” 改为 “Specify custom container”。
  1. 实现索引器方法:
// 获取所有可用文件
var availableFiles : [URL] {
    let fileManager = FileManager.default
    var allFiles : [URL] = []
    // Get the list of all local files
    if let localDocumentsFolder
        = fileManager.urls(for: .documentDirectory,
            in: .userDomainMask).first {
        do {
            let localFiles = try fileManager
                .contentsOfDirectory(atPath: localDocumentsFolder.path)
                .map({
                    localDocumentsFolder.appendingPathComponent($0,
                        isDirectory: false)
                })
            allFiles.append(contentsOf: localFiles)
        } catch {
            NSLog("Failed to get list of local files!")
        }
    }
    // Get the list of documents in iCloud
    if let documentsFolder = fileManager
        .url(forUbiquityContainerIdentifier: nil)?
        .appendingPathComponent("Documents", isDirectory: true) {
        do {
            // Get the list of files
            let iCloudFiles = try fileManager
                .contentsOfDirectory(atPath: documentsFolder.path)
                .map({
                    documentsFolder.appendingPathComponent($0,
                        isDirectory: false)
                })
            allFiles.append(contentsOf: iCloudFiles)
        } catch  {
            // Log an error and return the empty array
            NSLog("Failed to get contents of iCloud container")
            return []
        }
    }
    // Filter these to only those that end in ".note",
    // and return NSURLs of these
    return allFiles
        .filter({ $0.lastPathComponent.hasSuffix(".note") })
}

// 为给定的 URL 生成 CSSearchableItem
func itemForURL(_ url: URL) -> CSSearchableItem? {
    // If this URL doesn't exist, return nil
    if (url as NSURL).checkResourceIsReachableAndReturnError(nil) == false
{
        return nil
    }
    // Replace this with your own type identifier
    let attributeSet = CSSearchableItemAttributeSet(
        itemContentType: "au.com.secretlab.Note")
    attributeSet.title = url.lastPathComponent
    // Get the text in this file
    let textFileURL = url.appendingPathComponent(
        NoteDocumentFileNames.TextFile.rawValue)
    if let textData = try? Data(contentsOf: textFileURL),
       let text = try? NSAttributedString(data: textData,
           options:
[NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType],
           documentAttributes: nil) {
                attributeSet.contentDescription = text.string
    } else {
        attributeSet.contentDescription = ""
    }
    let item =
        CSSearchableItem(uniqueIdentifier: url.absoluteString,
        domainIdentifier: "au.com.secretlab.Notes",
        attributeSet: attributeSet)
    return item
}

// 重新索引所有可搜索项
override func searchableIndex(_ searchableIndex: CSSearchableIndex,
    reindexAllSearchableItemsWithAcknowledgementHandler
        acknowledgementHandler: @escaping () -> Void) {
    // Reindex all data with the provided index
    let files = availableFiles
    var allItems : [CSSearchableItem] = []
    for file in files {
        if let item = itemForURL(file) {
            allItems.append(item)
        }
    }
    searchableIndex.indexSearchableItems(allItems) { (error) -> Void in
        acknowledgementHandler()
    }
}

// 重新索引指定标识符的可搜索项
override func searchableIndex(_ searchableIndex: CSSearchableIndex,
              reindexSearchableItemsWithIdentifiers identifiers: [String],
                                  acknowledgementHandler: @escaping () -> Void) {
    // Reindex any items with the given identifiers and the provided index
    var itemsToIndex : [CSSearchableItem] = []
    var itemsToRemove : [String] = []
    for identifier in identifiers {
        if let url = URL(string: identifier), let item = itemForURL(url) {
            itemsToIndex.append(item)
        } else {
            itemsToRemove.append(identifier)
        }
    }
    searchableIndex.indexSearchableItems(itemsToIndex) { (error) -> Void in
        searchableIndex
            .deleteSearchableItems(withIdentifiers: itemsToRemove) {
                (error) -> Void in
                acknowledgementHandler()
            }
    }
}

通过以上步骤,应用将能够定期对所有文档进行索引,并在搜索结果中显示这些文档。

总结与展望

通过为 iOS 应用添加分享、接力和搜索等功能,我们可以显著提升应用的用户体验和实用性,使应用更好地融入用户的设备生态系统。这些功能的实现不仅可以让用户更方便地使用应用,还可以提高应用在用户设备中的可见性和使用率。

在实际开发过程中,我们需要注意以下几点:
1. 代码的兼容性 :确保代码在不同版本的 iOS 系统和设备上都能正常运行。
2. 性能优化 :对于搜索和索引等功能,要注意性能优化,避免影响应用的响应速度。
3. 用户体验 :在实现功能的同时,要注重用户体验,确保操作流程简单、直观。

未来,随着 iOS 系统的不断更新和发展,我们可以进一步探索更多的功能和优化方案,为用户提供更加优质的应用体验。例如,可以结合机器学习和人工智能技术,实现更智能的搜索和推荐功能;或者进一步优化接力功能,实现更无缝的设备切换体验。

希望以上内容对你有所帮助,祝你在应用开发中取得更大的成功!

为了更清晰地展示不同功能的关键代码和实现要点,我们可以用表格进行总结:
| 功能 | 关键代码 | 实现要点 |
| — | — | — |
| 附件删除功能 | self.attachmentsCollectionView?.deleteItems(at: [indexPath])
self.endEditMode() | 删除集合视图项目,结束编辑模式 |
| 分享功能 | let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil) | 创建分享控制器,根据屏幕大小设置显示方式 |
| 接力功能 | document.userActivity?.becomeCurrent()
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool | 设置活动为当前活动,处理接力活动 |
| 搜索功能 - 索引活动 | document.userActivity?.title = document.localizedName
document.userActivity?.isEligibleForSearch = true | 为用户活动添加元数据,设置可搜索 |
| 搜索功能 - Spotlight 扩展 | var availableFiles : [URL]
func itemForURL(_ url: URL) -> CSSearchableItem? | 获取所有可用文件,生成可搜索项 |

同时,我们可以用 mermaid 流程图来展示搜索功能中 Spotlight 扩展的实现流程:

graph LR
    A[添加扩展目标] --> B[授予 iCloud 访问权限]
    B --> C[实现索引器方法]
    C --> C1[获取所有可用文件]
    C --> C2[生成可搜索项]
    C --> C3[重新索引所有可搜索项]
    C --> C4[重新索引指定标识符的可搜索项]

通过以上的总结和分析,我们可以更加清晰地了解这些功能的实现原理和关键要点,为后续的开发和优化提供有力的支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值