【iOS小组件实战】时间状态提醒

659 阅读3分钟

前言

此次小组件实战需求参考 iOS 小组件开发第三篇:实战 ,记录开发和实现时间状态提醒小组件的过程,代码有所改动,末尾附完整代码示例。

需求


需求描述:实现一个可以展示上午下午夜里三种状态的小组件,分别用三个不同的图标来显示。

image.png

按照上述需求,我们简单地把这个小组件的时间线描述为 3个 阶段:

  • 早上 0 点到中午 12 点为 上午

  • 中午 12 点到下午 18 点为 下午

  • 下午 18 点到早上 24 点为 夜里

实现

1.TimelineEntry

TimelineEntry 是用来提供小组件显示内容和时间的,需要先写一个展示时间状态的枚举,用来表示上午、下午和晚上

enum TodayTime {
   case morning, afternoon, night
}

struct TodayEntry: TimelineEntry {
    let date: Date
    // 表示上午、下午、晚上
    let time: TodayTime
}

2.Provider时间线

先写一个获取当天时间的方法

// 指定当天时间
func getDate(hour: Int) -> Date {
    let calendar = Calendar.current
    var components = calendar.dateComponents([.year, .month, .day], from: Date())
    components.hour = hour
    components.minute = 0
    components.second = 0

    return calendar.date(from: components)!
}

时间线数据整理

struct TodayProvider: TimelineProvider {
    func placeholder(in context: Context) -> TodayEntry {
        TodayEntry(date: Date(), time: .morning)
    }

    func getSnapshot(in context: Context, completion: @escaping (TodayEntry) -> ()) {
        let entry = TodayEntry(date: Date(), time: .morning)
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [TodayEntry] = []
        
        // 根据当前时间指定刷新时间线
        let hour = Calendar.current.component(.hour, from: Date())
        switch hour {
        case 0..<12:
            entries.append(TodayEntry(date: Date(), time: .morning))
            entries.append(TodayEntry(date: getDate(hour: 12), time: .afternoon))
            entries.append(TodayEntry(date: getDate(hour: 18), time: .night))
        case 12..<18:
            entries.append(TodayEntry(date: Date(), time: .afternoon))
            entries.append(TodayEntry(date: getDate(hour: 18), time: .night))
        default:
            entries.append(TodayEntry(date: Date(), time: .night))
        }

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

3.UI展示

根据不同的 case 返回不同的图标和标题:

有两种方式,第一种直接在 TodayTime 枚举添加方法

enum TodayTime {
   case morning, afternoon, night
    
    var text: String {
        switch self {
        case .morning:
            return "上午"
        case .afternoon:
            return "下午"
        case .night:
            return "晚上"
        }
    }
    
    var icon: String {
        switch self {
        case .morning:
            return "sunrise"
        case .afternoon:
            return "sun.max.fill"
        case .night:
            return "sunset"
        }
    }
}

第二种利用 extension 扩展

extension TodayTime {
    var text: String {
        switch self {
        case .morning:
            return "上午"
        case .afternoon:
            return "下午"
        case .night:
            return "晚上"
        }
    }

    var icon: String {
        switch self {
        case .morning:
            return "sunrise"
        case .afternoon:
            return "sunset"
        case .night:
            return "moon.stars"
        }
    }
}

重新排版入口组件

struct TodayWidgetEntryView : View {
    var entry: TodayProvider.Entry

    var body: some View {
        VStack(spacing: 10) {
            Image(systemName: entry.time.icon)
                .imageScale(.large)
                .foregroundColor(.red)
            HStack {
                Text("\(entry.time.text)好")
            }
            .font(.title3)
        }
        .widgetBackground(Color.white)
    }
}

效果


image.png

完整代码


import SwiftUI
import WidgetKit

enum TodayTime {
   case morning, afternoon, night

    var text: String {
        switch self {
        case .morning:
            return "上午"
        case .afternoon:
            return "下午"
        case .night:
            return "晚上"
        }
    }

    var icon: String {
        switch self {
        case .morning:
            return "sunrise"
        case .afternoon:
            return "sunset"
        case .night:
            return "moon.stars"
        }
    }
}

struct TodayEntry: TimelineEntry {
    let date: Date
    // 表示上午、下午、晚上
    let time: TodayTime
}

// 指定当天时间
func getDate(hour: Int) -> Date {
    let calendar = Calendar.current
    var components = calendar.dateComponents([.year, .month, .day], from: Date())
    components.hour = hour
    components.minute = 0
    components.second = 0

    return calendar.date(from: components)!
}

extension View {
    // 背景
    @ViewBuilder
    func widgetBackground(_ backgroundView: some View) -> some View {
        // 如果是 iOS 17,则使用 containerBackground
        if #available(iOS 17.0, *) {
            containerBackground(for: .widget) {
                backgroundView
            }
        } else {
            background(backgroundView)
        }
    }
}

struct TodayProvider: TimelineProvider {
    func placeholder(in context: Context) -> TodayEntry {
        TodayEntry(date: Date(), time: .morning)
    }

    func getSnapshot(in context: Context, completion: @escaping (TodayEntry) -> ()) {
        let entry = TodayEntry(date: Date(), time: .morning)
        completion(entry)
    }

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

        // 根据当前时间指定刷新时间线
        let hour = Calendar.current.component(.hour, from: Date())
        switch hour {
        case 0..<12:
            entries.append(TodayEntry(date: Date(), time: .morning))
            entries.append(TodayEntry(date: getDate(hour: 12), time: .afternoon))
            entries.append(TodayEntry(date: getDate(hour: 18), time: .night))
        case 12..<18:
            entries.append(TodayEntry(date: Date(), time: .afternoon))
            entries.append(TodayEntry(date: getDate(hour: 18), time: .night))
        default:
            entries.append(TodayEntry(date: Date(), time: .night))
        }

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

struct TodayWidgetEntryView : View {
    var entry: TodayProvider.Entry

    var body: some View {
        VStack(spacing: 10) {
            Image(systemName: entry.time.icon)
                .imageScale(.large)
                .foregroundColor(.red)
            HStack {
                Text("\(entry.time.text)好")
            }
            .font(.title3)
        }
        .widgetBackground(Color.white)
    }
}

struct TodayWidget: Widget {
    let kind: String = "TextWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: TodayProvider()) { entry in
            TodayWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
        .supportedFamilies([.systemMedium])
    }
}

本文同步自微信公众号 "程序员小溪" ,这里只是同步,想看及时消息请移步我的公众号,不定时更新我的学习经验。