From 53425e08b6240430fb307c18fead6e0589b7a403 Mon Sep 17 00:00:00 2001 From: Andy Jacobs Date: Thu, 17 Jan 2019 20:16:53 +0100 Subject: [PATCH 1/2] Mergeable theme from multiple files with hot reloading --- Example/AppDelegate.swift | 3 +- Example/common.yml | 5 ++ Stylist.xcodeproj/project.pbxproj | 4 ++ .../contents.xcworkspacedata | 0 .../xcschemes/Stylist-iOS.xcscheme | 0 .../xcschemes/Stylist-tvOS.xcscheme | 0 Stylist/Stylist.swift | 49 +++++++++++++++++++ Stylist/Theme.swift | 27 ++++++++++ 8 files changed, 87 insertions(+), 1 deletion(-) mode change 100644 => 100755 Example/AppDelegate.swift create mode 100644 Example/common.yml mode change 100644 => 100755 Stylist.xcodeproj/project.pbxproj mode change 100644 => 100755 Stylist.xcodeproj/project.xcworkspace/contents.xcworkspacedata mode change 100644 => 100755 Stylist.xcodeproj/xcshareddata/xcschemes/Stylist-iOS.xcscheme mode change 100644 => 100755 Stylist.xcodeproj/xcshareddata/xcschemes/Stylist-tvOS.xcscheme mode change 100644 => 100755 Stylist/Stylist.swift mode change 100644 => 100755 Stylist/Theme.swift diff --git a/Example/AppDelegate.swift b/Example/AppDelegate.swift old mode 100644 new mode 100755 index 24646c2..0457ac1 --- a/Example/AppDelegate.swift +++ b/Example/AppDelegate.swift @@ -19,7 +19,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { let url = URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("Style.yaml") - Stylist.shared.watch(url: url, animateChanges: true) { error in + let commonURL = URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("common.yml") + Stylist.shared.localMergeWatch(urls: [url, commonURL], animateChanges: true) { error in print("Error loading theme:\n\(error)") } diff --git a/Example/common.yml b/Example/common.yml new file mode 100644 index 0000000..a8fb6a7 --- /dev/null +++ b/Example/common.yml @@ -0,0 +1,5 @@ +variables: + primaryColor: F000FF +styles: + largeFont: + font: title 1 diff --git a/Stylist.xcodeproj/project.pbxproj b/Stylist.xcodeproj/project.pbxproj old mode 100644 new mode 100755 index 21e4ede..4ca4501 --- a/Stylist.xcodeproj/project.pbxproj +++ b/Stylist.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 52D6D9871BEFF229002C0205 /* Stylist.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* Stylist.framework */; }; 535BD3EF11C3878517DE35E8 /* Pods_Stylist_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C186A7E7F661536D838BB3A0 /* Pods_Stylist_iOS.framework */; }; 5CC7AE9BC309934F44DC1CE1 /* Pods_Stylist_tvOS_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9245472812F659E9F0E1973B /* Pods_Stylist_tvOS_Tests.framework */; }; + 829ED8B321F0B85F00A572A2 /* common.yml in Resources */ = {isa = PBXBuildFile; fileRef = 829ED8B221F0B85F00A572A2 /* common.yml */; }; 8933C7851EB5B820000D00A4 /* Stylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Stylist.swift */; }; 8933C7881EB5B820000D00A4 /* Stylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Stylist.swift */; }; 8933C78E1EB5B82C000D00A4 /* StylistTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7891EB5B82A000D00A4 /* StylistTests.swift */; }; @@ -144,6 +145,7 @@ 52D6D9F01BEFFFBE002C0205 /* Stylist.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stylist.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6E17AEA69B829DF29439909B /* Pods_Stylist_macOS_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Stylist_macOS_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 77B3C447D96463E91495C16D /* Pods-Stylist-tvOS Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stylist-tvOS Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Stylist-tvOS Tests/Pods-Stylist-tvOS Tests.debug.xcconfig"; sourceTree = ""; }; + 829ED8B221F0B85F00A572A2 /* common.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = common.yml; sourceTree = ""; }; 859CDAE0E43DE87EEAF810A4 /* Pods-Stylist-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stylist-macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Stylist-macOS/Pods-Stylist-macOS.debug.xcconfig"; sourceTree = ""; }; 8933C7841EB5B820000D00A4 /* Stylist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stylist.swift; sourceTree = ""; }; 8933C7891EB5B82A000D00A4 /* StylistTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StylistTests.swift; sourceTree = ""; }; @@ -371,6 +373,7 @@ CC68ECD01F47A1540063AE1F /* Style.yaml */, CC0B571E20B568140022E84E /* SectionView.swift */, CC8C104A20C4F1D1001E593B /* ExampleViewController.swift */, + 829ED8B221F0B85F00A572A2 /* common.yml */, ); path = Example; sourceTree = ""; @@ -592,6 +595,7 @@ CC23851B1F478E8A00FB0073 /* LaunchScreen.storyboard in Resources */, CC2385181F478E8A00FB0073 /* Assets.xcassets in Resources */, CC2385161F478E8A00FB0073 /* Main.storyboard in Resources */, + 829ED8B321F0B85F00A572A2 /* common.yml in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Stylist.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Stylist.xcodeproj/project.xcworkspace/contents.xcworkspacedata old mode 100644 new mode 100755 diff --git a/Stylist.xcodeproj/xcshareddata/xcschemes/Stylist-iOS.xcscheme b/Stylist.xcodeproj/xcshareddata/xcschemes/Stylist-iOS.xcscheme old mode 100644 new mode 100755 diff --git a/Stylist.xcodeproj/xcshareddata/xcschemes/Stylist-tvOS.xcscheme b/Stylist.xcodeproj/xcshareddata/xcschemes/Stylist-tvOS.xcscheme old mode 100644 new mode 100755 diff --git a/Stylist/Stylist.swift b/Stylist/Stylist.swift old mode 100644 new mode 100755 index 5eda109..8b968aa --- a/Stylist/Stylist.swift +++ b/Stylist/Stylist.swift @@ -187,6 +187,55 @@ public class Stylist { } return fileWatcher } + + public func localMergeWatch(urls: [URL], animateChanges: Bool = true, parsingError: @escaping (ThemeError) -> Void) -> [FileWatcherProtocol] { + var fileWatchers = [FileWatcherProtocol]() + + var hasLoaded = false + func updateThemes() { + let stylist = self + do { + let theme = try Theme(paths: urls.map { $0.path }) + self.themes[urls.map { $0.path }.joined()] = theme + stylist.apply(theme: theme, animateChanges: animateChanges && hasLoaded) + hasLoaded = true + } catch let error as ThemeError { + parsingError(error) + } catch { + // unknown error occured + } + } + + var fileUpdatedCount = 0 + for url in urls { + let fileWatcher: FileWatcherProtocol + if url.isFileURL { + fileWatcher = FileWatcher.Local(path: url.path) + } else { + parsingError(ThemeError.remoteFileWatcherNotSupported) + break + } + + do { + try fileWatcher.start { result in + switch result { + case .noChanges: + break + case .updated: + fileUpdatedCount += 1 + if fileUpdatedCount >= urls.count { + updateThemes() + } + } + } + } catch { + parsingError(ThemeError.notFound) + } + + fileWatchers.append(fileWatcher) + } + return fileWatchers + } } struct WeakContainer { diff --git a/Stylist/Theme.swift b/Stylist/Theme.swift old mode 100644 new mode 100755 index d581981..2e3d0fc --- a/Stylist/Theme.swift +++ b/Stylist/Theme.swift @@ -34,6 +34,33 @@ extension Theme { try self.init(data: data) } + public init(paths: [String]) throws { + + var mergedDictionary = ["variables": [:], "styles": [:]] + + for path in paths { + guard let data = FileManager.default.contents(atPath: path) else { + throw ThemeError.notFound + } + guard let string = String(data: data, encoding: .utf8) else { + throw ThemeError.decodingError + } + let yaml = try Yams.load(yaml: string) + guard let dictionary = yaml as? [String: Any] else { + throw ThemeError.decodingError + } + let variables: [String: Any] = dictionary["variables"] as? [String: Any] ?? [:] + let stylesDictionary = (dictionary["styles"] as? [String: Any]) ?? [:] + + mergedDictionary["variables"] = (mergedDictionary["variables"] as? [String: Any])?.merging(variables, uniquingKeysWith: { (current, _) in current }) ?? [:] + + mergedDictionary["styles"] = (mergedDictionary["styles"] as? [String: Any])?.merging(stylesDictionary, uniquingKeysWith: { (current, _) in current }) ?? [:] + + } + + try self.init(dictionary: mergedDictionary) + } + public init(data: Data) throws { guard let string = String(data: data, encoding: .utf8) else { throw ThemeError.decodingError From 49ee057c9e5d9e4b730a8274080b9fe0b3a91286 Mon Sep 17 00:00:00 2001 From: Andy Jacobs Date: Fri, 18 Jan 2019 07:53:01 +0100 Subject: [PATCH 2/2] Add ThemeError.remoteFileWatcherNotSupported --- Stylist/ThemeError.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Stylist/ThemeError.swift b/Stylist/ThemeError.swift index 72234fe..1ef2e9d 100644 --- a/Stylist/ThemeError.swift +++ b/Stylist/ThemeError.swift @@ -19,4 +19,5 @@ public enum ThemeError: Error, Equatable { case invalidSizeClass(name: String, sizeClass: String) case invalidStyleContext(String) case invalidStyleSelector(String) + case remoteFileWatcherNotSupported }