【iOS小组件】灵动岛小组件

1,808 阅读4分钟

灵动岛简介


注意:

兼容版本 iOS 16.1 或更高版本

iPhone的灵动岛(Dynamic Island)功能是随着 iPhone 14 ProiPhone 14 Pro Max 的推出而首次亮相的,它在刘海屏区域提供动态的提醒和活动状态,如音乐播放、录音、导航等。

灵动岛需要 iOS 16.1 或更高版本 来支持,实现实时活动(Live Activities)的功能。

灵动岛非常适合展示一下实时信息,比如:

  • 计时应用

  • 当前播放的音乐内容

  • 实时导航信息

  • 外卖进度‍‍‍‍ ‍

灵动岛样式


灵动岛有三种渲染模式:

第一种是 紧凑型,当你的应用使用灵动岛时:

image.png

第二种叫 最小型样式,是当你的应用和别人的应用都在使用灵动岛时,会显示成下面这种样式,系统会决定展示哪个 App 的灵动岛,并使用每个活动的最小演示显示两个实时活动:

image.png

第三种是 展开的灵动岛样式,当用户触摸并按住紧凑或最小的演示文稿时,它会显示,这种样式可以显示更多的内容:

image.png

灵动岛配置


1.添加小组件扩展

操作路径:Xcode -> File -> New -> Target

image.png

一定要勾选 Include live Activity

image.png

2.灵动岛权限配置

2.1) OC项目

项目要启用灵动岛,需要在 Info.plist 文件中声明开启。

打开 Info.plist 文件添加 NSSupportsLiveActivities,并将其布尔值设置为 YES

2.2) Swift项目

打开 Targets -> Build Settings ->  Info.plist Values -> Supports Live Activities,并将其布尔值设置为 YES

image.png

3.代码部分

创建完项目后,会生成一个灵动岛小组件文件

image.png

3.1) ActivityAttributes

主要代码分为两部分,一部分是 ActivityAttributes,主要用来提供灵动岛上展示的数据:

struct MyWidgetAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        // Dynamic stateful properties about your activity go here!
        var emoji: String
    }

    // Fixed non-changing properties about your activity go here!
    var name: String
}

3.2) Widget

另一部分是灵动岛的 UI 展示

struct MyWidgetLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: MyWidgetAttributes.self) { context in
            // Lock screen/banner UI goes here
            VStack {
                Text("\(context.attributes.name) \(context.state.emoji)")
            }
            .activityBackgroundTint(Color.cyan)
            .activitySystemActionForegroundColor(Color.black)

        } dynamicIsland: { context in
            DynamicIsland {
                // Expanded UI goes here.  Compose the expanded UI through
                // various regions, like leading/trailing/center/bottom
                DynamicIslandExpandedRegion(.leading) {
                    Text("Leading")
                }
                DynamicIslandExpandedRegion(.trailing) {
                    Text("Trailing")
                }
                DynamicIslandExpandedRegion(.bottom) {
                    Text("Bottom \(context.state.emoji)")
                    Text("内容:\(context.attributes.name) 自定义内容")
                }
            } compactLeading: {
                Text("L")
            } compactTrailing: {
                Text("T \(context.state.emoji)")
            } minimal: {
                Text(context.state.emoji)
            }
            .widgetURL(URL(string: "http://www.apple.com"))
            .keylineTint(Color.red)
        }
    }
}
  • ActivityConfiguration 的第一个 block 用来渲染展示锁屏页面的 UI,dynamicIsland 就是用来渲染灵动岛的内容了。

  • DynamicIsland 对象中,第一部分用来渲染展开样式的 UI,compactLeading 用来展示紧凑型样式左边 UI,compactTrailing 用来展示紧凑型样式右边 UI,minimal 用来展示最小型样式 UI。

启动灵动岛


在主 App 的 ViewController 中渲染三个按钮来模拟用户达到某个条件后 启动/更新和关闭 灵动岛:

import UIKit
import ActivityKit

class ViewController: UIViewController {
    var activity: Activity<MyWidgetAttributes>? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        createButton(title: "启动灵动岛", y: view.center.y - 100, selector: #selector(start))
        createButton(title: "更新灵动岛", y: view.center.y - 50, selector: #selector(update))
        createButton(title: "关闭灵动岛", y: view.center.y, selector: #selector(end))
    }

    private func createButton(title: String, y: CGFloat, selector: Selector) {
        let button = UIButton(type: .system)
        button.setTitle(title, for: .normal)
        button.sizeToFit()
        view.addSubview(button)
        button.center.x = view.center.x
        button.frame.origin.y = y
        button.addTarget(self, action: selector, for: .touchUpInside)
    }

    @objc
    private func start() {
        // 创建灵动岛
        let attributes = MyWidgetAttributes(name: "iOS 新知")
        let state = MyWidgetAttributes.ContentState(emoji: "😄")
        let content = ActivityContent<MyWidgetAttributes.ContentState>(state: state, staleDate: nil)
        do {
            self.activity = try Activity<MyWidgetAttributes>.request(attributes: attributes, content: content)
        } catch let error {
            print("出错了:\(error.localizedDescription)")
        }
    }

    @objc
    private func update() {
        let state = MyWidgetAttributes.ContentState(emoji: "😂")
        Task {
            await activity?.update(using: state)
        }
    }

    @objc
    private func end() {
        Task {
            await activity?.end()
        }
    }
}
  • 启动灵动岛需要创建一个 ActivityAttributes 和一个 ActivityContent,然后调用 request(attributes: content:) 方法既可,这个方法会返回一个 Activity 对象,我们保留这个对象,后面更新和关闭灵动岛的时候使用。

  • 更新灵动岛只需要使用上边的 Activity 对象,调用 update(using:) 方法。

  • 关闭灵动岛只需要使用上边的 Activity 对象,调用 end() 方法。

效果


当我们启动 App,点击【启动灵动岛】按钮,把应用退到后台(前台不展示),就可以看到灵动岛上出现了我们设置的 UI:

image.png

然后锁屏,推送通知区域会看到出现了我们渲染的内容:

image.png

长按灵动岛,可以从紧凑模式变成扩展模式:

image.png

点击灵动岛可以打开 App,点击 【更新灵动岛】按钮 ,再次退到后台,可以看到灵动岛已经刷新了:

image.png

参考文档‍‍‍

mp.weixin.qq.com/s?__biz=Mzg…

本文同步自微信公众号 "程序员小溪" ,这里只是同步,想看及时消息请移步我的公众号,不定时更新我的学习经验。