iOS widget 小组件 秒级刷新

Text 控件

系统 Text控件 自带有时间计算的功能

优点: 不需要额外设置,系统级别刷新时间

缺点: 不能自定义

style 例子 说明
time 12:00 AM 时分 时分 上午/下午
date date June 3, 2019 年月日
relative 2 houers, 23 min/1 year, 1 month 写入日期对比现在日期的差多少 格式是 时分/年月
offset +2 hours/-3 months 写入日期对比现在日期的差多少 格式是 +多少/-多少
timer 10:30:11 时分秒

效果

16774703315351.png

实现

import WidgetKit
import SwiftUI
import Intents

struct TimerClockProvider: IntentTimelineProvider {
    func placeholder(in context: Context) -> TimerClockEntry {
        TimerClockEntry(date: Date(), configuration: TimerClockIntent(), displaySize: context.displaySize)
    }

    func getSnapshot(for configuration: TimerClockIntent, in context: Context, completion: @escaping (TimerClockEntry) -> ()) {
        let entry = TimerClockEntry(date: Date(), configuration: configuration, displaySize: context.displaySize)
        completion(entry)
    }

    func getTimeline(for configuration: TimerClockIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [TimerClockEntry] = []

        
        // 系统 text .timer
        let currentDate = Date()

        let entryDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)!
        let entry = TimerClockEntry(date: entryDate, configuration: configuration, displaySize: context.displaySize)
        entries.append(entry)

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    
  
    }
}

struct TimerClockEntry: TimelineEntry {
    let date: Date
    let configuration: TimerClockIntent
    let displaySize: CGSize
}

// MARK: - Entry
struct TimerClockEntryView : View {
    var entry: TimerClockProvider.Entry

    var body: some View {
        return VStack(alignment: .center) {
            
            // 系统 text .timer
            TimerClockView()
            
            
        }
        .background(
            Rectangle().fill(Color.black)
                .frame(width: entry.displaySize.width, height: entry.displaySize.height, alignment: .center)
        )
    }
}

// MARK: WidgetConfiguration
struct TimerClock: Widget {
    let kind: String = "TimerClock"
    var title: String = ""
    var desc: String = ""

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: TimerClockIntent.self, provider: TimerClockProvider()) { entry in
            TimerClockEntryView(entry: entry)
        }
        .configurationDisplayName(title)
        .description(desc)
        .supportedFamilies([.systemSmall])
    }
}



struct TimerClockView: View {
    
    var body: some View {
        VStack {
            
            Spacer()
            Text("text .timer")
                .foregroundColor(.red)

            Spacer()
  
            Text(Date().getCurrentDayStart(true), style: .timer)
                .multilineTextAlignment(.center)
                .foregroundColor(.red)
            Spacer()
            
        }
    }
}


//获取当天开始的日期,给Date增加一个拓展方法
extension Date {
   func getCurrentDayStart(_ isDayOf24Hours: Bool)-> Date {
       let calendar:Calendar = Calendar.current;
       let year = calendar.component(.year, from: self);
       let month = calendar.component(.month, from: self);
       let day = calendar.component(.day, from: self);

       let components = DateComponents(year: year, month: month, day: day, hour: 0, minute: 0, second: 0)
       return Calendar.current.date(from: components)!
   }
}



添加TimeLine

添加TimeLine 让小组件定时刷新

优点: 可以自定义UI

缺点: 因为小组件刷新机制限制,小组件会卡顿。

小组件一天可以刷新40至70次。在iOS 16以下系统。可以设置一天的TimeLine。即是((24 * 60 * 60)/70)。所以iOS 16之后只能设置((24 * 60 * 60)/40)。所以会导致小组件有时候卡顿,不刷新。

TimelineReloadPolicy

let timeline = Timeline(entries: entries, policy: .never)
TimelineReloadPolicy 说明
atEnd 等最后一个timeline 执行完刷新
never 不自动重新加载
after 指定时间刷新

倒计时小组件

在 getTimeline 设置刷新机制。 一分钟刷新

        for hourOffset in 0 ..< 61 {
            let entryDate = Calendar.current.date(byAdding: .second, value: hourOffset, to: currentDate)!
            let num = 60 - hourOffset
            let entry = CountdownClockEntry(date: entryDate, configuration: configuration, displaySize: context.displaySize, num: num)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .never)

效果

截屏2023-02-27 14.20.58.png

实现

import WidgetKit
import SwiftUI
import Intents

struct CountdownClockProvider: IntentTimelineProvider {
    func placeholder(in context: Context) -> CountdownClockEntry {
        CountdownClockEntry(date: Date(), configuration: CountdownClockIntent(), displaySize: context.displaySize, num: 0)
    }

    func getSnapshot(for configuration: CountdownClockIntent, in context: Context, completion: @escaping (CountdownClockEntry) -> ()) {
        let entry = CountdownClockEntry(date: Date(), configuration: configuration, displaySize: context.displaySize, num: 0)
        completion(entry)
    }

    func getTimeline(for configuration: CountdownClockIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [CountdownClockEntry] = []

        // 倒计时
        let currentDate = Date()

        for hourOffset in 0 ..< 61 {
            let entryDate = Calendar.current.date(byAdding: .second, value: hourOffset, to: currentDate)!
            let num = 60 - hourOffset
            let entry = CountdownClockEntry(date: entryDate, configuration: configuration, displaySize: context.displaySize, num: num)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .never)
        completion(timeline)
    }
}

struct CountdownClockEntry: TimelineEntry {
    let date: Date
    let configuration: CountdownClockIntent
    let displaySize: CGSize
    let num: Int
}

// MARK: - Entry
struct CountdownClockEntryView : View {

    var entry: CountdownClockProvider.Entry
    
    var body: some View {

        return VStack(alignment: .center) {
            // 倒计时
            CountdownDemo(countdown: entry.num)
        }
        .background(
            Rectangle().fill(Color.black)
                .frame(width: entry.displaySize.width, height: entry.displaySize.height, alignment: .center)
        )

    }

}

// MARK: WidgetConfiguration
struct CountdownClock: Widget {
    let kind: String = "CountdownClock"
    var title: String = ""
    var desc: String = ""

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: CountdownClockIntent.self, provider: CountdownClockProvider()) { entry in
            CountdownClockEntryView(entry: entry)
        }
        .configurationDisplayName(title)
        .description(desc)
        .supportedFamilies([.systemSmall])
    }
}


// MARK: - 倒计时
struct CountdownDemo: View {

    var countdown: Int
    
    
    var body: some View {
        VStack {
            
            Spacer()
            Text("倒计时")
                .foregroundColor(.red)

            Spacer()
            
            Text(countdown.description)
                .multilineTextAlignment(.center)
                .foregroundColor(.red)
            Spacer()
        }
    }
    
}

时钟小组件

效果


截屏2023-02-27 15.05.06.png

实现

import WidgetKit
import SwiftUI
import Intents

struct CustomClockProvider: IntentTimelineProvider {
    func placeholder(in context: Context) -> CustomClockEntry {
        CustomClockEntry(date: Date(), configuration: CustomClockIntent(), displaySize: context.displaySize)
    }

    func getSnapshot(for configuration: CustomClockIntent, in context: Context, completion: @escaping (CustomClockEntry) -> ()) {
        let entry = CustomClockEntry(date: Date(), configuration: configuration, displaySize: context.displaySize)
        completion(entry)
    }

    func getTimeline(for configuration: CustomClockIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [CustomClockEntry] = []

        // 自定义
        let currentDate = Date()

        // 小组件 一天有 40 ~ 70 次刷新机会
        var time = ((24 * 60 * 60)/40)
        if #available(iOS 16, *) {
            time = ((24 * 60 * 60)/70)
        }
        
        for hourOffset in 0 ..< time {
            let entryDate = Calendar.current.date(byAdding: .second, value: hourOffset, to: currentDate)!
            let entry = CustomClockEntry(date: entryDate, configuration: configuration, displaySize: context.displaySize)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
  
    }
}

struct CustomClockEntry: TimelineEntry {
    let date: Date
    let configuration: CustomClockIntent
    let displaySize: CGSize
}

// MARK: - Entry
struct CustomClockEntryView : View {
    
    @State var index = 0
    @State var start: Date = Date()
    
    var entry: CustomClockProvider.Entry
    
    var body: some View {
        let calendar = Calendar.current
        let dateComponents = calendar.dateComponents([.hour, .minute, .second], from: entry.date)
        var hour:Int = 0
        var min:Int = 0
        var second:Int = 0
        
        if let h =  dateComponents.hour,
           let m = dateComponents.minute,
           let s = dateComponents.second {
            
            hour = h
            min = m
            second = s
        }
        
        
        return VStack(alignment: .center) {

            // 自定义
            CustomClockDemo(hour: hour, min: min, second: second)
            
        }
        .background(
            Rectangle().fill(Color.black)
                .frame(width: 300, height: 300, alignment: .center)
        )

    }

}

// MARK: WidgetConfiguration
struct CustomClock: Widget {
    let kind: String = "CustomClock"
    var title: String = ""
    var desc: String = ""
    
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: CustomClockIntent.self, provider: CustomClockProvider()) { entry in
            CustomClockEntryView(entry: entry)
        }
        .configurationDisplayName(title)
        .description(desc)
        .supportedFamilies([.systemSmall])
    }
}


// MARK: - CustomClock
struct CustomClockDemo: View {
    var hour: Int = 0
    var min: Int = 0
    var second: Int = 0
    
    var body: some View {
        VStack {
            
            Spacer()
            Text("自定义")
                .foregroundColor(.red)

            Spacer()
            
            Text("\(hour):\(min):\(second)")
                .foregroundColor(.red)
            Spacer()
        }
    }
}

Github地址

懒得打的可以直接打开项目查看 Github地址 https://github.com/HahnLoving/iOS_Study

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容