iOS灵动岛开发实践

约束条件

1.Live Activity最多可以保持8小时的活动状态
2.已结束的Live Activity,在锁定屏幕上最多保留4个小时,所以实时活动在锁屏上最多保留12小时
3.最小化演示图像不能超过45x36.67pt
4.ActivityKit 更新和 ActivityKit 推送通知的更新动态数据大小不能超过 4 KB。
5.Live Activity的高度超过160pt,系统可能会截断该活动

灵动岛配置

1.创建Live Activity

1.在主项目中添加targetFile -> New -> Target...

选择Widget Extension.png

2.选择Widget Extension,勾选Include Live Activity,会自动创建模版代码

创建Live Activity.png

2.添加Info.plist配置

主项目Info.plist文件中添加Supports Live Activities配置,设置为YES

<key>NSSupportsLiveActivities</key>
<true/>

3.代码部分

主项目中

  1. 创建ActivityAttributes文件,用来提供灵动岛上展示的数据
import Foundation
import ActivityKit

struct LiveActvityWidgetAttributes: ActivityAttributes {
    typealias LiveActivityWidgetState = ContentState
    
    public struct ContentState: Codable, Hashable {
        // 可变的属性需要放在这里,activity调用update进行数据的更新
        var emoji: String
    }

    // 灵动岛的初始化数据,描述不可变的数据
    var name: String
    var logo: String
    var subName: String
}

2.将创建的LiveActvityWidgetAttributes共享给灵动岛扩展组件使用

共享文件配置.png

3.创建灵动岛并初始化数据

  • request(attributes:content:pushType:) 请求并启动Live activity
  • update(_:) 更新Live Activity的动态内容
  • end(_: dismissalPolicy:) 结束正在进行的Live activity
import UIKit
import ActivityKit

class ViewController: UIViewController {
    var liveActivity: Activity<LiveActvityWidgetAttributes>? = nil

    @IBOutlet weak var openButton: UIButton! {
        didSet {
            openButton.setTitle("开启灵动岛", for: .normal)
            openButton.setTitle("关闭灵动岛", for: .selected)
        }
    }
    @IBOutlet weak var updateButton: UIButton! {
        didSet {
            updateButton.setTitle("更新灵动岛", for: .normal)
            updateButton.setTitle("已更新灵动岛", for: .selected)
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    func actvityContent(emoji: String) -> ActivityContent<LiveActvityWidgetAttributes.LiveActivityWidgetState> {
        let state = LiveActvityWidgetAttributes.LiveActivityWidgetState(emoji: emoji)
        let content = ActivityContent<LiveActvityWidgetAttributes.LiveActivityWidgetState>(state: state, staleDate: nil)
        
        return content
    }

    @IBAction func onOpen(_ sender: Any) {
        guard ActivityAuthorizationInfo().areActivitiesEnabled else {
            print("不支持")
            return
        }
        
        openButton.isSelected.toggle()
        
        guard openButton.isSelected else {
             // 关闭灵动岛
            Task {
                await liveActivity?.end(nil, dismissalPolicy:.immediate)
            }
            updateButton.isSelected.toggle()
            return
        }
        
        do {
            // 创建灵动岛并初始化数据
            let attributes = LiveActvityWidgetAttributes(name: "灵动岛", logo: "apple.logo", subName: "这是长按展开显示的文案")
            let content = actvityContent(emoji: "😀")
            self.liveActivity = try Activity<LiveActvityWidgetAttributes>.request(attributes: attributes, content: content)
        } catch {
            debugPrint("打开灵动岛失败")
        }
    }
     // 更新灵动岛数据
    @IBAction func onUpdate(_ sender: Any) {
        guard openButton.isSelected else { return }
        
        updateButton.isSelected.toggle()
        
        Task {
            let content = actvityContent(emoji: "🤩")
            await liveActivity?.update(content)
        }
    }
}

扩展组件代码中

  • context.attributes 获取静态数据
  • context.state 获取动态数据
import ActivityKit
import WidgetKit
import SwiftUI

struct LiveActvityWidgetLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: LiveActvityWidgetAttributes.self) { context in
            // 小技巧:使用该方式进行日志打印
            let _ = debugPrint("context: \(context.attributes)")
            
            // 锁屏之后,显示的桌面通知栏位置,这里可以做相对复杂的布局
            VStack {
                Text("Hello \(context.state.emoji)")
            }
            .activityBackgroundTint(Color.cyan)
            .activitySystemActionForegroundColor(Color.black)

        } dynamicIsland: { context in
            // 灵动岛布局
            DynamicIsland {
                /*
                 长按灵动岛区域展开的UI
                 
                 有4个区域布局:左,右,中间(硬件下方),底部
                 */
                DynamicIslandExpandedRegion(.leading) {
                    VStack(alignment: .leading) {
                        Image(systemName: "\(context.attributes.logo)")
                            .resizable()
                            .frame(width: 30, height: 30)
                        Text("apple logo")
                    }
                }
                DynamicIslandExpandedRegion(.trailing) {
                    Image(systemName: "apple.terminal.on.rectangle.fill")
                }
                DynamicIslandExpandedRegion(.center) {
                    Text("\(context.attributes.name)")
                }
                DynamicIslandExpandedRegion(.bottom) {
                    VStack {
                        Image(systemName: "sun.max.fill")
                            .resizable()
                            .frame(width: 30, height: 30)
                            .foregroundColor(.yellow)
                        Text("\(context.attributes.subName)")
                    }
                }
            } compactLeading: {
                // 未展示开边的布局
                Image(systemName: "\(context.attributes.logo)")
            } compactTrailing: {
                // 未展示右边布局
                Text("\(context.state.emoji)")
            } minimal: {
                // 最小型样式,当有多个任务的情况下,位置在右边的一个圆圈区域
                Image(systemName: "figure.wave.circle.fill").foregroundColor(.red)
            }
            // 点击整个区域,通过deeplink将数据传递给主工程
            .widgetURL(URL(string: "http://www.apple.com"))
            .keylineTint(Color.red)
        }
    }
}

效果图

未展开样式.png

未展开更新数据效果.png

长按展示效果.png

参考文档:
ActivityKit文档
iOS灵动岛开发实践

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容