灵动岛DynamicIsland初探
在2022年9月刚刚结束的苹果秋季发布会上,除了常规的手机等硬件设备新品发布,伴随而来一个新的软件功能——灵动岛(Dynamic Island);灵动岛是将前置镜头和软件通知结合在一起的一个全新功能,2017年发布的iPhone10打电话时候,左上角的那颗胶囊应该就是这个功能的前身,灵动岛上更进一步,不同应用可以展现出不同的样式,本篇文章将从软件开发角度,探索灵动岛的展现形式、功能限制、如何具体实现、适用场景等各方面的问题;帮助还不了解相关信息的开发者快速理解这一新的展现形式,并结合自身产品形态做出创新。
元素及展示位置 iPhone锁屏(Lock Screen)页面 灵动岛(Dynamic Island) 灵动岛截图显示 要求和限制 基本流程 开发框架&配置 ActivityAttributes 创建ActivityWidget显示视图 生命周期管理 启动 状态更新 停止 跳转 适用场景初探 总结 参考 概览
灵动岛(Dynamic Island)相关API,隶属于Live Activity Framework,出现在iOS16.1系统;Live Activities负责在iPhone锁屏(Lock Screen)和灵动岛(Dynamic Island)中显示应用程序的最新数据。这使得人们一眼就能看到实时信息,并可以进行一些简单交互。 元素及展示位置
接下来,我们详细介绍一下能够利用Live Activity,展示UI元素与用户产生互动的位置; iPhone锁屏(Lock Screen)页面
锁屏页面上半部分可以定制增加小组件,需要iOS16+系统;
灵动岛(Dynamic Island)
灵动岛,要求iOS16.1+系统,iPhone 14 Pro & iPhone 14 Pro Max机型;
目前仅有以下系统应用已经支持灵动岛: Ongoing call SharePlay Music/Now playing apps Timer Maps directions Voice memos Screen Recording Personal Hotspot
我们通过一段动图展示一下灵动岛在实际使用中的效果(动图文件较大,可能需要时间加载);
通过简单分析可以看到,展示区域主要分为两个部分:音乐播放左侧显示封面,右侧显示波纹计时器;导航左侧显示即时图标,右侧显示剩余时间导航,下个路口,转弯方向;录音机,左侧显示录音状态,右侧录音时长;
另外,如果App实现了灵动岛,但是在不支持的设备上(即iPhone 14 Pro & iPhone 14 Pro Max机型之外的所有机型),会在Lock Screen会展示一种新的锁屏组件,位置在锁屏页面下半部分,如下图所示:
目前苹果已经开放了API给开发者定制属于自己APP的灵动岛功能。 灵动岛截图显示
在截图时,灵动岛区域是否会出现在截图照片中吗?经过初步测试, 如果灵动岛内无任务时,截图不会显示灵动岛 灵动岛有任务时,截图会显示灵动岛 要求和限制
一个Live Activity可以激活长达8小时,除非App通过API或用户终止它,否则。超过此限制,系统将自动结束。当一个Live Activity结束时,系统立即将其从灵动岛(Dynamic Island)中删除。然而,Live Activity会一直保持在锁定屏幕上,直到用户删除它。
每个Live Activity运行在自己的沙盒里,不像widget,不能获取网络或接受位置更新;要更新活跃的Live Activity的动态数据,有两种方式:应用程序中使用ActivityKit框架;允许Live Activity接收远程推送通知; 灵动岛实现基本流程
注:获得远程推送更新需要用户授权 开发框架&配置
灵动岛开发除了依赖新增的Live Activity框架,也需要依赖WidgetKit、SwiftUI,即你的App需要有一个小组件的Extension Target,并且创建WidgetBundle,增加新的ActivityWidget; @main struct HyWidget:WidgetBundle { var body: some Widget { HotWidget() UgcEntryWidget() //新增Live Activity CoffeeDeliveryActivityWidget() } }
此外,App的配置文件Info需要增加NSSupportsLiveActivities,并设置为true;
ActivityAttributes
ActivityAttributes相当于数据模型Model,具体细分为动态数据和静态数据;ActivityAttributes协议描述了出现在Live Activity中的内容。它的内部类型ContentState表示Live Activity的动态内容。
下面的例子展示了一个咖啡外卖应用程序的ActivityAttributes协议的实现。该应用程序的Live Activity显示已订购的咖啡名称为静态数据,而CoffeeDeliveryStatus为动态数据,随着时间的推移而变化。 struct CoffeeDeliveryAttributes: ActivityAttributes { public struct ContentState: Codable, Hashable { var currentStatus: CoffeeDeliveryStatus } var coffeeName: String } enum CoffeeDeliveryStatus: Codable, Sendable, CaseIterable { case recieved, preparing, outForDelivery var displayText: String { switch self { case .recieved: return "Recieved" case .preparing: return "Brewing" case .outForDelivery: return "On its way!" } } var longText: String { switch self { case .recieved: return "We"ve recieved your order." case .preparing: return "Our baristas are hard at work brewing up your cup!" case .outForDelivery: return "Get ready, your brew is speeding over to you right now." } } } 创建ActivityWidget显示视图
灵动岛有三种视图形式,从左到右依次是: 紧凑视图 最小视图 扩展视图
三种视图之间可以进行切换;当一个人在动态岛中触摸并长按紧凑或最小视图时,以及当一个实时活动更新时,扩展视图就会出现。 在不支持灵动岛的设备上,解锁屏幕展开的视图显示为Live Activity更新的横幅。为了确保系统能够在每个位置显示你的APP的实时活动,APP编写代码时必须支持所有视图。 扩展视图,由于区域空间较大,进一步分割出下面具体区域
每一部分均可程序定制展示内容;接下来了解代码如何实现各部分视图: struct CoffeeDeliveryActivityWidget: Widget { var body: some WidgetConfiguration { return ActivityConfiguration(for: CoffeeDeliveryAttributes.self) { context in CoffeeDeliveryActivityWidgetView( attributes: context.attributes, state: context.state ) } dynamicIsland: { context in DynamicIsland { DynamicIslandExpandedRegion(.leading, priority: .greatestFiniteMagnitude, content: { //扩展区域的左侧视图 }) DynamicIslandExpandedRegion(.trailing, content: { //扩展区域的右侧视图 }) } compactLeading: { //紧凑视图左侧区域视图 } compactTrailing: { //紧凑视图右侧区域视图 } minimal: { //最小视图展示 } } } }
其中ActivityConfiguration结构体是这部分的核心; public struct ActivityConfiguration : WidgetConfiguration where Attributes : ActivityAttributes
ActivityConfiguration结构体遵循WidgetConfiguration协议,其中Attributes泛型参数遵循ActivityAttributes协议,而前面介绍过的CoffeeDeliveryAttributes就是ActivityAttributes协议的实现;
ActivityConfiguration的初始化init方法如下所示: public init(for attributesType: Attributes.Type = Attributes.self, @ViewBuilder content: @escaping (ActivityViewContext) -> Content, dynamicIsland: @escaping (ActivityViewContext) -> DynamicIsland) where Content : View
上面代码中就是实现了ActivityConfiguration的init方法,包含三个参数:attributesType指明需要传入的content模型类型,这里指定为CoffeeDeliveryAttributes.self;第二个参数content,指定一个闭包,它为出现在锁定屏幕上的活动创建视图。当你提醒某人更新的实时活动内容时,这个视图也会以横幅的形式出现在不支持灵动岛的设备的主屏幕上;第三个参数dynamicIsland,构建灵动岛的内部UI实现。
DynamicIsland结构体,用于构建灵动岛各部分具体UI;用于实现紧凑视图、最小视图、扩展视图的各部分UI; 生命周期管理
ActivityKit中定义了Activity对象,用于管理Live Activity从启动到状态更新、结束,完整的生命周期流程; 启动API public static func request(attributes: Attributes, contentState: Activity.ContentState, pushType: PushType? = nil) throws -> Activity 示例 Task { let attributes = CoffeeDeliveryAttributes(coffeeName: coffeeName) let state = CoffeeDeliveryAttributes.ContentState(currentStatus: .recieved) do { try await MainActor.run { let activity = try Activity.request( attributes: attributes, contentState: state, pushType: nil ) //.... print("Started activity") } catch (let error) { print("Error starting activity (error) (error.localizedDescription)") } }
现在,让我们开始Activity,需要传入状态state和属性attributes,启动Activity。注意:这里特别区分attributes和state就是为了针对静态数据(attributes)和动态数据(state)分别处理,后续只有动态数据才会被更新。 状态更新API public func update(using contentState: Activity.ContentState) async 示例 func updateActivity(state: CoffeeDeliveryStatus) { let state = CoffeeDeliveryAttributes.ContentState(currentStatus: state) Task { await liveActivity?.update(using: state) } }
更新动态数据。 停止func stop() { Task { await liveActivity?.end(using: nil, dismissalPolicy: .immediate) await MainActor.run { liveActivity = nil } } }
立即停止liveActivity。 跳转
灵动岛支持DeepLink,可以跳转进入App;具体来说:对于所有小组件,包括灵动岛,在小组件的视图层次结构中添加widgetURL(_:)视图修饰符。如果小组件的视图层次结构包含多个widgetURL修饰符,则行为未定义。
对于使用WidgetFamily的小组件,如WidgetFamily.systemMedium,WidgetFamily.systemLarge或WidgetFamily.systemextrallarge,将一个或多个链接控件添加到小部件的视图层次结构中。可以同时使用widgetURL和Link控件。如果交互以Link控件为目标,则系统使用该控件中的URL。对于小部件中其他任何地方的交互,系统使用widgetURL视图修饰符中指定的URL。 @ViewBuilder var body: some View { ZStack { AvatarView(entry.character) .widgetURL(entry.character.url) .foregroundColor(.white) } .background(Color.gameBackground) } 适用场景初探
最后,我们从产品角度初步探讨一下Live Activity及灵动岛的适用场景;可以发现不论是在灵动岛展示信息还是在锁屏界面展示信息,这类信息需要满足实时性较强,用户持续关注,比如正在进行中的比赛分数,正在配送的餐食还有多久送达;这类信息在一个相对较短时间段内、高频被更新,比较适合呈现在灵动岛;所以一般来说,下面细分类型的App适合增加Live Activity及灵动岛的适配: 体育赛事类App 运动监测类App 外卖配送类App 总结
本篇文章完整介绍了创建灵动岛这一新的UI交互形式所需要的开发流程,开发者需要注意设计数据模型时,区分好哪些动态数据需要被更新,哪些静态数据保持不变;另外,我们也初步探讨了灵动岛的适用场景,对于那些产品形态需要及时更新状态信息的App,快速适配灵动岛,将会给用户带来更好的体验,值得尝试。 参考https://developer.apple.com/documentation/activitykit/displaying-live-data-on-the-lock-screen-with-live-activities https://developer.apple.com/documentation/widgetkit/creating-a-widget-extension
作者:狐友技术团队
来源:微信公众号:搜狐技术产品
出处:https://mp.weixin.qq.com/s/gI2vgx9cXiIAND08OgZNzQ
Steam喜加一街头破坏艺术游戏污泥人生免费领取第一人称开放世界街头破坏艺术类游戏污泥人生目前可以在Steam上免费领取了。截止时间3月31日凌晨1点。本作支持中文,目前特别好评(94)。污泥人生是一款第一人称开放世界街头破坏艺
Apex英雄太阳小队收集活动预告艾许传家宝双截棍公布Apex英雄公布了太阳小队收集活动的预告视频,预计将于3月28日正式上线,届时游戏将推出24款全新外观道具,包括艾许幻像暴雷的传说级造型以及其他内容,收集全部道具即可自动获得艾许传
男性到以下几点就有可能达到健康长寿!哪个年龄段的男性压力最大?答案是中年男性,除了照顾家中年迈的父母和子女,还要奔波于职场,面临着复杂的人际关系,种种行为压得男性喘不上气来。45岁正是男性健康的分水岭,45岁后体质逐
久沸的水不宜喝开水对人体有很多的好处,人体很大部分是由水构成的,饮用开水能够补充体内的水分,维持身体代谢。一个健康的人每天要喝78杯的白开水,只有这样才能更好的满足身体新陈代谢的需要。自来水中有
明朝时期,光禄寺及其相关的社会和经济问题,是怎么样的呢?(一)厨役在明朝光禄寺中有这样一群充当供应的人,他们分隶于光禄寺各署之中。在洪武中约800人,永乐中两京仅有300人,仁宗时6300人,宣德初却高达近万人,约9462名,宣德十年奏
不甘平庸探寻社会不公与改变的路径社会不公是一件普遍存在的事情,无论在哪个国家哪个时代,都难以彻底消除。当我们在经历社会不公时,身边的人往往会给出想开点啦,社会就是这样的建议,让我们接受这个现实并从中吸取教训。然而
古埃及的文明成就,对现代社会造成了怎样的影响在阅读此文之前,麻烦您点击一下关注,既方便您进行讨论和分享,又能给您带来不一样的参与感,感谢您的支持么文章以宁编辑以宁古埃及的文化和历史概述古埃及文明起源于公元前5000年左右,主
如果你讨厌人间,认为活着没意义,请牢记老天的这些忠告近些年来,网络上出现了一种观念人活着,其实没有任何意义。从宇宙的角度来说,活着,的确没有意义。但是,从个人微观的角度来说,活着的每一分每一秒,都是意义。有些时候,我也在思考,为什么
退休后最好的活法上止正退休了,离开职场,也没有人约束自己了,收入也不愁。本来,退休是好事,但是很多老人,忽然放松了自己,然后陷入了无所事事的状态,人生反而迷茫了。余生要怎么过?具体来说,可以去旅行听一听
人过五十,无论你多有本事,别这么爱子女作者闻秋声原创文章,抄袭必究01引言人生短短数十载,如白驹过隙。人这一生最重要的不是手里有多少钱,真正的富有是子女孝顺家庭和睦。人过五十,儿孙承欢膝下是一种福气。老了,要学会做一个
55岁到60岁这几年,跟这些人关系再好,也要把真心伪装起来俗话说为人只说三分话,不敢全抛一片心。人呐,不管跟谁打交道,只需要说三分的真话就行了。至于那一片的真心,就不要显露出来了。人,越是老实,越是没有好果子吃。以往,人性还比较纯粹,那我