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