iOS 14 widget(时钟、日历)

github demo 附在结尾

本文主要介绍iOS14 widget开发,自定义时钟、日历,app中切换widget背景图片、颜色。
创建一个项目,增加widget extension


widget添加.png

系统默认代码(可以略过~~)

//  配置时间线
struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent())
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration)
        completion(entry)
    }

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) {
        var entries: [SimpleEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}
// 数据源
struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
}
// 主视图
struct CYWidgetExtensionEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Text(entry.date, style: .time)
    }
}
// 入口
@main
struct CYWidgetExtension: Widget {
    let kind: String = "CYWidgetExtension"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            CYWidgetExtensionEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}
// Xcode 右侧快捷预览
struct CYWidgetExtension_Previews: PreviewProvider {
    static var previews: some View {
        CYWidgetExtensionEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

(成功略过,以下是主要实现代码~~)

// 自定义时钟
iOS14 widget 主要是基于swiftUI 布局,所以需要一些swiftUI基础。
创建自定义时钟表盘,指针

private struct CYWidgetCalendarView: View {
    var entry: SimpleEntry
    
    var body: some View {
        ZStack {
            Image(uiImage: entry.data.image)
                .resizable()
                .scaledToFill()
                .edgesIgnoringSafeArea(.all)

            ForEach(1..<13, id: \.self){ i in
                let sinX = sin(CGFloat(i)*30.0/180.0*CGFloat.pi)
                let sinY = -cos(CGFloat(i)*30.0/180.0*CGFloat.pi)
                let color = Color(UIColor(hexStr: entry.data.colorStr))
                Text("\(i)")
                    .foregroundColor(color)
                    .font(.system(size: RatioLen(14)))
                    .frame(width: RatioLen(16), height: RatioLen(16), alignment: .center)
                    .offset(x: 60*sinX, y: 60*sinY)
            }
            
            // sec
            /*
            Rectangle()
                .fill(Color.red)
                .frame(width: 2, height: RatioLen(40), alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
                .offset(y: -10)
                .rotationEffect(.init(degrees: Double(entry.clockTime().sec*6)))*/

            // min
            Rectangle()
                .fill(Color.blue)
                .frame(width: 2, height: RatioLen(30), alignment: .center)
                .offset(y: -15)
                .rotationEffect(.init(degrees: Double(entry.clockTime().min*6)))
            
            let hour = Double(entry.clockTime().min)*0.5 + Double(entry.clockTime().hour)*30.0
            // hour
            Rectangle()
                .fill(Color.primary)
                .frame(width: 2, height: RatioLen(20), alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
                .offset(y: -10)
                .rotationEffect(.init(degrees: Double(hour)))
            // link 只支持 systemMedium systemLarge
            /*
            Link(destination: URL(string: "https://www.baidu.com/")!, label: {
                    /*@START_MENU_TOKEN@*/Text("Link")/*@END_MENU_TOKEN@*/
                })*/

        } // widgetURL 支持全尺寸 推荐
        .widgetURL(URL(string: "https://www.baidu.com/clock"))
    }
}

关于在app中切换widget背景图片 和表盘刻度颜色,需要在xcode targets增加app group, target和widget extension 都需要添加且需要一致,app group id 是bundle id 前加group. 就好了。

控制器中代码实现,存储图片到group沙盒路径中,保存配置颜色值到group UserDefault

// group id
public let groupBundleKey: String = "group.www.cyan.com.CYWidgetNew"

// widget group 指定路径
let widgetMainPath = "/Library/cyan/widget/"

// widget 时钟颜色
let widgetClockColor = "widgetClockColor"
// widget 日历颜色
let widgetCalendarColor = "widgetCalendarColor"

class ViewController: UIViewController {

    lazy var button1: UIButton = {
        let button = UIButton(type: .custom)
        button.backgroundColor = UIColor.purple
        button.setTitle("刷新 1", for: .normal)
        button.addTarget(self, action: #selector(save(btn:)), for:.touchUpInside)
        return button
    }()
    
    lazy var button2: UIButton = {
        let button = UIButton(type: .custom)
        button.backgroundColor = UIColor.purple
        button.setTitle("刷新 2", for: .normal)
        button.addTarget(self, action: #selector(save(btn:)), for:.touchUpInside)
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        creatUI()
    }
    
    func creatUI() {
        button1.frame = CGRect(x: 30, y: 70, width: 100, height: 40)
        self.view.addSubview(button1)
    
        button2.frame = CGRect(x: 150, y: 70, width: 100, height: 40)
        self.view.addSubview(button2)
    }
    
    @objc func save(btn: UIButton) {
        let fileManager = FileManager.default
        let url = fileManager.containerURL(forSecurityApplicationGroupIdentifier: groupBundleKey)

        let mainPath = (url?.path ?? "") + widgetMainPath
        var isFolder: ObjCBool = false
        let isExists = fileManager.fileExists(atPath: mainPath, isDirectory: &isFolder)
        if isExists == false || isFolder.boolValue == false {
            try? fileManager.createDirectory(atPath: mainPath, withIntermediateDirectories: true, attributes: nil)
        }
        // temp1  temp2
        var name = ""
        var color = "000000"
        switch btn {
        case button1:
            name = "temp1"
            color = "000000"
        default:
            name = "temp2"
            color = "ffffff"
        }
        
        let data = UIImage(named: name)?.pngData()
        try? data?.write(to: URL(fileURLWithPath: mainPath + "widget1.jpg"), options: .atomic)

        print("mainPath : \(mainPath)")
        
        let userdefaults = UserDefaults.init(suiteName: groupBundleKey)
        userdefaults?.setValue(color, forKey: widgetClockColor)
        userdefaults?.setValue(color, forKey: widgetCalendarColor)
        userdefaults?.synchronize()
        
        // 立即刷新所有组件
        if #available(iOS 14.0, *) {
            DispatchQueue.main.async {
                WidgetCenter.shared.reloadAllTimelines()
                print("刷新成功")
            }
       
        } else {
            // Fallback on earlier versions
        }
    }

}

widget data 中从group沙盒中获取图片,从group userdefault中获取 配置颜色

// group id
public let groupBundleKey: String = "group.www.cyan.com.CYWidgetNew"

// 图片路径 存储名
let clockWidgetPath = "/Library/cyan/widget/widget1.jpg"

// widget 时钟颜色
let widgetClockColor = "widgetClockColor"
// widget 日历颜色
let widgetCalendarColor = "widgetCalendarColor"

let widgetTargetWidth: CGFloat = 329
let iPhoneHeight = UIScreen.main.bounds.size.height

enum CYWidgetType {
    case clock
    case calendar
    
    var path: String {
        clockWidgetPath
    }

}

struct CYWidgetData {
    let title: String
    let imageName : String
    let image   : UIImage
    let colorStr: String
}

struct CYWidgetDataLoader {
    
    static func getWidgetData(_ type: CYWidgetType) -> CYWidgetData {
        let userdefaults = UserDefaults.init(suiteName: groupBundleKey)
        var colorStr = ""
        switch type {
        case .calendar: colorStr = userdefaults?.string(forKey: widgetCalendarColor) ?? "ffffff"
        case .clock: colorStr = userdefaults?.string(forKey: widgetClockColor) ?? "ffffff"
        }
        
        let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupBundleKey)
        let imagePath = (url?.path ?? "") + type.path
        print("imagePath:\(imagePath)")
        if let image = UIImage(contentsOfFile: imagePath) {
            return CYWidgetData(title: "title:cyan", imageName: "", image: image, colorStr: colorStr)
        }
        return CYWidgetData(title: "title:cyan", imageName: "", image: UIImage(named: "widgetBackground")!, colorStr: colorStr)
    }
    
}

demo

你可能感兴趣的:(iOS 14 widget(时钟、日历))