├── Clocks ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── AppIcon-20.0x20.0@2x.png │ │ ├── AppIcon-20.0x20.0@3x.png │ │ ├── AppIcon-29.0x29.0@2x.png │ │ ├── AppIcon-29.0x29.0@3x.png │ │ ├── AppIcon-40.0x40.0@2x.png │ │ ├── AppIcon-40.0x40.0@3x.png │ │ ├── AppIcon-60.0x60.0@2x.png │ │ ├── AppIcon-60.0x60.0@3x.png │ │ ├── AppIcon-1024.0x1024.0@1x.png │ │ └── Contents.json │ └── AccentColor.colorset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── ClocksApp.swift ├── Extensions │ ├── Date.swift │ ├── DateFormatter.swift │ ├── EnvironmentValues.swift │ ├── Color.swift │ └── UIDevice.swift ├── Clocks.entitlements ├── Protocol │ └── ClockWidget.swift ├── Utils │ ├── WidgetDetailConfigFields.swift │ ├── DefaultClockStyle.swift │ ├── UserDefaults.swift │ ├── Image.swift │ └── DeviceWidget.swift ├── Views │ ├── ContentView.swift │ ├── Helper │ │ ├── WidgetBackground.swift │ │ ├── WidgetFamilyProvider.swift │ │ ├── ImagePicker.swift │ │ └── WidgetPreviews.swift │ ├── CurrentWidget │ │ ├── CurrentClockPreviewView.swift │ │ └── CurrentClocksView.swift │ ├── WidgetList │ │ ├── WidgetListPreviewView.swift │ │ └── WidgetListView.swift │ ├── WidgetViews │ │ ├── ClockWidgetBundleView.swift │ │ ├── SimplePicture.swift │ │ ├── SimpleClockView.swift │ │ └── SimpleClock1View.swift │ └── WidgetDetail │ │ ├── ClocksDetailView.swift │ │ ├── ClockDetailImageCropView.swift │ │ └── ClockDetailImageEditView.swift ├── ViewModels │ ├── ConfigKeysViewModel.swift │ ├── StorableClockConfig.swift │ └── ClockConfigViewModel.swift └── Info.plist ├── ClocksWidget ├── Assets.xcassets │ ├── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── WidgetBackground.colorset │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── ClocksWidget.swift └── ClocksWidgetIntents.intentdefinition ├── Clocks.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ ├── Clocks.xcscheme │ │ └── ClocksWidgetExtension.xcscheme └── project.pbxproj ├── README.md ├── ClocksWidgetExtension.entitlements ├── ClocksWidgetIntents ├── ClocksWidgetIntents.entitlements ├── IntentHandler.swift └── Info.plist ├── LICENSE └── .gitignore /Clocks/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ClocksWidget/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Clocks/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-20.0x20.0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-20.0x20.0@2x.png -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-20.0x20.0@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-20.0x20.0@3x.png -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-29.0x29.0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-29.0x29.0@2x.png -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-29.0x29.0@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-29.0x29.0@3x.png -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-40.0x40.0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-40.0x40.0@2x.png -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-40.0x40.0@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-40.0x40.0@3x.png -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-60.0x60.0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-60.0x60.0@2x.png -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-60.0x60.0@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-60.0x60.0@3x.png -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.0x1024.0@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.0x1024.0@1x.png -------------------------------------------------------------------------------- /Clocks.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 时钟小组件 2 | 3 | [笔记](https://github.com/zhangyu1818/blog/issues/39) 4 | 5 | [AppStore](https://apps.apple.com/cn/app/时钟小组件-简约时钟/id1548485543) 6 | 7 | 时间过于久远,超过苹果的多年不更新政策,已经被强制下架了。时过境迁,API也应该发生了很多变化,此仓库代码只做留念了。 8 | -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ClocksWidget/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ClocksWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Clocks/ClocksApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClocksApp.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2020/12/30. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct ClocksApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Clocks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Clocks/Extensions/Date.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/11. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | var endDayOfMonth: Int { 12 | let range = Calendar.current.range(of: .day, in: .month, for: self)! 13 | return range.count 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Clocks/Clocks.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.zhangyu1818.clocks 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ClocksWidgetExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.zhangyu1818.clocks 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ClocksWidgetIntents/ClocksWidgetIntents.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.zhangyu1818.clocks 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Clocks/Protocol/ClockWidget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClockWidget.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/11. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | protocol ClockWidget { 12 | static var clockName: String { get } 13 | static var nonConfigurableFields: [String] { get } 14 | 15 | var config: WidgetClockConfig { get } 16 | 17 | init(date: Date, config widgetConfig: WidgetClockConfig) 18 | } 19 | -------------------------------------------------------------------------------- /Clocks/Utils/WidgetDetailConfigFields.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NonConfigurableFields.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/2/5. 6 | // 7 | 8 | enum WidgetDetailConfigFields { 9 | static var textColor = "TEXT_COLOR" 10 | static var backgroundColor = "BACKGROUND_COLOR" 11 | static var is12Hour = "IS_12_HOUR" 12 | static var showDateInfo = "SHOW_DATE_INFO" 13 | static var backgroundImage = "BACKGROUND_IMAGE" 14 | } 15 | -------------------------------------------------------------------------------- /Clocks/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2020/12/30. 6 | // 7 | import SwiftUI 8 | 9 | struct ContentView: View { 10 | var body: some View { 11 | NavigationView { 12 | WidgetListView() 13 | } 14 | } 15 | } 16 | 17 | struct ContentView_Previews: PreviewProvider { 18 | static var previews: some View { 19 | ContentView() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Clocks/Extensions/DateFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormatter+TimeFormatter.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2020/12/30. 6 | // 7 | 8 | import Foundation 9 | 10 | extension DateFormatter { 11 | static func timeFormatter(_ dateFormat: String) -> DateFormatter { 12 | let formatter = DateFormatter() 13 | formatter.dateFormat = dateFormat 14 | formatter.locale = Locale(identifier: "zh_CN") 15 | return formatter 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Clocks/Views/Helper/WidgetBackground.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WidgetBackground.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/9. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WidgetBackground: View { 11 | let uiImage: UIImage? 12 | let blur: Bool 13 | 14 | var body: some View { 15 | if let image = uiImage { 16 | Image(uiImage: image) 17 | .resizable() 18 | .padding(.all, blur ? -35 : 0) 19 | .aspectRatio(contentMode: .fill) 20 | .blur(radius: blur ? 20.0 : 0) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Clocks/Utils/DefaultClockStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultClockStyle.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/12. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DefaultStyle { 11 | var textColor: Color 12 | var backgroundColor: Color 13 | } 14 | 15 | let defaultClockStyle = [ 16 | SimpleClockView.clockName: DefaultStyle(textColor: Color(hexString: "#ffffff"), backgroundColor: Color(UIColor.systemIndigo)), 17 | SimpleClock1View.clockName: DefaultStyle(textColor: Color(hexString: "#ffffff"), backgroundColor: Color(UIColor.systemTeal)), 18 | SimplePicture.clockName: DefaultStyle(textColor: Color(hexString: "#ffffff"), backgroundColor: Color.yellow), 19 | ] 20 | -------------------------------------------------------------------------------- /Clocks/Extensions/EnvironmentValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentValues.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/6. 6 | // 7 | import SwiftUI 8 | import WidgetKit 9 | 10 | enum PreviewsFamily { 11 | case small 12 | case medium 13 | case large 14 | } 15 | 16 | struct PreviewsFamilyEnvironmentKey: EnvironmentKey { 17 | static let defaultValue: WidgetFamily = .systemMedium 18 | } 19 | 20 | extension EnvironmentValues { 21 | var previewsFamily: WidgetFamily { 22 | get { 23 | return self[PreviewsFamilyEnvironmentKey] 24 | } 25 | set { 26 | self[PreviewsFamilyEnvironmentKey] = newValue 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Clocks/ViewModels/ConfigKeysViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserWidgetViewModel.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/9. 6 | // 7 | import SwiftUI 8 | 9 | /** 10 | 对已保存的小组件的configKey做管理 11 | */ 12 | class ConfigKeysViewModel: ObservableObject { 13 | static let shared = ConfigKeysViewModel() 14 | 15 | @Published private(set) var configKeys: [String] { 16 | didSet { 17 | saveConfigKeys(configKeys: configKeys) 18 | } 19 | } 20 | 21 | private init() { 22 | configKeys = getSavedConfigKeys() 23 | } 24 | 25 | // 新增configKey 26 | func add(configKey: String) { 27 | configKeys.append(configKey) 28 | } 29 | 30 | // 删除configKey 31 | func delete(configKey: String) { 32 | configKeys = configKeys.filter { $0 != configKey } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Clocks/Views/Helper/WidgetFamilyProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WidgetFamilyProvider.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/6. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | /** 12 | 将widgetFamily转为自定义的previewsFamily传递 13 | */ 14 | struct WidgetFamilyProvider: View { 15 | @Environment(\.widgetFamily) private var widgetFamily 16 | 17 | let widget: Widget 18 | 19 | init(@ViewBuilder _ widget: @escaping () -> Widget) { 20 | self.widget = widget() 21 | } 22 | 23 | var body: some View { 24 | widget.environment(\.previewsFamily, widgetFamily) 25 | } 26 | } 27 | 28 | struct WidgetFamilyProvider_Previews: PreviewProvider { 29 | static var previews: some View { 30 | WidgetFamilyProvider { 31 | Text("Provider") 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Clocks/Views/CurrentWidget/CurrentClockPreviewView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentClockPreviewView.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/10. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | /** 12 | 展示已保存的Clock 13 | */ 14 | struct CurrentClockPreview: View { 15 | @StateObject private var config: ClockConfigViewModel 16 | 17 | init(key: String) { 18 | let config = ClockConfigManager.shared.getConfigViewModel(key) 19 | _config = StateObject(wrappedValue: config) 20 | } 21 | 22 | var body: some View { 23 | NavigationLink( 24 | destination: ClocksDetailView(config: config.clone()) { detailConfig in 25 | ClockWidgetBundleView(date: Date(), config: detailConfig) 26 | }, 27 | label: { 28 | WidgetPreviews(widgetFamily: [.systemMedium]) { 29 | ClockWidgetBundleView(date: Date(), config: config.toWidgetConfig()) 30 | } 31 | } 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ClocksWidget/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | ClocksWidget 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSExtension 24 | 25 | NSExtensionPointIdentifier 26 | com.apple.widgetkit-extension 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ZHANGYU 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Clocks/Views/CurrentWidget/CurrentClocksView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentClocksView.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/9. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CurrentClocksView: View { 11 | @StateObject private var widgetConfigs = ConfigKeysViewModel.shared 12 | 13 | var body: some View { 14 | if !widgetConfigs.configKeys.isEmpty { 15 | VStack { 16 | HStack { 17 | Text("保存的小组件").font(.headline) 18 | Spacer() 19 | } 20 | ScrollView(.horizontal, showsIndicators: false) { 21 | HStack(spacing: 12) { 22 | ForEach(0 ..< widgetConfigs.configKeys.count, id: \.self) { index in 23 | let key = widgetConfigs.configKeys[index] 24 | CurrentClockPreview(key: key) 25 | } 26 | } 27 | .frame(height: 220) 28 | } 29 | 30 | Divider() 31 | } 32 | .padding(.horizontal) 33 | } 34 | } 35 | } 36 | 37 | struct CurrentClocksView_Previews: PreviewProvider { 38 | static var previews: some View { 39 | CurrentClocksView() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ClocksWidgetIntents/IntentHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntentHandler.swift 3 | // ClocksWidgetIntents 4 | // 5 | // Created by ZhangYu on 2021/1/9. 6 | // 7 | 8 | import Intents 9 | 10 | class IntentHandler: INExtension, ClocksWidgetIntentHandling { 11 | func provideCurrentWidgetOptionsCollection(for _: ClocksWidgetIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { 12 | let savedConfig = getSavedConfigKeys() 13 | let widgetConfigs = savedConfig.enumerated().map { index, key in WidgetConfig(identifier: key, display: "小组件 \(index + 1)") } 14 | let collection = INObjectCollection(items: widgetConfigs) 15 | 16 | completion(collection, nil) 17 | } 18 | 19 | func defaultCurrentWidget(for _: ClocksWidgetIntent) -> WidgetConfig? { 20 | let savedConfig = getSavedConfigKeys() 21 | guard let lastConfigKey = savedConfig.last else { 22 | return nil 23 | } 24 | return WidgetConfig(identifier: lastConfigKey, display: "小组件 \(savedConfig.count)") 25 | } 26 | 27 | override func handler(for _: INIntent) -> Any { 28 | // This is the default implementation. If you want different objects to handle different intents, 29 | // you can override this and return the handler you want for that particular intent. 30 | 31 | return self 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Clocks/Views/WidgetList/WidgetListPreviewView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WidgetListPreviewView.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2020/12/30. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /** 11 | 新的组件每次需要一个不同的ConfigViewModel,所以得用一个新的View包一下 12 | */ 13 | struct NewDetail: View { 14 | let clockName: String 15 | 16 | var body: some View { 17 | ClocksDetailView(config: ClockConfigManager.shared.createConfigViewModel(clockName: clockName)) { detailConfig in 18 | ClockWidgetBundleView(date: Date(), config: detailConfig) 19 | } 20 | } 21 | } 22 | 23 | struct WidgetListPreviewView: View { 24 | let title: String 25 | let content: Content 26 | 27 | init(title: String, @ViewBuilder content: @escaping () -> Content) { 28 | self.title = title 29 | self.content = content() 30 | } 31 | 32 | var body: some View { 33 | VStack(alignment: .leading) { 34 | Text(title) 35 | .font(.headline) 36 | .padding(.horizontal) 37 | 38 | VStack(spacing: 30) { 39 | self.content 40 | } 41 | .padding(.horizontal) 42 | } 43 | } 44 | } 45 | 46 | struct ClocksListRowView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | WidgetListPreviewView(title: "标题") {} 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Clocks/Views/WidgetViews/ClockWidgetBundleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClockWidgetBundleView.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/11. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ClockWidgetBundleView: ClockWidget, View { 11 | static let clockName: String = "" 12 | static let nonConfigurableFields: [String] = [] 13 | 14 | let date: Date 15 | let config: WidgetClockConfig 16 | 17 | let clockName: String 18 | 19 | init(date: Date, config widgetConfig: WidgetClockConfig = WidgetClockConfig.createEmpty(clockName: Self.clockName)) { 20 | self.date = date 21 | config = widgetConfig 22 | 23 | clockName = config.clockName 24 | } 25 | 26 | var body: some View { 27 | switch clockName { 28 | case SimpleClockView.clockName: 29 | SimpleClockView(date: date, config: config) 30 | case SimpleClock1View.clockName: 31 | SimpleClock1View(date: date, config: config) 32 | case SimplePicture.clockName: 33 | SimplePicture(date: date, config: config) 34 | default: 35 | GeometryReader { geo in 36 | Text("请选择要显示的小组件") 37 | .font(.subheadline) 38 | .foregroundColor(.primary) 39 | .background(Color.clear) 40 | .frame(width: geo.size.width, height: geo.size.height) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Clocks/Views/Helper/ImagePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePicker.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/6. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ImagePicker: UIViewControllerRepresentable { 11 | @Environment(\.presentationMode) var presentationMode 12 | 13 | var onSelect: ((UIImage) -> Void)? 14 | 15 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController { 16 | let imagePicker = UIImagePickerController() 17 | imagePicker.delegate = context.coordinator 18 | return imagePicker 19 | } 20 | 21 | func updateUIViewController(_: UIImagePickerController, context _: UIViewControllerRepresentableContext) {} 22 | 23 | func makeCoordinator() -> Coordinator { 24 | Coordinator(self) 25 | } 26 | 27 | class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { 28 | let parent: ImagePicker 29 | 30 | init(_ parent: ImagePicker) { 31 | self.parent = parent 32 | } 33 | 34 | func imagePickerController(_: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { 35 | if let uiImage = info[.originalImage] as? UIImage { 36 | parent.onSelect?(uiImage) 37 | } 38 | 39 | parent.presentationMode.wrappedValue.dismiss() 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Clocks/Views/WidgetViews/SimplePicture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimplePicture.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/2/5. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SimplePicture: ClockWidget, View { 11 | static let clockName = "简单图片" 12 | static let nonConfigurableFields: [String] = [ 13 | WidgetDetailConfigFields.textColor, 14 | WidgetDetailConfigFields.is12Hour, 15 | WidgetDetailConfigFields.showDateInfo, 16 | ] 17 | 18 | @Environment(\.colorScheme) private var colorScheme 19 | 20 | let config: WidgetClockConfig 21 | 22 | private var preferredBackgroundImage: UIImage? { 23 | config.preferredBackgroundImage(colorScheme: colorScheme) 24 | } 25 | 26 | init(date _: Date, config widgetConfig: WidgetClockConfig = WidgetClockConfig.createEmpty(clockName: Self.clockName)) { 27 | config = widgetConfig 28 | } 29 | 30 | var body: some View { 31 | GeometryReader { geo in 32 | ZStack { 33 | WidgetBackground(uiImage: preferredBackgroundImage, blur: config.blur) 34 | .frame(width: geo.size.width, height: geo.size.height) 35 | } 36 | } 37 | .background(preferredBackgroundImage != nil ? Color.clear : config.backgroundColor) 38 | } 39 | } 40 | 41 | struct SimplePicture_Previews: PreviewProvider { 42 | static var previews: some View { 43 | SimplePicture(date: Date()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ClocksWidgetIntents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | ClocksWidgetIntents 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | IntentsRestrictedWhileLocked 28 | 29 | IntentsRestrictedWhileProtectedDataUnavailable 30 | 31 | IntentsSupported 32 | 33 | ClocksWidgetIntent 34 | 35 | 36 | NSExtensionPointIdentifier 37 | com.apple.intents-service 38 | NSExtensionPrincipalClass 39 | $(PRODUCT_MODULE_NAME).IntentHandler 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Clocks/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-20.0x20.0@2x.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "AppIcon-20.0x20.0@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "AppIcon-29.0x29.0@2x.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "AppIcon-29.0x29.0@3x.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "AppIcon-40.0x40.0@2x.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "AppIcon-40.0x40.0@3x.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "AppIcon-60.0x60.0@2x.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "AppIcon-60.0x60.0@3x.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "AppIcon-1024.0x1024.0@1x.png", 53 | "idiom" : "ios-marketing", 54 | "scale" : "1x", 55 | "size" : "1024x1024" 56 | } 57 | ], 58 | "info" : { 59 | "author" : "xcode", 60 | "version" : 1 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Clocks/Views/Helper/WidgetPreviews.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WidgetPreviews.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2020/12/30. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | /** 12 | 根据WidgetFamily显示不同大小的widget 13 | */ 14 | struct WidgetPreviews: View { 15 | let widget: Widget 16 | let widgetFamily: [WidgetFamily] 17 | 18 | private let widgetSizeSmall = DeviceWidgetSize.small 19 | private let widgetSizeMeduim = DeviceWidgetSize.meduim 20 | 21 | init(widgetFamily: [WidgetFamily] = [.systemSmall, .systemMedium], @ViewBuilder _ widget: @escaping () -> Widget) { 22 | self.widget = widget() 23 | self.widgetFamily = widgetFamily 24 | } 25 | 26 | var body: some View { 27 | HStack(spacing: 24) { 28 | Group { 29 | if widgetFamily.contains(.systemSmall) { 30 | widget 31 | .frame(width: widgetSizeSmall.width, height: widgetSizeSmall.height) 32 | .environment(\.previewsFamily, .systemSmall) 33 | } 34 | if widgetFamily.contains(.systemMedium) { 35 | widget 36 | .frame(width: widgetSizeMeduim.width, height: widgetSizeMeduim.height) 37 | .environment(\.previewsFamily, .systemMedium) 38 | } 39 | } 40 | .cornerRadius(24) 41 | } 42 | } 43 | } 44 | 45 | struct WidgetPreviews_Previews: PreviewProvider { 46 | static var previews: some View { 47 | WidgetPreviews { 48 | Text("preview") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Clocks/Utils/UserDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/7. 6 | // 7 | 8 | import Foundation 9 | 10 | func appUserDefaults() -> UserDefaults? { 11 | UserDefaults(suiteName: "group.zhangyu1818.clocks") 12 | } 13 | 14 | func getWidgetConfig(_ key: String, defaultValue: T, parseConfig: (StorableClockConfig) -> T) -> T { 15 | guard let userDefaults = appUserDefaults() else { 16 | return defaultValue 17 | } 18 | 19 | guard let data = userDefaults.object(forKey: key) as? Data else { 20 | return defaultValue 21 | } 22 | 23 | guard let config = try? JSONDecoder().decode(StorableClockConfig.self, from: data) else { 24 | return defaultValue 25 | } 26 | 27 | return parseConfig(config) 28 | } 29 | 30 | let CONFIG_KEY_NAME = "savedWidgetConfig" 31 | 32 | func getSavedConfigKeys() -> [String] { 33 | guard let userDefaults = appUserDefaults() else { 34 | return [] 35 | } 36 | 37 | guard let data = userDefaults.array(forKey: CONFIG_KEY_NAME) as? [String] else { 38 | return [] 39 | } 40 | return data 41 | } 42 | 43 | func saveConfigKeys(configKeys: [String]) { 44 | guard let userDefaults = appUserDefaults() else { 45 | return 46 | } 47 | 48 | userDefaults.setValue(configKeys, forKey: CONFIG_KEY_NAME) 49 | } 50 | 51 | func addConfigKeys(key: String) { 52 | var configKeys = getSavedConfigKeys() 53 | configKeys.append(key) 54 | saveConfigKeys(configKeys: configKeys) 55 | } 56 | 57 | func deleteConfigKeys(key: String) { 58 | guard let userDefaults = appUserDefaults() else { return } 59 | userDefaults.removeObject(forKey: key) 60 | } 61 | -------------------------------------------------------------------------------- /Clocks/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleAllowMixedLocalizations 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLocalizations 18 | 19 | zh 20 | 21 | CFBundleName 22 | $(PRODUCT_NAME) 23 | CFBundlePackageType 24 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 25 | CFBundleShortVersionString 26 | $(MARKETING_VERSION) 27 | CFBundleVersion 28 | $(CURRENT_PROJECT_VERSION) 29 | LSRequiresIPhoneOS 30 | 31 | NSUserActivityTypes 32 | 33 | ClocksWidgetIntent 34 | 35 | UIApplicationSceneManifest 36 | 37 | UIApplicationSupportsMultipleScenes 38 | 39 | 40 | UIApplicationSupportsIndirectInputEvents 41 | 42 | UILaunchScreen 43 | 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ClocksWidget/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Clocks/Extensions/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/1. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension Color { 11 | static let systemBackground = Color(UIColor.systemBackground) 12 | static let secondarySystemBackground = Color(UIColor.secondarySystemBackground) 13 | static let tertiarySystemBackground = Color(UIColor.tertiarySystemBackground) 14 | 15 | static let systemGroupedBackground = Color(UIColor.systemGroupedBackground) 16 | static let secondarySystemGroupedBackground = Color(UIColor.secondarySystemGroupedBackground) 17 | static let tertiarySystemGroupedBackground = Color(UIColor.tertiarySystemGroupedBackground) 18 | 19 | static let separator = Color(UIColor.separator) 20 | } 21 | 22 | extension Color { 23 | init(hexString: String) { 24 | var cString: String = hexString.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() 25 | 26 | if cString.hasPrefix("#") { 27 | cString.remove(at: cString.startIndex) 28 | } 29 | 30 | var rgbValue: UInt64 = 0 31 | Scanner(string: cString).scanHexInt64(&rgbValue) 32 | 33 | self.init(UIColor( 34 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, 35 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, 36 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0, 37 | alpha: CGFloat(1.0) 38 | )) 39 | } 40 | 41 | func toHexString() -> String { 42 | var r: CGFloat = 0 43 | var g: CGFloat = 0 44 | var b: CGFloat = 0 45 | var a: CGFloat = 0 46 | 47 | let uiColor = UIColor(self) 48 | uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) 49 | let rgb: Int = (Int)(r * 255) << 16 | (Int)(g * 255) << 8 | (Int)(b * 255) << 0 50 | return String(format: "#%06x", rgb) 51 | } 52 | 53 | var contrastColor: Color { 54 | let ciColor = CIColor(color: UIColor(self)) 55 | 56 | // get the current values and make the difference from white: 57 | let compRed: CGFloat = 1.0 - ciColor.red 58 | let compGreen: CGFloat = 1.0 - ciColor.green 59 | let compBlue: CGFloat = 1.0 - ciColor.blue 60 | 61 | return Color(UIColor(red: compRed, green: compGreen, blue: compBlue, alpha: 1.0)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Clocks/Views/WidgetList/WidgetListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WidgetListView.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2020/12/30. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | struct WidgetListView: View { 12 | var body: some View { 13 | ScrollView { 14 | CurrentClocksView() 15 | .padding(.bottom) 16 | 17 | WidgetListPreviewView(title: "时间") { 18 | NavigationLink( 19 | destination: NewDetail(clockName: SimpleClockView.clockName), 20 | label: { 21 | WidgetPreviews(widgetFamily: [.systemMedium]) { 22 | SimpleClockView(date: Date()) 23 | } 24 | } 25 | ) 26 | NavigationLink( 27 | destination: NewDetail(clockName: SimpleClock1View.clockName), 28 | label: { 29 | WidgetPreviews(widgetFamily: [.systemMedium]) { 30 | SimpleClock1View(date: Date()) 31 | } 32 | } 33 | ) 34 | } 35 | 36 | Spacer().padding() 37 | 38 | WidgetListPreviewView(title: "图片") { 39 | NavigationLink( 40 | destination: NewDetail(clockName: SimplePicture.clockName), 41 | label: { 42 | WidgetPreviews(widgetFamily: [.systemMedium]) { 43 | SimplePicture(date: Date()) 44 | } 45 | } 46 | ) 47 | } 48 | } 49 | .toolbar { 50 | ToolbarItem(placement: .primaryAction) { 51 | HStack { 52 | Button(action: { 53 | WidgetCenter.shared.reloadAllTimelines() 54 | }) { 55 | VStack { 56 | Image(systemName: "arrow.clockwise.circle") 57 | Text("刷新") 58 | .font(.subheadline) 59 | } 60 | } 61 | .frame(width: 44, height: 44) 62 | } 63 | } 64 | } 65 | .navigationBarTitle("小组件列表") 66 | } 67 | } 68 | 69 | struct ClocksListView_Previews: PreviewProvider { 70 | static var previews: some View { 71 | WidgetListView() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | .DS_Store 93 | -------------------------------------------------------------------------------- /Clocks/Utils/Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/6. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | func removeImages(_ imageNames: [String?]) { 12 | guard let documentsDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.zhangyu1818.clocks") else { return } 13 | 14 | imageNames.forEach { imageName in 15 | if let fileName = imageName { 16 | let fileURL = documentsDirectory.appendingPathComponent(fileName) 17 | 18 | // Checks if file exists, removes it if so. 19 | if FileManager.default.fileExists(atPath: fileURL.path) { 20 | do { 21 | try FileManager.default.removeItem(atPath: fileURL.path) 22 | print("Removed image") 23 | } catch let removeError { 24 | print("couldn't remove file at path", removeError) 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | func saveImage(imageName: String, image: UIImage, onCompleted: ((URL) -> Void)?) { 32 | guard let documentsDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.zhangyu1818.clocks") else { return } 33 | 34 | let fileName = imageName 35 | let fileURL = documentsDirectory.appendingPathComponent(fileName) 36 | guard let data = image.jpegData(compressionQuality: 1) else { return } 37 | 38 | // Checks if file exists, removes it if so. 39 | if FileManager.default.fileExists(atPath: fileURL.path) { 40 | do { 41 | try FileManager.default.removeItem(atPath: fileURL.path) 42 | print("Removed old image") 43 | } catch let removeError { 44 | print("couldn't remove file at path", removeError) 45 | } 46 | } 47 | 48 | do { 49 | try data.write(to: fileURL) 50 | onCompleted?(fileURL) 51 | } catch { 52 | print("error saving file with error", error) 53 | } 54 | } 55 | 56 | func cropImage( 57 | _ inputImage: UIImage, 58 | toRect cropRect: CGRect, 59 | viewWidth: CGFloat = UIScreen.main.bounds.width, 60 | viewHeight: CGFloat = UIScreen.main.bounds.height 61 | ) -> UIImage? { 62 | let imageViewScale = max(inputImage.size.width / viewWidth, 63 | inputImage.size.height / viewHeight) 64 | 65 | // Scale cropRect to handle images larger than shown-on-screen size 66 | let cropZone = CGRect(x: cropRect.origin.x * imageViewScale, 67 | y: cropRect.origin.y * imageViewScale, 68 | width: cropRect.size.width * imageViewScale, 69 | height: cropRect.size.height * imageViewScale) 70 | 71 | // Perform cropping in Core Graphics 72 | guard let cutImageRef: CGImage = inputImage.cgImage?.cropping(to: cropZone) 73 | else { 74 | return nil 75 | } 76 | 77 | // Return image to UIImage 78 | let croppedImage = UIImage(cgImage: cutImageRef) 79 | return croppedImage 80 | } 81 | -------------------------------------------------------------------------------- /Clocks.xcodeproj/xcshareddata/xcschemes/Clocks.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Clocks/Extensions/UIDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDevice.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/11. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension UIDevice { 11 | enum Model: String { 12 | // Simulator 13 | case simulator = "simulator/sandbox", 14 | 15 | // iPod 16 | iPod7 = "iPod 7", 17 | 18 | // iPhone 19 | iPhone6S = "iPhone 6S", 20 | iPhone6SPlus = "iPhone 6S Plus", 21 | iPhoneSE = "iPhone SE", 22 | iPhone7 = "iPhone 7", 23 | iPhone7Plus = "iPhone 7 Plus", 24 | iPhone8 = "iPhone 8", 25 | iPhone8Plus = "iPhone 8 Plus", 26 | iPhoneX = "iPhone X", 27 | iPhoneXS = "iPhone XS", 28 | iPhoneXSMax = "iPhone XS Max", 29 | iPhoneXR = "iPhone XR", 30 | iPhone11 = "iPhone 11", 31 | iPhone11Pro = "iPhone 11 Pro", 32 | iPhone11ProMax = "iPhone 11 Pro Max", 33 | iPhoneSE2 = "iPhone SE 2nd gen", 34 | iPhone12Mini = "iPhone 12 Mini", 35 | iPhone12 = "iPhone 12", 36 | iPhone12Pro = "iPhone 12 Pro", 37 | iPhone12ProMax = "iPhone 12 Pro Max", 38 | 39 | unrecognized = "?unrecognized?" 40 | } 41 | 42 | var type: Model { 43 | var systemInfo = utsname() 44 | uname(&systemInfo) 45 | let modelCode = withUnsafePointer(to: &systemInfo.machine) { 46 | $0.withMemoryRebound(to: CChar.self, capacity: 1) { 47 | ptr in String(validatingUTF8: ptr) 48 | } 49 | } 50 | 51 | let modelMap: [String: Model] = [ 52 | // Simulator 53 | "i386": .simulator, 54 | "x86_64": .simulator, 55 | 56 | // iPod 57 | "iPod9,1": .iPod7, 58 | 59 | // iPhone 60 | "iPhone8,1": .iPhone6S, 61 | "iPhone8,2": .iPhone6SPlus, 62 | "iPhone8,4": .iPhoneSE, 63 | "iPhone9,1": .iPhone7, 64 | "iPhone9,3": .iPhone7, 65 | "iPhone9,2": .iPhone7Plus, 66 | "iPhone9,4": .iPhone7Plus, 67 | "iPhone10,1": .iPhone8, 68 | "iPhone10,4": .iPhone8, 69 | "iPhone10,2": .iPhone8Plus, 70 | "iPhone10,5": .iPhone8Plus, 71 | "iPhone10,3": .iPhoneX, 72 | "iPhone10,6": .iPhoneX, 73 | "iPhone11,2": .iPhoneXS, 74 | "iPhone11,4": .iPhoneXSMax, 75 | "iPhone11,6": .iPhoneXSMax, 76 | "iPhone11,8": .iPhoneXR, 77 | "iPhone12,1": .iPhone11, 78 | "iPhone12,3": .iPhone11Pro, 79 | "iPhone12,5": .iPhone11ProMax, 80 | "iPhone12,8": .iPhoneSE2, 81 | "iPhone13,1": .iPhone12Mini, 82 | "iPhone13,2": .iPhone12, 83 | "iPhone13,3": .iPhone12Pro, 84 | "iPhone13,4": .iPhone12ProMax, 85 | ] 86 | 87 | if let model = modelMap[String(validatingUTF8: modelCode!)!] { 88 | if model == .simulator { 89 | if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] { 90 | if let simModel = modelMap[String(validatingUTF8: simModelCode)!] { 91 | return simModel 92 | } 93 | } 94 | } 95 | return model 96 | } 97 | return Model.unrecognized 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /ClocksWidget/ClocksWidget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClocksWidget.swift 3 | // ClocksWidget 4 | // 5 | // Created by ZhangYu on 2020/12/30. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | func getEntries(widgetKey: String?) -> ([WidgetEntry], Date) { 12 | let times = 15 13 | 14 | let currentDate = Date() 15 | 16 | let updatesDate = Calendar.current.date(byAdding: .minute, value: times, to: currentDate)! 17 | 18 | var config = WidgetClockConfig.createEmpty(clockName: "defaultClock") 19 | 20 | if widgetKey != nil { 21 | config = getWidgetConfig(widgetKey!, defaultValue: config) { config in 22 | WidgetClockConfig(fromStorableConfig: config) 23 | } 24 | } 25 | 26 | var entries = [WidgetEntry]() 27 | 28 | for offset in 0 ..< 60 * times { 29 | let entryDate = Calendar.current.date(byAdding: .second, value: offset, to: currentDate)! 30 | entries.append(WidgetEntry(date: entryDate, config: config)) 31 | } 32 | 33 | return (entries, updatesDate) 34 | } 35 | 36 | struct Provider: IntentTimelineProvider { 37 | func placeholder(in _: Context) -> WidgetEntry { 38 | WidgetEntry(date: Date()) 39 | } 40 | 41 | func getSnapshot(for _: ClocksWidgetIntent, in _: Context, completion: @escaping (WidgetEntry) -> Void) { 42 | let entry = WidgetEntry(date: Date()) 43 | completion(entry) 44 | } 45 | 46 | func getTimeline(for configuration: ClocksWidgetIntent, in _: Context, completion: @escaping (Timeline) -> Void) { 47 | let currentWidgetKey = configuration.currentWidget?.identifier 48 | 49 | let (entries, updatesDate) = getEntries(widgetKey: currentWidgetKey) 50 | 51 | print("entries length:\(entries.count),updates date:\(DateFormatter.timeFormatter("yyyy-MM-dd HH:mm:ss").string(from: updatesDate))") 52 | 53 | let timeline = Timeline(entries: entries, policy: .after(updatesDate)) 54 | completion(timeline) 55 | } 56 | } 57 | 58 | struct WidgetEntry: TimelineEntry { 59 | let date: Date 60 | let config: WidgetClockConfig? 61 | init(date: Date, config: WidgetClockConfig? = nil) { 62 | self.date = date 63 | self.config = config 64 | } 65 | } 66 | 67 | struct ClocksWidgetEntryView: View { 68 | var entry: Provider.Entry 69 | 70 | var body: some View { 71 | WidgetFamilyProvider { 72 | if let config = entry.config { 73 | ClockWidgetBundleView(date: entry.date, config: config) 74 | } else { 75 | ClockWidgetBundleView(date: entry.date) 76 | } 77 | } 78 | } 79 | } 80 | 81 | @main 82 | struct ClocksWidget: Widget { 83 | let kind: String = "ClocksWidget" 84 | 85 | var body: some WidgetConfiguration { 86 | IntentConfiguration(kind: kind, intent: ClocksWidgetIntent.self, provider: Provider()) { entry in 87 | ClocksWidgetEntryView(entry: entry) 88 | } 89 | .configurationDisplayName("时钟小组件") 90 | .description("添加小组件来显示时间") 91 | .supportedFamilies([.systemSmall, .systemMedium]) 92 | } 93 | } 94 | 95 | struct ClocksWidget_Previews: PreviewProvider { 96 | static var previews: some View { 97 | ClocksWidgetEntryView(entry: WidgetEntry(date: Date())) 98 | .previewContext(WidgetPreviewContext(family: .systemSmall)) 99 | ClocksWidgetEntryView(entry: WidgetEntry(date: Date())) 100 | .previewContext(WidgetPreviewContext(family: .systemMedium)) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Clocks/Views/WidgetViews/SimpleClockView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleClockView.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2020/12/30. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SimpleClockView: ClockWidget, View { 11 | static let clockName = "简单时钟" 12 | static let nonConfigurableFields: [String] = [] 13 | 14 | @Environment(\.previewsFamily) private var previewsFamily 15 | @Environment(\.colorScheme) private var colorScheme 16 | 17 | let config: WidgetClockConfig 18 | 19 | private let currentTime: String 20 | private let period: String 21 | private let dateInfo: String 22 | 23 | private var preferredBackgroundImage: UIImage? { 24 | config.preferredBackgroundImage(colorScheme: colorScheme) 25 | } 26 | 27 | init(date: Date, config widgetConfig: WidgetClockConfig = WidgetClockConfig.createEmpty(clockName: Self.clockName)) { 28 | config = widgetConfig 29 | 30 | let hourFormat = config.is12Hour ? "h" : "H" 31 | 32 | let formatter = DateFormatter.timeFormatter("\(hourFormat):mm/a/M月d日 E") 33 | formatter.amSymbol = "AM" 34 | formatter.pmSymbol = "PM" 35 | 36 | let times = formatter 37 | .string(from: date) 38 | .components(separatedBy: "/") 39 | 40 | currentTime = times[0] 41 | period = times[1] 42 | dateInfo = times[2] 43 | } 44 | 45 | private func getFontSize(_ geo: GeometryProxy) -> CGFloat { 46 | switch previewsFamily { 47 | case .systemSmall: 48 | return geo.size.width / 4 49 | case .systemMedium: 50 | return geo.size.width / 6 51 | default: 52 | return 72 53 | } 54 | } 55 | 56 | var body: some View { 57 | GeometryReader { geo in 58 | ZStack { 59 | WidgetBackground(uiImage: preferredBackgroundImage, blur: config.blur) 60 | .frame(width: geo.size.width, height: geo.size.height) 61 | Group { 62 | switch previewsFamily { 63 | case .systemSmall: 64 | VStack(alignment: .leading) { 65 | Text(currentTime) 66 | Text(period) 67 | if config.showDateInfo { 68 | Text(dateInfo) 69 | .font(.subheadline) 70 | .fontWeight(.bold) 71 | .padding(.top) 72 | } 73 | } 74 | default: 75 | VStack { 76 | Text("\(currentTime) \(period)") 77 | if config.showDateInfo { 78 | Text(dateInfo) 79 | .font(.subheadline) 80 | .fontWeight(.bold) 81 | } 82 | } 83 | } 84 | } 85 | .foregroundColor(config.textColor) 86 | .font(Font.system(size: getFontSize(geo)).weight(.bold)) 87 | } 88 | .frame(width: geo.size.width, height: geo.size.height) 89 | .background(preferredBackgroundImage != nil ? Color.clear : config.backgroundColor) 90 | } 91 | // .clipShape(ContainerRelativeShape()) 92 | } 93 | } 94 | 95 | struct SimpleText_Previews: PreviewProvider { 96 | static var previews: some View { 97 | WidgetPreviews { 98 | WidgetFamilyProvider { 99 | SimpleClockView(date: Date()) 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Clocks/ViewModels/StorableClockConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClockConfig.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/6. 6 | // 7 | import SwiftUI 8 | 9 | /** 10 | 储存在UserDefaults里的原始数据 11 | */ 12 | struct StorableClockConfig: Codable { 13 | var configKey: String 14 | var clockName: String 15 | var textColor: String = "#ffffff" 16 | var backgroundColor: String = "#000000" 17 | 18 | var is12Hour: Bool = false 19 | var showDateInfo: Bool = true 20 | 21 | var blur: Bool = false 22 | 23 | var lightBasicImgPath: String? 24 | var darkBasicImgPath: String? 25 | 26 | var lightMaskImgPath: String? 27 | var darkMaskImgPath: String? 28 | } 29 | 30 | /** 31 | 组件读取的config信息 32 | */ 33 | struct WidgetClockConfig { 34 | var clockName: String 35 | var textColor: Color 36 | var backgroundColor: Color 37 | 38 | var is12Hour: Bool = false 39 | var showDateInfo: Bool = true 40 | 41 | var lightBasicImg: UIImage? 42 | var darkBasicImg: UIImage? 43 | 44 | var lightMaskImg: UIImage? 45 | var darkMaskImg: UIImage? 46 | 47 | var blur: Bool = false 48 | 49 | init( 50 | clockName: String, 51 | textColor: Color, 52 | backgroundColor: Color, 53 | is12Hour: Bool, 54 | showDateInfo: Bool, 55 | blur: Bool, 56 | lightBasicImg: UIImage?, 57 | darkBasicImg: UIImage?, 58 | lightMaskImg: UIImage?, 59 | darkMaskImg: UIImage? 60 | ) { 61 | self.clockName = clockName 62 | self.textColor = textColor 63 | self.backgroundColor = backgroundColor 64 | self.is12Hour = is12Hour 65 | self.showDateInfo = showDateInfo 66 | self.blur = blur 67 | self.lightBasicImg = lightBasicImg 68 | self.darkBasicImg = darkBasicImg 69 | self.lightMaskImg = lightMaskImg 70 | self.darkMaskImg = darkMaskImg 71 | } 72 | 73 | // 通过StorableClockConfig来初始化 74 | init(fromStorableConfig config: StorableClockConfig) { 75 | var lightBasicImg: UIImage? 76 | var darkBasicImg: UIImage? 77 | 78 | if let imgPath = config.lightBasicImgPath { 79 | lightBasicImg = UIImage(contentsOfFile: imgPath) 80 | } 81 | if let imgPath = config.darkBasicImgPath { 82 | darkBasicImg = UIImage(contentsOfFile: imgPath) 83 | } 84 | 85 | var lightMaskImg: UIImage? 86 | var darkMaskImg: UIImage? 87 | 88 | if let imgPath = config.lightMaskImgPath { 89 | lightMaskImg = UIImage(contentsOfFile: imgPath) 90 | } 91 | if let imgPath = config.darkMaskImgPath { 92 | darkMaskImg = UIImage(contentsOfFile: imgPath) 93 | } 94 | 95 | self.init( 96 | clockName: config.clockName, 97 | textColor: Color(hexString: config.textColor), 98 | backgroundColor: Color(hexString: config.backgroundColor), 99 | is12Hour: config.is12Hour, 100 | showDateInfo: config.showDateInfo, 101 | blur: config.blur, 102 | lightBasicImg: lightBasicImg, 103 | darkBasicImg: darkBasicImg, 104 | lightMaskImg: lightMaskImg, 105 | darkMaskImg: darkMaskImg 106 | ) 107 | } 108 | 109 | func preferredBackgroundImage(colorScheme: ColorScheme) -> UIImage? { 110 | if colorScheme == .light { 111 | return lightMaskImg ?? lightBasicImg ?? darkMaskImg ?? darkBasicImg 112 | } else if colorScheme == .dark { 113 | return darkMaskImg ?? darkBasicImg ?? lightMaskImg ?? lightBasicImg 114 | } 115 | return nil 116 | } 117 | 118 | static func createEmpty(clockName: String) -> WidgetClockConfig { 119 | let defaultStyle = defaultClockStyle[clockName] 120 | if let style = defaultStyle { 121 | return Self(fromStorableConfig: StorableClockConfig(configKey: "defaultKey", clockName: clockName, textColor: style.textColor.toHexString(), backgroundColor: style.backgroundColor.toHexString())) 122 | } 123 | return Self(fromStorableConfig: StorableClockConfig(configKey: "defaultKey", clockName: "defaultName")) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Clocks/Views/WidgetViews/SimpleClock1View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleClock1View.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/12. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SimpleClock1View: ClockWidget, View { 11 | static let clockName = "简单时钟1" 12 | static let nonConfigurableFields: [String] = [] 13 | 14 | @Environment(\.previewsFamily) private var previewsFamily 15 | @Environment(\.colorScheme) private var colorScheme 16 | 17 | let config: WidgetClockConfig 18 | 19 | private let currentTime: String 20 | private let period: String 21 | private let dateInfo: String 22 | 23 | private var preferredBackgroundImage: UIImage? { 24 | config.preferredBackgroundImage(colorScheme: colorScheme) 25 | } 26 | 27 | private var dateInfoSize: Font { previewsFamily == .systemSmall ? .headline : .title3 } 28 | 29 | private func getPeriodSize(_ geo: GeometryProxy) -> Font { 30 | previewsFamily == .systemSmall ? Font.system(size: geo.size.width / 1.8).weight(.medium) : Font.system(size: geo.size.width / 2).weight(.medium) 31 | } 32 | 33 | init(date: Date, config widgetConfig: WidgetClockConfig = WidgetClockConfig.createEmpty(clockName: Self.clockName)) { 34 | config = widgetConfig 35 | 36 | let hourFormat = config.is12Hour ? "h" : "H" 37 | 38 | let formatter = DateFormatter.timeFormatter("\(hourFormat):mm/a/M月d日,E") 39 | formatter.amSymbol = "AM" 40 | formatter.pmSymbol = "PM" 41 | 42 | let times = formatter 43 | .string(from: date) 44 | .components(separatedBy: "/") 45 | 46 | currentTime = times[0] 47 | period = times[1] 48 | dateInfo = times[2] 49 | } 50 | 51 | private func getFontSize(_ geo: GeometryProxy) -> CGFloat { 52 | switch previewsFamily { 53 | case .systemSmall: 54 | return geo.size.width / 4 55 | case .systemMedium: 56 | return geo.size.width / 6 57 | default: 58 | return 72 59 | } 60 | } 61 | 62 | var body: some View { 63 | GeometryReader { geo in 64 | ZStack { 65 | WidgetBackground(uiImage: preferredBackgroundImage, blur: config.blur) 66 | .frame(width: geo.size.width, height: geo.size.height) 67 | VStack { 68 | if previewsFamily == .systemSmall { 69 | Spacer() 70 | } 71 | HStack { 72 | Spacer() 73 | Text(period) 74 | .font(getPeriodSize(geo)) 75 | .fixedSize() 76 | .foregroundColor(Color.white.opacity(0.2)) 77 | } 78 | } 79 | .frame(height: geo.size.height) 80 | HStack { 81 | VStack(alignment: .leading) { 82 | if config.showDateInfo { 83 | Text("\(dateInfo)") 84 | .font(dateInfoSize) 85 | .fontWeight(.medium) 86 | } 87 | Spacer() 88 | Text(currentTime) 89 | .font(Font.system(size: geo.size.width / (previewsFamily == .systemSmall ? 3 : 4.5)).weight(.semibold)) 90 | .fixedSize() 91 | .offset(y: geo.size.width / 15) 92 | } 93 | Spacer() 94 | } 95 | .foregroundColor(config.textColor) 96 | .padding() 97 | } 98 | .frame(width: geo.size.width, height: geo.size.height) 99 | .background(preferredBackgroundImage != nil ? Color.clear : config.backgroundColor) 100 | } 101 | // .clipShape(ContainerRelativeShape()) 102 | } 103 | } 104 | 105 | struct SimpleClock1View_Previews: PreviewProvider { 106 | static var previews: some View { 107 | WidgetPreviews { 108 | WidgetFamilyProvider { 109 | SimpleClock1View(date: Date()) 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Clocks/Views/WidgetDetail/ClocksDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClocksDetail.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2020/12/31. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | struct ClocksDetailView: View { 12 | @Environment(\.presentationMode) private var presentationMode 13 | 14 | @StateObject private var config: ClockConfigViewModel 15 | 16 | @State private var showAlert = false 17 | 18 | let clockContent: (WidgetClockConfig) -> Content 19 | let clockName: String 20 | 21 | init(config: ClockConfigViewModel, @ViewBuilder content: @escaping (WidgetClockConfig) -> Content) { 22 | clockContent = content 23 | 24 | clockName = config.clockName 25 | 26 | _config = StateObject(wrappedValue: config) 27 | } 28 | 29 | private func configable(_ fieldName: String) -> Bool { 30 | !config.nonConfigurableFields.contains(fieldName) 31 | } 32 | 33 | var body: some View { 34 | VStack { 35 | // 小组件展示 36 | ScrollView(.horizontal, showsIndicators: false) { 37 | WidgetPreviews { 38 | clockContent(config.toWidgetConfig()) 39 | .shadow(radius: 4) 40 | } 41 | .padding() 42 | } 43 | 44 | // 小组件配置信息 45 | Form { 46 | Section(header: Text("基础设置")) { 47 | if configable(WidgetDetailConfigFields.textColor) { 48 | ColorPicker("字体色", selection: $config.textColor, supportsOpacity: false) 49 | } 50 | if configable(WidgetDetailConfigFields.backgroundColor) { 51 | ColorPicker("背景色", selection: $config.backgroundColor, supportsOpacity: false) 52 | } 53 | if configable(WidgetDetailConfigFields.is12Hour) { 54 | Toggle(isOn: $config.is12Hour) { 55 | Text("12小时制") 56 | } 57 | } 58 | if configable(WidgetDetailConfigFields.showDateInfo) { 59 | Toggle(isOn: $config.showDateInfo) { 60 | Text("显示日期") 61 | } 62 | } 63 | } 64 | 65 | if configable(WidgetDetailConfigFields.backgroundImage) { 66 | ClockDetailImageEditView(config) 67 | } 68 | 69 | if !config.isNewConfig { 70 | Section { 71 | GeometryReader { geo in 72 | Button("删除小组件") { showAlert = true } 73 | .frame(width: geo.size.width, height: geo.size.height) 74 | .foregroundColor(.red) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | .alert(isPresented: $showAlert) { 81 | Alert( 82 | title: Text("确定要删除吗?"), 83 | primaryButton: .destructive(Text("删除")) { 84 | ClockConfigManager.shared.deleteConfig(config: config) 85 | 86 | presentationMode.wrappedValue.dismiss() 87 | 88 | WidgetCenter.shared.reloadAllTimelines() 89 | }, 90 | secondaryButton: .cancel(Text("取消")) 91 | ) 92 | } 93 | .toolbar { 94 | ToolbarItem(placement: .confirmationAction) { 95 | Button(config.isNewConfig ? "添加小组件" : "保存") { 96 | ClockConfigManager.shared.updateConfig(newConfig: config) 97 | 98 | presentationMode.wrappedValue.dismiss() 99 | 100 | WidgetCenter.shared.reloadAllTimelines() 101 | } 102 | } 103 | } 104 | .navigationBarTitle(clockName, displayMode: .inline) 105 | } 106 | } 107 | 108 | struct ClocksDetail_Previews: PreviewProvider { 109 | static var previews: some View { 110 | ClocksDetailView(config: ClockConfigManager.shared.createConfigViewModel(clockName: "简单时钟")) { _ in 111 | SimpleClockView(date: Date()) 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Clocks.xcodeproj/xcshareddata/xcschemes/ClocksWidgetExtension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 60 | 62 | 68 | 69 | 70 | 71 | 75 | 76 | 80 | 81 | 85 | 86 | 87 | 88 | 95 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /ClocksWidget/ClocksWidgetIntents.intentdefinition: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | INEnums 6 | 7 | INIntentDefinitionModelVersion 8 | 1.2 9 | INIntentDefinitionNamespace 10 | 3dzq2X 11 | INIntentDefinitionSystemVersion 12 | 20C69 13 | INIntentDefinitionToolsBuildVersion 14 | 12C33 15 | INIntentDefinitionToolsVersion 16 | 12.3 17 | INIntents 18 | 19 | 20 | INIntentCategory 21 | information 22 | INIntentDescriptionID 23 | SgtSZa 24 | INIntentEligibleForWidgets 25 | 26 | INIntentIneligibleForSuggestions 27 | 28 | INIntentLastParameterTag 29 | 2 30 | INIntentName 31 | ClocksWidget 32 | INIntentParameters 33 | 34 | 35 | INIntentParameterConfigurable 36 | 37 | INIntentParameterDisplayName 38 | 当前小组件 39 | INIntentParameterDisplayNameID 40 | KL9kET 41 | INIntentParameterDisplayPriority 42 | 1 43 | INIntentParameterName 44 | currentWidget 45 | INIntentParameterObjectType 46 | WidgetConfig 47 | INIntentParameterObjectTypeNamespace 48 | 3dzq2X 49 | INIntentParameterPromptDialogs 50 | 51 | 52 | INIntentParameterPromptDialogCustom 53 | 54 | INIntentParameterPromptDialogType 55 | Configuration 56 | 57 | 58 | INIntentParameterPromptDialogCustom 59 | 60 | INIntentParameterPromptDialogType 61 | Primary 62 | 63 | 64 | INIntentParameterSupportsDynamicEnumeration 65 | 66 | INIntentParameterTag 67 | 2 68 | INIntentParameterType 69 | Object 70 | 71 | 72 | INIntentResponse 73 | 74 | INIntentResponseCodes 75 | 76 | 77 | INIntentResponseCodeName 78 | success 79 | INIntentResponseCodeSuccess 80 | 81 | 82 | 83 | INIntentResponseCodeName 84 | failure 85 | 86 | 87 | 88 | INIntentTitle 89 | Clocks Widget 90 | INIntentTitleID 91 | ljjxwi 92 | INIntentType 93 | Custom 94 | INIntentVerb 95 | View 96 | 97 | 98 | INTypes 99 | 100 | 101 | INTypeDisplayName 102 | Widget Config 103 | INTypeDisplayNameID 104 | FmdyZ9 105 | INTypeLastPropertyTag 106 | 99 107 | INTypeName 108 | WidgetConfig 109 | INTypeProperties 110 | 111 | 112 | INTypePropertyDefault 113 | 114 | INTypePropertyDisplayPriority 115 | 1 116 | INTypePropertyName 117 | identifier 118 | INTypePropertyTag 119 | 1 120 | INTypePropertyType 121 | String 122 | 123 | 124 | INTypePropertyDefault 125 | 126 | INTypePropertyDisplayPriority 127 | 2 128 | INTypePropertyName 129 | displayString 130 | INTypePropertyTag 131 | 2 132 | INTypePropertyType 133 | String 134 | 135 | 136 | INTypePropertyDefault 137 | 138 | INTypePropertyDisplayPriority 139 | 3 140 | INTypePropertyName 141 | pronunciationHint 142 | INTypePropertyTag 143 | 3 144 | INTypePropertyType 145 | String 146 | 147 | 148 | INTypePropertyDefault 149 | 150 | INTypePropertyDisplayPriority 151 | 4 152 | INTypePropertyName 153 | alternativeSpeakableMatches 154 | INTypePropertySupportsMultipleValues 155 | 156 | INTypePropertyTag 157 | 4 158 | INTypePropertyType 159 | SpeakableString 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /Clocks/Views/WidgetDetail/ClockDetailImageCropView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClockDetailImageCropView.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/8. 6 | // 7 | 8 | /** 9 | iphone 11 prox max 宽414,小组件横间距22,屏幕边距27,上小组件上边距76 10 | 小 11 | 左上 CGRect(x: 27, y: 76, width: 169, height: 169) 12 | 右上 CGRect(x: 218, y: 76, width: 169, height: 169) 13 | 左中 CGRect(x: 27, y: 286, width: 169, height: 169) 14 | 右中 CGRect(x: 218, y: 286, width: 169, height: 169) 15 | 左下 CGRect(x: 27, y: 495.3, width: 169, height: 169) 16 | 右下 CGRect(x: 218, y: 495.3, width: 169, height: 169) 17 | */ 18 | 19 | import SwiftUI 20 | import WidgetKit 21 | 22 | struct ClockDetailImageCropView: View { 23 | @Environment(\.presentationMode) private var presentationMode 24 | 25 | @State private var widgetFamily: WidgetFamily = .systemSmall 26 | 27 | let lightUIImage: UIImage? 28 | let darkUIImage: UIImage? 29 | let onMaskImageCrop: ((UIImage?, UIImage?)) -> Void 30 | 31 | enum WidgetCropPostion { 32 | case smallTopLeft 33 | case smallTopRight 34 | case smallCenterLeft 35 | case smallCenterRight 36 | case smallBottomLeft 37 | case smallBottomRight 38 | 39 | case mediumTop 40 | case mediumCenter 41 | case mediumBottom 42 | 43 | func getRect() -> CGRect { 44 | switch self { 45 | case .smallTopLeft: 46 | return CGRect(origin: DeviceWidgetPosition.smallTopLeft, size: DeviceWidgetSize.small) 47 | case .smallTopRight: 48 | return CGRect(origin: DeviceWidgetPosition.smallTopRight, size: DeviceWidgetSize.small) 49 | case .smallCenterLeft: 50 | return CGRect(origin: DeviceWidgetPosition.smallCenterLeft, size: DeviceWidgetSize.small) 51 | case .smallCenterRight: 52 | return CGRect(origin: DeviceWidgetPosition.smallCenterRight, size: DeviceWidgetSize.small) 53 | case .smallBottomLeft: 54 | return CGRect(origin: DeviceWidgetPosition.smallBottomLeft, size: DeviceWidgetSize.small) 55 | case .smallBottomRight: 56 | return CGRect(origin: DeviceWidgetPosition.smallBottomRight, size: DeviceWidgetSize.small) 57 | case .mediumTop: 58 | return CGRect(origin: DeviceWidgetPosition.mediumTop, size: DeviceWidgetSize.meduim) 59 | case .mediumCenter: 60 | return CGRect(origin: DeviceWidgetPosition.mediumCenter, size: DeviceWidgetSize.meduim) 61 | case .mediumBottom: 62 | return CGRect(origin: DeviceWidgetPosition.mediumBottom, size: DeviceWidgetSize.meduim) 63 | } 64 | } 65 | } 66 | 67 | var body: some View { 68 | ZStack { 69 | if lightUIImage != nil || darkUIImage != nil { 70 | Image(uiImage: lightUIImage ?? darkUIImage!) 71 | .resizable() 72 | } 73 | 74 | if widgetFamily == .systemSmall { 75 | TapRectangle("上左", ratio: DeviceWidgetSize.small, postion: .smallTopLeft) 76 | TapRectangle("上右", ratio: DeviceWidgetSize.small, postion: .smallTopRight) 77 | TapRectangle("中左", ratio: DeviceWidgetSize.small, postion: .smallCenterLeft) 78 | TapRectangle("中右", ratio: DeviceWidgetSize.small, postion: .smallCenterRight) 79 | // iphone se 都不显示6个 80 | if UIDevice().type != .iPhoneSE { 81 | TapRectangle("下左", ratio: DeviceWidgetSize.small, postion: .smallBottomLeft) 82 | TapRectangle("下右", ratio: DeviceWidgetSize.small, postion: .smallBottomRight) 83 | } 84 | } 85 | if widgetFamily == .systemMedium { 86 | TapRectangle("上方", ratio: DeviceWidgetSize.meduim, postion: .mediumTop) 87 | TapRectangle("中间", ratio: DeviceWidgetSize.meduim, postion: .mediumCenter) 88 | if UIDevice().type != .iPhoneSE { 89 | TapRectangle("下方", ratio: DeviceWidgetSize.meduim, postion: .mediumBottom) 90 | } 91 | } 92 | 93 | VStack { 94 | Spacer() 95 | VStack { 96 | Text("小组件会呈现对应区域的透明效果") 97 | .foregroundColor(.secondary) 98 | .font(.system(size: 12)) 99 | Picker("组件大小", selection: $widgetFamily) { 100 | Text("小组件").tag(WidgetFamily.systemSmall) 101 | Text("中组件").tag(WidgetFamily.systemMedium) 102 | } 103 | .pickerStyle(SegmentedPickerStyle()) 104 | 105 | Button("返回") { 106 | onMaskImageCrop((nil, nil)) 107 | presentationMode.wrappedValue.dismiss() 108 | } 109 | .foregroundColor(.red) 110 | .font(.subheadline) 111 | } 112 | .padding() 113 | .background(Color.tertiarySystemGroupedBackground) 114 | .cornerRadius(16.0) 115 | .padding(24) 116 | } 117 | } 118 | .ignoresSafeArea(.all) 119 | .navigationTitle("设置透明背景") 120 | } 121 | 122 | @ViewBuilder 123 | func TapRectangle(_ positionName: String, ratio: CGSize, postion: WidgetCropPostion) -> some View { 124 | let ox = postion.getRect().origin.x 125 | let oy = postion.getRect().origin.y 126 | let width = postion.getRect().width 127 | let height = postion.getRect().height 128 | Rectangle() 129 | .fill(Color.tertiarySystemGroupedBackground) 130 | .overlay( 131 | ZStack { 132 | Text("\(Image(systemName: "paintbrush")) 点击设置\(positionName)") 133 | .font(.subheadline) 134 | .foregroundColor(.secondary) 135 | } 136 | ) 137 | .aspectRatio(ratio, contentMode: .fit) 138 | .cornerRadius(16) 139 | .onTapGesture { 140 | let croppedLightBgImg = lightUIImage != nil ? cropImage(lightUIImage!, toRect: postion.getRect()) : nil 141 | let croppedDarkBgImg = darkUIImage != nil ? cropImage(darkUIImage!, toRect: postion.getRect()) : nil 142 | 143 | onMaskImageCrop((croppedLightBgImg, croppedDarkBgImg)) 144 | 145 | presentationMode.wrappedValue.dismiss() 146 | } 147 | .frame(width: width, height: height) 148 | .position(x: ox + width / 2, y: oy + height / 2) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Clocks/ViewModels/ClockConfigViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClockConfigViewModel.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/4. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /** 11 | 小组件所需要的StateObject 12 | */ 13 | class ClockConfigViewModel: ObservableObject { 14 | @Published var textColor: Color 15 | @Published var backgroundColor: Color 16 | 17 | @Published var is12Hour: Bool 18 | @Published var showDateInfo: Bool 19 | @Published var blur: Bool 20 | 21 | @Published var lightBasicImgPath: String? 22 | @Published var darkBasicImgPath: String? 23 | 24 | @Published var lightMaskImgPath: String? 25 | @Published var darkMaskImgPath: String? 26 | 27 | // configKey作为userdefaults的键 28 | fileprivate(set) var configKey: String 29 | fileprivate(set) var clockName: String 30 | fileprivate(set) var isNewConfig = false 31 | 32 | init( 33 | configKey: String, 34 | clockName: String, 35 | textColor: Color = Color.clear, 36 | backgroundColor: Color = Color.clear, 37 | is12Hour: Bool = false, 38 | showDateInfo: Bool = true, 39 | blur: Bool = false, 40 | lightBasicImgPath: String? = nil, 41 | darkBasicImgPath: String? = nil, 42 | lightMaskImgPath: String? = nil, 43 | darkMaskImgPath: String? = nil 44 | ) { 45 | self.configKey = configKey 46 | 47 | self.clockName = clockName 48 | self.textColor = textColor 49 | self.backgroundColor = backgroundColor 50 | self.is12Hour = is12Hour 51 | self.showDateInfo = showDateInfo 52 | self.blur = blur 53 | self.lightBasicImgPath = lightBasicImgPath 54 | self.darkBasicImgPath = darkBasicImgPath 55 | self.lightMaskImgPath = lightMaskImgPath 56 | self.darkMaskImgPath = darkMaskImgPath 57 | 58 | guard let defaultStyle = defaultClockStyle[clockName] else { return } 59 | if self.textColor == Color.clear { 60 | self.textColor = defaultStyle.textColor 61 | } 62 | if self.backgroundColor == Color.clear { 63 | self.backgroundColor = defaultStyle.backgroundColor 64 | } 65 | } 66 | 67 | // 转为可储存在UserDefaults里的config类型 68 | func toStorableConfig() -> StorableClockConfig { 69 | StorableClockConfig( 70 | configKey: configKey, 71 | clockName: clockName, 72 | textColor: textColor.toHexString(), 73 | backgroundColor: backgroundColor.toHexString(), 74 | is12Hour: is12Hour, 75 | showDateInfo: showDateInfo, 76 | blur: blur, 77 | lightBasicImgPath: lightBasicImgPath, 78 | darkBasicImgPath: darkBasicImgPath, 79 | lightMaskImgPath: lightMaskImgPath, 80 | darkMaskImgPath: darkMaskImgPath 81 | ) 82 | } 83 | 84 | // 转为小组件所需类型的配置信息 85 | func toWidgetConfig() -> WidgetClockConfig { 86 | WidgetClockConfig(fromStorableConfig: toStorableConfig()) 87 | } 88 | 89 | // clone一份在详情页面里使用 90 | func clone() -> ClockConfigViewModel { 91 | ClockConfigViewModel( 92 | configKey: configKey, 93 | clockName: clockName, 94 | textColor: textColor, 95 | backgroundColor: backgroundColor, 96 | is12Hour: is12Hour, 97 | showDateInfo: showDateInfo, 98 | blur: blur, 99 | lightBasicImgPath: lightBasicImgPath, 100 | darkBasicImgPath: darkBasicImgPath, 101 | lightMaskImgPath: lightMaskImgPath, 102 | darkMaskImgPath: darkMaskImgPath 103 | ) 104 | } 105 | 106 | // 删除所有图片 107 | func deleteImage() { 108 | // 从本地删除图片 109 | removeImages( 110 | [ 111 | lightBasicImgPath, 112 | lightMaskImgPath, 113 | darkBasicImgPath, 114 | darkMaskImgPath, 115 | ] 116 | ) 117 | 118 | lightBasicImgPath = nil 119 | lightMaskImgPath = nil 120 | darkBasicImgPath = nil 121 | darkMaskImgPath = nil 122 | } 123 | 124 | var nonConfigurableFields: [String] { 125 | switch clockName { 126 | case SimpleClockView.clockName: 127 | return SimpleClockView.nonConfigurableFields 128 | case SimpleClock1View.clockName: 129 | return SimpleClock1View.nonConfigurableFields 130 | case SimplePicture.clockName: 131 | return SimplePicture.nonConfigurableFields 132 | default: 133 | return [] 134 | } 135 | } 136 | } 137 | 138 | /** 139 | 管理小组件的配置 140 | */ 141 | class ClockConfigManager { 142 | static let shared = ClockConfigManager() 143 | 144 | private var configs = [String: ClockConfigViewModel]() 145 | 146 | private init() {} 147 | 148 | // 创建一个新的ViewModel,作为新增配置时使用 149 | func createConfigViewModel(clockName: String) -> ClockConfigViewModel { 150 | let newConfigKey = UUID().uuidString 151 | let config = ClockConfigViewModel(configKey: newConfigKey, clockName: clockName) 152 | config.isNewConfig = true 153 | return config 154 | } 155 | 156 | // 通过configKey从现有的获取 157 | func getConfigViewModel(_ configKey: String) -> ClockConfigViewModel { 158 | guard let config = configs[configKey] else { 159 | let newConfig = readConfig(key: configKey) 160 | configs[configKey] = newConfig 161 | return configs[configKey]! 162 | } 163 | return config 164 | } 165 | 166 | // 更新config 167 | func updateConfig(newConfig: ClockConfigViewModel) { 168 | // 如果是新的config需要重新分配一个uuid 169 | if newConfig.isNewConfig { 170 | newConfig.isNewConfig = false 171 | } 172 | // 更新已存在的 173 | if let config = configs[newConfig.configKey] { 174 | config.textColor = newConfig.textColor 175 | config.backgroundColor = newConfig.backgroundColor 176 | config.is12Hour = newConfig.is12Hour 177 | config.showDateInfo = newConfig.showDateInfo 178 | config.blur = newConfig.blur 179 | 180 | config.lightBasicImgPath = newConfig.lightBasicImgPath 181 | config.darkBasicImgPath = newConfig.darkBasicImgPath 182 | config.lightMaskImgPath = newConfig.lightMaskImgPath 183 | config.darkMaskImgPath = newConfig.darkMaskImgPath 184 | } else { 185 | // 不存在添加新的配置 186 | let configKey = newConfig.configKey 187 | configs[configKey] = newConfig 188 | 189 | // 同步更新ConfigKeys到ConfigKeysViewModel 190 | ConfigKeysViewModel.shared.add(configKey: configKey) 191 | } 192 | 193 | // 保存 194 | saveConfig(key: newConfig.configKey, config: newConfig.toStorableConfig()) 195 | } 196 | 197 | // 删除config 198 | func deleteConfig(config: ClockConfigViewModel) { 199 | let configKey = config.configKey 200 | // 删除字典里的config 201 | configs.removeValue(forKey: configKey) 202 | // 删除userdefaults里的config 203 | deleteConfigKeys(key: configKey) 204 | // 删除保存组件列表里的config 205 | ConfigKeysViewModel.shared.delete(configKey: configKey) 206 | } 207 | 208 | // 删除所有config 209 | func deleteAllConfig() { 210 | configs.values.forEach { config in deleteConfig(config: config) } 211 | } 212 | 213 | // 从userDefaults读取配置,如果没有就返回一个默认值 214 | private func readConfig(key: String) -> ClockConfigViewModel { 215 | let defaultValue = ClockConfigViewModel(configKey: "defaultKey", clockName: "defaultName") 216 | 217 | return getWidgetConfig(key, defaultValue: defaultValue) { config in 218 | ClockConfigViewModel( 219 | configKey: config.configKey, 220 | clockName: config.clockName, 221 | textColor: Color(hexString: config.textColor), 222 | backgroundColor: Color(hexString: config.backgroundColor), 223 | is12Hour: config.is12Hour, 224 | showDateInfo: config.showDateInfo, 225 | blur: config.blur, 226 | lightBasicImgPath: config.lightBasicImgPath, 227 | darkBasicImgPath: config.darkBasicImgPath, 228 | lightMaskImgPath: config.lightMaskImgPath, 229 | darkMaskImgPath: config.darkMaskImgPath 230 | ) 231 | } 232 | } 233 | 234 | // 保存配置到userDefaults 235 | private func saveConfig(key: String, config: StorableClockConfig) { 236 | let data = try? JSONEncoder().encode(config) 237 | if let userDefaults = appUserDefaults() { 238 | userDefaults.setValue(data, forKey: key) 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Clocks/Views/WidgetDetail/ClockDetailImageEditView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClockDetailImageEditView.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/7. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /** 11 | 小组件详情的背景图片添加和删除 12 | */ 13 | struct ClockDetailImageEditView: View { 14 | enum BackgroundType: String, Identifiable { 15 | var id: String { rawValue } 16 | case light 17 | case dark 18 | } 19 | 20 | @StateObject private var config: ClockConfigViewModel 21 | 22 | @State private var backgroundType: BackgroundType = .light 23 | 24 | @State private var maskImageToggle: Bool 25 | 26 | // 当前Detail的背景图片和遮罩图片 27 | @State private var lightBasicImg: UIImage? 28 | @State private var darkBasicImg: UIImage? 29 | 30 | @State private var lightMaskImg: UIImage? 31 | @State private var darkMaskImg: UIImage? 32 | 33 | // 控制选择添加图片类型的ActionSheet弹出 34 | @State private var showImagePicker = false 35 | // 图片裁剪弹出 36 | @State private var showCropEditor = false 37 | 38 | private let imagePrefix: String 39 | 40 | // 是否有存在的图片 41 | private var isBasicImageExist: Bool { 42 | lightBasicImg != nil || darkBasicImg != nil 43 | } 44 | 45 | private var isBasicImageAllExist: Bool { 46 | lightBasicImg != nil && darkBasicImg != nil 47 | } 48 | 49 | // 是否浅色深色遮罩图都存在 50 | private var isAllMaskImageExist: Bool { 51 | lightMaskImg != nil && darkMaskImg != nil 52 | } 53 | 54 | init(_ config: ClockConfigViewModel) { 55 | imagePrefix = config.configKey 56 | 57 | _config = StateObject(wrappedValue: config) 58 | 59 | let lightBasicImg = config.lightBasicImgPath != nil ? UIImage(contentsOfFile: config.lightBasicImgPath!) : nil 60 | let darkBasicImg = config.darkBasicImgPath != nil ? UIImage(contentsOfFile: config.darkBasicImgPath!) : nil 61 | 62 | let lightMaskImg = config.lightMaskImgPath != nil ? UIImage(contentsOfFile: config.lightMaskImgPath!) : nil 63 | let darkMaskImg = config.darkMaskImgPath != nil ? UIImage(contentsOfFile: config.darkMaskImgPath!) : nil 64 | 65 | _maskImageToggle = State(initialValue: lightMaskImg != nil || darkMaskImg != nil) 66 | 67 | _lightBasicImg = State(initialValue: lightBasicImg) 68 | _darkBasicImg = State(initialValue: darkBasicImg) 69 | 70 | _lightMaskImg = State(initialValue: lightMaskImg) 71 | _darkMaskImg = State(initialValue: darkMaskImg) 72 | } 73 | 74 | private func deleteAllImage() { 75 | lightBasicImg = nil 76 | lightMaskImg = nil 77 | darkBasicImg = nil 78 | darkMaskImg = nil 79 | 80 | // Todo 删除bug 81 | config.lightBasicImgPath = nil 82 | config.lightMaskImgPath = nil 83 | config.darkBasicImgPath = nil 84 | config.darkMaskImgPath = nil 85 | // 删除图片还原图片效果 86 | config.blur = false 87 | } 88 | 89 | private func deleteMaskImage() { 90 | lightMaskImg = nil 91 | darkMaskImg = nil 92 | 93 | config.lightMaskImgPath = nil 94 | config.darkMaskImgPath = nil 95 | } 96 | 97 | // 把图片存到本地 98 | private func updateImagPath(_ image: UIImage?, imageName: String, onCompleted: @escaping (URL) -> Void) { 99 | if let image = image { 100 | saveImage(imageName: imageName, image: image, onCompleted: onCompleted) 101 | } 102 | } 103 | 104 | var body: some View { 105 | Section(header: HStack { 106 | Text("背景图片") 107 | Spacer() 108 | if isBasicImageExist { 109 | Button("删除图片") { 110 | deleteAllImage() 111 | } 112 | .foregroundColor(.red) 113 | } 114 | }) { 115 | VStack { 116 | Picker("外观", selection: $backgroundType) { 117 | Text("浅色外观").tag(BackgroundType.light) 118 | Text("深色外观").tag(BackgroundType.dark) 119 | } 120 | .pickerStyle(SegmentedPickerStyle()) 121 | Divider() 122 | ZStack { 123 | if !isBasicImageAllExist { 124 | Text("\(Image(systemName: "photo")) 点击添加图片") 125 | .foregroundColor(.accentColor) 126 | .contentShape(Rectangle()) 127 | .onTapGesture { 128 | showImagePicker = true 129 | } 130 | } 131 | 132 | if backgroundType == .light && lightBasicImg != nil { 133 | GeometryReader { geo in 134 | Image(uiImage: lightBasicImg!) 135 | .resizable() 136 | .aspectRatio(contentMode: .fill) 137 | .frame(width: geo.size.width, height: geo.size.height) 138 | } 139 | } 140 | 141 | if backgroundType == .dark && darkBasicImg != nil { 142 | GeometryReader { geo in 143 | Image(uiImage: darkBasicImg!) 144 | .resizable() 145 | .aspectRatio(contentMode: .fill) 146 | .frame(width: geo.size.width, height: geo.size.height) 147 | } 148 | } 149 | Rectangle().fill(Color.clear) 150 | .fullScreenCover(isPresented: $showCropEditor) { 151 | ClockDetailImageCropView(lightUIImage: lightBasicImg, darkUIImage: darkBasicImg) { lightMaskImg, darkMaskImg in 152 | // 保存浅色外观原始图片 153 | updateImagPath(lightMaskImg, imageName: "\(imagePrefix)_lightMaskImg") { fileURL in 154 | config.lightMaskImgPath = fileURL.path 155 | } 156 | // 保存深色外观原始图片 157 | updateImagPath(darkMaskImg, imageName: "\(imagePrefix)_darkMaskImg") { fileURL in 158 | config.darkMaskImgPath = fileURL.path 159 | } 160 | 161 | // 如果关闭裁剪窗时候一个都没设置,就要把开关给关了 162 | if config.lightMaskImgPath == nil, config.darkMaskImgPath == nil { 163 | maskImageToggle = false 164 | } 165 | } 166 | } 167 | Rectangle().fill(Color.clear) 168 | // 背景图选择的View 169 | .sheet(isPresented: $showImagePicker) { 170 | ImagePicker { image in 171 | if backgroundType == .light { 172 | withAnimation { 173 | lightBasicImg = image 174 | } 175 | // 保存浅色外观原始图片 176 | updateImagPath(lightBasicImg!, imageName: "\(imagePrefix)_lightBasicImg") { fileURL in 177 | config.lightBasicImgPath = fileURL.path 178 | } 179 | } 180 | if backgroundType == .dark { 181 | withAnimation { 182 | darkBasicImg = image 183 | } 184 | // 保存深色外观原始图片 185 | updateImagPath(darkBasicImg!, imageName: "\(imagePrefix)_darkBasicImg") { fileURL in 186 | config.darkBasicImgPath = fileURL.path 187 | } 188 | } 189 | } 190 | } 191 | } 192 | .aspectRatio(2.13, contentMode: .fill) 193 | .cornerRadius(8.0) 194 | .clipped() 195 | } 196 | } 197 | if isBasicImageExist { 198 | Section(header: Text("图片效果")) { 199 | Toggle(isOn: $config.blur, label: { 200 | Text("高斯模糊") 201 | }) 202 | 203 | // 要把image path转换成bool的开关,逻辑绕了 204 | Toggle(isOn: $maskImageToggle, label: { 205 | Text("透明背景") 206 | }) 207 | .onTapGesture { 208 | // 为false的情况才需要弹出裁剪 209 | if !maskImageToggle { 210 | showCropEditor = true 211 | } 212 | } 213 | .onChange(of: maskImageToggle, perform: { value in 214 | // 值变为false就需要删除所有的mask image 215 | if !value { 216 | deleteMaskImage() 217 | } 218 | }) 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /Clocks/Utils/DeviceWidget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceWidget.swift 3 | // Clocks 4 | // 5 | // Created by ZhangYu on 2021/1/11. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // 小组件在屏幕上的位置 11 | struct DeviceWidgetPosition { 12 | // 小组件 左上 13 | static var smallTopLeft: CGPoint { 14 | switch UIDevice().type { 15 | case .iPhone12ProMax: 16 | return CGPoint(x: 32, y: 82) 17 | case .iPhone12Pro, .iPhone12: 18 | return CGPoint(x: 26, y: 77) 19 | case .iPhone12Mini: 20 | return CGPoint(x: 23, y: 77) 21 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 22 | return CGPoint(x: 27, y: 76) 23 | case .iPhone11Pro, .iPhoneXS, .iPhoneX: 24 | return CGPoint(x: 23, y: 71) 25 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 26 | return CGPoint(x: 33, y: 38) 27 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 28 | return CGPoint(x: 28, y: 30) 29 | case .iPhoneSE, .iPod7: 30 | return CGPoint(x: 14, y: 30) 31 | default: 32 | return CGPoint(x: 27, y: 76) 33 | } 34 | } 35 | 36 | // 小组件 右上 37 | static var smallTopRight: CGPoint { 38 | switch UIDevice().type { 39 | case .iPhone12ProMax: 40 | return CGPoint(x: 226, y: 82) 41 | case .iPhone12Pro, .iPhone12: 42 | return CGPoint(x: 206, y: 77) 43 | case .iPhone12Mini: 44 | return CGPoint(x: 197, y: 77) 45 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 46 | return CGPoint(x: 218, y: 76) 47 | case .iPhone11Pro, .iPhoneXS, .iPhoneX: 48 | return CGPoint(x: 197, y: 71) 49 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 50 | return CGPoint(x: 224, y: 38) 51 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 52 | return CGPoint(x: 200, y: 30) 53 | case .iPhoneSE, .iPod7: 54 | return CGPoint(x: 165, y: 30) 55 | default: 56 | return CGPoint(x: 218, y: 76) 57 | } 58 | } 59 | 60 | // 小组件 中左 61 | static var smallCenterLeft: CGPoint { 62 | switch UIDevice().type { 63 | case .iPhone12ProMax: 64 | return CGPoint(x: 32, y: 294) 65 | case .iPhone12Pro, .iPhone12: 66 | return CGPoint(x: 26, y: 273) 67 | case .iPhone12Mini: 68 | return CGPoint(x: 23, y: 267) 69 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 70 | return CGPoint(x: 27, y: 286) 71 | case .iPhone11Pro, .iPhoneXS, .iPhoneX: 72 | return CGPoint(x: 23, y: 261) 73 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 74 | return CGPoint(x: 33, y: 232) 75 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 76 | return CGPoint(x: 27, y: 206) 77 | case .iPhoneSE, .iPod7: 78 | return CGPoint(x: 14, y: 200) 79 | default: 80 | return CGPoint(x: 218, y: 76) 81 | } 82 | } 83 | 84 | // 小组件 中右 85 | static var smallCenterRight: CGPoint { 86 | switch UIDevice().type { 87 | case .iPhone12ProMax: 88 | return CGPoint(x: 226, y: 294) 89 | case .iPhone12Pro, .iPhone12: 90 | return CGPoint(x: 206, y: 273) 91 | case .iPhone12Mini: 92 | return CGPoint(x: 197, y: 267) 93 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 94 | return CGPoint(x: 218, y: 286) 95 | case .iPhone11Pro, .iPhoneXS, .iPhoneX: 96 | return CGPoint(x: 197, y: 261) 97 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 98 | return CGPoint(x: 224, y: 232) 99 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 100 | return CGPoint(x: 200, y: 206) 101 | case .iPhoneSE, .iPod7: 102 | return CGPoint(x: 165, y: 200) 103 | default: 104 | return CGPoint(x: 218, y: 76) 105 | } 106 | } 107 | 108 | // 小组件 下左 109 | static var smallBottomLeft: CGPoint { 110 | switch UIDevice().type { 111 | case .iPhone12ProMax: 112 | return CGPoint(x: 32, y: 506) 113 | case .iPhone12Pro, .iPhone12: 114 | return CGPoint(x: 26, y: 469) 115 | case .iPhone12Mini: 116 | return CGPoint(x: 23, y: 457) 117 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 118 | return CGPoint(x: 27, y: 495.3333) 119 | case .iPhone11Pro, .iPhoneXS, .iPhoneX: 120 | return CGPoint(x: 23, y: 451) 121 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 122 | return CGPoint(x: 33, y: 426) 123 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 124 | return CGPoint(x: 27, y: 382) 125 | case .iPhoneSE, .iPod7: 126 | return CGPoint(x: 0, y: 0) 127 | default: 128 | return CGPoint(x: 218, y: 76) 129 | } 130 | } 131 | 132 | // 小组件 下右 133 | static var smallBottomRight: CGPoint { 134 | switch UIDevice().type { 135 | case .iPhone12ProMax: 136 | return CGPoint(x: 226, y: 506) 137 | case .iPhone12Pro, .iPhone12: 138 | return CGPoint(x: 206, y: 469) 139 | case .iPhone12Mini: 140 | return CGPoint(x: 197, y: 457) 141 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 142 | return CGPoint(x: 218, y: 495.3333) 143 | case .iPhone11Pro, .iPhoneXS, .iPhoneX: 144 | return CGPoint(x: 197, y: 451) 145 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 146 | return CGPoint(x: 224, y: 426) 147 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 148 | return CGPoint(x: 200, y: 382) 149 | case .iPhoneSE, .iPod7: 150 | return CGPoint(x: 0, y: 0) 151 | default: 152 | return CGPoint(x: 218, y: 76) 153 | } 154 | } 155 | 156 | // 中组件 上方 157 | static var mediumTop: CGPoint { 158 | switch UIDevice().type { 159 | case .iPhone12ProMax: 160 | return CGPoint(x: 32, y: 82) 161 | case .iPhone12Pro, .iPhone12: 162 | return CGPoint(x: 26, y: 77) 163 | case .iPhone12Mini: 164 | return CGPoint(x: 23, y: 77) 165 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 166 | return CGPoint(x: 27, y: 76) 167 | case .iPhone11Pro, .iPhoneXS, .iPhoneX: 168 | return CGPoint(x: 23, y: 71) 169 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 170 | return CGPoint(x: 33, y: 38) 171 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 172 | return CGPoint(x: 28, y: 30) 173 | case .iPhoneSE, .iPod7: 174 | return CGPoint(x: 14, y: 30) 175 | default: 176 | return CGPoint(x: 218, y: 76) 177 | } 178 | } 179 | 180 | // 中组件 中间 181 | static var mediumCenter: CGPoint { 182 | switch UIDevice().type { 183 | case .iPhone12ProMax: 184 | return CGPoint(x: 32, y: 294) 185 | case .iPhone12Pro, .iPhone12: 186 | return CGPoint(x: 26, y: 273) 187 | case .iPhone12Mini: 188 | return CGPoint(x: 23, y: 267) 189 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 190 | return CGPoint(x: 27, y: 286) 191 | case .iPhone11Pro, .iPhoneXS, .iPhoneX: 192 | return CGPoint(x: 23, y: 261) 193 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 194 | return CGPoint(x: 33, y: 232) 195 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 196 | return CGPoint(x: 27, y: 206) 197 | case .iPhoneSE, .iPod7: 198 | return CGPoint(x: 14, y: 200) 199 | default: 200 | return CGPoint(x: 218, y: 76) 201 | } 202 | } 203 | 204 | // 中组件 下方 205 | static var mediumBottom: CGPoint { 206 | switch UIDevice().type { 207 | case .iPhone12ProMax: 208 | return CGPoint(x: 32, y: 506) 209 | case .iPhone12Pro, .iPhone12: 210 | return CGPoint(x: 26, y: 469) 211 | case .iPhone12Mini: 212 | return CGPoint(x: 23, y: 457) 213 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 214 | return CGPoint(x: 27, y: 495.3333) 215 | case .iPhone11Pro, .iPhoneXS, .iPhoneX: 216 | return CGPoint(x: 23, y: 451) 217 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 218 | return CGPoint(x: 33, y: 426) 219 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 220 | return CGPoint(x: 27, y: 382) 221 | case .iPhoneSE, .iPod7: 222 | return CGPoint(x: 0, y: 0) 223 | default: 224 | return CGPoint(x: 218, y: 76) 225 | } 226 | } 227 | } 228 | 229 | // 小组件的大小 230 | enum DeviceWidgetSize { 231 | static var small: CGSize { 232 | switch UIDevice().type { 233 | case .iPhone12ProMax: 234 | return CGSize(width: 170, height: 170) 235 | case .iPhone12Pro, .iPhone12: 236 | return CGSize(width: 158, height: 158) 237 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 238 | return CGSize(width: 169, height: 169) 239 | case .iPhone12Mini, .iPhone11Pro, .iPhoneXS, .iPhoneX: 240 | return CGSize(width: 155, height: 155) 241 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 242 | return CGSize(width: 159, height: 159) 243 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 244 | return CGSize(width: 148, height: 148) 245 | case .iPhoneSE, .iPod7: 246 | return CGSize(width: 141, height: 144) 247 | default: 248 | return CGSize(width: 169, height: 169) 249 | } 250 | } 251 | 252 | static var meduim: CGSize { 253 | switch UIDevice().type { 254 | case .iPhone12ProMax: 255 | return CGSize(width: 364, height: 170) 256 | case .iPhone12Pro, .iPhone12: 257 | return CGSize(width: 338, height: 158) 258 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR: 259 | return CGSize(width: 360, height: 169) 260 | case .iPhone12Mini, .iPhone11Pro, .iPhoneXS, .iPhoneX: 261 | return CGSize(width: 329, height: 155) 262 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus: 263 | return CGSize(width: 348, height: 159) 264 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2: 265 | return CGSize(width: 322, height: 148) 266 | case .iPhoneSE, .iPod7: 267 | return CGSize(width: 291, height: 144) 268 | default: 269 | return CGSize(width: 360, height: 169) 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /Clocks.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2A1B4C19259DB90D0082FD18 /* ClocksDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C18259DB90D0082FD18 /* ClocksDetailView.swift */; }; 11 | 2A1B4C37259EE8880082FD18 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C36259EE8880082FD18 /* Color.swift */; }; 12 | 2A1B4C7925A2F1180082FD18 /* ClockConfigViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C7825A2F1180082FD18 /* ClockConfigViewModel.swift */; }; 13 | 2A29B38F259C5998003B7085 /* ClocksApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B38E259C5998003B7085 /* ClocksApp.swift */; }; 14 | 2A29B393259C599A003B7085 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A29B392259C599A003B7085 /* Assets.xcassets */; }; 15 | 2A29B396259C599A003B7085 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A29B395259C599A003B7085 /* Preview Assets.xcassets */; }; 16 | 2A29B3A5259C59B5003B7085 /* SimpleClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */; }; 17 | 2A29B3A6259C59B5003B7085 /* WidgetListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A1259C59B5003B7085 /* WidgetListView.swift */; }; 18 | 2A29B3A7259C59B5003B7085 /* WidgetPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */; }; 19 | 2A29B3A8259C59B5003B7085 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A4259C59B5003B7085 /* ContentView.swift */; }; 20 | 2A29B3B3259C5A09003B7085 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A29B3B2259C5A09003B7085 /* WidgetKit.framework */; }; 21 | 2A29B3B5259C5A09003B7085 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A29B3B4259C5A09003B7085 /* SwiftUI.framework */; }; 22 | 2A29B3B8259C5A09003B7085 /* ClocksWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3B7259C5A09003B7085 /* ClocksWidget.swift */; }; 23 | 2A29B3BA259C5A0A003B7085 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A29B3B9259C5A0A003B7085 /* Assets.xcassets */; }; 24 | 2A29B3BE259C5A0A003B7085 /* ClocksWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 2A29B3B0259C5A09003B7085 /* ClocksWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 25 | 2A29B3CA259C5A44003B7085 /* SimpleClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */; }; 26 | 2A29B3D5259C5D1A003B7085 /* WidgetListPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3D4259C5D1A003B7085 /* WidgetListPreviewView.swift */; }; 27 | 2A6D80F325ADC6830071F823 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */; }; 28 | 2A6D80F725ADC6830071F823 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */; }; 29 | 2A6D810225ADC69D0071F823 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C36259EE8880082FD18 /* Color.swift */; }; 30 | 2A6D810625ADC6A00071F823 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C36259EE8880082FD18 /* Color.swift */; }; 31 | 2A6D810D25ADC6A30071F823 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */; }; 32 | 2A6D810E25ADC6A40071F823 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */; }; 33 | 2A6D811525ADC6A80071F823 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D8A1F25AC1627007A8D38 /* Date.swift */; }; 34 | 2A6D811625ADC6A90071F823 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D8A1F25AC1627007A8D38 /* Date.swift */; }; 35 | 2A6D811D25ADC6AC0071F823 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */; }; 36 | 2A6D811E25ADC6AD0071F823 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */; }; 37 | 2A6D812525ADC7220071F823 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */; }; 38 | 2A6D812925ADC72A0071F823 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */; }; 39 | 2A6D812D25ADC7300071F823 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D8A1F25AC1627007A8D38 /* Date.swift */; }; 40 | 2A6D813125ADC7330071F823 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */; }; 41 | 2A6D813525ADC79A0071F823 /* ClockWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */; }; 42 | 2A6D813925ADC7AB0071F823 /* WidgetBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */; }; 43 | 2A6D813D25ADC7B80071F823 /* WidgetPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */; }; 44 | 2A6D814125ADC7BB0071F823 /* WidgetFamilyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */; }; 45 | 2A6D814525ADC7D90071F823 /* DeviceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */; }; 46 | 2A6D814A25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */; }; 47 | 2A6D814B25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */; }; 48 | 2A6D814C25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */; }; 49 | 2A6D815325ADC9020071F823 /* SimpleClock1View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */; }; 50 | 2A6D815425ADC9020071F823 /* SimpleClock1View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */; }; 51 | 2A6D815525ADC9030071F823 /* SimpleClock1View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */; }; 52 | 2A7443EE25A5860C00A7C944 /* WidgetFamilyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */; }; 53 | 2A7443EF25A5860C00A7C944 /* WidgetFamilyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */; }; 54 | 2A7443F925A592D900A7C944 /* StorableClockConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */; }; 55 | 2A7443FA25A592D900A7C944 /* StorableClockConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */; }; 56 | 2A74440025A5B00E00A7C944 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443FF25A5B00E00A7C944 /* ImagePicker.swift */; }; 57 | 2A74440825A5B8B400A7C944 /* WidgetPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */; }; 58 | 2A8C0DC925A5EC6A00B94216 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DC825A5EC6A00B94216 /* Image.swift */; }; 59 | 2A8C0DD025A5F13E00B94216 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DC825A5EC6A00B94216 /* Image.swift */; }; 60 | 2A8C0DE025A6C29700B94216 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */; }; 61 | 2A8C0DE125A6C29700B94216 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */; }; 62 | 2A8C0DF925A6FA1100B94216 /* ClockDetailImageEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DF825A6FA1100B94216 /* ClockDetailImageEditView.swift */; }; 63 | 2A8C0E2525A87CD900B94216 /* ClockDetailImageCropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E2425A87CD900B94216 /* ClockDetailImageCropView.swift */; }; 64 | 2A8C0E3125A9662900B94216 /* WidgetBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */; }; 65 | 2A8C0E3225A9662900B94216 /* WidgetBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */; }; 66 | 2A8C0E3A25A99CB500B94216 /* ClocksWidgetIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */; }; 67 | 2A8C0E3B25A99CB500B94216 /* ClocksWidgetIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */; }; 68 | 2A8C0EDE25A9AA8500B94216 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A8C0EDD25A9AA8500B94216 /* Intents.framework */; }; 69 | 2A8C0EE125A9AA8500B94216 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0EE025A9AA8500B94216 /* IntentHandler.swift */; }; 70 | 2A8C0EE525A9AA8500B94216 /* ClocksWidgetIntents.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 2A8C0EDC25A9AA8500B94216 /* ClocksWidgetIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 71 | 2A8C0EF825A9ACFF00B94216 /* ClocksWidgetIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */; }; 72 | 2A8C0F1825A9AF0800B94216 /* StorableClockConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */; }; 73 | 2A8C0F2625A9AF3C00B94216 /* ClockConfigViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C7825A2F1180082FD18 /* ClockConfigViewModel.swift */; }; 74 | 2A8C0F3825A9B1A500B94216 /* CurrentClocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0F3725A9B1A500B94216 /* CurrentClocksView.swift */; }; 75 | 2A8C8AAA25ADC48900C3E148 /* SimpleClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */; }; 76 | 2A8C8AD925ADC54D00C3E148 /* ClockWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */; }; 77 | 2AB6A96F25AC0B6100B85BAE /* ClockWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */; }; 78 | 2ABCCC9325A9B66D00E6F96D /* ConfigKeysViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ABCCC9225A9B66D00E6F96D /* ConfigKeysViewModel.swift */; }; 79 | 2ABCCCA725AA1C2D00E6F96D /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */; }; 80 | 2ABCCCB925AAB92A00E6F96D /* ConfigKeysViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ABCCC9225A9B66D00E6F96D /* ConfigKeysViewModel.swift */; }; 81 | 2ABCCCBE25AABF6E00E6F96D /* CurrentClockPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ABCCCBD25AABF6E00E6F96D /* CurrentClockPreviewView.swift */; }; 82 | 2ACE48C725AC3E5E006A7FB9 /* ClockWidgetBundleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48C625AC3E5E006A7FB9 /* ClockWidgetBundleView.swift */; }; 83 | 2ACE48C825AC3E5E006A7FB9 /* ClockWidgetBundleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48C625AC3E5E006A7FB9 /* ClockWidgetBundleView.swift */; }; 84 | 2ACE48D025AC5FA4006A7FB9 /* DeviceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */; }; 85 | 2ACE48D125AC5FA4006A7FB9 /* DeviceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */; }; 86 | 2AF9604A25CD11060033766D /* SimplePicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9604925CD11060033766D /* SimplePicture.swift */; }; 87 | 2AF9604B25CD11060033766D /* SimplePicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9604925CD11060033766D /* SimplePicture.swift */; }; 88 | 2AF9604C25CD11060033766D /* SimplePicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9604925CD11060033766D /* SimplePicture.swift */; }; 89 | 2AF9605125CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */; }; 90 | 2AF9605225CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */; }; 91 | 2AF9605325CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */; }; 92 | /* End PBXBuildFile section */ 93 | 94 | /* Begin PBXContainerItemProxy section */ 95 | 2A29B3BC259C5A0A003B7085 /* PBXContainerItemProxy */ = { 96 | isa = PBXContainerItemProxy; 97 | containerPortal = 2A29B383259C5998003B7085 /* Project object */; 98 | proxyType = 1; 99 | remoteGlobalIDString = 2A29B3AF259C5A09003B7085; 100 | remoteInfo = ClocksWidgetExtension; 101 | }; 102 | 2A8C0EE325A9AA8500B94216 /* PBXContainerItemProxy */ = { 103 | isa = PBXContainerItemProxy; 104 | containerPortal = 2A29B383259C5998003B7085 /* Project object */; 105 | proxyType = 1; 106 | remoteGlobalIDString = 2A8C0EDB25A9AA8500B94216; 107 | remoteInfo = ClocksWidgetIntents; 108 | }; 109 | /* End PBXContainerItemProxy section */ 110 | 111 | /* Begin PBXCopyFilesBuildPhase section */ 112 | 2A29B3C2259C5A0A003B7085 /* Embed App Extensions */ = { 113 | isa = PBXCopyFilesBuildPhase; 114 | buildActionMask = 2147483647; 115 | dstPath = ""; 116 | dstSubfolderSpec = 13; 117 | files = ( 118 | 2A29B3BE259C5A0A003B7085 /* ClocksWidgetExtension.appex in Embed App Extensions */, 119 | 2A8C0EE525A9AA8500B94216 /* ClocksWidgetIntents.appex in Embed App Extensions */, 120 | ); 121 | name = "Embed App Extensions"; 122 | runOnlyForDeploymentPostprocessing = 0; 123 | }; 124 | /* End PBXCopyFilesBuildPhase section */ 125 | 126 | /* Begin PBXFileReference section */ 127 | 2A1B4C18259DB90D0082FD18 /* ClocksDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClocksDetailView.swift; sourceTree = ""; }; 128 | 2A1B4C36259EE8880082FD18 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; 129 | 2A1B4C3A25A2E7210082FD18 /* Clocks.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Clocks.entitlements; sourceTree = ""; }; 130 | 2A1B4C4725A2E8D30082FD18 /* ClocksWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClocksWidgetExtension.entitlements; sourceTree = ""; }; 131 | 2A1B4C7825A2F1180082FD18 /* ClockConfigViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockConfigViewModel.swift; sourceTree = ""; }; 132 | 2A29B38B259C5998003B7085 /* Clocks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Clocks.app; sourceTree = BUILT_PRODUCTS_DIR; }; 133 | 2A29B38E259C5998003B7085 /* ClocksApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClocksApp.swift; sourceTree = ""; }; 134 | 2A29B392259C599A003B7085 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 135 | 2A29B395259C599A003B7085 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 136 | 2A29B397259C599A003B7085 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 137 | 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleClockView.swift; sourceTree = ""; }; 138 | 2A29B3A1259C59B5003B7085 /* WidgetListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetListView.swift; sourceTree = ""; }; 139 | 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetPreviews.swift; sourceTree = ""; }; 140 | 2A29B3A4259C59B5003B7085 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 141 | 2A29B3B0259C5A09003B7085 /* ClocksWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ClocksWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 142 | 2A29B3B2259C5A09003B7085 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 143 | 2A29B3B4259C5A09003B7085 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 144 | 2A29B3B7259C5A09003B7085 /* ClocksWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClocksWidget.swift; sourceTree = ""; }; 145 | 2A29B3B9259C5A0A003B7085 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 146 | 2A29B3BB259C5A0A003B7085 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 147 | 2A29B3D4259C5D1A003B7085 /* WidgetListPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetListPreviewView.swift; sourceTree = ""; }; 148 | 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatter.swift; sourceTree = ""; }; 149 | 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultClockStyle.swift; sourceTree = ""; }; 150 | 2A6D8A1F25AC1627007A8D38 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; 151 | 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; }; 152 | 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetFamilyProvider.swift; sourceTree = ""; }; 153 | 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorableClockConfig.swift; sourceTree = ""; }; 154 | 2A7443FF25A5B00E00A7C944 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; 155 | 2A8C0DC825A5EC6A00B94216 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; 156 | 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; 157 | 2A8C0DF825A6FA1100B94216 /* ClockDetailImageEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockDetailImageEditView.swift; sourceTree = ""; }; 158 | 2A8C0E2425A87CD900B94216 /* ClockDetailImageCropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockDetailImageCropView.swift; sourceTree = ""; }; 159 | 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBackground.swift; sourceTree = ""; }; 160 | 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = ClocksWidgetIntents.intentdefinition; sourceTree = ""; }; 161 | 2A8C0EDC25A9AA8500B94216 /* ClocksWidgetIntents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ClocksWidgetIntents.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 162 | 2A8C0EDD25A9AA8500B94216 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; 163 | 2A8C0EE025A9AA8500B94216 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; 164 | 2A8C0EE225A9AA8500B94216 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 165 | 2A8C0EFF25A9AE5F00B94216 /* ClocksWidgetIntents.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClocksWidgetIntents.entitlements; sourceTree = ""; }; 166 | 2A8C0F3725A9B1A500B94216 /* CurrentClocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentClocksView.swift; sourceTree = ""; }; 167 | 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockWidget.swift; sourceTree = ""; }; 168 | 2ABCCC9225A9B66D00E6F96D /* ConfigKeysViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigKeysViewModel.swift; sourceTree = ""; }; 169 | 2ABCCCBD25AABF6E00E6F96D /* CurrentClockPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentClockPreviewView.swift; sourceTree = ""; }; 170 | 2ACE48C625AC3E5E006A7FB9 /* ClockWidgetBundleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockWidgetBundleView.swift; sourceTree = ""; }; 171 | 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceWidget.swift; sourceTree = ""; }; 172 | 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; 173 | 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleClock1View.swift; sourceTree = ""; }; 174 | 2AF9604925CD11060033766D /* SimplePicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplePicture.swift; sourceTree = ""; }; 175 | 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDetailConfigFields.swift; sourceTree = ""; }; 176 | /* End PBXFileReference section */ 177 | 178 | /* Begin PBXFrameworksBuildPhase section */ 179 | 2A29B388259C5998003B7085 /* Frameworks */ = { 180 | isa = PBXFrameworksBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | }; 186 | 2A29B3AD259C5A09003B7085 /* Frameworks */ = { 187 | isa = PBXFrameworksBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 2A29B3B5259C5A09003B7085 /* SwiftUI.framework in Frameworks */, 191 | 2A29B3B3259C5A09003B7085 /* WidgetKit.framework in Frameworks */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | 2A8C0ED925A9AA8500B94216 /* Frameworks */ = { 196 | isa = PBXFrameworksBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 2A8C0EDE25A9AA8500B94216 /* Intents.framework in Frameworks */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXFrameworksBuildPhase section */ 204 | 205 | /* Begin PBXGroup section */ 206 | 2A29B382259C5998003B7085 = { 207 | isa = PBXGroup; 208 | children = ( 209 | 2A1B4C4725A2E8D30082FD18 /* ClocksWidgetExtension.entitlements */, 210 | 2A29B38D259C5998003B7085 /* Clocks */, 211 | 2A29B3B6259C5A09003B7085 /* ClocksWidget */, 212 | 2A8C0EDF25A9AA8500B94216 /* ClocksWidgetIntents */, 213 | 2A29B3B1259C5A09003B7085 /* Frameworks */, 214 | 2A29B38C259C5998003B7085 /* Products */, 215 | ); 216 | sourceTree = ""; 217 | }; 218 | 2A29B38C259C5998003B7085 /* Products */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 2A29B38B259C5998003B7085 /* Clocks.app */, 222 | 2A29B3B0259C5A09003B7085 /* ClocksWidgetExtension.appex */, 223 | 2A8C0EDC25A9AA8500B94216 /* ClocksWidgetIntents.appex */, 224 | ); 225 | name = Products; 226 | sourceTree = ""; 227 | }; 228 | 2A29B38D259C5998003B7085 /* Clocks */ = { 229 | isa = PBXGroup; 230 | children = ( 231 | 2AB6A96D25AC0B4E00B85BAE /* Protocol */, 232 | 2A1B4C3A25A2E7210082FD18 /* Clocks.entitlements */, 233 | 2A29B397259C599A003B7085 /* Info.plist */, 234 | 2A29B38E259C5998003B7085 /* ClocksApp.swift */, 235 | 2A29B392259C599A003B7085 /* Assets.xcassets */, 236 | 2A29B3EA259C6DCA003B7085 /* Extensions */, 237 | 2A29B394259C599A003B7085 /* Preview Content */, 238 | 2A8C0DC725A5EC5400B94216 /* Utils */, 239 | 2A29B3F1259C713B003B7085 /* ViewModels */, 240 | 2A29B39E259C59B5003B7085 /* Views */, 241 | ); 242 | path = Clocks; 243 | sourceTree = ""; 244 | }; 245 | 2A29B394259C599A003B7085 /* Preview Content */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 2A29B395259C599A003B7085 /* Preview Assets.xcassets */, 249 | ); 250 | path = "Preview Content"; 251 | sourceTree = ""; 252 | }; 253 | 2A29B39E259C59B5003B7085 /* Views */ = { 254 | isa = PBXGroup; 255 | children = ( 256 | 2A29B3A4259C59B5003B7085 /* ContentView.swift */, 257 | 2A29B39F259C59B5003B7085 /* WidgetViews */, 258 | 2ABCCCC525AAC05400E6F96D /* WidgetDetail */, 259 | 2ABCCCC725AAC08A00E6F96D /* WidgetList */, 260 | 2ABCCCC625AAC07300E6F96D /* CurrentWidget */, 261 | 2A29B3A2259C59B5003B7085 /* Helper */, 262 | ); 263 | path = Views; 264 | sourceTree = ""; 265 | }; 266 | 2A29B39F259C59B5003B7085 /* WidgetViews */ = { 267 | isa = PBXGroup; 268 | children = ( 269 | 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */, 270 | 2ACE48C625AC3E5E006A7FB9 /* ClockWidgetBundleView.swift */, 271 | 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */, 272 | 2AF9604925CD11060033766D /* SimplePicture.swift */, 273 | ); 274 | path = WidgetViews; 275 | sourceTree = ""; 276 | }; 277 | 2A29B3A2259C59B5003B7085 /* Helper */ = { 278 | isa = PBXGroup; 279 | children = ( 280 | 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */, 281 | 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */, 282 | 2A7443FF25A5B00E00A7C944 /* ImagePicker.swift */, 283 | 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */, 284 | ); 285 | path = Helper; 286 | sourceTree = ""; 287 | }; 288 | 2A29B3B1259C5A09003B7085 /* Frameworks */ = { 289 | isa = PBXGroup; 290 | children = ( 291 | 2A29B3B2259C5A09003B7085 /* WidgetKit.framework */, 292 | 2A29B3B4259C5A09003B7085 /* SwiftUI.framework */, 293 | 2A8C0EDD25A9AA8500B94216 /* Intents.framework */, 294 | ); 295 | name = Frameworks; 296 | sourceTree = ""; 297 | }; 298 | 2A29B3B6259C5A09003B7085 /* ClocksWidget */ = { 299 | isa = PBXGroup; 300 | children = ( 301 | 2A29B3B7259C5A09003B7085 /* ClocksWidget.swift */, 302 | 2A29B3B9259C5A0A003B7085 /* Assets.xcassets */, 303 | 2A29B3BB259C5A0A003B7085 /* Info.plist */, 304 | 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */, 305 | ); 306 | path = ClocksWidget; 307 | sourceTree = ""; 308 | }; 309 | 2A29B3EA259C6DCA003B7085 /* Extensions */ = { 310 | isa = PBXGroup; 311 | children = ( 312 | 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */, 313 | 2A1B4C36259EE8880082FD18 /* Color.swift */, 314 | 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */, 315 | 2A6D8A1F25AC1627007A8D38 /* Date.swift */, 316 | 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */, 317 | ); 318 | path = Extensions; 319 | sourceTree = ""; 320 | }; 321 | 2A29B3F1259C713B003B7085 /* ViewModels */ = { 322 | isa = PBXGroup; 323 | children = ( 324 | 2A1B4C7825A2F1180082FD18 /* ClockConfigViewModel.swift */, 325 | 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */, 326 | 2ABCCC9225A9B66D00E6F96D /* ConfigKeysViewModel.swift */, 327 | ); 328 | path = ViewModels; 329 | sourceTree = ""; 330 | }; 331 | 2A8C0DC725A5EC5400B94216 /* Utils */ = { 332 | isa = PBXGroup; 333 | children = ( 334 | 2A8C0DC825A5EC6A00B94216 /* Image.swift */, 335 | 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */, 336 | 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */, 337 | 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */, 338 | 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */, 339 | ); 340 | path = Utils; 341 | sourceTree = ""; 342 | }; 343 | 2A8C0EDF25A9AA8500B94216 /* ClocksWidgetIntents */ = { 344 | isa = PBXGroup; 345 | children = ( 346 | 2A8C0EFF25A9AE5F00B94216 /* ClocksWidgetIntents.entitlements */, 347 | 2A8C0EE025A9AA8500B94216 /* IntentHandler.swift */, 348 | 2A8C0EE225A9AA8500B94216 /* Info.plist */, 349 | ); 350 | path = ClocksWidgetIntents; 351 | sourceTree = ""; 352 | }; 353 | 2AB6A96D25AC0B4E00B85BAE /* Protocol */ = { 354 | isa = PBXGroup; 355 | children = ( 356 | 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */, 357 | ); 358 | path = Protocol; 359 | sourceTree = ""; 360 | }; 361 | 2ABCCCC525AAC05400E6F96D /* WidgetDetail */ = { 362 | isa = PBXGroup; 363 | children = ( 364 | 2A1B4C18259DB90D0082FD18 /* ClocksDetailView.swift */, 365 | 2A8C0DF825A6FA1100B94216 /* ClockDetailImageEditView.swift */, 366 | 2A8C0E2425A87CD900B94216 /* ClockDetailImageCropView.swift */, 367 | ); 368 | path = WidgetDetail; 369 | sourceTree = ""; 370 | }; 371 | 2ABCCCC625AAC07300E6F96D /* CurrentWidget */ = { 372 | isa = PBXGroup; 373 | children = ( 374 | 2A8C0F3725A9B1A500B94216 /* CurrentClocksView.swift */, 375 | 2ABCCCBD25AABF6E00E6F96D /* CurrentClockPreviewView.swift */, 376 | ); 377 | path = CurrentWidget; 378 | sourceTree = ""; 379 | }; 380 | 2ABCCCC725AAC08A00E6F96D /* WidgetList */ = { 381 | isa = PBXGroup; 382 | children = ( 383 | 2A29B3D4259C5D1A003B7085 /* WidgetListPreviewView.swift */, 384 | 2A29B3A1259C59B5003B7085 /* WidgetListView.swift */, 385 | ); 386 | path = WidgetList; 387 | sourceTree = ""; 388 | }; 389 | /* End PBXGroup section */ 390 | 391 | /* Begin PBXNativeTarget section */ 392 | 2A29B38A259C5998003B7085 /* Clocks */ = { 393 | isa = PBXNativeTarget; 394 | buildConfigurationList = 2A29B39A259C599A003B7085 /* Build configuration list for PBXNativeTarget "Clocks" */; 395 | buildPhases = ( 396 | 2A29B387259C5998003B7085 /* Sources */, 397 | 2A29B388259C5998003B7085 /* Frameworks */, 398 | 2A29B389259C5998003B7085 /* Resources */, 399 | 2A29B3C2259C5A0A003B7085 /* Embed App Extensions */, 400 | ); 401 | buildRules = ( 402 | ); 403 | dependencies = ( 404 | 2A29B3BD259C5A0A003B7085 /* PBXTargetDependency */, 405 | 2A8C0EE425A9AA8500B94216 /* PBXTargetDependency */, 406 | ); 407 | name = Clocks; 408 | productName = Clocks; 409 | productReference = 2A29B38B259C5998003B7085 /* Clocks.app */; 410 | productType = "com.apple.product-type.application"; 411 | }; 412 | 2A29B3AF259C5A09003B7085 /* ClocksWidgetExtension */ = { 413 | isa = PBXNativeTarget; 414 | buildConfigurationList = 2A29B3BF259C5A0A003B7085 /* Build configuration list for PBXNativeTarget "ClocksWidgetExtension" */; 415 | buildPhases = ( 416 | 2A29B3AC259C5A09003B7085 /* Sources */, 417 | 2A29B3AD259C5A09003B7085 /* Frameworks */, 418 | 2A29B3AE259C5A09003B7085 /* Resources */, 419 | ); 420 | buildRules = ( 421 | ); 422 | dependencies = ( 423 | ); 424 | name = ClocksWidgetExtension; 425 | productName = ClocksWidgetExtension; 426 | productReference = 2A29B3B0259C5A09003B7085 /* ClocksWidgetExtension.appex */; 427 | productType = "com.apple.product-type.app-extension"; 428 | }; 429 | 2A8C0EDB25A9AA8500B94216 /* ClocksWidgetIntents */ = { 430 | isa = PBXNativeTarget; 431 | buildConfigurationList = 2A8C0EE625A9AA8500B94216 /* Build configuration list for PBXNativeTarget "ClocksWidgetIntents" */; 432 | buildPhases = ( 433 | 2A8C0ED825A9AA8500B94216 /* Sources */, 434 | 2A8C0ED925A9AA8500B94216 /* Frameworks */, 435 | 2A8C0EDA25A9AA8500B94216 /* Resources */, 436 | ); 437 | buildRules = ( 438 | ); 439 | dependencies = ( 440 | ); 441 | name = ClocksWidgetIntents; 442 | productName = ClocksWidgetIntents; 443 | productReference = 2A8C0EDC25A9AA8500B94216 /* ClocksWidgetIntents.appex */; 444 | productType = "com.apple.product-type.app-extension"; 445 | }; 446 | /* End PBXNativeTarget section */ 447 | 448 | /* Begin PBXProject section */ 449 | 2A29B383259C5998003B7085 /* Project object */ = { 450 | isa = PBXProject; 451 | attributes = { 452 | LastSwiftUpdateCheck = 1230; 453 | LastUpgradeCheck = 1230; 454 | TargetAttributes = { 455 | 2A29B38A259C5998003B7085 = { 456 | CreatedOnToolsVersion = 12.3; 457 | }; 458 | 2A29B3AF259C5A09003B7085 = { 459 | CreatedOnToolsVersion = 12.3; 460 | }; 461 | 2A8C0EDB25A9AA8500B94216 = { 462 | CreatedOnToolsVersion = 12.3; 463 | }; 464 | }; 465 | }; 466 | buildConfigurationList = 2A29B386259C5998003B7085 /* Build configuration list for PBXProject "Clocks" */; 467 | compatibilityVersion = "Xcode 9.3"; 468 | developmentRegion = en; 469 | hasScannedForEncodings = 0; 470 | knownRegions = ( 471 | en, 472 | Base, 473 | ); 474 | mainGroup = 2A29B382259C5998003B7085; 475 | productRefGroup = 2A29B38C259C5998003B7085 /* Products */; 476 | projectDirPath = ""; 477 | projectRoot = ""; 478 | targets = ( 479 | 2A29B38A259C5998003B7085 /* Clocks */, 480 | 2A29B3AF259C5A09003B7085 /* ClocksWidgetExtension */, 481 | 2A8C0EDB25A9AA8500B94216 /* ClocksWidgetIntents */, 482 | ); 483 | }; 484 | /* End PBXProject section */ 485 | 486 | /* Begin PBXResourcesBuildPhase section */ 487 | 2A29B389259C5998003B7085 /* Resources */ = { 488 | isa = PBXResourcesBuildPhase; 489 | buildActionMask = 2147483647; 490 | files = ( 491 | 2A29B396259C599A003B7085 /* Preview Assets.xcassets in Resources */, 492 | 2A29B393259C599A003B7085 /* Assets.xcassets in Resources */, 493 | ); 494 | runOnlyForDeploymentPostprocessing = 0; 495 | }; 496 | 2A29B3AE259C5A09003B7085 /* Resources */ = { 497 | isa = PBXResourcesBuildPhase; 498 | buildActionMask = 2147483647; 499 | files = ( 500 | 2A29B3BA259C5A0A003B7085 /* Assets.xcassets in Resources */, 501 | ); 502 | runOnlyForDeploymentPostprocessing = 0; 503 | }; 504 | 2A8C0EDA25A9AA8500B94216 /* Resources */ = { 505 | isa = PBXResourcesBuildPhase; 506 | buildActionMask = 2147483647; 507 | files = ( 508 | ); 509 | runOnlyForDeploymentPostprocessing = 0; 510 | }; 511 | /* End PBXResourcesBuildPhase section */ 512 | 513 | /* Begin PBXSourcesBuildPhase section */ 514 | 2A29B387259C5998003B7085 /* Sources */ = { 515 | isa = PBXSourcesBuildPhase; 516 | buildActionMask = 2147483647; 517 | files = ( 518 | 2A1B4C19259DB90D0082FD18 /* ClocksDetailView.swift in Sources */, 519 | 2ACE48D025AC5FA4006A7FB9 /* DeviceWidget.swift in Sources */, 520 | 2A29B3D5259C5D1A003B7085 /* WidgetListPreviewView.swift in Sources */, 521 | 2A6D814A25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */, 522 | 2ACE48C725AC3E5E006A7FB9 /* ClockWidgetBundleView.swift in Sources */, 523 | 2A6D811525ADC6A80071F823 /* Date.swift in Sources */, 524 | 2A6D811E25ADC6AD0071F823 /* UIDevice.swift in Sources */, 525 | 2A1B4C37259EE8880082FD18 /* Color.swift in Sources */, 526 | 2A6D80F325ADC6830071F823 /* DateFormatter.swift in Sources */, 527 | 2AF9605125CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */, 528 | 2A8C0E3125A9662900B94216 /* WidgetBackground.swift in Sources */, 529 | 2A8C0DE025A6C29700B94216 /* UserDefaults.swift in Sources */, 530 | 2AB6A96F25AC0B6100B85BAE /* ClockWidget.swift in Sources */, 531 | 2A7443EE25A5860C00A7C944 /* WidgetFamilyProvider.swift in Sources */, 532 | 2A6D815325ADC9020071F823 /* SimpleClock1View.swift in Sources */, 533 | 2A29B3A5259C59B5003B7085 /* SimpleClockView.swift in Sources */, 534 | 2A29B3A6259C59B5003B7085 /* WidgetListView.swift in Sources */, 535 | 2A7443F925A592D900A7C944 /* StorableClockConfig.swift in Sources */, 536 | 2A8C0DF925A6FA1100B94216 /* ClockDetailImageEditView.swift in Sources */, 537 | 2A6D810D25ADC6A30071F823 /* EnvironmentValues.swift in Sources */, 538 | 2ABCCC9325A9B66D00E6F96D /* ConfigKeysViewModel.swift in Sources */, 539 | 2ABCCCBE25AABF6E00E6F96D /* CurrentClockPreviewView.swift in Sources */, 540 | 2A8C0DC925A5EC6A00B94216 /* Image.swift in Sources */, 541 | 2A29B3A8259C59B5003B7085 /* ContentView.swift in Sources */, 542 | 2A29B3A7259C59B5003B7085 /* WidgetPreviews.swift in Sources */, 543 | 2AF9604A25CD11060033766D /* SimplePicture.swift in Sources */, 544 | 2A74440025A5B00E00A7C944 /* ImagePicker.swift in Sources */, 545 | 2A8C0F3825A9B1A500B94216 /* CurrentClocksView.swift in Sources */, 546 | 2A8C0E3A25A99CB500B94216 /* ClocksWidgetIntents.intentdefinition in Sources */, 547 | 2A8C0E2525A87CD900B94216 /* ClockDetailImageCropView.swift in Sources */, 548 | 2A29B38F259C5998003B7085 /* ClocksApp.swift in Sources */, 549 | 2A1B4C7925A2F1180082FD18 /* ClockConfigViewModel.swift in Sources */, 550 | ); 551 | runOnlyForDeploymentPostprocessing = 0; 552 | }; 553 | 2A29B3AC259C5A09003B7085 /* Sources */ = { 554 | isa = PBXSourcesBuildPhase; 555 | buildActionMask = 2147483647; 556 | files = ( 557 | 2A7443FA25A592D900A7C944 /* StorableClockConfig.swift in Sources */, 558 | 2A8C0E3B25A99CB500B94216 /* ClocksWidgetIntents.intentdefinition in Sources */, 559 | 2A6D80F725ADC6830071F823 /* DateFormatter.swift in Sources */, 560 | 2A8C0DE125A6C29700B94216 /* UserDefaults.swift in Sources */, 561 | 2A74440825A5B8B400A7C944 /* WidgetPreviews.swift in Sources */, 562 | 2ACE48C825AC3E5E006A7FB9 /* ClockWidgetBundleView.swift in Sources */, 563 | 2A8C0F2625A9AF3C00B94216 /* ClockConfigViewModel.swift in Sources */, 564 | 2A6D815425ADC9020071F823 /* SimpleClock1View.swift in Sources */, 565 | 2A8C0E3225A9662900B94216 /* WidgetBackground.swift in Sources */, 566 | 2ABCCCB925AAB92A00E6F96D /* ConfigKeysViewModel.swift in Sources */, 567 | 2AF9604B25CD11060033766D /* SimplePicture.swift in Sources */, 568 | 2A6D811625ADC6A90071F823 /* Date.swift in Sources */, 569 | 2A8C8AD925ADC54D00C3E148 /* ClockWidget.swift in Sources */, 570 | 2A6D810225ADC69D0071F823 /* Color.swift in Sources */, 571 | 2A29B3CA259C5A44003B7085 /* SimpleClockView.swift in Sources */, 572 | 2A6D810E25ADC6A40071F823 /* EnvironmentValues.swift in Sources */, 573 | 2A6D811D25ADC6AC0071F823 /* UIDevice.swift in Sources */, 574 | 2A6D814B25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */, 575 | 2A29B3B8259C5A09003B7085 /* ClocksWidget.swift in Sources */, 576 | 2A7443EF25A5860C00A7C944 /* WidgetFamilyProvider.swift in Sources */, 577 | 2AF9605225CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */, 578 | 2ACE48D125AC5FA4006A7FB9 /* DeviceWidget.swift in Sources */, 579 | 2A8C0DD025A5F13E00B94216 /* Image.swift in Sources */, 580 | ); 581 | runOnlyForDeploymentPostprocessing = 0; 582 | }; 583 | 2A8C0ED825A9AA8500B94216 /* Sources */ = { 584 | isa = PBXSourcesBuildPhase; 585 | buildActionMask = 2147483647; 586 | files = ( 587 | 2A6D812925ADC72A0071F823 /* DateFormatter.swift in Sources */, 588 | 2A6D815525ADC9030071F823 /* SimpleClock1View.swift in Sources */, 589 | 2A6D814525ADC7D90071F823 /* DeviceWidget.swift in Sources */, 590 | 2A6D812D25ADC7300071F823 /* Date.swift in Sources */, 591 | 2A8C0EE125A9AA8500B94216 /* IntentHandler.swift in Sources */, 592 | 2A6D814C25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */, 593 | 2AF9605325CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */, 594 | 2ABCCCA725AA1C2D00E6F96D /* UserDefaults.swift in Sources */, 595 | 2A6D813D25ADC7B80071F823 /* WidgetPreviews.swift in Sources */, 596 | 2A6D810625ADC6A00071F823 /* Color.swift in Sources */, 597 | 2A6D814125ADC7BB0071F823 /* WidgetFamilyProvider.swift in Sources */, 598 | 2A6D813925ADC7AB0071F823 /* WidgetBackground.swift in Sources */, 599 | 2A6D813125ADC7330071F823 /* UIDevice.swift in Sources */, 600 | 2A8C0F1825A9AF0800B94216 /* StorableClockConfig.swift in Sources */, 601 | 2A6D813525ADC79A0071F823 /* ClockWidget.swift in Sources */, 602 | 2AF9604C25CD11060033766D /* SimplePicture.swift in Sources */, 603 | 2A8C8AAA25ADC48900C3E148 /* SimpleClockView.swift in Sources */, 604 | 2A8C0EF825A9ACFF00B94216 /* ClocksWidgetIntents.intentdefinition in Sources */, 605 | 2A6D812525ADC7220071F823 /* EnvironmentValues.swift in Sources */, 606 | ); 607 | runOnlyForDeploymentPostprocessing = 0; 608 | }; 609 | /* End PBXSourcesBuildPhase section */ 610 | 611 | /* Begin PBXTargetDependency section */ 612 | 2A29B3BD259C5A0A003B7085 /* PBXTargetDependency */ = { 613 | isa = PBXTargetDependency; 614 | target = 2A29B3AF259C5A09003B7085 /* ClocksWidgetExtension */; 615 | targetProxy = 2A29B3BC259C5A0A003B7085 /* PBXContainerItemProxy */; 616 | }; 617 | 2A8C0EE425A9AA8500B94216 /* PBXTargetDependency */ = { 618 | isa = PBXTargetDependency; 619 | target = 2A8C0EDB25A9AA8500B94216 /* ClocksWidgetIntents */; 620 | targetProxy = 2A8C0EE325A9AA8500B94216 /* PBXContainerItemProxy */; 621 | }; 622 | /* End PBXTargetDependency section */ 623 | 624 | /* Begin XCBuildConfiguration section */ 625 | 2A29B398259C599A003B7085 /* Debug */ = { 626 | isa = XCBuildConfiguration; 627 | buildSettings = { 628 | ALWAYS_SEARCH_USER_PATHS = NO; 629 | CLANG_ANALYZER_NONNULL = YES; 630 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 631 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 632 | CLANG_CXX_LIBRARY = "libc++"; 633 | CLANG_ENABLE_MODULES = YES; 634 | CLANG_ENABLE_OBJC_ARC = YES; 635 | CLANG_ENABLE_OBJC_WEAK = YES; 636 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 637 | CLANG_WARN_BOOL_CONVERSION = YES; 638 | CLANG_WARN_COMMA = YES; 639 | CLANG_WARN_CONSTANT_CONVERSION = YES; 640 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 641 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 642 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 643 | CLANG_WARN_EMPTY_BODY = YES; 644 | CLANG_WARN_ENUM_CONVERSION = YES; 645 | CLANG_WARN_INFINITE_RECURSION = YES; 646 | CLANG_WARN_INT_CONVERSION = YES; 647 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 648 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 649 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 650 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 651 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 652 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 653 | CLANG_WARN_STRICT_PROTOTYPES = YES; 654 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 655 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 656 | CLANG_WARN_UNREACHABLE_CODE = YES; 657 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 658 | COPY_PHASE_STRIP = NO; 659 | DEBUG_INFORMATION_FORMAT = dwarf; 660 | ENABLE_STRICT_OBJC_MSGSEND = YES; 661 | ENABLE_TESTABILITY = YES; 662 | GCC_C_LANGUAGE_STANDARD = gnu11; 663 | GCC_DYNAMIC_NO_PIC = NO; 664 | GCC_NO_COMMON_BLOCKS = YES; 665 | GCC_OPTIMIZATION_LEVEL = 0; 666 | GCC_PREPROCESSOR_DEFINITIONS = ( 667 | "DEBUG=1", 668 | "$(inherited)", 669 | ); 670 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 671 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 672 | GCC_WARN_UNDECLARED_SELECTOR = YES; 673 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 674 | GCC_WARN_UNUSED_FUNCTION = YES; 675 | GCC_WARN_UNUSED_VARIABLE = YES; 676 | IPHONEOS_DEPLOYMENT_TARGET = 14.3; 677 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 678 | MTL_FAST_MATH = YES; 679 | ONLY_ACTIVE_ARCH = YES; 680 | SDKROOT = iphoneos; 681 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 682 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 683 | }; 684 | name = Debug; 685 | }; 686 | 2A29B399259C599A003B7085 /* Release */ = { 687 | isa = XCBuildConfiguration; 688 | buildSettings = { 689 | ALWAYS_SEARCH_USER_PATHS = NO; 690 | CLANG_ANALYZER_NONNULL = YES; 691 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 692 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 693 | CLANG_CXX_LIBRARY = "libc++"; 694 | CLANG_ENABLE_MODULES = YES; 695 | CLANG_ENABLE_OBJC_ARC = YES; 696 | CLANG_ENABLE_OBJC_WEAK = YES; 697 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 698 | CLANG_WARN_BOOL_CONVERSION = YES; 699 | CLANG_WARN_COMMA = YES; 700 | CLANG_WARN_CONSTANT_CONVERSION = YES; 701 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 702 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 703 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 704 | CLANG_WARN_EMPTY_BODY = YES; 705 | CLANG_WARN_ENUM_CONVERSION = YES; 706 | CLANG_WARN_INFINITE_RECURSION = YES; 707 | CLANG_WARN_INT_CONVERSION = YES; 708 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 709 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 710 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 711 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 712 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 713 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 714 | CLANG_WARN_STRICT_PROTOTYPES = YES; 715 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 716 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 717 | CLANG_WARN_UNREACHABLE_CODE = YES; 718 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 719 | COPY_PHASE_STRIP = NO; 720 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 721 | ENABLE_NS_ASSERTIONS = NO; 722 | ENABLE_STRICT_OBJC_MSGSEND = YES; 723 | GCC_C_LANGUAGE_STANDARD = gnu11; 724 | GCC_NO_COMMON_BLOCKS = YES; 725 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 726 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 727 | GCC_WARN_UNDECLARED_SELECTOR = YES; 728 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 729 | GCC_WARN_UNUSED_FUNCTION = YES; 730 | GCC_WARN_UNUSED_VARIABLE = YES; 731 | IPHONEOS_DEPLOYMENT_TARGET = 14.3; 732 | MTL_ENABLE_DEBUG_INFO = NO; 733 | MTL_FAST_MATH = YES; 734 | SDKROOT = iphoneos; 735 | SWIFT_COMPILATION_MODE = wholemodule; 736 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 737 | VALIDATE_PRODUCT = YES; 738 | }; 739 | name = Release; 740 | }; 741 | 2A29B39B259C599A003B7085 /* Debug */ = { 742 | isa = XCBuildConfiguration; 743 | buildSettings = { 744 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 745 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 746 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 747 | CODE_SIGN_ENTITLEMENTS = Clocks/Clocks.entitlements; 748 | CODE_SIGN_STYLE = Automatic; 749 | CURRENT_PROJECT_VERSION = 1; 750 | DEVELOPMENT_ASSET_PATHS = "\"Clocks/Preview Content\""; 751 | DEVELOPMENT_TEAM = SRD476XUQA; 752 | ENABLE_PREVIEWS = YES; 753 | INFOPLIST_FILE = Clocks/Info.plist; 754 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 755 | LD_RUNPATH_SEARCH_PATHS = ( 756 | "$(inherited)", 757 | "@executable_path/Frameworks", 758 | ); 759 | MARKETING_VERSION = 1.2; 760 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget"; 761 | PRODUCT_NAME = "$(TARGET_NAME)"; 762 | SWIFT_VERSION = 5.0; 763 | TARGETED_DEVICE_FAMILY = 1; 764 | }; 765 | name = Debug; 766 | }; 767 | 2A29B39C259C599A003B7085 /* Release */ = { 768 | isa = XCBuildConfiguration; 769 | buildSettings = { 770 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 771 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 772 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 773 | CODE_SIGN_ENTITLEMENTS = Clocks/Clocks.entitlements; 774 | CODE_SIGN_STYLE = Automatic; 775 | CURRENT_PROJECT_VERSION = 1; 776 | DEVELOPMENT_ASSET_PATHS = "\"Clocks/Preview Content\""; 777 | DEVELOPMENT_TEAM = SRD476XUQA; 778 | ENABLE_PREVIEWS = YES; 779 | INFOPLIST_FILE = Clocks/Info.plist; 780 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 781 | LD_RUNPATH_SEARCH_PATHS = ( 782 | "$(inherited)", 783 | "@executable_path/Frameworks", 784 | ); 785 | MARKETING_VERSION = 1.2; 786 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget"; 787 | PRODUCT_NAME = "$(TARGET_NAME)"; 788 | SWIFT_VERSION = 5.0; 789 | TARGETED_DEVICE_FAMILY = 1; 790 | }; 791 | name = Release; 792 | }; 793 | 2A29B3C0259C5A0A003B7085 /* Debug */ = { 794 | isa = XCBuildConfiguration; 795 | buildSettings = { 796 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 797 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; 798 | CODE_SIGN_ENTITLEMENTS = ClocksWidgetExtension.entitlements; 799 | CODE_SIGN_STYLE = Automatic; 800 | CURRENT_PROJECT_VERSION = 1; 801 | DEVELOPMENT_TEAM = SRD476XUQA; 802 | INFOPLIST_FILE = ClocksWidget/Info.plist; 803 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 804 | LD_RUNPATH_SEARCH_PATHS = ( 805 | "$(inherited)", 806 | "@executable_path/Frameworks", 807 | "@executable_path/../../Frameworks", 808 | ); 809 | MARKETING_VERSION = 1.2; 810 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget.widget-extension"; 811 | PRODUCT_NAME = "$(TARGET_NAME)"; 812 | SKIP_INSTALL = YES; 813 | SWIFT_VERSION = 5.0; 814 | TARGETED_DEVICE_FAMILY = 1; 815 | }; 816 | name = Debug; 817 | }; 818 | 2A29B3C1259C5A0A003B7085 /* Release */ = { 819 | isa = XCBuildConfiguration; 820 | buildSettings = { 821 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 822 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; 823 | CODE_SIGN_ENTITLEMENTS = ClocksWidgetExtension.entitlements; 824 | CODE_SIGN_STYLE = Automatic; 825 | CURRENT_PROJECT_VERSION = 1; 826 | DEVELOPMENT_TEAM = SRD476XUQA; 827 | INFOPLIST_FILE = ClocksWidget/Info.plist; 828 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 829 | LD_RUNPATH_SEARCH_PATHS = ( 830 | "$(inherited)", 831 | "@executable_path/Frameworks", 832 | "@executable_path/../../Frameworks", 833 | ); 834 | MARKETING_VERSION = 1.2; 835 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget.widget-extension"; 836 | PRODUCT_NAME = "$(TARGET_NAME)"; 837 | SKIP_INSTALL = YES; 838 | SWIFT_VERSION = 5.0; 839 | TARGETED_DEVICE_FAMILY = 1; 840 | }; 841 | name = Release; 842 | }; 843 | 2A8C0EE725A9AA8500B94216 /* Debug */ = { 844 | isa = XCBuildConfiguration; 845 | buildSettings = { 846 | CODE_SIGN_ENTITLEMENTS = ClocksWidgetIntents/ClocksWidgetIntents.entitlements; 847 | CODE_SIGN_STYLE = Automatic; 848 | CURRENT_PROJECT_VERSION = 1; 849 | DEVELOPMENT_TEAM = SRD476XUQA; 850 | INFOPLIST_FILE = ClocksWidgetIntents/Info.plist; 851 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 852 | LD_RUNPATH_SEARCH_PATHS = ( 853 | "$(inherited)", 854 | "@executable_path/Frameworks", 855 | "@executable_path/../../Frameworks", 856 | ); 857 | MARKETING_VERSION = 1.2; 858 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget.widget-intents"; 859 | PRODUCT_NAME = "$(TARGET_NAME)"; 860 | SKIP_INSTALL = YES; 861 | SWIFT_VERSION = 5.0; 862 | TARGETED_DEVICE_FAMILY = 1; 863 | }; 864 | name = Debug; 865 | }; 866 | 2A8C0EE825A9AA8500B94216 /* Release */ = { 867 | isa = XCBuildConfiguration; 868 | buildSettings = { 869 | CODE_SIGN_ENTITLEMENTS = ClocksWidgetIntents/ClocksWidgetIntents.entitlements; 870 | CODE_SIGN_STYLE = Automatic; 871 | CURRENT_PROJECT_VERSION = 1; 872 | DEVELOPMENT_TEAM = SRD476XUQA; 873 | INFOPLIST_FILE = ClocksWidgetIntents/Info.plist; 874 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 875 | LD_RUNPATH_SEARCH_PATHS = ( 876 | "$(inherited)", 877 | "@executable_path/Frameworks", 878 | "@executable_path/../../Frameworks", 879 | ); 880 | MARKETING_VERSION = 1.2; 881 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget.widget-intents"; 882 | PRODUCT_NAME = "$(TARGET_NAME)"; 883 | SKIP_INSTALL = YES; 884 | SWIFT_VERSION = 5.0; 885 | TARGETED_DEVICE_FAMILY = 1; 886 | }; 887 | name = Release; 888 | }; 889 | /* End XCBuildConfiguration section */ 890 | 891 | /* Begin XCConfigurationList section */ 892 | 2A29B386259C5998003B7085 /* Build configuration list for PBXProject "Clocks" */ = { 893 | isa = XCConfigurationList; 894 | buildConfigurations = ( 895 | 2A29B398259C599A003B7085 /* Debug */, 896 | 2A29B399259C599A003B7085 /* Release */, 897 | ); 898 | defaultConfigurationIsVisible = 0; 899 | defaultConfigurationName = Release; 900 | }; 901 | 2A29B39A259C599A003B7085 /* Build configuration list for PBXNativeTarget "Clocks" */ = { 902 | isa = XCConfigurationList; 903 | buildConfigurations = ( 904 | 2A29B39B259C599A003B7085 /* Debug */, 905 | 2A29B39C259C599A003B7085 /* Release */, 906 | ); 907 | defaultConfigurationIsVisible = 0; 908 | defaultConfigurationName = Release; 909 | }; 910 | 2A29B3BF259C5A0A003B7085 /* Build configuration list for PBXNativeTarget "ClocksWidgetExtension" */ = { 911 | isa = XCConfigurationList; 912 | buildConfigurations = ( 913 | 2A29B3C0259C5A0A003B7085 /* Debug */, 914 | 2A29B3C1259C5A0A003B7085 /* Release */, 915 | ); 916 | defaultConfigurationIsVisible = 0; 917 | defaultConfigurationName = Release; 918 | }; 919 | 2A8C0EE625A9AA8500B94216 /* Build configuration list for PBXNativeTarget "ClocksWidgetIntents" */ = { 920 | isa = XCConfigurationList; 921 | buildConfigurations = ( 922 | 2A8C0EE725A9AA8500B94216 /* Debug */, 923 | 2A8C0EE825A9AA8500B94216 /* Release */, 924 | ); 925 | defaultConfigurationIsVisible = 0; 926 | defaultConfigurationName = Release; 927 | }; 928 | /* End XCConfigurationList section */ 929 | }; 930 | rootObject = 2A29B383259C5998003B7085 /* Project object */; 931 | } 932 | --------------------------------------------------------------------------------