diff --git a/README.md b/README.md index 58c7579..4f42f23 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Each object inside the JSON file should contain the name of the UIView as a key This would apply HelveticaNeue-Bold with size 20 to all the UIButtons except the ones contained inside the LoginView class in your app. -Custom classes must be namespaced by the name of the module they are contained in. e.g. `StyleKitDemo.SKTextField` +Custom classes can be namespaced by the name of the module they are contained in. e.g. `StyleKitDemo.SKTextField` ### Aliases diff --git a/StyleKit.xcodeproj/project.pbxproj b/StyleKit.xcodeproj/project.pbxproj index 1b8a6f9..3778ff9 100644 --- a/StyleKit.xcodeproj/project.pbxproj +++ b/StyleKit.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 059932DC1D59E0DF0085E522 /* SKTryCatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 059932DB1D59E0DF0085E522 /* SKTryCatch.m */; }; 059932DD1D59E1950085E522 /* SKTryCatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 059932D81D59E09D0085E522 /* SKTryCatch.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3B7872691FB1C3BB00A87FB2 /* ParagraphStyleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7872681FB1C3BB00A87FB2 /* ParagraphStyleHelper.swift */; }; + 3BD119FA1FB1B6EB000980B0 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 3BD119F91FB1B6EB000980B0 /* README.md */; }; 992723731D5B36D700B74CDD /* UIAppearance+Swift.h in Headers */ = {isa = PBXBuildFile; fileRef = 992723711D5B36D700B74CDD /* UIAppearance+Swift.h */; settings = {ATTRIBUTES = (Public, ); }; }; 992723741D5B36D700B74CDD /* UIAppearance+Swift.m in Sources */ = {isa = PBXBuildFile; fileRef = 992723721D5B36D700B74CDD /* UIAppearance+Swift.m */; }; 996775991D5A2E8E005DA08A /* StyleParsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996775981D5A2E8E005DA08A /* StyleParsable.swift */; }; @@ -56,6 +58,8 @@ /* Begin PBXFileReference section */ 059932D81D59E09D0085E522 /* SKTryCatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SKTryCatch.h; sourceTree = ""; }; 059932DB1D59E0DF0085E522 /* SKTryCatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SKTryCatch.m; sourceTree = ""; }; + 3B7872681FB1C3BB00A87FB2 /* ParagraphStyleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParagraphStyleHelper.swift; sourceTree = ""; }; + 3BD119F91FB1B6EB000980B0 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 992723711D5B36D700B74CDD /* UIAppearance+Swift.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAppearance+Swift.h"; sourceTree = ""; }; 992723721D5B36D700B74CDD /* UIAppearance+Swift.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAppearance+Swift.m"; sourceTree = ""; }; 996775981D5A2E8E005DA08A /* StyleParsable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyleParsable.swift; sourceTree = ""; }; @@ -126,6 +130,7 @@ A10FD6901D53FA2600341EDD = { isa = PBXGroup; children = ( + 3BD119F91FB1B6EB000980B0 /* README.md */, A10FD69B1D53FA2600341EDD /* Products */, A10FD69C1D53FA2600341EDD /* StyleKit */, A10FD6A81D53FA2600341EDD /* StyleKitTests */, @@ -178,6 +183,7 @@ 9967759E1D5A3691005DA08A /* ColorHelper.swift */, 996775A01D5A38E8005DA08A /* ControlStateHelper.swift */, 9967759C1D5A3607005DA08A /* FontHelper.swift */, + 3B7872681FB1C3BB00A87FB2 /* ParagraphStyleHelper.swift */, ); path = Helpers; sourceTree = ""; @@ -344,6 +350,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3BD119FA1FB1B6EB000980B0 /* README.md in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -378,6 +385,7 @@ A1E4676E1D59494C003B1AB2 /* SKLogger.swift in Sources */, A10FD6BF1D55357400341EDD /* FileLoader.swift in Sources */, 059932DC1D59E0DF0085E522 /* SKTryCatch.m in Sources */, + 3B7872691FB1C3BB00A87FB2 /* ParagraphStyleHelper.swift in Sources */, A10FD6C81D556A9C00341EDD /* Stylist.swift in Sources */, A104CE431D580D6A00626F2A /* ColorExtensions.swift in Sources */, 9967759F1D5A3691005DA08A /* ColorHelper.swift in Sources */, diff --git a/StyleKit/Helpers/ParagraphStyleHelper.swift b/StyleKit/Helpers/ParagraphStyleHelper.swift new file mode 100644 index 0000000..4dc3b4f --- /dev/null +++ b/StyleKit/Helpers/ParagraphStyleHelper.swift @@ -0,0 +1,98 @@ +// +// ParagraphStyleHelper.swift +// StyleKit +// +// Created by Jakub Petrík on 11/7/17. +// Copyright © 2017 Bernard Gatt. All rights reserved. +// + +import Foundation + +public enum ParagraphStyleHelper { + public static func parseParagraphStyle(_ name: String, value: [String: Any]) -> NSParagraphStyle? { + guard name == "NSParagraphStyle" else { return nil } + + let style = NSMutableParagraphStyle() + if let alignmentValue = value["alignment"] as? String { + style.alignment = parseTextAlignment(alignmentValue) + } + if let indent = value["firstLineHeadIndent"] as? Double { + style.firstLineHeadIndent = CGFloat(indent) + } + if let indent = value["headIndent"] as? Double { + style.headIndent = CGFloat(indent) + } + if let indent = value["tailIndent"] as? Double { + style.tailIndent = CGFloat(indent) + } + if let breakMode = value["lineBreakMode"] as? String { + style.lineBreakMode = parseLineBreakMode(breakMode) + } + if let maxLineHeight = value["maximumLineHeight"] as? Double { + style.maximumLineHeight = CGFloat(maxLineHeight) + } + if let minLineHeight = value["minimumLineHeight"] as? Double { + style.minimumLineHeight = CGFloat(minLineHeight) + } + if let spacing = value["lineSpacing"] as? Double { + style.lineSpacing = CGFloat(spacing) + } + if let spacing = value["paragraphSpacing"] as? Double { + style.paragraphSpacing = CGFloat(spacing) + } + if let spacing = value["paragraphSpacingBefore"] as? Double { + style.paragraphSpacingBefore = CGFloat(spacing) + } + if let writingDirection = value["baseWritingDirection"] as? String { + style.baseWritingDirection = parseWritingDirection(writingDirection) + } + if let height = value["lineHeightMultiple"] as? Double { + style.lineHeightMultiple = CGFloat(height) + } + + return style.copy() as? NSParagraphStyle + } + + private static func parseTextAlignment(_ value: String) -> NSTextAlignment { + switch value { + case "left": + return .left + case "right": + return .right + case "center": + return .center + case "justified": + return .justified + default: + return .natural + } + } + + private static func parseLineBreakMode(_ value: String) -> NSLineBreakMode { + switch value { + case "byCharWrapping": + return .byCharWrapping + case "byClipping": + return .byClipping + case "byTruncatingHead": + return .byTruncatingHead + case "byTruncatingTail": + return .byTruncatingTail + case "byTruncatingMiddle": + return .byTruncatingMiddle + default: + return .byWordWrapping + } + } + + private static func parseWritingDirection(_ value: String) -> NSWritingDirection { + switch value { + case "leftToRight": + return .leftToRight + case "rightToLeft": + return .rightToLeft + default: + return .natural + } + } +} diff --git a/StyleKit/StyleKit.swift b/StyleKit/StyleKit.swift index 28b9fb5..0894486 100644 --- a/StyleKit/StyleKit.swift +++ b/StyleKit/StyleKit.swift @@ -4,7 +4,7 @@ public class StyleKit { let stylist: Stylist - public init?(fileUrl: URL, styleParser: StyleParsable? = nil, logLevel: SKLogLevel = .error) { + public init?(fileUrl: URL, styleParser: StyleParsable? = nil, moduleName: String? = nil, logLevel: SKLogLevel = .error) { let log = SKLogger.defaultInstance() log.setup(logLevel, showLogIdentifier: false, @@ -17,7 +17,7 @@ public class StyleKit { let fileLoader = FileLoader.init(fileUrl: fileUrl) if let data = fileLoader.load() { - self.stylist = Stylist.init(data: data, styleParser: styleParser) + self.stylist = Stylist.init(data: data, styleParser: styleParser, moduleName: moduleName) } else { return nil } diff --git a/StyleKit/StyleParser.swift b/StyleKit/StyleParser.swift index d067659..a027039 100644 --- a/StyleKit/StyleParser.swift +++ b/StyleKit/StyleParser.swift @@ -9,9 +9,13 @@ class StyleParser: StyleParsable { return font } else if let color = ColorHelper.parseColor(value) { return color + } + } else if let value = value as? [String: Any] { + if let paragraphStyle = ParagraphStyleHelper.parseParagraphStyle(name, value: value) { + return paragraphStyle } } return value } -} \ No newline at end of file +} diff --git a/StyleKit/Stylist.swift b/StyleKit/Stylist.swift index 15a7d49..d7bae4b 100644 --- a/StyleKit/Stylist.swift +++ b/StyleKit/Stylist.swift @@ -8,10 +8,11 @@ class Stylist { let data: Style let aliases: Style let styleParser: StyleParsable + let moduleName: String? var currentComponent: AnyClass? var viewStack = [UIAppearanceContainer.Type]() - init(data: Style, styleParser: StyleParsable?) { + init(data: Style, styleParser: StyleParsable?, moduleName: String?) { self.styleParser = styleParser ?? StyleParser() var tmpAlias = Style() @@ -27,6 +28,7 @@ class Stylist { self.data = tmpData self.aliases = tmpAlias + self.moduleName = moduleName } func apply() { @@ -40,8 +42,8 @@ class Stylist { private func validateAndApply(_ data: Style) { for (key, value) in data { - if let value = value as? Style , NSClassFromString(key) != nil { - if selectCurrentComponent(key), let appearanceContainer = self.currentComponent! as? UIAppearanceContainer.Type { + if let value = value as? Style, let component = resolveComponent(from: key) { + if selectCurrentComponent(component), let appearanceContainer = self.currentComponent! as? UIAppearanceContainer.Type { viewStack.append(appearanceContainer) } validateAndApply(value) @@ -63,16 +65,25 @@ class Stylist { } } } - - private func selectCurrentComponent(_ name: String) -> Bool { - - SKLogger.debug("Switching to: \(name)") - - guard let currentComponent = NSClassFromString(name) else { - SKLogger.debug("Component \(name) cannot be selected") - return false + + private typealias Component = (klass: AnyClass, name: String) + + private func resolveComponent(from key: String) -> Component? { + let resolved: Component? + if let klass = NSClassFromString(key) { + resolved = (klass, key) + } else if let name = moduleName.flatMap({ "\($0).\(key)" }) + , let klass = NSClassFromString(name) { + resolved = (klass, name) + } else { + resolved = nil } - self.currentComponent = currentComponent + return resolved + } + + private func selectCurrentComponent(_ component: Component) -> Bool { + SKLogger.debug("Switching to: \(component.name)") + self.currentComponent = component.klass return true } @@ -92,11 +103,11 @@ class Stylist { if let styles = object as? Stylist.Style { var stylesToApply = Stylist.Style() for (style, value) in styles { - stylesToApply[style] = styleParser.getStyle(forName: name, value: self.getValue(value)) + stylesToApply[style] = styleParser.getStyle(forName: style, value: getValue(value)) } - callAppearanceSelector(selectorName, valueOne: stylesToApply as AnyObject?, valueTwo: state) + callAppearanceSelector(selectorName, valueOne: stylesToApply as AnyObject, valueTwo: state) } else { - let value = styleParser.getStyle(forName: name, value: self.getValue(object)) + let value = styleParser.getStyle(forName: name, value: getValue(object)) callAppearanceSelector(selectorName, valueOne: value, valueTwo: state) } } diff --git a/StyleKitDemo/StyleKitDemo/AppDelegate.swift b/StyleKitDemo/StyleKitDemo/AppDelegate.swift index 90a6ab8..dc01f58 100644 --- a/StyleKitDemo/StyleKitDemo/AppDelegate.swift +++ b/StyleKitDemo/StyleKitDemo/AppDelegate.swift @@ -15,11 +15,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { //StyleKit(fileUrl: styleFile, styleParser: StyleParser())?.apply() // Uses default style parser - StyleKit(fileUrl: styleFile, logLevel: .debug)?.apply() + StyleKit(fileUrl: styleFile, moduleName: "StyleKitDemo", logLevel: .debug)?.apply() } - - return true } diff --git a/StyleKitDemo/StyleKitDemo/Base.lproj/Main.storyboard b/StyleKitDemo/StyleKitDemo/Base.lproj/Main.storyboard index 517b961..b967e5c 100644 --- a/StyleKitDemo/StyleKitDemo/Base.lproj/Main.storyboard +++ b/StyleKitDemo/StyleKitDemo/Base.lproj/Main.storyboard @@ -1,9 +1,14 @@ - - + + + + + - + + + @@ -11,21 +16,22 @@ - + - + - + - + - + - + - + - + - + - +