iOS开发:搜索、多任务功能实现全解析
在iOS开发中,实现用户活动搜索、多任务处理等功能能显著提升用户体验。下面将详细介绍如何实现这些功能。
让用户活动可搜索
在应用中,我们可能希望用户在应用内的活动能够被搜索到。用户活动的类型为
NSUserActivity
。
解决方案
:使用
NSUserActivity
类的
isEligibleForSearch
和
eligibleForPublicIndexing
属性将活动标记为可搜索。
操作步骤
:
1.
设置UI
:在视图控制器中添加一个文本字段和一个文本视图。文本字段用于用户输入文本,文本视图用于记录日志信息。将文本字段和文本视图与代码关联,并将文本字段的代理设置为视图控制器。
2.
遵循协议并实现方法
:让视图控制器遵循
UITextFieldDelegate
和
NSUserActivityDelegate
协议,并实现相关的代理方法。
func userActivityWasContinued(_ userActivity: NSUserActivity) {
log("Activity was continued")
}
func userActivityWillSave(_ userActivity: NSUserActivity) {
log("Activity will save")
}
- 编写辅助方法 :编写用于记录日志和读取文本字段内容的方法。
func log(_ t: String){
DispatchQueue.main.async {
self.status.text = t + "\n" + self.status.text
}
}
func textFieldText() -> String{
if let txt = self.textField.text{
return txt
} else {
return ""
}
}
- 创建可搜索的用户活动 :创建一个懒加载的用户活动,并将其标记为可搜索。
// TODO: change this ID to something relevant to your app
let activityType = "se.pixolity.Making-User-Activities-Searchable.editText"
let activityTxtKey = "se.pixolity.Making-User-Activities-Searchable.txt"
lazy var activity: NSUserActivity = {
let a = NSUserActivity(activityType: self.activityType)
a.title = "Text Editing"
a.isEligibleForHandoff = true
a.isEligibleForSearch = true
// do this only if it makes sense
// a.isEligibleForPublicIndexing = true
a.delegate = self
a.keywords = ["txt", "text", "edit", "update"]
let att = CSSearchableItemAttributeSet(
itemContentType: kUTTypeText as String)
att.title = a.title
att.contentDescription = "Editing text right in the app"
att.keywords = Array(a.keywords)
if let u = Bundle.main.url(forResource: "Icon", withExtension: "png"){
att.thumbnailData = try? Data(contentsOf: u)
}
a.contentAttributeSet = att
return a
}()
- 处理文本字段事件 :当文本字段开始编辑时,将活动标记为当前活动;当文本字段结束编辑时,取消活动的当前状态。
func textFieldDidBeginEditing(_ textField: UITextField) {
log("Activity is current")
userActivity = activity
activity.becomeCurrent()
}
func textFieldDidEndEditing(_ textField: UITextField) {
log("Activity resigns being current")
activity.resignCurrent()
userActivity = nil
}
- 更新用户活动状态 :当文本字段内容发生变化时,标记用户活动需要更新。
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
activity.needsSave = true
return true
}
-
定期更新活动状态
:在视图控制器的
updateUserActivityState方法中更新活动的用户信息字典。
override func updateUserActivityState(_ a: NSUserActivity) {
log("We are asked to update the activity state")
a.addUserInfoEntries(
from: [self.activityTxtKey : self.textFieldText()])
super.updateUserActivityState(a)
}
通过以上步骤,当用户在文本字段中输入文本并将应用置于后台后,用户可以在主屏幕上搜索该活动并继续之前的操作。
删除应用的可搜索内容
如果我们在Spotlight中索引了一些项目,现在想删除这些内容,可以使用
CSSearchableIndex
类的相关方法。
解决方案
:使用
CSSearchableIndex
类的以下方法组合:
-
deleteAllSearchableItems(completionHandler:)
-
deleteSearchableItems(withDomainIdentifiers:completionHandler:)
-
deleteSearchableItems(withIdentifiers:completionHandler:)
操作步骤
:
1.
获取
CSSearchableIndex
实例
:
let identifiers = [
"com.yourcompany.etc1",
"com.yourcompany.etc2",
"com.yourcompany.etc3"
]
let i = CSSearchableIndex(name: Bundle.main.bundleIdentifier!)
-
获取最新应用状态
:使用
fetchLastClientState(_:completionHandler:)方法获取最新的应用状态。 -
开始批量删除
:使用
beginIndexBatch()方法开始批量更新,然后使用deleteSearchableItems(withIdentifiers:completionHandler:)方法删除指定标识符的项目。
i.fetchLastClientState {clientState, err in
guard err == nil else{
print("Could not fetch last client state")
return
}
let state: Data
if let s = clientState{
state = s
} else {
state = Data()
}
i.beginBatch()
i.deleteSearchableItems(withIdentifiers: identifiers) {err in
if let e = err{
print("Error happened \(e)")
} else {
print("Successfully deleted the given identifiers")
}
}
i.endBatch(withClientState: state, completionHandler: {err in
guard err == nil else{
print("Error happened in ending batch updates = \(err!)")
return
}
print("Successfully batch updated the index")
})
}
支持分屏视图
在iPad上,我们可能希望通用应用支持分屏视图,即用户可以在应用运行时将另一个应用拖到屏幕右侧,使两个应用并排显示。
解决方案
:
-
新项目
:使用最新版本的Xcode创建项目,默认情况下,应用将在较大的显示屏(如iPad)上启用分屏视图。
-
旧项目
:如果是旧项目,需要进行以下操作:
1. 添加一个名为
LaunchScreen.storyboard
的文件,并将其设置为应用的启动屏幕故事板。
2. 将项目的基础SDK设置为最新的Xcode版本中的可用SDK。
3. 在
info.plist
文件中,在
UISupportedInterfaceOrientations~ipad
键下声明支持所有方向。
4. 确保
UIRequiresFullScreen
键在
plist
文件中被移除,或者其值为
NO
。
代码示例 :为了确保UI组件在分屏视图下正确工作,我们可以添加约束来调整视图的大小。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let newView = UIView()
newView.backgroundColor = .orange
newView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(newView)
newView.leadingAnchor.constraint(equalTo:
view.leadingAnchor).isActive = true
newView.trailingAnchor.constraint(equalTo:
view.trailingAnchor).isActive = true
newView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
newView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
添加画中画播放功能
我们可能希望用户能够将视频缩小到屏幕的一部分,以便在查看和与其他应用中的内容进行交互时继续观看视频。
解决方案
:
1.
创建视图
:创建一个具有
AVPlayerLayer
类型图层的视图。
2.
实例化视频项目
:实例化一个
AVPlayerItem
来表示视频。
3.
创建播放器
:将播放器项目放入
AVPlayer
实例中。
4.
设置播放器
:将播放器分配给视图的图层播放器对象。
5.
开始播放
:将视图分配给视图控制器的主视图,并调用
play()
方法开始正常播放。
6.
监听状态变化
:使用KVO监听播放器的
currentItem.status
属性变化,当状态变为
ReadyToPlay
时,创建
AVPictureInPictureController
实例。
7.
检查画中画可用性
:监听
AVPictureInPictureController
的
pictureInPicturePossible
属性变化,当该值变为
true
时,通知用户可以进入画中画模式。
8.
启动画中画
:当用户按下按钮启动画中画时,检查
pictureInPicturePossible
的值,如果为
true
,调用
startPictureInPicture()
方法启动画中画。
操作步骤
:
1.
创建
PipView
类
:创建一个名为
PipView
的视图类,并使其具有
AVPlayerLayer
类型的图层。
import Foundation
import UIKit
import AVFoundation
protocol Pippable{
var pipLayer: AVPlayerLayer{get}
var pipLayerPlayer: AVPlayer? {get set}
}
extension UIView : Pippable{
var pipLayer: AVPlayerLayer{
get{return layer as! AVPlayerLayer}
}
// shortcut into pipLayer.player
var pipLayerPlayer: AVPlayer?{
get{return pipLayer.player}
set{pipLayer.player = newValue}
}
open public func awakeFromNib() {
super.awakeFromNib()
backgroundColor = .black
}
}
class PipView : UIView{
override class var layerClass: AnyClass{
return AVPlayerLayer.self
}
}
-
设置视图控制器
:在视图控制器的故事板中,将主视图的类设置为
PipView,并在导航栏上添加“Play”和“PiP”按钮。 - 导入框架并定义属性 :在视图控制器中导入必要的框架,并定义KVO上下文和相关属性。
import UIKit
import AVKit
import AVFoundation
import SharedCode
private var kvoContext = 0
let pipPossible = "pictureInPicturePossible"
let currentItemStatus = "currentItem.status"
protocol PippableViewController{
var pipView: PipView {get}
}
extension ViewController : PippableViewController{
var pipView: PipView{
return view as! PipView
}
}
@IBOutlet var beginPipBtn: UIBarButtonItem!
lazy var player: AVPlayer = {
let p = AVPlayer()
p.addObserver(self, forKeyPath: currentItemStatus,
options: .new, context: &kvoContext)
return p
}()
var pipController: AVPictureInPictureController?
var videoUrl: URL? = nil{
didSet{
if let u = videoUrl{
let asset = AVAsset(url: u)
let item = AVPlayerItem(asset: asset,
automaticallyLoadedAssetKeys: ["playable"])
player.replaceCurrentItem(with: item)
pipView.pipLayerPlayer = player
}
}
}
-
实现播放和画中画方法
:实现
play()和beginPip()方法。
@IBAction func play() {
guard setAudioCategory() else{
alert("Could not set the audio category")
return
}
guard let u = embeddedVideo else{
alert("Cannot find the embedded video")
return
}
videoUrl = u
player.play()
}
@IBAction func beginPip() {
guard isPipSupported() else{
alert("PiP is not supported on your machine")
return
}
guard let controller = pipController else{
alert("Could not instantiate the pip controller")
return
}
controller.addObserver(self, forKeyPath: pipPossible,
options: .new, context: &kvoContext)
if controller.isPictureInPicturePossible{
controller.startPictureInPicture()
} else {
alert("Pip is not possible")
}
}
-
监听KVO消息
:实现
observeValue方法监听KVO消息。
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?, change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
guard context == &kvoContext else{
return
}
if keyPath == pipPossible{
guard let possibleInt = change?[NSKeyValueChangeKey.newKey]
as? NSNumber else{
beginPipBtn.isEnabled = false
return
}
beginPipBtn.isEnabled = possibleInt.boolValue
}
else if keyPath == currentItemStatus{
guard let statusInt = change?[NSKeyValueChangeKey.newKey] as? NSNumber,
let status = AVPlayerItemStatus(rawValue: statusInt.intValue),
status == .readyToPlay else{
return
}
startPipController()
}
}
处理低电量模式并提供替代方案
我们可能需要知道设备是否处于低电量模式,并在用户更改该模式状态时得到更新。
解决方案
:读取
NSProcessInfo
的
lowPowerModeEnabled
属性值来确定设备是否处于低电量模式,并监听
NSProcessInfoPowerStateDidChangeNotification
通知以了解状态变化。
操作步骤
:
1.
监听通知
:在视图控制器的
viewDidLoad
方法中添加通知监听器。
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(ViewController.powerModeChanged(_:)),
name: NSNotification.Name.NSProcessInfoPowerStateDidChange, object: nil)
downloadNow()
}
-
实现下载方法
:实现
downloadNow方法,避免在低电量模式下下载文件。
func downloadNow(){
guard let url = URL(string: "http://localhost:8888/video.mp4"),
!processInfo.isLowPowerModeEnabled else{
return
}
// do the download here
print(url)
mustDownloadVideo = false
}
-
处理电量模式变化
:实现
powerModeChanged方法,当电量模式变化时检查是否需要下载文件。
import UIKit
class ViewController: UIViewController {
var mustDownloadVideo = true
let processInfo = ProcessInfo.processInfo
func powerModeChanged(_ notif: Notification){
guard mustDownloadVideo else{
return
}
downloadNow()
}
}
通过以上方法,我们可以在iOS应用中实现用户活动搜索、删除可搜索内容、支持分屏视图、添加画中画播放功能以及处理低电量模式等功能,提升应用的用户体验和实用性。
iOS开发:搜索、多任务功能实现全解析
技术点总结与对比
为了更清晰地展示各项功能的关键信息,我们将上述功能的关键技术点整理成如下表格:
| 功能 | 关键类/协议 | 关键方法 | 主要操作步骤 |
| — | — | — | — |
| 让用户活动可搜索 |
NSUserActivity
、
UITextFieldDelegate
、
NSUserActivityDelegate
|
isEligibleForSearch
、
becomeCurrent()
、
resignCurrent()
、
updateUserActivityState
| 设置UI、遵循协议并实现方法、编写辅助方法、创建可搜索的用户活动、处理文本字段事件、更新用户活动状态 |
| 删除应用的可搜索内容 |
CSSearchableIndex
|
deleteAllSearchableItems
、
deleteSearchableItems(withIdentifiers:)
| 获取
CSSearchableIndex
实例、获取最新应用状态、开始批量删除 |
| 支持分屏视图 | - | - | 新项目:用最新Xcode创建;旧项目:添加
LaunchScreen.storyboard
、设置基础SDK、声明支持方向、处理
UIRequiresFullScreen
键 |
| 添加画中画播放功能 |
AVPlayerLayer
、
AVPlayerItem
、
AVPlayer
、
AVPictureInPictureController
、
Pippable
、
PippableViewController
|
startPictureInPicture()
、
observeValue
| 创建视图、实例化视频项目、创建播放器、设置播放器、开始播放、监听状态变化、检查画中画可用性、启动画中画 |
| 处理低电量模式并提供替代方案 |
NSProcessInfo
| - | 监听通知、实现下载方法、处理电量模式变化 |
功能实现流程梳理
下面通过mermaid格式的流程图来梳理各个功能的实现流程。
让用户活动可搜索流程
graph LR
A[设置UI] --> B[遵循协议并实现方法]
B --> C[编写辅助方法]
C --> D[创建可搜索的用户活动]
D --> E[处理文本字段事件]
E --> F[更新用户活动状态]
添加画中画播放功能流程
graph LR
A[创建视图] --> B[实例化视频项目]
B --> C[创建播放器]
C --> D[设置播放器]
D --> E[开始播放]
E --> F[监听状态变化]
F --> G[检查画中画可用性]
G --> H[启动画中画]
代码优化建议
在实现这些功能的过程中,我们可以对代码进行一些优化,以提高代码的可读性和可维护性。
-
代码复用
:对于一些重复使用的代码片段,我们可以将其封装成独立的函数或类。例如,在处理低电量模式时,
downloadNow方法中的URL可以作为参数传入,这样可以提高代码的灵活性。
func downloadNow(url: URL) {
guard !processInfo.isLowPowerModeEnabled else {
return
}
// do the download here
print(url)
mustDownloadVideo = false
}
- 错误处理 :在代码中添加更详细的错误处理逻辑,以提高代码的健壮性。例如,在获取视频URL时,如果URL无效,可以给出更明确的错误提示。
@IBAction func play() {
guard setAudioCategory() else {
alert("Could not set the audio category")
return
}
guard let u = embeddedVideo else {
alert("Cannot find the embedded video. Please check the video resource.")
return
}
videoUrl = u
player.play()
}
注意事项
在实现这些功能时,还需要注意以下几点:
- 设备兼容性 :分屏视图和画中画功能需要设备具有足够的屏幕空间和资源,如iPad Pro支持分屏视图,而画中画功能需要特定的设备支持。在开发过程中,要进行充分的测试,确保功能在不同设备上都能正常工作。
- 权限问题 :在处理音频和视频播放时,可能需要获取相应的权限。例如,在设置音频类别时,要确保应用具有音频播放的权限。
- 性能优化 :在监听状态变化时,要注意避免频繁的状态检查,以免影响应用的性能。例如,在监听播放器状态时,可以合理设置KVO的监听频率。
通过以上对iOS开发中搜索、多任务功能的详细介绍,我们可以看到这些功能的实现虽然有一定的复杂度,但只要按照正确的步骤和方法进行,就可以在应用中实现这些功能,为用户带来更好的体验。希望本文能对iOS开发者有所帮助,让大家在开发过程中更加得心应手。
超级会员免费看

被折叠的 条评论
为什么被折叠?



