首先,灵动岛实际上是一个小组件,就像 iOS 14 小部件一样,这也是为什么我会放在小组件的部分来讲。
让我们来创建一个新的?Widget Target
,来到?File -> New -> Target...
,创建一个?Widget Extension
一定要勾选?Include live Activity
,然后输入名称,点击完成既可。
项目要启用灵动岛,需要在?Info.plist
?文件中声明开启,打开?Info.plist
?文件添加?NSSupportsLiveActivities
,并将其布尔值设置为 YES。
当我们创建这个?Target Extension
?并且勾选了?Include live Activity
?之后,Xcode 会自动帮我们创建出来一个灵动岛的模版,我目前用的是 Xcode 15,可以看下 Xcode 自动生成的模版预览:
灵动岛有三种渲染模式,第一种是紧凑型,当只有你的应用在应用在使用灵动岛时,会展示成这样:
第二种叫最小型样式,是当你的应用和别人的应用都在使用灵动岛时,会显示成下面这种样式,系统会决定展示哪个 App 的灵动岛,并使用每个活动的最小演示显示两个实时活动:
第三种是展开的灵动岛样式,当用户触摸并按住紧凑或最小的演示文稿时,它会显示,这种样式可以显示更多的内容:
主要代码分为两部分,一部分是?ActivityAttributes
,主要用来提供灵动岛上展示的数据:
css
复制代码
struct MyLiveActivityAttributes: ActivityAttributes { public struct ContentState: Codable, Hashable { var emoji: String } var name: String }
当我们需要展示灵动岛的时候需要创建一份?ActivityAttributes
。
另一部分是灵动岛的 UI 展示:
scss
复制代码
struct MyLiveActivityLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: MyLiveActivityAttributes.self) { context in // 展示锁屏页面的 UI VStack { Text("\(context.attributes.name) \(context.state.emoji)") } .activityBackgroundTint(Color.cyan) .activitySystemActionForegroundColor(Color.black) } dynamicIsland: { context in DynamicIsland { // 展开样式 UI DynamicIslandExpandedRegion(.leading) { Text("左侧") } DynamicIslandExpandedRegion(.trailing) { Text("右侧") } DynamicIslandExpandedRegion(.bottom) { Text("底部 \(context.state.emoji)") Text("内容:\(context.attributes.name) 自定义内容") } } compactLeading: { // 紧凑型样式左边 UI Text("左") } compactTrailing: { // 紧凑型样式右边 UI Text("右 \(context.state.emoji)") } minimal: { // 最小型样式 UI 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。
关于 UI 部分,可以自由发挥,但要注意每个区域的大小。
我们在主?App
?的?ViewController
?中渲染三个按钮来模拟用户达到某个条件后启动/更新和关闭灵动岛:
swift
复制代码
import ActivityKit class ViewController: UIViewController { var activity: Activity<MyLiveActivityAttributes>? = 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 = MyLiveActivityAttributes(name: "iOS 新知") let state = MyLiveActivityAttributes.ContentState(emoji: " ") let content = ActivityContent<MyLiveActivityAttributes.ContentState>(state: state, staleDate: nil) do { self.activity = try Activity<MyLiveActivityAttributes>.request(attributes: attributes, content: content) } catch let error { print("出错了:\(error.localizedDescription)") } } @objc private func update() { let state = MyLiveActivityAttributes.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:
然后看下锁屏页面,推送通知区域会看到出现了我们渲染的内容:
长按灵动岛,可以从紧凑模式变成扩展模式:
点击灵动岛可以打开 App,然后点击 "更新灵动岛" 按钮,再次退到后台,可以看到灵动岛已经刷新了:
最后点击 "关闭灵动岛" 按钮,再退到后台,灵动岛消失。
这里每天分享一个 iOS 的新知识,快来关注我吧