diff --git a/.travis.yml b/.travis.yml index 127091b..086faf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ matrix: include: - env: DESTINATION="OS=10.3,name=iPhone 7" SCHEME=Matrioska SDK="iphonesimulator10.3" COVERAGE=YES before_install: + - brew update && brew install swiftlint - gem install danger --no-ri --no-doc - gem install danger-swiftlint --no-ri --no-doc before_script: diff --git a/Example/.swiftlint.yml b/Example/.swiftlint.yml new file mode 100644 index 0000000..269072f --- /dev/null +++ b/Example/.swiftlint.yml @@ -0,0 +1,23 @@ +disabled_rules: + - trailing_whitespace # seems like Xcode does this by default + +opt_in_rules: + - empty_count + - force_unwrapping + - private_outlet + - vertical_whitespace +# - missing_docs # broken + - closure_spacing + - conditional_returns_on_newline + - overridden_super_call + - redundant_nil_coalesing +# - switch_case_on_newline # broken + +included: + - MatrioskaExample + +excluded: + - Pods + - Example + +reporter: "xcode" diff --git a/Example/MatrioskaExample.xcodeproj/project.pbxproj b/Example/MatrioskaExample.xcodeproj/project.pbxproj index 9c76b8f..340f4ec 100644 --- a/Example/MatrioskaExample.xcodeproj/project.pbxproj +++ b/Example/MatrioskaExample.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 21F6211E1E3147AF006C0DBE /* JSONReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F6211D1E3147AF006C0DBE /* JSONReader.swift */; }; + 21F621201E3147D5006C0DBE /* app_structure.json in Resources */ = {isa = PBXBuildFile; fileRef = 21F6211F1E3147D5006C0DBE /* app_structure.json */; }; + 21F621221E3155D1006C0DBE /* MatrioskaCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F621211E3155D1006C0DBE /* MatrioskaCodeViewController.swift */; }; 3E97AAB4FE1D9B124FD14CAB /* Pods_MatrioskaExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0188753F69D4AFD24551958B /* Pods_MatrioskaExample.framework */; }; DE7C386A1E005E3C00A399A3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE7C38691E005E3C00A399A3 /* AppDelegate.swift */; }; DE7C386C1E005E3C00A399A3 /* TileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE7C386B1E005E3C00A399A3 /* TileViewController.swift */; }; @@ -17,6 +20,9 @@ /* Begin PBXFileReference section */ 0188753F69D4AFD24551958B /* Pods_MatrioskaExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MatrioskaExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 10AE7CFAC2D2657BBA53BF57 /* Pods-MatrioskaExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MatrioskaExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MatrioskaExample/Pods-MatrioskaExample.debug.xcconfig"; sourceTree = "<group>"; }; + 21F6211D1E3147AF006C0DBE /* JSONReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONReader.swift; sourceTree = "<group>"; }; + 21F6211F1E3147D5006C0DBE /* app_structure.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = app_structure.json; sourceTree = "<group>"; }; + 21F621211E3155D1006C0DBE /* MatrioskaCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatrioskaCodeViewController.swift; sourceTree = "<group>"; }; DE7C38661E005E3C00A399A3 /* MatrioskaExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatrioskaExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; DE7C38691E005E3C00A399A3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; DE7C386B1E005E3C00A399A3 /* TileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileViewController.swift; sourceTree = "<group>"; }; @@ -68,11 +74,14 @@ DE7C38681E005E3C00A399A3 /* MatrioskaExample */ = { isa = PBXGroup; children = ( + 21F6211F1E3147D5006C0DBE /* app_structure.json */, + 21F6211D1E3147AF006C0DBE /* JSONReader.swift */, DE7C38691E005E3C00A399A3 /* AppDelegate.swift */, DE7C386B1E005E3C00A399A3 /* TileViewController.swift */, DE7C38701E005E3C00A399A3 /* Assets.xcassets */, DE7C38721E005E3C00A399A3 /* LaunchScreen.storyboard */, DE7C38751E005E3C00A399A3 /* Info.plist */, + 21F621211E3155D1006C0DBE /* MatrioskaCodeViewController.swift */, ); path = MatrioskaExample; sourceTree = "<group>"; @@ -98,6 +107,7 @@ DE7C38641E005E3C00A399A3 /* Resources */, 4AE147B018A9543A80727648 /* [CP] Embed Pods Frameworks */, AC908C2BE3E29B7037E5E673 /* [CP] Copy Pods Resources */, + 21F621231E315F15006C0DBE /* swiftlint */, ); buildRules = ( ); @@ -148,6 +158,7 @@ buildActionMask = 2147483647; files = ( DE7C38741E005E3C00A399A3 /* LaunchScreen.storyboard in Resources */, + 21F621201E3147D5006C0DBE /* app_structure.json in Resources */, DE7C38711E005E3C00A399A3 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -155,6 +166,20 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 21F621231E315F15006C0DBE /* swiftlint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = swiftlint; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + }; 4AE147B018A9543A80727648 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -208,7 +233,9 @@ buildActionMask = 2147483647; files = ( DE7C386C1E005E3C00A399A3 /* TileViewController.swift in Sources */, + 21F6211E1E3147AF006C0DBE /* JSONReader.swift in Sources */, DE7C386A1E005E3C00A399A3 /* AppDelegate.swift in Sources */, + 21F621221E3155D1006C0DBE /* MatrioskaCodeViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/MatrioskaExample/AppDelegate.swift b/Example/MatrioskaExample/AppDelegate.swift index 13479e0..f9842cb 100644 --- a/Example/MatrioskaExample/AppDelegate.swift +++ b/Example/MatrioskaExample/AppDelegate.swift @@ -14,72 +14,60 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - let rootComponent = ClusterLayout.tabBar( - children: [ - blankComponent( - meta: ClusterLayout.TabConfig(title: "First", iconName: "tabIcon") - ), - blankComponent( - meta: ClusterLayout.TabConfig(title: "Second", iconName: "tabIcon") - ), - navComponent(child: - ClusterLayout.stack( - children: [ - tileComponent(meta: TileConfig(text: "One", color: .red)), - tileComponent(meta: TileConfig(text: "Two", color: .green)), - ClusterLayout.stack( - children: [ - tileComponent(meta: TileConfig(text: "A", color: .red)), - tileComponent(meta: TileConfig(text: "B", color: .green)), - tileComponent(meta: TileConfig(text: "C", color: .orange)), - tileComponent(meta: TileConfig(text: "D", color: .yellow)) - ], - meta: ClusterLayout.StackConfig(axis: .horizontal) - ), - tileComponent(meta: TileConfig(text: "Three", color: .orange)), - tileComponent(meta: TileConfig(text: "Four", color: .yellow)) - ], - meta: ZipMeta(ClusterLayout.TabConfig(title: "Third", iconName: "tabIcon")) - ) - ), - blankComponent( - meta: ClusterLayout.TabConfig(title: "Fourth", iconName: "tabIcon") - ), - blankComponent( - meta: ClusterLayout.TabConfig(title: "Fifth", iconName: "tabIcon") - ) - ], - meta: ClusterLayout.TabBarConfig(selectedIndex: 2) - ) - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) - window?.rootViewController = rootComponent.viewController() - window?.makeKeyAndVisible() + + let factory = JSONFactory() + + let tabBarBuilder: JSONFactory.ClusterBuilder = { (children, meta) in + ClusterLayout.tabBar(children: children, meta: meta) + } + + let stackBuilder: JSONFactory.ClusterBuilder = { (children, meta) in + ClusterLayout.stack(children: children, meta: meta) + } + + let navigationBuilder: JSONFactory.WrapperBuilder = { (child, meta) in + Component.wrapper(viewBuilder: { (child, meta) in + guard let vc = child.viewController() else { + return nil + } + return UINavigationController(rootViewController: vc) + }, child: child, meta: meta) + } + + let tileBuilder: JSONFactory.SingleBuilder = { (meta) in + Component.single(viewBuilder: { (meta) -> UIViewController? in + TileViewController.init(meta: TileConfig.materialize(meta)) + }, meta: meta) + } + + let matrioskaCodeBuilder: JSONFactory.SingleBuilder = { (meta) in + Component.single(viewBuilder: {_ in + return MatrioskaCodeViewController() + }, meta: meta) + } + + factory.register(builder: tabBarBuilder, forType: "tabbar") + factory.register(builder: stackBuilder, forType: "stack") + factory.register(builder: navigationBuilder, forType: "navigation") + factory.register(builder: tileBuilder, forType: "tile") + factory.register(builder: matrioskaCodeBuilder, forType: "matrioska") + + do { + if let json = try JSONReader.jsonObject(from: "app_structure") { + + let rootComponent = try factory.makeComponent(json: json) + window?.rootViewController = rootComponent?.viewController() + window?.makeKeyAndVisible() + } + + } catch { + assert(false, "JSON could not be parsed") + } return true } } - -private func navComponent(child: Component) -> Component { - let viewBuilder: Component.WrapperViewBuilder = { (child, meta) -> UIViewController? in - return child.viewController().map { UINavigationController(rootViewController: $0) } - } - - return Component.wrapper(viewBuilder: viewBuilder, child: child, meta: child.meta) -} - -private func blankComponent(meta: ComponentMeta?) -> Component { - let viewBuilder: Component.SingleViewBuilder = { (meta) in - let vc = UIViewController() - vc.view.backgroundColor = .white - return vc - } - return Component.single(viewBuilder: viewBuilder, meta: meta) -} - -private func tileComponent(meta: TileConfig) -> Component { - return Component.single(viewBuilder: TileViewController.init(meta:), meta: meta) -} diff --git a/Example/MatrioskaExample/Assets.xcassets/home.imageset/Contents.json b/Example/MatrioskaExample/Assets.xcassets/home.imageset/Contents.json new file mode 100644 index 0000000..47149c6 --- /dev/null +++ b/Example/MatrioskaExample/Assets.xcassets/home.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "home.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/MatrioskaExample/Assets.xcassets/home.imageset/home.png b/Example/MatrioskaExample/Assets.xcassets/home.imageset/home.png new file mode 100644 index 0000000..9e57a60 Binary files /dev/null and b/Example/MatrioskaExample/Assets.xcassets/home.imageset/home.png differ diff --git a/Example/MatrioskaExample/Assets.xcassets/list.imageset/Contents.json b/Example/MatrioskaExample/Assets.xcassets/list.imageset/Contents.json new file mode 100644 index 0000000..e0478e1 --- /dev/null +++ b/Example/MatrioskaExample/Assets.xcassets/list.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "list.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/MatrioskaExample/Assets.xcassets/list.imageset/list.png b/Example/MatrioskaExample/Assets.xcassets/list.imageset/list.png new file mode 100644 index 0000000..bcff29b Binary files /dev/null and b/Example/MatrioskaExample/Assets.xcassets/list.imageset/list.png differ diff --git a/Example/MatrioskaExample/JSONReader.swift b/Example/MatrioskaExample/JSONReader.swift new file mode 100644 index 0000000..dbb149b --- /dev/null +++ b/Example/MatrioskaExample/JSONReader.swift @@ -0,0 +1,42 @@ +// +// JSONReader.swift +// Matrioska +// +// Created by Andreas Thenn on 12/01/2017. +// Copyright © 2017 runtastic. All rights reserved. +// + +import Foundation +@testable import Matrioska + +/// A JSONReader used to convert to JSONObject +final class JSONReader { + + /// Serializes from a given JSON Data into JSONObject + /// + /// - Parameter data: the Data object used in serialization + /// - Returns: an optional serialized JSONObject + /// - Throws: throws an error in case of failure or invalid JSON data + class func jsonObject(from data: Data) throws -> JSONObject? { + let json = try JSONSerialization.jsonObject(with: data) as? JSONObject + + return json + } + + /// Serializes from a given JSON file into JSONObject + /// + /// - Parameters: + /// - jsonFilename: the file name + /// - bundle: the bundle where the file is located + /// - Returns: an optional serialized JSONObject + /// - Throws: throws an error in case of failure or invalid JSON data + class func jsonObject(from jsonFilename: String, bundle: Bundle = .main) throws -> JSONObject? { + guard let filePath = bundle.path(forResource: jsonFilename, ofType: "json") else { + return nil + } + + let url = URL(fileURLWithPath: filePath) + + return try jsonObject(from: Data(contentsOf: url, options: .uncached)) + } +} diff --git a/Example/MatrioskaExample/MatrioskaCodeViewController.swift b/Example/MatrioskaExample/MatrioskaCodeViewController.swift new file mode 100644 index 0000000..fb7a35f --- /dev/null +++ b/Example/MatrioskaExample/MatrioskaCodeViewController.swift @@ -0,0 +1,57 @@ +// +// MatrioskaCodeViewController.swift +// MatrioskaExample +// +// Created by Mathias Aichinger on 19/01/2017. +// Copyright © 2017 runtastic. All rights reserved. +// + +import UIKit +import Matrioska +import SnapKit + +class MatrioskaCodeViewController: UIViewController { + + let rootComponent = ClusterLayout.stack( + children: [ + tileComponent(meta: TileConfig(text: "One", color: .red)), + tileComponent(meta: TileConfig(text: "Two", color: .green)), + ClusterLayout.stack( + children: [ + tileComponent(meta: TileConfig(text: "A", color: .red)), + tileComponent(meta: TileConfig(text: "B", color: .green)), + tileComponent(meta: TileConfig(text: "C", color: .orange)), + tileComponent(meta: TileConfig(text: "D", color: .yellow)) + ], + meta: ClusterLayout.StackConfig(axis: .horizontal) + ), + tileComponent(meta: TileConfig(text: "Three", color: .orange)), + tileComponent(meta: TileConfig(text: "Four", color: .yellow)) + ], + meta: ClusterLayout.StackConfig(title: "Test", + spacing: CGFloat(10.0), + axis: .vertical, + preserveParentWidth: true, + backgroundColor: .blue) + ) + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .red + + guard let rootViewController = rootComponent.viewController(), let rootView = rootViewController.view else { + return + } + + addChildViewController(rootViewController) + view.addSubview(rootView) + rootViewController.didMove(toParentViewController: self) + rootView.snp.makeConstraints { (make) in + make.edges.equalTo(self.view).inset(0) + } + } +} + +private func tileComponent(meta: TileConfig) -> Component { + return Component.single(viewBuilder: TileViewController.init(meta:), meta: meta) +} diff --git a/Example/MatrioskaExample/TileViewController.swift b/Example/MatrioskaExample/TileViewController.swift index 25a23cb..2e2c222 100644 --- a/Example/MatrioskaExample/TileViewController.swift +++ b/Example/MatrioskaExample/TileViewController.swift @@ -15,7 +15,10 @@ struct TileConfig: ExpressibleByComponentMeta { init?(meta: ComponentMeta) { text = meta["text"] as? String - color = meta["color"] as? UIColor + + let hexColor = meta["color"] as? String + let color = UIColor(hexString: hexColor ?? "") + self.color = color } init(text: String?, color: UIColor) { diff --git a/Example/MatrioskaExample/app_structure.json b/Example/MatrioskaExample/app_structure.json new file mode 100644 index 0000000..b655342 --- /dev/null +++ b/Example/MatrioskaExample/app_structure.json @@ -0,0 +1,70 @@ +{ + "structure": { + "type": "tabbar", + "meta": { + "default_tab_id": "main_tab" + }, + "children": [ + { + "type": "navigation", + "meta": { + "title": "history_title", + "icon_name": "home" + }, + "children": [ + { + "type": "stack", + "meta": { + "background_color": "1111AB" + }, + "children": [ + { + "type": "tile", + "meta": { + "text": "test", + "color": "FF6532" + } + }, + { + "type": "stack", + "meta": { + "axis": 0 + }, + "children": [ + { + "type": "tile", + "meta": { + "text": "test", + "color": "FF6532" + } + }, + { + "type": "tile", + "meta": { + "text": "test", + "color": "F8B53D" + } + }, + { + "type": "tile", + "meta": { + "text": "test", + "color": "FF6532" + } + } + ] + } + ] + } + ] + }, + { + "type": "matrioska", + "meta": { + "title": "main_tab_title", + "icon_name": "list" + } + } + ] + } +}