77、iOS 通知与活动视图开发全解析

iOS 通知与活动视图开发全解析

1. 本地通知管理

在 iOS 开发中,我们可以对本地通知进行灵活管理,包括移除待处理的通知请求和已送达的通知。
- 移除待处理通知请求
- removePendingNotificationRequests(withIdentifiers:) :可根据标识符移除特定的待处理通知请求。
- removeAllPendingNotificationRequests :移除所有待处理的通知请求。
- 通过移除通知、进行必要修改后重新添加,可实现通知的重新调度。
- 管理已送达通知
- getDeliveredNotifications(completionHandler:) :获取已送达但尚未从用户通知中心移除的通知列表。
- removeDeliveredNotifications(withIdentifiers:) :根据标识符移除已送达的通知。
- removeAllDeliveredNotifications :移除所有已送达的通知。

合理移除通知可以保持用户通知中心的整洁,例如只让最近送达的通知显示。甚至可以修改已送达通知的文本,只需添加一个标识符与现有通知相同的新通知即可。

需要注意的是,取消重复的本地通知需要开发者在代码中实现,若未为用户提供取消方式,用户可能只能通过删除应用来阻止通知重复出现。

2. 通知内容扩展

可以通过编写通知内容扩展来定制通知的二级界面。这是一个独立于应用目标的目标,因为系统可能在应用未运行时访问它。

2.1 添加通知内容扩展

要为应用添加通知内容扩展,可按以下步骤操作:
1. 创建一个新目标,选择 iOS → Application Extension → Notification Content Extension。
2. 该模板提供了故事板和对应的视图控制器代码。代码文件会导入 User Notifications 框架和 User Notifications UI 框架,视图控制器会采用 UNNotificationContentExtension 协议。

视图控制器代码中包含 didReceive(_:) 方法的存根实现,这是唯一必需的方法。通过该方法可以从 UNNotification 中提取信息来配置界面。若要提取附件,需使用以下 URL 方法:

• startAccessingSecurityScopedResource
• stopAccessingSecurityScopedResource

视图控制器还需设置 preferredContentSize 来指定自定义界面的尺寸,也可以使用自动布局。

以下是一个示例,展示如何实现自定义界面:

override func viewDidLoad() {
    super.viewDidLoad()
    self.preferredContentSize = CGSize(320, 80)
}

func didReceive(_ notification: UNNotification) {
    let req = notification.request
    let content = req.content
    let atts = content.attachments
    if let att = atts.first, att.identifier == "cup" {
        if att.url.startAccessingSecurityScopedResource() {
            if let data = try? Data(contentsOf: att.url) {
                self.imageView.image = UIImage(data: data)
            }
            att.url.stopAccessingSecurityScopedResource()
        }
    }
    self.view.setNeedsLayout()
}
2.2 配置 Info.plist

模板包含一个 Info.plist 文件,需要配置以下键:
| 键名 | 说明 |
| ---- | ---- |
| UNNotificationExtensionCategory | 对应本地通知的 categoryIdentifier 的字符串,用于关联通知内容扩展和通知。 |
| UNNotificationExtensionInitialContentSizeRatio | 自定义界面宽度与高度的比值,为系统提供大致尺寸信息。 |
| UNNotificationExtensionDefaultContentHidden | 可选布尔值,设置为 YES 可消除自定义界面中本地通知标题、副标题和正文的默认显示。 |
| UNNotificationExtensionOverridesDefaultTitle | 可选布尔值,设置为 YES 可自定义界面顶部应用名称的显示。 |

3. 操作按钮管理

二级界面可以包含自定义操作按钮。当用户点击这些按钮时,用户通知中心代理的 userNotificationCenter(_:didReceive:withCompletionHandler:) 方法会被调用。不过,通知内容扩展视图控制器可以通过实现 didReceive(_:completionHandler:) 方法进行干预。

在实现 didReceive(_:completionHandler:) 方法时,需要调用完成函数并传入以下响应之一:
- .doNotDismiss :本地通知警报保持不变,继续显示自定义二级界面。
- .dismiss :警报被关闭。
- .dismissAndForwardAction :警报被关闭,操作传递给用户通知中心代理的 userNotificationCenter(_:didReceive:withCompletionHandler:) 方法。

在 iOS 12 中,通知内容扩展视图控制器可以在代码中动态创建和移除自定义操作。通过 UIViewController extensionContext 属性的 notificationActions 数组来管理操作按钮。这意味着可以在视图控制器的 didReceive(_:completionHandler:) 方法中创建自定义操作,而不一定在类别配置中创建。不过,在类别配置中创建自定义操作按钮可以在没有通知内容扩展的情况下使用自定义按钮。

4. 界面交互

在 iOS 12 之前,自定义二级界面(通知内容扩展视图控制器的主视图)是不可交互的,用户点击只会触发通知的默认操作,即关闭通知并打开应用。不过,运行时可以为包含视频或音频材料的自定义界面添加可点击的播放/暂停按钮,通过重写三个 UNNotificationContentExtension 属性和实现两个方法来控制按钮的显示和响应。

在 iOS 12 中,视图控制器的主视图可以变得可交互。只需在 Info.plist NSExtensionAttributes 中添加 UNNotificationExtensionUserInteractionEnabled 布尔键并设置为 YES 。此时,用户可以点击界面中的按钮调用视图控制器中的方法。

由于界面可交互后,用户不能再通过点击视图触发通知的默认操作,因此在 iOS 12 中提供了触发默认操作的方法:调用扩展上下文的 performNotificationDefaultAction 。同时,也可以调用 dismissNotificationContentExtension 方法在不打开应用的情况下关闭通知。

5. 今日扩展

当用户在锁定屏幕、主屏幕或通知中心横向滑动时,会显示今日列表,应用可以提供今日小部件。为应用添加今日扩展的步骤如下:
1. 创建一个新目标,选择 iOS → Application Extension → Today Extension。
2. 模板提供了故事板和采用 NCWidgetProviding 协议的视图控制器代码。可能需要编辑扩展的 Info.plist 来设置 “Bundle display name” 条目,这是扩展上方显示的标题。

今日扩展目标会显式链接到 Notification Center 框架,不要修改此链接,否则扩展可能会崩溃。

设计扩展界面时,可以在提供的故事板中进行。通过提供足够的约束或设置视图控制器的 preferredContentSize 来确定扩展的高度。

每次扩展界面即将显示时,视图控制器的 widgetPerformUpdate(completionHandler:) 方法会被调用,用于更新界面。完成后需调用 completionHandler 并传入 NCUpdateResult .newData .noData .failed )。耗时的工作应在后台线程执行。

func widgetPerformUpdate(completionHandler:
    @escaping (NCUpdateResult) -> ()) {
        // ... do stuff quickly ...
        completionHandler(.newData)
}

与应用的通信可能有点复杂。例如,通过扩展视图控制器的 extensionContext open(_:completionHandler:) 方法打开应用,并使用自定义 URL 方案传递信息。应用需要在 Info.plist 中声明自定义 URL 方案,并在应用代理中实现 application(_:open:options:) 方法来处理 URL。

今日扩展的小部件界面可以有紧凑和展开两种高度。若要实现此功能:
1. 在视图控制器的 viewDidLoad 方法中运行以下代码:

self.extensionContext?.widgetLargestAvailableDisplayMode = .expanded
  1. 实现 widgetActiveDisplayModeDidChange(_:withMaximumSize:) 方法,根据显示模式更改视图控制器的 preferredContentSize

如果应用有今日扩展,当用户使用 3D 触摸手势召唤快速操作时,今日扩展小部件会自动显示,且可以是交互式的,可能可以替代快速操作按钮。

6. 活动视图

活动视图通常在用户点击分享按钮时显示,它属于 UIActivityViewController 。显示活动视图时,需要提供一个或多个数据对象,如字符串或图像,活动视图会显示所有可以处理这些数据类型的活动图标。

6.1 呈现活动视图

要呈现活动视图,可按以下步骤操作:
1. 实例化 UIActivityViewController ,使用 init(activityItems:applicationActivities:) 初始化方法。 activityItems: 参数是要共享或操作的对象数组。
2. 设置活动视图控制器的 completionWithItemsHandler 属性,该函数会在用户与活动界面的交互结束时被调用。
3. 呈现活动视图控制器,在 iPad 上它会是一个弹出框,需要配置弹出框呈现控制器。

以下是一个示例:

let url = Bundle.main.url(forResource:"sunglasses", withExtension:"png")!
let things : [Any] = ["This is a cool picture", url]
let avc = UIActivityViewController(
    activityItems:things, applicationActivities:nil)
avc.completionWithItemsHandler = { type, ok, items, err in
    // ...
}
self.present(avc, animated:true)
if let pop = avc.popoverPresentationController {
    let v = sender as! UIView
    pop.sourceView = v
    pop.sourceRect = v.bounds
}

活动视图会自动填充可以处理 activityItems: 参数中数据类型的系统活动。这些活动由 UIActivity.ActivityType 常量指定,如 .postToFacebook .postToTwitter 等。若不想包含某些系统活动,需要通过设置 UIActivityViewController excludedActivityTypes 属性来排除。

如果 activityItems: 数组中的元素是提供数据的对象而不是数据本身,可以让该对象采用 UIActivityItemSource 协议。例如:

extension ViewController : UIActivityItemSource {
    func activityViewControllerPlaceholderItem(
        _ activityViewController: UIActivityViewController) -> Any {
            return ""
    }
    func activityViewController(
        _ activityViewController: UIActivityViewController,
        itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
            return "Coolness"
    }
}

如果提供 activityItems: 数据耗时,可以使用 UIActivityItemProvider 子类的实例。

6.2 自定义活动

init(activityItems:applicationActivities:) 方法的 applicationActivities: 参数用于列出应用内部实现的额外活动。这些活动是 UIActivity 子类的实例,它们的图标会显示在活动视图的下一行。

以创建一个名为 “Be Cool” 的简单活动为例,该活动接受字符串活动项。首先创建 MyCoolActivity 类:

var items : [Any]?
var image : UIImage
override init() {
    // ... construct self.image ...
    super.init()
}
override class var activityCategory : UIActivity.ActivityCategory {
    return .action // the default
}
override var activityType : UIActivity.ActivityType {
    return UIActivity.ActivityType("com.neuburg.matt.coolActivity")
}
override var activityTitle : String? {
    return "Be Cool"
}
override var activityImage : UIImage? {
    return self.image
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
    for obj in activityItems {
        if obj is String {
            return true
        }
    }
    return false
}
override func prepare(withActivityItems activityItems: [Any]) {
    self.items = activityItems
}

如果 canPerform(withActivityItems:) 方法返回 true ,活动图标会显示在活动视图中。当用户点击图标时, prepare(withActivityItems:) 方法会被调用,将活动项保存到 items 属性中。

要执行活动,需要实现以下两种方法之一:
- perform 方法:直接执行活动,若活动耗时应在后台线程执行。
- activityViewController 属性:提供一个 UIViewController 子类的实例,活动视图机制会为我们呈现该视图控制器。

无论采用哪种方法,最终都要调用活动实例的 activityDidFinish(_:) 方法,以通知活动视图机制活动已结束。例如:

override func perform() {
    // ... do something with self.items here ...
    self.activityDidFinish(true)
}

通过以上内容,我们全面了解了 iOS 开发中本地通知管理、通知内容扩展、今日扩展和活动视图的相关知识和实现方法,能够为用户提供更丰富、更个性化的交互体验。

iOS 通知与活动视图开发全解析

7. 系统活动类型及处理

活动视图中的系统活动类型丰富多样,涵盖了社交分享、通信、文件处理等多个方面。以下是一些常见的系统活动类型及其可处理的数据类型:
| 活动类型 | 可处理的数据类型 |
| ---- | ---- |
| .postToFacebook | 字符串、图像、文件(如图片文件) |
| .postToTwitter | 字符串、图像、文件 |
| .message | 字符串、图像、文件 |
| .mail | 字符串、图像、文件 |
| .print | 字符串、图像、文件 |
| .copyToPasteboard | 字符串、图像、文件 |
| .assignToContact | 字符串、图像、文件 |
| .saveToCameraRoll | 图像、视频文件 |
| .addToReadingList | 网页链接 |
| .postToFlickr | 图像、视频文件 |
| .postToVimeo | 视频文件 |
| .postToTencentWeibo | 字符串、图像、文件 |
| .airDrop | 字符串、图像、文件 |
| .openInIBooks | 电子书文件 |
| .markupAsPDF | PDF 文件 |

在使用活动视图时,我们可以根据需要排除某些系统活动。例如,如果不想让用户使用邮件分享功能,可以这样设置:

let url = Bundle.main.url(forResource:"sunglasses", withExtension:"png")!
let things : [Any] = ["This is a cool picture", url]
let avc = UIActivityViewController(
    activityItems:things, applicationActivities:nil)
avc.excludedActivityTypes = [.mail]
avc.completionWithItemsHandler = { type, ok, items, err in
    // ...
}
self.present(avc, animated:true)
if let pop = avc.popoverPresentationController {
    let v = sender as! UIView
    pop.sourceView = v
    pop.sourceRect = v.bounds
}
8. 自定义活动的进阶实现

前面我们创建了一个简单的自定义活动 “Be Cool”,下面进一步探讨自定义活动的其他方面。

8.1 自定义活动的图标设计

自定义活动的图标在活动视图中起着重要的视觉标识作用。图标应作为模板图像处理,尺寸建议不超过 60×60(iPad 上为 76×76),且稍微小于这个尺寸效果更好,因为系统会在图标周围绘制圆角矩形,图标应适当内缩。图标不必是正方形,系统会自动将其居中显示。我们可以在 MyCoolActivity 的初始化方法中创建图标:

override init() {
    let imagePath = Bundle.main.path(forResource: "cool_icon", ofType: "png")
    if let path = imagePath {
        self.image = UIImage(contentsOfFile: path)!
    } else {
        self.image = UIImage()
    }
    super.init()
}
8.2 复杂活动的实现

如果自定义活动比较复杂,需要展示更多的界面给用户,可以实现 activityViewController 属性。以下是一个示例:

class MyCoolActivity: UIActivity {
    var items : [Any]?
    var image : UIImage
    override init() {
        let imagePath = Bundle.main.path(forResource: "cool_icon", ofType: "png")
        if let path = imagePath {
            self.image = UIImage(contentsOfFile: path)!
        } else {
            self.image = UIImage()
        }
        super.init()
    }
    override class var activityCategory : UIActivity.ActivityCategory {
        return .action
    }
    override var activityType : UIActivity.ActivityType {
        return UIActivity.ActivityType("com.neuburg.matt.coolActivity")
    }
    override var activityTitle : String? {
        return "Be Cool"
    }
    override var activityImage : UIImage? {
        return self.image
    }
    override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
        for obj in activityItems {
            if obj is String {
                return true
            }
        }
        return false
    }
    override func prepare(withActivityItems activityItems: [Any]) {
        self.items = activityItems
    }
    override var activityViewController: UIViewController? {
        let vc = UIViewController()
        vc.view.backgroundColor = .white
        let label = UILabel(frame: CGRect(x: 50, y: 50, width: 200, height: 50))
        label.text = "Cool Activity Interface"
        vc.view.addSubview(label)
        return vc
    }
    override func activityDidFinish(_ completed: Bool) {
        // 活动结束处理
    }
}

当用户点击自定义活动的图标时,活动视图机制会自动呈现 activityViewController 所提供的视图控制器。

9. 活动视图的交互流程

下面是活动视图的交互流程 mermaid 流程图:

graph TD;
    A[用户点击分享按钮] --> B[实例化 UIActivityViewController];
    B --> C[设置 completionWithItemsHandler];
    C --> D[呈现 UIActivityViewController];
    D --> E{用户选择活动};
    E -- 选择系统活动 --> F[执行系统活动操作];
    E -- 选择自定义活动 --> G{自定义活动类型};
    G -- 直接执行活动 --> H[执行 perform 方法];
    G -- 展示额外界面 --> I[呈现 activityViewController];
    H --> J[调用 activityDidFinish(_:)];
    I --> J;
    J --> K[活动结束,调用 completionWithItemsHandler];
10. 通知与活动视图的综合应用场景

在实际的 iOS 应用开发中,通知和活动视图可以结合使用,为用户提供更丰富的交互体验。例如,当用户完成某项任务后,应用可以发送本地通知提醒用户,同时在通知的二级界面中添加分享活动按钮。用户点击分享按钮时,弹出活动视图供用户选择分享方式。

以下是一个简单的代码示例,展示如何在通知内容扩展中添加分享活动按钮:

class NotificationContentViewController: UIViewController, UNNotificationContentExtension {
    func didReceive(_ notification: UNNotification) {
        let req = notification.request
        let content = req.content
        let atts = content.attachments
        if let att = atts.first, att.identifier == "cup" {
            if att.url.startAccessingSecurityScopedResource() {
                if let data = try? Data(contentsOf: att.url) {
                    let image = UIImage(data: data)
                    let shareButton = UIButton(frame: CGRect(x: 50, y: 50, width: 100, height: 50))
                    shareButton.setTitle("Share", for: .normal)
                    shareButton.addTarget(self, action: #selector(shareAction), for: .touchUpInside)
                    self.view.addSubview(shareButton)
                }
                att.url.stopAccessingSecurityScopedResource()
            }
        }
        self.view.setNeedsLayout()
    }

    @objc func shareAction() {
        let url = Bundle.main.url(forResource:"sunglasses", withExtension:"png")!
        let things : [Any] = ["This is a cool picture", url]
        let avc = UIActivityViewController(
            activityItems:things, applicationActivities:nil)
        avc.completionWithItemsHandler = { type, ok, items, err in
            // ...
        }
        self.present(avc, animated:true)
    }
}

通过以上综合应用,我们可以将通知和活动视图的功能有机结合,提升应用的用户体验和交互性。

综上所述,iOS 开发中的通知和活动视图功能为开发者提供了强大的工具,能够实现丰富多样的交互场景。通过合理运用本地通知管理、通知内容扩展、今日扩展和活动视图等技术,我们可以开发出更具吸引力和实用性的 iOS 应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值