美文网首页iOS14Widget学习转载文章
iOS14 Widget小组件开发实践2——自定义Widget

iOS14 Widget小组件开发实践2——自定义Widget

作者: SAW_ | 来源:发表于2020-09-20 23:16 被阅读0次

    今天要来自定义一个展示诗词的小组件Widget,它显示的内容包括:诗词名字、作者、前两句(为什么只显示前两句呢,因为我找的免费API它只有给前两句😎)。
    切入正题,接下里开始实现,还不了解Widget的同学可以先看看这里iOS14 Widget小组件开发实践1

    1、新建一个Widget Extension,命名PoetryWidget
    2、获取网络数据请求处理
    先看下免费的API接口:https://v1.alapi.cn/api/shici?type=shuqing的返回参数:

    {
        "code": 200,
        "msg": "success",
        "data": {
            "content": "范增一去无谋主,韩信原来是逐臣。",
            "origin": "乌江项王庙",
            "author": "严遂成",
            "category": "古诗文-抒情-爱国"
        },
        "author": {
            "name": "Alone88",
            "desc": "由Alone88提供的免费API 服务,官方文档:www.alapi.cn"
        }
    }
    

    声明一个诗词需要的数据model

    struct Poetry {
        let content: String // 内容
        let origin: String // 名字
        let author: String // 作者
    }
    

    既然是请求API接口,那就需要一个请求函数,并且回调请求参数,声明一个请求工具,实现数据请求以及jsonmodel

    struct PoetryRequest {
        static func request(completion: @escaping (Result<Poetry, Error>) -> Void) {
            let url = URL(string: "https://v1.alapi.cn/api/shici?type=shuqing")!
            let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
                guard error == nil else {
                    completion(.failure(error!))
                    return
                }
                let poetry = poetryFromJson(fromData: data!)
                completion(.success(poetry))
            }
            task.resume()
        }
        
        static func poetryFromJson(fromData data: Data) -> Poetry {
            let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
            //因为免费接口请求频率问题,如果太频繁了,请求可能失败,这里做下处理,放置crash
            guard let data = json["data"] as? [String: Any] else {
                return Poetry(content: "诗词加载失败,请稍微再试!", origin: "耐依福", author: "佚名")
            }
            let content = data["content"] as! String
            let origin = data["origin"] as! String
            let author = data["author"] as! String
            return Poetry(content: content, origin: origin, author: author)
        }
    }
    

    3、搭建展示界面
    数据有了,就可以实现界面搭建了,这里必须用SwiftUI实现:

    struct PoetryWidgetView: View {
        let entry: PoetryEntry
    
        var body: some View {
            VStack(alignment: .leading, spacing: 4) {
                Text(entry.poetry.origin)
                    .font(.system(size: 20))
                    .fontWeight(.bold)
                Text(entry.poetry.author)
                    .font(.system(size: 16))
                Text(entry.poetry.content)
                    .font(.system(size: 18))
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .leading)
            .padding()
            .background(LinearGradient(gradient: Gradient(colors: [.init(red: 144 / 255.0, green: 252 / 255.0, blue: 231 / 255.0), .init(red: 50 / 204, green: 188 / 255.0, blue: 231 / 255.0)]), startPoint: .topLeading, endPoint: .bottomTrailing))
        }
    }
    

    这里用了三个Text文本组件来展示三部分内容,很明显这里没有分别处理三种样式的UI,因此在编辑预览界面,三种样式的Widget能提供的内容是一样的,如果要实现三种样式各显示不同的内容,需要获取WidgetFamily的实例属性,实现不同的界面展示。

    @Environment(\.widgetFamily) var family: WidgetFamily
    

    来看看苹果官方文档的相关实现代码片段例子:

    struct GameStatusView : View {
        @Environment(\.widgetFamily) var family: WidgetFamily
        var gameStatus: GameStatus
    
        @ViewBuilder
        var body: some View {
            switch family {
            case .systemSmall: GameTurnSummary(gameStatus)
            case .systemMedium: GameStatusWithLastTurnResult(gameStatus)
            case .systemLarge: GameStatusWithStatistics(gameStatus)
            default: GameDetailsNotAvailable()
            }
        }
    }
    

    4、实现PoetryEntry
    让结构体PoetryEntry遵守TimelineEntry协议:

    struct PoetryEntry: TimelineEntry {
        var date: Date
        let poetry: Poetry // 可以理解为绑定了Poetry模型数据
    }
    

    5、实现PoetryProvider
    让结构体PoetryProvider遵守TimelineProvider协议,同时实现:

    struct PoetryProvider: TimelineProvider {
        func placeholder(in context: Context) -> PoetryEntry { ...... }
        func getSnapshot(in context: Context, completion: @escaping (PoetryEntry) -> Void) { ...... }
        func getTimeline(in context: Context, completion: @escaping (Timeline<PoetryEntry>) -> Void) { ...... }
    }
    

    placeholder:实现默认视图

    func placeholder(in context: Context) -> PoetryEntry {
         let poetry = Poetry(content: "床前明月光,疑似地上霜", origin: "耐依福", author: "佚名")
         return PoetryEntry(date: Date(), poetry: poetry)
    }
    

    getSnapshot:在组件的添加页面可以看到效果

    func getSnapshot(in context: Context, completion: @escaping (PoetryEntry) -> Void) {
        let poetry = Poetry(content: "床前明月光,疑似地上霜", origin: "月光光", author: "佚名")
        let entry = PoetryEntry(date: Date(), poetry: poetry)
        completion(entry)
    }
    

    getTimeline:在这个方法内可以进行网络请求,拿到的数据保存在对应的entry中,调用completion之后会到刷新小组件

    这里有坑高能!!!

    这里关于刷新策略,根据官方文档来看,Timeline的刷新策略是会延迟的,并不一定根据你设定的时间来。同时官方规定每个配置的窗口小部件每天都接收有限数量的刷新,具体的详情说明请看官方解释:TimelineProvider

    func getTimeline(in context: Context, completion: @escaping (Timeline<PoetryEntry>) -> Void) {
        let currentDate = Date()
        // 下一次更新间隔以分钟为单位,间隔5分钟请求一次新的数据
        let updateDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)
        PoetryRequest.request { result in
            let poetry: Poetry
            if case .success(let response) = result {
                poetry = response
            } else {
                poetry = Poetry(content: "诗词加载失败,请稍微再试!", origin: "耐依福", author: "佚名")
            }
            let entry = PoetryEntry(date: currentDate, poetry: poetry)
            let timeline = Timeline(entries: [entry], policy: .after(updateDate!))
            completion(timeline)
        }
    }
    

    实现PoetryWidget
    @main:代表着Widget的主入口,系统从这里加载
    kind:是Widget的唯一标识
    StaticConfiguration:初始化配置代码
    configurationDisplayName:添加编辑界面展示的标题
    description:添加编辑界面展示的描述内容
    supportedFamilies这里可以限制要提供三个样式中的哪几个

    @main
    struct PoetryWidget: Widget {
        var body: some WidgetConfiguration {
            StaticConfiguration(kind: "PoetryWidget", provider: PoetryProvider()) { entry in
                PoetryWidgetView(entry: entry)
            }
            .configurationDisplayName("每日一湿")
            .description("默读并背诵全文")
        }
    }
    

    到这里一个完整的Widget小组件的实现就完成了,然后就让代码跑起来,看看效果。


    参考资料

    creating-a-widget-extension
    TimelineProvider
    https://swiftrocks.com

    相关文章

      网友评论

        本文标题:iOS14 Widget小组件开发实践2——自定义Widget

        本文链接:https://www.haomeiwen.com/subject/jhopyktx.html