diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d64d8da --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: objective-c + +xcode_project: StyleKit.xcodeproj +xcode_scheme: StyleKit +osx_image: xcode8.3 +xcode_sdk: macosx10.11 + +script: + - xcodebuild clean build test -project StyleKit.xcodeproj -scheme StyleKit -destination 'platform=iOS Simulator,name=iPhone 7,OS=10.3' diff --git a/README.md b/README.md index 7c19c74..2dbf132 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ ![alt text](https://i.imgur.com/IqDIU4q.png "StyleKit - A powerful & easy to use styling framework written in Swift") -[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Build Status](https://travis-ci.org/146BC/StyleKit.svg?branch=develop)](https://travis-ci.org/146BC/StyleKit) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) StyleKit is a microframework that enables you to style your applications using a simple JSON file. Behind the scenes, StyleKit uses [UIAppearance](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAppearance_Protocol/) and some selector magic to apply the styles. You can also customize the parser for greater flexibility. -###How does it work? +### How does it work? -####Create a JSON file in the following format +#### Create a JSON file in the following format ```js { + "@headingFont": "HelveticaNeue-Bold:30.0", "UILabel": { - "font": "HelveticaNeue-Bold:30.0", + "font": "@headingFont", "backgroundColor": "#000FFF" }, "StyleKitDemo.SKView": { @@ -30,7 +31,7 @@ StyleKit is a microframework that enables you to style your applications using a "StyleKitDemo.SKNavigationBar": { "titleTextAttributes": { "NSColor": "#000FFF", - "NSFont": "HelveticaNeue-Bold:30.0" + "NSFont": "@headingFont" } }, "StyleKitDemo.SKTextField": { @@ -39,7 +40,7 @@ StyleKit is a microframework that enables you to style your applications using a } } ``` -####Load JSON file +#### Load JSON file *AppDelegate.swift* @@ -58,7 +59,7 @@ func application(_ application: UIApplication, On application launch the JSON file will be loaded and the styles applied. -###The JSON file structure +### The JSON file structure Each object inside the JSON file should contain the name of the UIView as a key and the object inside should either contain the properties/functions that need to be set/called or another UIView, this will give you the ability to apply styles on views when contained in other views, an example of this would be the following. @@ -79,7 +80,25 @@ This would apply HelveticaNeue-Bold with size 20 to all the UIButtons except the Custom classes must be namespaced by the name of the module they are contained in. e.g. `StyleKitDemo.SKTextField` -###Bring Your Own Parser +### Aliases + +```js +{ + "@mainFont": "HelveticaNeue-Bold:20.0", + "@primaryColor": "#000FFF", + "UIButton": { + "font": "@mainFont" + }, + "MyApp.LoginView": { + "UIButton": { + "font": "HelveticaNeue-Light:25.0", + "titleColor:normal": "@primaryColor" + } + } +} +``` + +### Bring Your Own Parser StyleKit's initialiser supports passing a custom parser which should conform to the `StyleParsable` protocol. @@ -115,7 +134,7 @@ func application(_ application: UIApplication, } ``` -###Logging### +### Logging By default, StyleKit will log any errors to the console. To customise the level of logging, you can pass a logLevel parameter as follows: @@ -131,23 +150,23 @@ The levels of logging are: * ```.none``` -###How to install? +### How to install? -####Carthage +#### Carthage -#####Swift 2.2 & 2.3 +##### Swift 2.2 & 2.3 ```ogdl github "146BC/StyleKit" ~> 0.4 ``` -#####Swift 3 +##### Swift 3 ```ogdl -github "146BC/StyleKit" ~> 0.5 +github "146BC/StyleKit" ~> 0.6 ``` -####CocoaPods +#### CocoaPods Add the 146BC Source @@ -156,14 +175,14 @@ source 'https://github.com/146BC/Specs.git' source 'https://github.com/CocoaPods/Specs.git' ``` -#####Swift 2.2 & 2.3 +##### Swift 2.2 & 2.3 ```ruby pod 'StyleKit', '~> 0.4' ``` -#####Swift 3 +##### Swift 3 ```ruby -pod 'StyleKit', '~> 0.5' -``` \ No newline at end of file +pod 'StyleKit', '~> 0.6' +``` diff --git a/StyleKit.xcodeproj/project.pbxproj b/StyleKit.xcodeproj/project.pbxproj index 8d5b17e..7117634 100644 --- a/StyleKit.xcodeproj/project.pbxproj +++ b/StyleKit.xcodeproj/project.pbxproj @@ -15,6 +15,11 @@ 9967759D1D5A3607005DA08A /* FontHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9967759C1D5A3607005DA08A /* FontHelper.swift */; }; 9967759F1D5A3691005DA08A /* ColorHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9967759E1D5A3691005DA08A /* ColorHelper.swift */; }; 996775A11D5A38E8005DA08A /* ControlStateHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996775A01D5A38E8005DA08A /* ControlStateHelper.swift */; }; + A100097F1DB02AF300C761CC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A100097E1DB02AF300C761CC /* AppDelegate.swift */; }; + A10009961DB02D9D00C761CC /* SKLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10009931DB02D9D00C761CC /* SKLabel.swift */; }; + A10009971DB02D9D00C761CC /* SKView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10009941DB02D9D00C761CC /* SKView.swift */; }; + A10009981DB02D9D00C761CC /* SKButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10009951DB02D9D00C761CC /* SKButton.swift */; }; + A10009A01DB0313200C761CC /* style.json in Resources */ = {isa = PBXBuildFile; fileRef = A10009911DB02D8D00C761CC /* style.json */; }; A104CE3F1D57E9D200626F2A /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A104CE3E1D57E9D200626F2A /* StringExtensions.swift */; }; A104CE431D580D6A00626F2A /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A104CE421D580D6A00626F2A /* ColorExtensions.swift */; }; A10FD69E1D53FA2600341EDD /* StyleKit.h in Headers */ = {isa = PBXBuildFile; fileRef = A10FD69D1D53FA2600341EDD /* StyleKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -26,11 +31,19 @@ A155AE911D5A9A8700C1BC28 /* HelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A155AE901D5A9A8700C1BC28 /* HelperTests.swift */; }; A1C5156B1D5A7DC00016BF1E /* success-style.json in Resources */ = {isa = PBXBuildFile; fileRef = A1C5156A1D5A7DC00016BF1E /* success-style.json */; }; A1C5156D1D5A7DE20016BF1E /* fail-style.json in Resources */ = {isa = PBXBuildFile; fileRef = A1C5156C1D5A7DE20016BF1E /* fail-style.json */; }; + A1C5E1291DAAF1BB0026464A /* StyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C5E1281DAAF1BB0026464A /* StyleTests.swift */; }; A1D8B1221D57C28A00355807 /* StyleParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D8B1211D57C28A00355807 /* StyleParser.swift */; }; A1E4676E1D59494C003B1AB2 /* SKLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E4676D1D59494C003B1AB2 /* SKLogger.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + A100098E1DB02B1A00C761CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A10FD6911D53FA2600341EDD /* Project object */; + proxyType = 1; + remoteGlobalIDString = A100097B1DB02AF300C761CC; + remoteInfo = StyleKitTestsApp; + }; A10FD6A61D53FA2600341EDD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A10FD6911D53FA2600341EDD /* Project object */; @@ -49,6 +62,13 @@ 9967759C1D5A3607005DA08A /* FontHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FontHelper.swift; sourceTree = ""; }; 9967759E1D5A3691005DA08A /* ColorHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorHelper.swift; sourceTree = ""; }; 996775A01D5A38E8005DA08A /* ControlStateHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlStateHelper.swift; sourceTree = ""; }; + A100097C1DB02AF300C761CC /* StyleKitTestsHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StyleKitTestsHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A100097E1DB02AF300C761CC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + A100098A1DB02AF300C761CC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A10009911DB02D8D00C761CC /* style.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = style.json; path = Style/style.json; sourceTree = ""; }; + A10009931DB02D9D00C761CC /* SKLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKLabel.swift; sourceTree = ""; }; + A10009941DB02D9D00C761CC /* SKView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKView.swift; sourceTree = ""; }; + A10009951DB02D9D00C761CC /* SKButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKButton.swift; sourceTree = ""; }; A104CE3E1D57E9D200626F2A /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; A104CE421D580D6A00626F2A /* ColorExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorExtensions.swift; sourceTree = ""; }; A10FD69A1D53FA2600341EDD /* StyleKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StyleKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -63,11 +83,19 @@ A155AE901D5A9A8700C1BC28 /* HelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelperTests.swift; sourceTree = ""; }; A1C5156A1D5A7DC00016BF1E /* success-style.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "success-style.json"; path = "Style/success-style.json"; sourceTree = ""; }; A1C5156C1D5A7DE20016BF1E /* fail-style.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "fail-style.json"; path = "Style/fail-style.json"; sourceTree = ""; }; + A1C5E1281DAAF1BB0026464A /* StyleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyleTests.swift; sourceTree = ""; }; A1D8B1211D57C28A00355807 /* StyleParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyleParser.swift; sourceTree = ""; }; A1E4676D1D59494C003B1AB2 /* SKLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKLogger.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + A10009791DB02AF300C761CC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; A10FD6961D53FA2600341EDD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -86,6 +114,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + A100097D1DB02AF300C761CC /* StyleKitTestsHost */ = { + isa = PBXGroup; + children = ( + A100097E1DB02AF300C761CC /* AppDelegate.swift */, + A100098A1DB02AF300C761CC /* Info.plist */, + ); + path = StyleKitTestsHost; + sourceTree = ""; + }; A10FD6901D53FA2600341EDD = { isa = PBXGroup; children = ( @@ -100,6 +137,7 @@ children = ( A10FD69A1D53FA2600341EDD /* StyleKit.framework */, A10FD6A41D53FA2600341EDD /* StyleKitTests.xctest */, + A100097C1DB02AF300C761CC /* StyleKitTestsHost.app */, ); name = Products; sourceTree = ""; @@ -110,12 +148,12 @@ A10FD6C01D55376600341EDD /* Helpers */, A1D8B11C1D57BD8900355807 /* Utilities */, A10FD6BE1D55357400341EDD /* FileLoader.swift */, - A10FD69F1D53FA2600341EDD /* Info.plist */, - A10FD69D1D53FA2600341EDD /* StyleKit.h */, A10FD6C51D556A1F00341EDD /* StyleKit.swift */, 996775981D5A2E8E005DA08A /* StyleParsable.swift */, A1D8B1211D57C28A00355807 /* StyleParser.swift */, A10FD6C71D556A9C00341EDD /* Stylist.swift */, + A10FD69D1D53FA2600341EDD /* StyleKit.h */, + A10FD69F1D53FA2600341EDD /* Info.plist */, ); path = StyleKit; sourceTree = ""; @@ -123,7 +161,10 @@ A10FD6A81D53FA2600341EDD /* StyleKitTests */ = { isa = PBXGroup; children = ( + A100097D1DB02AF300C761CC /* StyleKitTestsHost */, + A1C5E1231DA91B7C0026464A /* UI Extensions */, A1C515691D5A7D2B0016BF1E /* Style */, + A1C5E1281DAAF1BB0026464A /* StyleTests.swift */, A10FD6A91D53FA2600341EDD /* JSONLoadTests.swift */, A155AE901D5A9A8700C1BC28 /* HelperTests.swift */, A10FD6AB1D53FA2600341EDD /* Info.plist */, @@ -146,10 +187,21 @@ children = ( A1C5156A1D5A7DC00016BF1E /* success-style.json */, A1C5156C1D5A7DE20016BF1E /* fail-style.json */, + A10009911DB02D8D00C761CC /* style.json */, ); name = Style; sourceTree = ""; }; + A1C5E1231DA91B7C0026464A /* UI Extensions */ = { + isa = PBXGroup; + children = ( + A10009931DB02D9D00C761CC /* SKLabel.swift */, + A10009941DB02D9D00C761CC /* SKView.swift */, + A10009951DB02D9D00C761CC /* SKButton.swift */, + ); + path = "UI Extensions"; + sourceTree = ""; + }; A1D8B11C1D57BD8900355807 /* Utilities */ = { isa = PBXGroup; children = ( @@ -180,6 +232,23 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + A100097B1DB02AF300C761CC /* StyleKitTestsHost */ = { + isa = PBXNativeTarget; + buildConfigurationList = A100098D1DB02AF300C761CC /* Build configuration list for PBXNativeTarget "StyleKitTestsHost" */; + buildPhases = ( + A10009781DB02AF300C761CC /* Sources */, + A10009791DB02AF300C761CC /* Frameworks */, + A100097A1DB02AF300C761CC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StyleKitTestsHost; + productName = StyleKitTestsApp; + productReference = A100097C1DB02AF300C761CC /* StyleKitTestsHost.app */; + productType = "com.apple.product-type.application"; + }; A10FD6991D53FA2600341EDD /* StyleKit */ = { isa = PBXNativeTarget; buildConfigurationList = A10FD6AE1D53FA2600341EDD /* Build configuration list for PBXNativeTarget "StyleKit" */; @@ -210,6 +279,7 @@ ); dependencies = ( A10FD6A71D53FA2600341EDD /* PBXTargetDependency */, + A100098F1DB02B1A00C761CC /* PBXTargetDependency */, ); name = StyleKitTests; productName = StyleKitTests; @@ -222,10 +292,14 @@ A10FD6911D53FA2600341EDD /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0730; + LastSwiftUpdateCheck = 0800; LastUpgradeCheck = 0800; ORGANIZATIONNAME = "Bernard Gatt"; TargetAttributes = { + A100097B1DB02AF300C761CC = { + CreatedOnToolsVersion = 8.0; + ProvisioningStyle = Automatic; + }; A10FD6991D53FA2600341EDD = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 0800; @@ -233,6 +307,7 @@ A10FD6A31D53FA2600341EDD = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 0800; + TestTargetID = A100097B1DB02AF300C761CC; }; }; }; @@ -242,6 +317,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = A10FD6901D53FA2600341EDD; productRefGroup = A10FD69B1D53FA2600341EDD /* Products */; @@ -250,11 +326,19 @@ targets = ( A10FD6991D53FA2600341EDD /* StyleKit */, A10FD6A31D53FA2600341EDD /* StyleKitTests */, + A100097B1DB02AF300C761CC /* StyleKitTestsHost */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + A100097A1DB02AF300C761CC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; A10FD6981D53FA2600341EDD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -266,6 +350,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A10009A01DB0313200C761CC /* style.json in Resources */, A1C5156B1D5A7DC00016BF1E /* success-style.json in Resources */, A1C5156D1D5A7DE20016BF1E /* fail-style.json in Resources */, ); @@ -274,6 +359,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + A10009781DB02AF300C761CC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A100097F1DB02AF300C761CC /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A10FD6951D53FA2600341EDD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -298,7 +391,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A10009971DB02D9D00C761CC /* SKView.swift in Sources */, + A10009981DB02D9D00C761CC /* SKButton.swift in Sources */, A10FD6AA1D53FA2600341EDD /* JSONLoadTests.swift in Sources */, + A1C5E1291DAAF1BB0026464A /* StyleTests.swift in Sources */, + A10009961DB02D9D00C761CC /* SKLabel.swift in Sources */, A155AE911D5A9A8700C1BC28 /* HelperTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -306,6 +403,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + A100098F1DB02B1A00C761CC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A100097B1DB02AF300C761CC /* StyleKitTestsHost */; + targetProxy = A100098E1DB02B1A00C761CC /* PBXContainerItemProxy */; + }; A10FD6A71D53FA2600341EDD /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = A10FD6991D53FA2600341EDD /* StyleKit */; @@ -314,6 +416,37 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + A100098B1DB02AF300C761CC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + INFOPLIST_FILE = "$(SRCROOT)/StyleKitTests/StyleKitTestsHost/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = OneFourSixBC.StyleKitTestsHost; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + A100098C1DB02AF300C761CC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + INFOPLIST_FILE = "$(SRCROOT)/StyleKitTests/StyleKitTestsHost/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = OneFourSixBC.StyleKitTestsHost; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; A10FD6AC1D53FA2600341EDD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -471,6 +604,7 @@ PRODUCT_BUNDLE_IDENTIFIER = BernardGatt.StyleKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StyleKitTestsHost.app/StyleKitTestsHost"; }; name = Debug; }; @@ -482,12 +616,22 @@ PRODUCT_BUNDLE_IDENTIFIER = BernardGatt.StyleKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StyleKitTestsHost.app/StyleKitTestsHost"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + A100098D1DB02AF300C761CC /* Build configuration list for PBXNativeTarget "StyleKitTestsHost" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A100098B1DB02AF300C761CC /* Debug */, + A100098C1DB02AF300C761CC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; A10FD6941D53FA2600341EDD /* Build configuration list for PBXProject "StyleKit" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/StyleKit/Stylist.swift b/StyleKit/Stylist.swift index b3559a0..15a7d49 100644 --- a/StyleKit/Stylist.swift +++ b/StyleKit/Stylist.swift @@ -6,20 +6,34 @@ class Stylist { typealias setValueForControlState = @convention(c) (AnyObject, Selector, AnyObject, UInt) -> Void let data: Style + let aliases: Style let styleParser: StyleParsable var currentComponent: AnyClass? var viewStack = [UIAppearanceContainer.Type]() init(data: Style, styleParser: StyleParsable?) { self.styleParser = styleParser ?? StyleParser() - self.data = data + + var tmpAlias = Style() + var tmpData = Style() + + for (key, value) in data { + if key.hasPrefix("@") { + tmpAlias[key] = value + } else { + tmpData[key] = value + } + } + + self.data = tmpData + self.aliases = tmpAlias } func apply() { SKTryCatch.try( { self.validateAndApply(self.data) }, catch: { exception in - SKLogger.severe("There was an error while applying styles: \(exception)") + SKLogger.severe("There was an error while applying styles: \(exception.debugDescription)") }, finallyBlock: nil) } @@ -44,7 +58,7 @@ class Stylist { SKLogger.error("Style \(value) not applied on \(key) for \(self.currentComponent.debugDescription)") } }, catch: { exception in - SKLogger.error("Style \(value) not applied on \(key) for \(self.currentComponent.debugDescription) Error \(exception)") + SKLogger.error("Style \(value) not applied on \(key) for \(self.currentComponent.debugDescription) Error \(exception.debugDescription)") }, finallyBlock: nil) } } @@ -78,14 +92,34 @@ 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: value) + stylesToApply[style] = styleParser.getStyle(forName: name, value: self.getValue(value)) } callAppearanceSelector(selectorName, valueOne: stylesToApply as AnyObject?, valueTwo: state) } else { - let value = styleParser.getStyle(forName: name, value: object) + let value = styleParser.getStyle(forName: name, value: self.getValue(object)) callAppearanceSelector(selectorName, valueOne: value, valueTwo: state) } + } + + /** + Checks if the value is an alias, and if it is resolves to the correct value. + Otherwise, it will return the passed value. + + - Parameter value: The alias name or actual value + */ + private func getValue(_ value: AnyObject) -> AnyObject { + + guard let stringValue = value as? String, stringValue.hasPrefix("@") else { + return value + } + if let aliasValue = aliases[stringValue] { + return aliasValue + } else { + SKLogger.error("Value declared is using Alias \(value) which has not been defined") + + return value + } } private func callAppearanceSelector(_ selector: String, valueOne: AnyObject?, valueTwo: AnyObject?) { @@ -101,40 +135,39 @@ class Stylist { if valueOne == nil && valueTwo == nil { if isViewStackRelevant { if #available(iOS 9.0, *) { - _ = self.currentComponent?.appearance(whenContainedInInstancesOf: viewStack).perform(Selector(modifiedSelector)) + _ = (self.currentComponent!.appearance(whenContainedInInstancesOf: viewStack) as! NSObject).perform(Selector(modifiedSelector)) } else { - _ = self.currentComponent?.styleKitAppearanceWhenContained(within: viewStack).perform(Selector(modifiedSelector)) + _ = (self.currentComponent!.styleKitAppearanceWhenContained(within: viewStack) as! NSObject).perform(Selector(modifiedSelector)) } } else { - _ = self.currentComponent?.appearance().perform(Selector(modifiedSelector)) + _ = (self.currentComponent!.appearance() as! NSObject).perform(Selector(modifiedSelector)) } } else if valueOne != nil && valueTwo != nil { if isViewStackRelevant { if #available(iOS 9.0, *) { - let methodSignature = self.currentComponent!.appearance(whenContainedInInstancesOf: viewStack).method(for: Selector(modifiedSelector)) + let methodSignature = (self.currentComponent!.appearance(whenContainedInInstancesOf: viewStack) as! NSObject).method(for: Selector(modifiedSelector)) let callback = unsafeBitCast(methodSignature, to: setValueForControlState.self) callback(self.currentComponent!.appearance(whenContainedInInstancesOf: viewStack), Selector(modifiedSelector), valueOne!, valueTwo as! UInt) } else { - let methodSignature = self.currentComponent!.styleKitAppearanceWhenContained(within: viewStack).method(for: Selector(modifiedSelector)) + let methodSignature = (self.currentComponent!.styleKitAppearanceWhenContained(within: viewStack) as! NSObject).method(for: Selector(modifiedSelector)) let callback = unsafeBitCast(methodSignature, to: setValueForControlState.self) callback(self.currentComponent!.styleKitAppearanceWhenContained(within: viewStack), Selector(modifiedSelector), valueOne!, valueTwo as! UInt) } } else { - let methodSignature = self.currentComponent!.appearance().method(for: Selector(modifiedSelector)) + let methodSignature = (self.currentComponent!.appearance() as! NSObject).method(for: Selector(modifiedSelector)) let callback = unsafeBitCast(methodSignature, to: setValueForControlState.self) callback(self.currentComponent!.appearance(), Selector(modifiedSelector), valueOne!, valueTwo as! UInt) } } else if valueOne != nil { if isViewStackRelevant { if #available(iOS 9.0, *) { - _ = self.currentComponent?.appearance(whenContainedInInstancesOf: viewStack).perform(Selector(modifiedSelector), with: valueOne!) + _ = (self.currentComponent!.appearance(whenContainedInInstancesOf: viewStack) as! NSObject).perform(Selector(modifiedSelector), with: valueOne!) } else { - _ = self.currentComponent?.styleKitAppearanceWhenContained(within: viewStack).perform(Selector(modifiedSelector), with: valueOne!) + _ = self.currentComponent!.styleKitAppearanceWhenContained(within: viewStack).perform(Selector(modifiedSelector), with: valueOne!) } } else { - _ = self.currentComponent?.appearance().perform(Selector(modifiedSelector), with: valueOne!) + _ = (self.currentComponent!.appearance() as! NSObject).perform(Selector(modifiedSelector), with: valueOne!) } } } - } diff --git a/StyleKit/Utilities/SKLogger.swift b/StyleKit/Utilities/SKLogger.swift index 631e4b3..7fec0b5 100644 --- a/StyleKit/Utilities/SKLogger.swift +++ b/StyleKit/Utilities/SKLogger.swift @@ -114,7 +114,7 @@ internal class SKConsoleLogDestination: CustomDebugStringConvertible { if let threadName = Thread.current.name , !threadName.isEmpty { extendedDetails += "[" + threadName + "] " } - else if let queueName = String(validatingUTF8: logQueue!.label) , !queueName.isEmpty { + else if let label = logQueue?.label, let queueName = String(validatingUTF8: label) , !queueName.isEmpty { extendedDetails += "[" + queueName + "] " } else { diff --git a/StyleKitDemo/StyleKitDemo/Style/style.json b/StyleKitDemo/StyleKitDemo/Style/style.json index e78d1bb..ba105f4 100644 --- a/StyleKitDemo/StyleKitDemo/Style/style.json +++ b/StyleKitDemo/StyleKitDemo/Style/style.json @@ -1,41 +1,46 @@ { + "@mainFont": "HelveticaNeue-Bold:20.0", + "@secondaryFont": "HelveticaNeue-Medium:13.0", + "@primaryColor": "#cd0073", + "@secondaryColor": "#8000c6", + "@transparencyValue": 90, "StyleKitDemo.SKNavigationBar": { - "tintColor": "#000", + "tintColor": "@secondaryColor", "titleTextAttributes": { - "NSColor": "#99000000", - "NSFont": "HelveticaNeue-Light:18.0" + "NSColor": "@primaryColor", + "NSFont": "@mainFont" } }, "UITableView": { "UITableViewCell": { "UILabel": { - "font": "HelveticaNeue-Medium:13.0", + "font": "@secondaryFont", "color": "#000" } } }, "UITableViewCell": { "UILabel": { - "font": "HelveticaNeue-Medium:13.0", + "font": "@secondaryFont", "color": "#000" } }, "StyleKitDemo.SKButton": { - "backgroundColor:normal": "#cd0073", - "backgroundColor:highlighted": "#8000c6", + "backgroundColor:normal": "@primaryColor", + "backgroundColor:highlighted": "@secondaryColor", "titleColor:normal": "#fff", "titleColor:highlighted": "#fff", "font": "HelveticaNeue-Light:13.0", - "transparency":70 + "transparency": "@transparencyValue" }, "UIButton": { - "font": "HelveticaNeue-Medium:13.0", + "font": "@secondaryFont", "titleColor:normal": "#000", "titleColor:highlighted": "#555" }, "StyleKitDemo.SKLabel": { "font": "HelveticaNeue-Light:17.0", - "color": "#cd0073" + "color": "@primaryColor" }, "UILabel": { "font": "HelveticaNeue-Bold:15.0", @@ -49,7 +54,7 @@ "StyleKitDemo.SKView": { "StyleKitDemo.SKLabel": { "font": "HelveticaNeue-Bold:15.0", - "color": "#cd0073", + "color": "@primaryColor", "padding": [0,30,0,0], "backgroundColor": "#eee" } @@ -64,13 +69,13 @@ } }, "StyleKitDemo.SKTextField": { - "textColor": "#cd0073", + "textColor": "@primaryColor", "UILabel": { - "color": "#cd0073" + "color": "@primaryColor" } }, "UISegmentedControl": { - "tintColor": "#cd0073", + "tintColor": "@primaryColor", "titleTextAttributes:normal": { "NSColor": "#999", "NSFont": "HelveticaNeue-Light:15.0" @@ -81,6 +86,6 @@ } }, "UIActivityIndicatorView": { - "color": "#cd0073" + "color": "@primaryColor" } -} \ No newline at end of file +} diff --git a/StyleKitTests/JSONLoadTests.swift b/StyleKitTests/JSONLoadTests.swift index 1146a51..c839be3 100644 --- a/StyleKitTests/JSONLoadTests.swift +++ b/StyleKitTests/JSONLoadTests.swift @@ -6,8 +6,8 @@ class JSONLoadTests: XCTestCase { func testSuccessLoad() { guard let styleFile = Bundle(for: type(of: self)).url(forResource: "success-style", withExtension: "json") else { - XCTFail("Unable to find success-style.json file") - return + XCTFail("Unable to find success-style.json file") + return } XCTAssertNotNil(StyleKit(fileUrl: styleFile), "success-style.json failed") diff --git a/StyleKitTests/Style/style.json b/StyleKitTests/Style/style.json new file mode 100644 index 0000000..d61e028 --- /dev/null +++ b/StyleKitTests/Style/style.json @@ -0,0 +1,52 @@ +{ + "@ButtonTitleColor": "#FAA", + "@ButtonBackgroundColor": "#0F0", + "@NestedLabelColor": "#999", + "@UINavBarFont": "HelveticaNeue-Light:18.0", + "UINavigationBar": { + "tintColor": "#000", + "titleTextAttributes": { + "NSColor": "#FF000000", + "NSFont": "@UINavBarFont" + } + }, + "UILabel": { + "font": "HelveticaNeue-Bold:15.0", + "textColor": "#fff", + "backgroundColor": "#eee" + }, + "StyleKitTests.SKLabel": { + "font": "HelveticaNeue-Light:17.0", + "color": "#000" + }, + "UIButton": { + "font": "HelveticaNeue-Medium:13.0", + "titleColor:normal": "@ButtonTitleColor", + "titleColor:highlighted": "#fff" + }, + "StyleKitTests.SKButton": { + "backgroundColor:normal": "@ButtonBackgroundColor", + "backgroundColor:highlighted": "#fff", + "titleColor:normal": "@ButtonTitleColor", + "titleColor:highlighted": "#000", + "font": "HelveticaNeue-Light:13.0", + "transparency":70 + }, + "StyleKitTests.SKView": { + "backgroundColor": "#000" + }, + "UIScrollView": { + "UILabel": { + "font": "HelveticaNeue-Medium:20.0", + "color": "@NestedLabelColor" + }, + "StyleKitTests.SKView": { + "backgroundColor": "#888", + "StyleKitTests.SKLabel": { + "font": "HelveticaNeue-Light:40.0", + "color": "#fff", + "padding": [0,30,0,0] + } + } + } +} diff --git a/StyleKitTests/Style/success-style.json b/StyleKitTests/Style/success-style.json index 5b4f91d..03a487c 100644 --- a/StyleKitTests/Style/success-style.json +++ b/StyleKitTests/Style/success-style.json @@ -10,4 +10,4 @@ "titleColor:normal":"#FFFFFF", "titleColor:highlighted":"#000000" } -} \ No newline at end of file +} diff --git a/StyleKitTests/StyleKitTestsHost/AppDelegate.swift b/StyleKitTests/StyleKitTestsHost/AppDelegate.swift new file mode 100644 index 0000000..a5b337e --- /dev/null +++ b/StyleKitTests/StyleKitTestsHost/AppDelegate.swift @@ -0,0 +1,18 @@ +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + + self.window = UIWindow(frame: UIScreen.main.bounds) + let viewController = UIViewController(nibName: nil, bundle: nil) + self.window?.rootViewController = viewController + self.window?.makeKeyAndVisible() + + return true + } + +} diff --git a/StyleKitTests/StyleKitTestsHost/Info.plist b/StyleKitTests/StyleKitTestsHost/Info.plist new file mode 100644 index 0000000..2035eae --- /dev/null +++ b/StyleKitTests/StyleKitTestsHost/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + + diff --git a/StyleKitTests/StyleTests.swift b/StyleKitTests/StyleTests.swift new file mode 100644 index 0000000..db470bf --- /dev/null +++ b/StyleKitTests/StyleTests.swift @@ -0,0 +1,122 @@ +import XCTest +import UIKit +@testable import StyleKit + +class StyleTests: XCTestCase { + + let viewController = UIViewController() + let skView = SKView() + let skButton = SKButton() + let skLabel = SKLabel() + let label = UILabel() + let navigationBar = UINavigationBar() + let button = UIButton() + + let scrollView = UIScrollView() + let nestedLabel = UILabel() + let nestedSKView = SKView() + let nestedSKLabel = SKLabel() + + var loaded = false + + func loadStyles() { + if let styleFile = Bundle(for: type(of: self)).url(forResource: "style", withExtension: "json") { + guard let styleKit = StyleKit(fileUrl: styleFile) else { + XCTFail("Unable to load style.json file") + return + } + styleKit.apply() + loaded = true + } else { + XCTFail("Unable to find style.json file") + } + } + + override func setUp() { + + if !loaded { + loadStyles() + + viewController.view.addSubview(skView) + viewController.view.addSubview(skButton) + viewController.view.addSubview(skLabel) + viewController.view.addSubview(label) + viewController.view.addSubview(navigationBar) + viewController.view.addSubview(button) + viewController.view.addSubview(scrollView) + + nestedSKView.addSubview(nestedSKLabel) + scrollView.addSubview(nestedSKView) + scrollView.addSubview(nestedLabel) + + UIApplication.shared.keyWindow?.rootViewController = viewController + } + + } + + func testBasicComponents() { + + // UINavigationBar + XCTAssertEqual(navigationBar.tintColor, UIColor(hexString: "#000")) + + if let navigationBarColor = navigationBar.titleTextAttributes?["NSColor"] as? UIColor { + XCTAssertEqual(navigationBarColor, UIColor(hexString: "#FF000000")) + } else { + XCTFail("Color not found in navigation") + } + + if let navigationBarFont = navigationBar.titleTextAttributes?["NSFont"] as? UIFont { + XCTAssertEqual(navigationBarFont.fontName, "HelveticaNeue-Light") + XCTAssertEqual(navigationBarFont.pointSize, 18.0) + } else { + XCTFail("Font not found in navigation") + } + + // UILabel + XCTAssertEqual(label.font.fontName, "HelveticaNeue-Bold") + XCTAssertEqual(label.font.pointSize, 15.0) + XCTAssertEqual(label.textColor, UIColor(hexString: "#fff")) + XCTAssertEqual(label.backgroundColor, UIColor(hexString: "#eee")) + + // UIButton + XCTAssertEqual(button.titleLabel?.font.fontName, "HelveticaNeue-Medium") + XCTAssertEqual(button.titleLabel?.font.pointSize, 13.0) + XCTAssertEqual(button.titleColor(for: .normal), UIColor(hexString: "#FAA")) + XCTAssertEqual(button.titleColor(for: .highlighted), UIColor(hexString: "#fff")) + } + + func testExtendedComponents() { + // SKButton + XCTAssertEqual(skButton.titleLabel?.font.fontName, "HelveticaNeue-Light") + XCTAssertEqual(skButton.titleLabel?.font.pointSize, 13.0) + XCTAssertEqual(skButton.titleColor(for: .normal), UIColor(hexString: "#FAA")) + XCTAssertEqual(skButton.titleColor(for: .highlighted), UIColor(hexString: "#000")) + XCTAssertEqual(skButton.backgroundColor, UIColor(hexString: "#0F0")) + XCTAssert(fabs(Float(skButton.alpha) - 0.7) < Float.ulpOfOne) + + // SKLabel + XCTAssertEqual(skLabel.font.fontName, "HelveticaNeue-Light") + XCTAssertEqual(skLabel.font.pointSize, 17.0) + XCTAssertEqual(skLabel.textColor, UIColor(hexString: "#000")) + + // SKView + XCTAssertEqual(skView.backgroundColor, UIColor(hexString: "#000")) + } + + func testNestedComponents() { + // Label inside Scroll View + XCTAssertEqual(nestedLabel.font.fontName, "HelveticaNeue-Medium") + XCTAssertEqual(nestedLabel.font.pointSize, 20.0) + XCTAssertEqual(nestedLabel.textColor, UIColor(hexString: "#999")) + + // SKView inside Scroll View + XCTAssertEqual(nestedSKView.backgroundColor, UIColor(hexString: "#888")) + + // SKLabel inside nested SKView + XCTAssertEqual(nestedSKLabel.font.fontName, "HelveticaNeue-Light") + XCTAssertEqual(nestedSKLabel.font.pointSize, 40.0) + XCTAssertEqual(nestedSKLabel.textColor, UIColor(hexString: "#fff")) + XCTAssertEqual(nestedSKLabel.padding, [0,30,0,0]) + } + +} diff --git a/StyleKitTests/UI Extensions/SKButton.swift b/StyleKitTests/UI Extensions/SKButton.swift new file mode 100644 index 0000000..f01da28 --- /dev/null +++ b/StyleKitTests/UI Extensions/SKButton.swift @@ -0,0 +1,35 @@ +import Foundation +import UIKit + +class SKButton: UIButton { + + private var backgroundColors: [UInt: UIColor] = [:] + + override var isSelected: Bool { + didSet { + updateBackgroundColorForState(state) + } + } + + override var isHighlighted: Bool { + didSet { + updateBackgroundColorForState(state) + } + } + + func setTransparency(_ alpha: NSNumber) { + self.alpha = CGFloat(alpha.floatValue) / 100.0 + } + + func setBackgroundColor(_ color: UIColor, forState state: UIControlState) { + backgroundColors[state.rawValue] = color + + if state == UIControlState() { + updateBackgroundColorForState(state) + } + } + + private func updateBackgroundColorForState(_ state: UIControlState) { + backgroundColor = backgroundColors[state.rawValue] + } +} diff --git a/StyleKitTests/UI Extensions/SKLabel.swift b/StyleKitTests/UI Extensions/SKLabel.swift new file mode 100644 index 0000000..4d0cbef --- /dev/null +++ b/StyleKitTests/UI Extensions/SKLabel.swift @@ -0,0 +1,13 @@ +import Foundation +import UIKit + +class SKLabel: UILabel { + + var padding: [CGFloat] = [0,0,0,0] + + override func drawText(in rect: CGRect) { + let insets = UIEdgeInsets.init(top: padding[0], left: padding[1], bottom: padding[2], right: padding[3]) + super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) + } + +} diff --git a/StyleKitTests/UI Extensions/SKView.swift b/StyleKitTests/UI Extensions/SKView.swift new file mode 100644 index 0000000..b9bffeb --- /dev/null +++ b/StyleKitTests/UI Extensions/SKView.swift @@ -0,0 +1,6 @@ +import Foundation +import UIKit + +class SKView: UIView { + +}