iOS14 Widget小组件开发问题记录
本文记录一些开发Widget小组件中遇到的问题和解决方法。
onAppear/onDisappear 不执行
SwiftUI中onAppear/onDisappear在Widget中使用不会被执行,不能使用在Widget中,如果想要统计展示次数这种需求无法实现,Widget的设计理念是静态的。
数据共享
Widget与主工程之间共享数据可以配置AppGroup ,使用UserDefault 不能使用Keychain共享数据,无法获得数据
断点不走
如果在widget代码中加入断点,经常会走不到断点
解决办法: 1、在主工程中主动调用widget刷新
import WidgetKit
if #available(iOS 14.2, *) {
WidgetCenter.shared.reloadAllTimelines()
} else {
// Fallback on earlier versions
}
2、在getTimeline函数中加入断点,运行主工程,这样会走到断点
黑暗模式
Widget会默认适配黑暗模式,如果你没有设置背景色,黑暗模式下就会变成黑色,如果你的字体指定了黑色就会显示不出来,如果没有指定颜色,则会跟随模式切换。
所以如果需要指定UI不同部分颜色,在Assets中添加Color Set,指定颜色名称,同时区分dark和light不同颜色
时间显示
Widget中如果需要显示时间,随系统时间变化,一般两种方法:
1、 在getTimeline函数中降低时间间隔,满足刷新时间需要,每次获得当前Date传入Timeline,调用回掉,这种方式的好处是展示时间的UI样式可以自由定制
2、使用iOS14新增的DateStyle,无需自己代码刷新
Text(event.startDate, style: .time) //11:23PM
Text(event.startDate, style: .date) //June 3, 2019
Text(event.startDate, style: .relative) //2 hours, 23 minutes
Text(event.startDate, style: .offset) //+2 hours
Text(event.startDate, style: .timer) //36:59:01
但是展示样式不能定制,Text第一个参数必须是Date,不能是String
Widget中TimelineProviderContext
在widget中Provider的三个方法placeholder,getSnapshot,getTimeline都有一个参数context: Context
,这个context的类型是TimelineProviderContext
public struct TimelineProviderContext {
/// A structure containing all varieties of environments where a widget
/// could appear.
///
/// When changes occur in environment values that affect display, like
/// `.colorScheme`, WidgetKit renders your widget's views. If your widget
/// uses assets that take time to generate or depend on the specific
/// environment they're rendered in, you can generate those assets in
/// advance based on the new environment values.
///
/// For example, in macOS, if the user has a mixture of @1x and @2x
/// displays, the value for `.displayScale` includes both scales. With
/// these values, you can prepare your content in advance, if needed, to
/// handle either type of display.
@dynamicMemberLookup public struct EnvironmentVariants {
public subscript<T>(dynamicMember keyPath: WritableKeyPath<EnvironmentValues, T>) -> [T]? { get }
public subscript<T>(keyPath: WritableKeyPath<EnvironmentValues, T>) -> [T]? { get }
}
/// All environment values that might be set when a widget appears.
public let environmentVariants: TimelineProviderContext.EnvironmentVariants
/// The user-configured family of the widget: small, medium, or large.
public let family: WidgetFamily
/// A Boolean value that indicates when the widget appears in the widget gallery.
public let isPreview: Bool
/// The size, in points, of the widget.
public let displaySize: CGSize
}
其中:
- displaySize可以获得到Widget在不同机型上具体的宽,高
- family 可以获得到当前视图是小,中,大
- isPreview 布尔值,是否在Review视图中
另外,它还实现了EnvironmentVariants,所以这些参数都可以通过Environment在SwiftUI的View中获得
struct widgetEntryView : View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family
var body: some View {
VStack{
switch family {
case .systemSmall:
VStack(alignment: .leading){
}
case .systemMedium:
default:
Text(entry.date, style: .time)
}
}
}
}
网络请求
只有getTimeline中可以发网络请求,拿到的数据保存在对应的entry中,调用completion之后会刷新Widget
参数policy:刷新的时机
- .never:不刷新
- .atEnd:Timeline 中最后一个 Entry 显示完毕之后自动刷新。Timeline 方法会重新调用
- .after(date):到达某个特定时间后自动刷新
Widget 刷新的时间由系统统一决定,如果需要强制刷新Widget,可以在 App 中使用 WidgetCenter 来重新加载所有时间线:WidgetCenter.shared.reloadAllTimelines()
如果你请求一个接口,返回数据,其中包含图片链接,你需要执行同步操作先下载完图片,data转UIImage,然后再传递给Timeline,不能多次异步调用Timeline的回掉completion