iOS灵动岛开发实践

6,065 阅读4分钟
背景

这篇文章主要做一个引导,以及开发过程中遇到的疑难点进行讲述。具体的实操部分网上已经很多文章,可以去找找。

开发灵动岛必看官方文档:

  1. 灵动岛设计开发规范,这篇文档你们UI和产品会需要,你也需要看一下,讲述了灵动岛的尺寸、样式等。《Live Activities
  2. 灵动岛开发指导,这篇文章主要讲解灵动岛的相关接口使用,开发必看。《Displaying live data with Live Activities
问题点
1、灵动岛和实时活动如何实现动画?

目前灵动岛和实时活动还不支持自定义动画,如果自己写了动画,会被系统强制屏蔽。但iOS17开始,可以使用时序曲线实现一些线性的动画效果,具体可以去了解一下。 image.png

2、如何屏蔽或者修改系统自带的模糊淡入和淡出的动画效果?

可以使用控件的转场动画.contentTransition()接口,对内容动画进行调整。注意.transition()和.contentTransition()方法的区别,.transition()方法是针对整个控件的转场动画,如果只是控件内容发生改变,不会有什么效果的;.contentTransition()是对内容的转场动画,比如更新Text的内容,就用到它。.identity屏蔽系统动画,还有其他枚举,可以调整动画显示的类型。

//屏蔽系统自带的动画淡入淡出效果
Text(context.state.time)
    .multilineTextAlignment(.center)
    .foregroundColor(context.state.color)
    .font(.system(size: 14).weight(.semibold))
    .padding(.trailing, 7)
    .contentTransition(.identity)
3、如何在灵动岛或者实时活动添加按钮?

iOS17及以上才支持添加按钮点击事件,在此之前添加按钮到灵动岛或者实时活动页上,点击按钮时,点击事件是不生效的,会直接打开App。 image.png

4、灵动岛/实时活动如何和主App进行通讯,传递事件或者数据?

灵动岛/实时活动和主App进行通讯,分为两个方向的数据传递:主App-->灵动岛灵动岛-->主App。 讲这个前,首先我们要清楚一个前提:当主App被杀掉时(没运行的时候),灵动岛和实时活动仍然是可以展示和更新的,而灵动岛和实时活动作为一个小组件,并不知道主App的生命周期。因此灵动岛-->主App同步发送数据或事件这条链路是行不通的。 那问题来了,我们要怎么实现数据传递呢?无法同步实现,我们可以异步实现是不?

  • 主App-->灵动岛

这个链路就比较容易实现,苹果提供了相关的接口:

    /// 显示灵动岛和实时活动
    @objc public func show(title:String, time:String, islandIcon:String, islandColor:String, content:String, isRecording: Bool) {
        let attributes = CCityWidgetAttributes(title: title)
        let contentState = CCityWidgetAttributes.ContentState(time: time, islandIcon: islandIcon, islandColor: islandColor, content: content, isRecording: isRecording)
        do {
            activity = try Activity<CCityWidgetAttributes>.request(attributes: attributes, contentState: contentState, pushType: nil)
        } catch {
            print(error.localizedDescription)
        }
    }
    
    /// 更新灵动岛内容
    @objc public func update(time:String, islandIcon:String, islandColor:String, content:String, isRecording: Bool) {
        let contentState = CCityWidgetAttributes.ContentState(time: time, islandIcon: islandIcon, islandColor:islandColor, content: content, isRecording: isRecording)
        Task {
            await self.activity?.update(using: contentState)
        }
    }
    
    /// 结束灵动岛显示
    @objc public func end() {
        Task {
            for activity in Activity<CCityWidgetAttributes>.activities{
                await activity.end(dismissalPolicy: .immediate)
            }
        }
    }
@available(iOS 16.1, *)
struct CCityWidgetAttributes: ActivityAttributes {

    public struct ContentState: Codable, Hashable {
        // 动态属性,可以在update方法中更新的属性,需要一直更新的内容,可以放到ContentState中。
        var time: String
        var islandIcon: String
        var islandColor: String
        var content: String
        var isRecording: Bool
        
        var color: Color {
            return Color(hexString: islandColor)
        }
    }
    // 静态属性,只有在show的时候才会更新的属性,不变的属性放在这里
    var title: String
}
  • 灵动岛-->主App

这个链路目前苹果并没有提供直接同步传递数据的方式。我们只能通过在接收用户的点击事件时,link链接唤起APP,url传参给APP,实现数据的传递。

/// 单个控件的点击响应,并传参给主APP,用Link()
Link(destination: url) {
    Image(entry.itemIcons[3])
        .frame(width: 24, height: 24)
}

/// 整个面板点击响应,用widgetURL
DynamicIsland {

} compactLeading: {
                
} compactTrailing: {
                
} minimal: {
                
}
.widgetURL(url)

然后再Appdelegate中的代理回调监听外部url唤起

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {

}
4. 其他一些开发过程中遇到的问题。
  • 连续同步update灵动岛,数据只会更新一次,苹果内部接口应该对灵动岛的更新有一定的时间限制,因此建议将数据缓存一次性进行更新。
  • 当锁屏时(真正的锁屏,即没有解锁脸部识别),实时活动的更新频率会降低,比如几秒钟才会更新一次(哪怕你代码是每秒钟都调update接口),且更新动画也会被屏蔽,苹果应该是为了省电吧。