diff --git a/.gitignore b/.gitignore index fc314ee..f855b2c 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ xcuserdata Pods/ *.xcuserstate + +# Jazzy docs +docs/ diff --git a/.hound.yml b/.hound.yml index 65fa0c8..3651673 100644 --- a/.hound.yml +++ b/.hound.yml @@ -1,3 +1,5 @@ swift: enabled: true config_file: .swiftlint.yml +ruby: + enabled: false diff --git a/.slather.yml b/.slather.yml new file mode 100644 index 0000000..7855005 --- /dev/null +++ b/.slather.yml @@ -0,0 +1,4 @@ +coverage_service: cobertura_xml +xcodeproj: SettingsKit.xcodeproj +source_directory: Sources +output_directory: . diff --git a/.swiftlint.yml b/.swiftlint.yml index e9ba55e..e9427b7 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,7 +1,3 @@ -# disabled_rules: # rule identifiers to exclude from running - # - valid_docs - # Find all the available rules by running: - # swiftlint rules included: # paths to include during linting. `--path` is ignored if present. - Sources # parameterized rules can be customized from this configuration file @@ -10,4 +6,3 @@ line_length: 120 type_body_length: - 300 # warning - 400 # error -# reporter: "csv" # reporter type (xcode, json, csv, checkstyle) diff --git a/.travis.yml b/.travis.yml index 95e83e3..0380ae1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,8 @@ before_install: - rvm use $RVM_RUBY_VERSION - gem install cocoapods - pod install +after_success: + - bash <(curl -s https://codecov.io/bash) script: - set -o pipefail && xcodebuild clean test -workspace SettingsKit.xcworkspace -scheme SettingsKit -destination 'platform=iOS Simulator,name=iPhone 6s' ONLY_ACTIVE_ARCH=YES | xcpretty - pod lib lint diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cfc855..5dee3f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ Released on 2016-02-26. #### Added - Core feature set for initial release: - - Creates version string using Major.Minor.Build - - Displays version string in Settings screen -- Documentation & Screenshots + - Store & Retrieve settings + - Observe settings to be able to respond to changes +- Documentation - Example iOS project --- diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index b3420fb..b533953 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -7,39 +7,29 @@ objects = { /* Begin PBXBuildFile section */ + 92DF00B9CDC2B6E743B19AFF /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C05B7ADB503F56F194536E /* Settings.swift */; }; C5C8ED421C80C3AD001BC3F7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C5C8ED411C80C3AD001BC3F7 /* LaunchScreen.storyboard */; }; C5C8ED441C80C3BE001BC3F7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C5C8ED431C80C3BE001BC3F7 /* Main.storyboard */; }; C5D7BDB01C80B9640061D9DD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D7BDA61C80B9640061D9DD /* AppDelegate.swift */; }; C5D7BDB51C80B9640061D9DD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = C5D7BDAE1C80B9640061D9DD /* Settings.bundle */; }; - C5D7BDB61C80B9640061D9DD /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D7BDAF1C80B9640061D9DD /* Settings.swift */; }; + E290B9A46F8F7A04F0BB7F4F /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF86464863B18B629F048EE9 /* Pods.framework */; }; + E57DB01B8F53140FF03745B7 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48F05F7A68D93C05C48EF7C9 /* Pods_Example.framework */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - C5D7BDD11C80BE9F0061D9DD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C5D7BDCC1C80BE9F0061D9DD /* SettingsKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = C5D7BD931C80B5FD0061D9DD; - remoteInfo = SettingsKit; - }; - C5D7BDD31C80BE9F0061D9DD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C5D7BDCC1C80BE9F0061D9DD /* SettingsKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = C5D7BDBC1C80BA040061D9DD; - remoteInfo = SettingsKitTests; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXFileReference section */ + 16C05B7ADB503F56F194536E /* Settings.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Settings.swift; path = Source/Settings.swift; sourceTree = SOURCE_ROOT; }; + 19D681994CF94AF2C19D11C2 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + 48F05F7A68D93C05C48EF7C9 /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7862F4332F44F1B704BB396B /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; + 827D4939CFA397AD32F0E777 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; + A791B33752B6DAAB534A2B2D /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; C56921C61C765DB200CC8F56 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; C5C8ED411C80C3AD001BC3F7 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = Source/Interface/LaunchScreen.storyboard; sourceTree = SOURCE_ROOT; }; C5C8ED431C80C3BE001BC3F7 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Source/Interface/Main.storyboard; sourceTree = SOURCE_ROOT; }; C5D7BDA61C80B9640061D9DD /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Source/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; C5D7BDAD1C80B9640061D9DD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Source/Info.plist; sourceTree = SOURCE_ROOT; }; C5D7BDAE1C80B9640061D9DD /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Settings.bundle; path = Source/Settings.bundle; sourceTree = SOURCE_ROOT; }; - C5D7BDAF1C80B9640061D9DD /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Settings.swift; path = Source/Settings.swift; sourceTree = SOURCE_ROOT; }; - C5D7BDCC1C80BE9F0061D9DD /* SettingsKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SettingsKit.xcodeproj; path = ../SettingsKit.xcodeproj; sourceTree = ""; }; + DF86464863B18B629F048EE9 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -47,18 +37,41 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E57DB01B8F53140FF03745B7 /* Pods_Example.framework in Frameworks */, + E290B9A46F8F7A04F0BB7F4F /* Pods.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7D67E5DE028596A97467C9AC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 48F05F7A68D93C05C48EF7C9 /* Pods_Example.framework */, + DF86464863B18B629F048EE9 /* Pods.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + BF87885EE54D31150CD1E786 /* Pods */ = { + isa = PBXGroup; + children = ( + 7862F4332F44F1B704BB396B /* Pods-Example.debug.xcconfig */, + A791B33752B6DAAB534A2B2D /* Pods-Example.release.xcconfig */, + 827D4939CFA397AD32F0E777 /* Pods.debug.xcconfig */, + 19D681994CF94AF2C19D11C2 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; C56921BD1C765DB200CC8F56 = { isa = PBXGroup; children = ( - C5D7BDCC1C80BE9F0061D9DD /* SettingsKit.xcodeproj */, C56921C81C765DB200CC8F56 /* Example */, C56921C71C765DB200CC8F56 /* Products */, + 7D67E5DE028596A97467C9AC /* Frameworks */, + BF87885EE54D31150CD1E786 /* Pods */, ); sourceTree = ""; }; @@ -75,7 +88,7 @@ children = ( C5D7BDAE1C80B9640061D9DD /* Settings.bundle */, C5D7BDA61C80B9640061D9DD /* AppDelegate.swift */, - C5D7BDAF1C80B9640061D9DD /* Settings.swift */, + 16C05B7ADB503F56F194536E /* Settings.swift */, C5C8ED451C80C3C3001BC3F7 /* Interface */, C5D7BDB71C80B9990061D9DD /* Supporting Files */, ); @@ -99,15 +112,6 @@ name = "Supporting Files"; sourceTree = ""; }; - C5D7BDCD1C80BE9F0061D9DD /* Products */ = { - isa = PBXGroup; - children = ( - C5D7BDD21C80BE9F0061D9DD /* SettingsKit.framework */, - C5D7BDD41C80BE9F0061D9DD /* SettingsKitTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -115,10 +119,13 @@ isa = PBXNativeTarget; buildConfigurationList = C56921D81C765DB200CC8F56 /* Build configuration list for PBXNativeTarget "Example" */; buildPhases = ( + E95A4735D72E990900CB7893 /* Check Pods Manifest.lock */, + C5D7BD821C7F92700061D9DD /* SettingsKit */, C56921C21C765DB200CC8F56 /* Sources */, C56921C31C765DB200CC8F56 /* Frameworks */, C56921C41C765DB200CC8F56 /* Resources */, - C5D7BD821C7F92700061D9DD /* SettingsKit */, + CA95665E272F21B8600C2F2D /* Embed Pods Frameworks */, + E552EBDED7D9FB1E79869BC2 /* Copy Pods Resources */, ); buildRules = ( ); @@ -155,12 +162,6 @@ mainGroup = C56921BD1C765DB200CC8F56; productRefGroup = C56921C71C765DB200CC8F56 /* Products */; projectDirPath = ""; - projectReferences = ( - { - ProductGroup = C5D7BDCD1C80BE9F0061D9DD /* Products */; - ProjectRef = C5D7BDCC1C80BE9F0061D9DD /* SettingsKit.xcodeproj */; - }, - ); projectRoot = ""; targets = ( C56921C51C765DB200CC8F56 /* Example */, @@ -168,23 +169,6 @@ }; /* End PBXProject section */ -/* Begin PBXReferenceProxy section */ - C5D7BDD21C80BE9F0061D9DD /* SettingsKit.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = SettingsKit.framework; - remoteRef = C5D7BDD11C80BE9F0061D9DD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - C5D7BDD41C80BE9F0061D9DD /* SettingsKitTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = SettingsKitTests.xctest; - remoteRef = C5D7BDD31C80BE9F0061D9DD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - /* Begin PBXResourcesBuildPhase section */ C56921C41C765DB200CC8F56 /* Resources */ = { isa = PBXResourcesBuildPhase; @@ -205,15 +189,60 @@ files = ( ); inputPaths = ( - "$(SRCROOT)/Example/Example/Settings.swift", + "$(SRCROOT)/Source/Settings.bundle/Root.plist", ); name = SettingsKit; outputPaths = ( - "$(DERIVED_FILE_DIR)/SettingsKit/configure", + "$(SRCROOT)/Source/Settings.swift", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "PATH=$(bash -l -c 'echo $PATH')\n$PODS_ROOT/SettingsKit/build -p $PROJECT_FILE_PATH -s $SCRIPT_INPUT_FILE_0 -o $SCRIPT_OUTPUT_FILE_0\n"; + }; + CA95665E272F21B8600C2F2D /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E552EBDED7D9FB1E79869BC2 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + E95A4735D72E990900CB7893 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "echo \"SettingsKit: Configuring Settings.bundle\"\n#cat ~/Dev/src/About/Sources/About.swift $(SCRIPT_INPUT_FILE_0) ~/Dev/src/About/Sources/generate.swift | swiftc - -o $(DERIVED_FILE_DIR)/SettingsKit/configure && ./!!$"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -222,8 +251,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C5D7BDB61C80B9640061D9DD /* Settings.swift in Sources */, C5D7BDB01C80B9640061D9DD /* AppDelegate.swift in Sources */, + 92DF00B9CDC2B6E743B19AFF /* Settings.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -313,6 +342,7 @@ }; C56921D91C765DB200CC8F56 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 827D4939CFA397AD32F0E777 /* Pods.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = Source/Info.plist; @@ -325,6 +355,7 @@ }; C56921DA1C765DB200CC8F56 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 19D681994CF94AF2C19D11C2 /* Pods.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = Source/Info.plist; diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme new file mode 100644 index 0000000..9cb856a --- /dev/null +++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example.xcworkspace/contents.xcworkspacedata b/Example/Example.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..a37cf19 --- /dev/null +++ b/Example/Example.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Example/Podfile b/Example/Podfile new file mode 100644 index 0000000..c1cee72 --- /dev/null +++ b/Example/Podfile @@ -0,0 +1,4 @@ +source 'https://github.com/CocoaPods/Specs.git' +use_frameworks! + +pod 'SettingsKit', :path => '../' diff --git a/Example/Podfile.lock b/Example/Podfile.lock new file mode 100644 index 0000000..2d7c1ec --- /dev/null +++ b/Example/Podfile.lock @@ -0,0 +1,14 @@ +PODS: + - SettingsKit (0.1.0) + +DEPENDENCIES: + - SettingsKit (from `../`) + +EXTERNAL SOURCES: + SettingsKit: + :path: "../" + +SPEC CHECKSUMS: + SettingsKit: a682389b9bf71bfa897bd93bd667c4b2a731e321 + +COCOAPODS: 0.39.0 diff --git a/Example/Source/AppDelegate.swift b/Example/Source/AppDelegate.swift index ddb422b..a7d756e 100644 --- a/Example/Source/AppDelegate.swift +++ b/Example/Source/AppDelegate.swift @@ -20,20 +20,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Settings.set(.AppVersion, "1.0.1") // set values/state for custom preference items - - // Method A Settings.set(.ApiEnvironment, "Staging") Settings.set(.Contrast, 80) Settings.set(.EnableAnalytics, true) Settings.set(.FavoriteColor, "#00FF00") Settings.set(.FirstName, "Han") - // Method B - Settings.ApiEnvironment.set("Staging") - Settings.Contrast.set(80) - Settings.EnableAnalytics.set(true) - Settings.FavoriteColor.set("#00FF00") - Settings.FirstName.set("Han") + // retrieve the current setting values + let name = Settings.get(.FirstName) + print("Hello, \(name).") + + // observe and respond to changes to any settings + Settings.subscribe(.FavoriteColor) { (newValue) -> Void in + print("Favorite color was changed to \(newValue)") + } return true } diff --git a/Example/Source/Settings.swift b/Example/Source/Settings.swift index 25d23ca..d72e5d2 100644 --- a/Example/Source/Settings.swift +++ b/Example/Source/Settings.swift @@ -1,5 +1,14 @@ -import SettingsKit - +// +// Settings.swift +// Auto-generated settings manifest file, +// for use with SettingsKit. If you need to make changes, +// edit the Settings.bundle and build the project. +// +// Any manual changes to this file will be overwritten at build time. +// +// Generated by the SettingsKit build tool on 2/29/16. +// +import SettingsKit1 enum Settings: SettingsKit { case ApiEnvironment @@ -8,7 +17,7 @@ enum Settings: SettingsKit { case EnableAnalytics case FavoriteColor case FirstName - + var identifier: String { switch self { case .ApiEnvironment: diff --git a/Podfile b/Podfile index be83231..2d1cab6 100644 --- a/Podfile +++ b/Podfile @@ -4,4 +4,4 @@ use_frameworks! target 'SettingsKitTests', :exclusive => true do pod 'Quick', '~> 0.8.0' pod 'Nimble', '3.0.0' -end \ No newline at end of file +end diff --git a/Project/Project_base.xcconfig b/Project/Project_base.xcconfig new file mode 100644 index 0000000..c53551e --- /dev/null +++ b/Project/Project_base.xcconfig @@ -0,0 +1,31 @@ +ALWAYS_SEARCH_USER_PATHS = NO +CLANG_CXX_LANGUAGE_STANDARD = gnu++0x +CLANG_CXX_LIBRARY = libc++ +CLANG_ENABLE_MODULES = YES +CLANG_ENABLE_OBJC_ARC = YES +CLANG_WARN_BOOL_CONVERSION = YES +CLANG_WARN_CONSTANT_CONVERSION = YES +CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR +CLANG_WARN_EMPTY_BODY = YES +CLANG_WARN_ENUM_CONVERSION = YES +CLANG_WARN_INT_CONVERSION = YES +CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR +CLANG_WARN_UNREACHABLE_CODE = YES +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer +COPY_PHASE_STRIP = NO +CURRENT_PROJECT_VERSION = 1 +ENABLE_STRICT_OBJC_MSGSEND = YES +GCC_C_LANGUAGE_STANDARD = gnu99 +GCC_NO_COMMON_BLOCKS = YES +GCC_WARN_64_TO_32_BIT_CONVERSION = YES +GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR +GCC_WARN_UNDECLARED_SELECTOR = YES +GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE +GCC_WARN_UNUSED_FUNCTION = YES +GCC_WARN_UNUSED_VARIABLE = YES +IPHONEOS_DEPLOYMENT_TARGET = 9.2 +SDKROOT = iphoneos +TARGETED_DEVICE_FAMILY = 1,2 +VERSIONING_SYSTEM = apple-generic +VERSION_INFO_PREFIX = \ No newline at end of file diff --git a/Project/Project_debug.xcconfig b/Project/Project_debug.xcconfig new file mode 100644 index 0000000..668b7da --- /dev/null +++ b/Project/Project_debug.xcconfig @@ -0,0 +1,9 @@ +#include "Project/Project_base.xcconfig" +DEBUG_INFORMATION_FORMAT = dwarf +ENABLE_TESTABILITY = YES +GCC_DYNAMIC_NO_PIC = NO +GCC_OPTIMIZATION_LEVEL = 0 +GCC_PREPROCESSOR_DEFINITIONS = ["DEBUG=1", "$(inherited)"] +MTL_ENABLE_DEBUG_INFO = YES +ONLY_ACTIVE_ARCH = YES +SWIFT_OPTIMIZATION_LEVEL = -Onone \ No newline at end of file diff --git a/Project/Project_release.xcconfig b/Project/Project_release.xcconfig new file mode 100644 index 0000000..9a94329 --- /dev/null +++ b/Project/Project_release.xcconfig @@ -0,0 +1,5 @@ +#include "Project/Project_base.xcconfig" +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym +ENABLE_NS_ASSERTIONS = NO +MTL_ENABLE_DEBUG_INFO = NO +VALIDATE_PRODUCT = YES \ No newline at end of file diff --git a/README.md b/README.md index 10a29ac..408fbdc 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,144 @@ + # SettingsKit [![CI Status](http://img.shields.io/travis/dtrenz/SettingsKit.svg?style=flat)](https://travis-ci.org/dtrenz/SettingsKit) [![Version](https://img.shields.io/cocoapods/v/SettingsKit.svg?style=flat)](http://cocoapods.org/pods/SettingsKit) [![License](https://img.shields.io/cocoapods/l/SettingsKit.svg?style=flat)](http://cocoapods.org/pods/SettingsKit) [![Platform](https://img.shields.io/cocoapods/p/SettingsKit.svg?style=flat)](http://cocoapods.org/pods/SettingsKit) +[![codecov.io](https://codecov.io/github/dtrenz/SettingsKit/coverage.svg?branch=develop)](https://codecov.io/github/dtrenz/SettingsKit?branch=develop) [![Sponsored by Detroit Labs](https://img.shields.io/badge/sponsor-Detroit%20Labs-000000.svg?style=flat)](http://www.detroitlabs.com) -`SettingsKit` is a small library that makes it very simple to add useful app metadata -(i.e. app version & build number) information to the iOS Settings screen for -your app. +`SettingsKit` is a small library & build tool that makes it easier to work +with app preferences in the iOS Settings app. It also makes working with +settings a bit safer by using enums instead of "magic" strings, which are +vulnerable to typos. + + +## How does it work? +`SettingsKit` comes bundled with a tool that generates an enum (`Settings.swift`) +at build-time based on the preference items you have configured in your Settings.bundle. +You can then use the `Settings` enum to easily access, update and observe individual +settings. + +Here is a Settings screen w/ generated enum and sample implementation code: + +[![Settings Example](https://raw.githubusercontent.com/dtrenz/SettingsKit/develop/Screenshots/how-it-works.png)](https://raw.githubusercontent.com/dtrenz/SettingsKit/develop/Screenshots/how-it-works.png) + + +## When would I want to use SettingKit? + +Here are just a few ways you could use SettingsKit; + - display the current app version & build number to users or testers, which can + be very helpful for handling bug reports. + - give users simple controls for app preferences (e.g. enable a color blindness theme) + - enable testers to see which back-end environment (i.e. "Staging") the current + build is using, and also allow them to change the back-end environment at runtime, + without having to provide seperate builds for each environment. + +The types of settings you use are completely up to you. `SettingsKit` simply +makes it easier to add settings to your app. + + +## Usage + +### Fetch a setting + +```swift +let name = Settings.get(.FirstName) +``` + +### Modify a setting + +```swift +Settings.set(.ApiEnvironment, "Staging") +Settings.set(.Contrast, 80) +Settings.set(.EnableAnalytics, true) +``` + +### Observe a setting + +```swift +// observe and respond to any changes made to a given setting +Settings.subscribe(.FavoriteColor) { (newValue) -> Void in + print("Favorite color was changed to \(newValue)") +} +``` + + +## Installation + +`SettingsKit` is available through [CocoaPods](http://cocoapods.org). To install +it, simply add the following line to your Podfile: + +```ruby +pod "SettingsKit" +``` + +You will also need the `xcodeproj` ruby gem in order to use the `SettingsKit` +build tool to auto-generate the Settings.swift file. + +```bash +$ gem install xcodeproj +``` ## Setup + +### Create a Settings bundle In order to add custom items to Settings your app must have a Settings bundle -with a `Root.plist`. +with a `Root.plist`. To fetch, update, and/or observe settings you will need to +add the settings you would like to use to the "Preference Items" array in your +`Settings.bundle/Root.plist`. +> Example: +> +> ![Settings.bundle/Root.plist](https://raw.githubusercontent.com/dtrenz/SettingsKit/develop/Screenshots/setup-root-plist.png) -## Usage +Once you have added one or more preference items, build your project to have +`SettingsKit` generate a `Settings.swift` file from your `Settings.bundle/Root.plist`. + +> **Important** — Anytime you change the preference items in your `Root.plist` +you will need build your project to generate an updated `Settings.swift` file. + +### Add a run script build phase +To allow `SettingsKit` to auto-generate a Settings enum file you will need to +add a run script build phase to your app's target: Target > Build Phases > "+" > "New Run Script Phase" + +**IMPORTANT** Make sure you put the new run script phase above the "Compile Sources" +phase. +**Step 1:** Copy/paste the following into the script box: -### Custom Settings Items +```bash +PATH=$(bash -l -c 'echo $PATH') +$PODS_ROOT/SettingsKit/build -p $PROJECT_FILE_PATH -s $SCRIPT_INPUT_FILE_0 -o $SCRIPT_OUTPUT_FILE_0 +``` + +**Step 2:** Add the path to your `Settings.bundle/Root.plist` to "Input Files". + +> Example: +> +> ![Run script: Input files example](https://raw.githubusercontent.com/dtrenz/SettingsKit/develop/Screenshots/setup-input-file.png) + +**Step 3:** Add the path to where you would like the generated `Settings.swift` +file to be live (i.e. where you put your app's source files). + +> Example: +> +> ![Run script: Output files example](https://raw.githubusercontent.com/dtrenz/SettingsKit/develop/Screenshots/setup-output-file.png) + +When you are done with all of the above steps, your `SettingsKit` run script +phase should look something like this: + +![Run script phase example](https://raw.githubusercontent.com/dtrenz/SettingsKit/develop/Screenshots/setup-run-script.png) + +Finally, build (⌘B) your project. If the project builds successfully, you should +now see a new `Settings.swift` file in your project. Feel free to move it to any +group in your project. + +From now on, everytime you build, `SettingsKit` will update the `Settings.swift` +file to match the Preference Items you have configured in your +`Settings.bundle/Root.plist`. You should never need to manually edit `Settings.swift`. ## Author @@ -30,4 +149,3 @@ Dan Trenz ([@dtrenz](http://www.twitter.com/dtrenz)) c/o [Detroit Labs](http://w ## License SettingsKit is available under the Apache License, Version 2.0. See the LICENSE file for more info. - diff --git a/Screenshots/how-it-works.png b/Screenshots/how-it-works.png new file mode 100644 index 0000000..17d92c2 Binary files /dev/null and b/Screenshots/how-it-works.png differ diff --git a/Screenshots/setup-input-file.png b/Screenshots/setup-input-file.png new file mode 100644 index 0000000..21abcee Binary files /dev/null and b/Screenshots/setup-input-file.png differ diff --git a/Screenshots/setup-output-file.png b/Screenshots/setup-output-file.png new file mode 100644 index 0000000..29ac1b4 Binary files /dev/null and b/Screenshots/setup-output-file.png differ diff --git a/Screenshots/setup-root-plist.png b/Screenshots/setup-root-plist.png new file mode 100644 index 0000000..66bce85 Binary files /dev/null and b/Screenshots/setup-root-plist.png differ diff --git a/Screenshots/setup-run-script.png b/Screenshots/setup-run-script.png new file mode 100644 index 0000000..0e25d71 Binary files /dev/null and b/Screenshots/setup-run-script.png differ diff --git a/SettingsKit.podspec b/SettingsKit.podspec index 33ff33d..79fa2a8 100644 --- a/SettingsKit.podspec +++ b/SettingsKit.podspec @@ -2,15 +2,20 @@ Pod::Spec.new do |s| s.name = "SettingsKit" s.version = "0.1.0" s.summary = <<-SUMMARY + SettingsKit provides an elegant wrapper for iOS app settings. SUMMARY s.description = <<-DESC + SettingsKit makes working with app settings easier & safer + by providing a tidy API for retrieving, updating, and + observing preference items in your app's Settings bundle. DESC s.homepage = "https://github.com/dtrenz/SettingsKit" - s.license = 'Apache 2.0' + s.license = "Apache 2.0" s.author = { "Dan Trenz" => "dtrenz@gmail.com" } s.source = { :git => "https://github.com/dtrenz/SettingsKit.git", :tag => s.version.to_s } - s.social_media_url = 'https://twitter.com/dtrenz' - s.platform = :ios, '8.3' + s.social_media_url = "https://twitter.com/dtrenz" + s.platform = :ios, "8.3" s.requires_arc = true - s.source_files = 'Sources/**/*' + s.source_files = [ "Sources/**/*", "cli/**/*", "build" ] + s.preserve_paths = [ "cli/**/*", "build" ] end diff --git a/SettingsKit.xcodeproj/project.pbxproj b/SettingsKit.xcodeproj/project.pbxproj index f26ea98..c65ecfe 100644 --- a/SettingsKit.xcodeproj/project.pbxproj +++ b/SettingsKit.xcodeproj/project.pbxproj @@ -7,11 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 11632309D98671B6F6CB12C4 /* Pods_SettingsKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A2BAC4EBCA0CC0DB3C4DB824 /* Pods_SettingsKitTests.framework */; }; C5D7BDA21C80B75B0061D9DD /* SettingsKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D7BD9F1C80B75B0061D9DD /* SettingsKit.swift */; }; C5D7BDA41C80B75B0061D9DD /* SettingsKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C5D7BDA11C80B75B0061D9DD /* SettingsKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; C5D7BDC11C80BA040061D9DD /* SettingsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5D7BD931C80B5FD0061D9DD /* SettingsKit.framework */; }; C5D7BDCB1C80BC220061D9DD /* SettingsKitSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D7BDC71C80BA510061D9DD /* SettingsKitSpec.swift */; }; - EAA4ECE80C4B859338E363F5 /* Pods_SettingsKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87381E81F8199BE1A377CA61 /* Pods_SettingsKitTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -27,7 +27,7 @@ /* Begin PBXFileReference section */ 29E698A8BD2666F3874BE529 /* Pods-SettingsKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SettingsKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SettingsKitTests/Pods-SettingsKitTests.release.xcconfig"; sourceTree = ""; }; 4412B3D0A0055F6A6A496486 /* Pods-SettingsKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SettingsKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SettingsKitTests/Pods-SettingsKitTests.debug.xcconfig"; sourceTree = ""; }; - 87381E81F8199BE1A377CA61 /* Pods_SettingsKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SettingsKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A2BAC4EBCA0CC0DB3C4DB824 /* Pods_SettingsKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SettingsKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C5D7BD931C80B5FD0061D9DD /* SettingsKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SettingsKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C5D7BD9F1C80B75B0061D9DD /* SettingsKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SettingsKit.swift; path = Sources/SettingsKit.swift; sourceTree = SOURCE_ROOT; }; C5D7BDA01C80B75B0061D9DD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/Info.plist; sourceTree = SOURCE_ROOT; }; @@ -50,7 +50,7 @@ buildActionMask = 2147483647; files = ( C5D7BDC11C80BA040061D9DD /* SettingsKit.framework in Frameworks */, - EAA4ECE80C4B859338E363F5 /* Pods_SettingsKitTests.framework in Frameworks */, + 11632309D98671B6F6CB12C4 /* Pods_SettingsKitTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -60,7 +60,7 @@ 5BCBCA55AFC06C317DF97761 /* Frameworks */ = { isa = PBXGroup; children = ( - 87381E81F8199BE1A377CA61 /* Pods_SettingsKitTests.framework */, + A2BAC4EBCA0CC0DB3C4DB824 /* Pods_SettingsKitTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -68,7 +68,7 @@ C5D7BD891C80B5FD0061D9DD = { isa = PBXGroup; children = ( - C5D7BD951C80B5FD0061D9DD /* SettingsKit */, + C5D7BD951C80B5FD0061D9DD /* Sources */, C5D7BDBD1C80BA040061D9DD /* Tests */, C5D7BD941C80B5FD0061D9DD /* Products */, CF3538BCE9D95A7D40B0B97E /* Pods */, @@ -85,12 +85,13 @@ name = Products; sourceTree = ""; }; - C5D7BD951C80B5FD0061D9DD /* SettingsKit */ = { + C5D7BD951C80B5FD0061D9DD /* Sources */ = { isa = PBXGroup; children = ( C5D7BD9F1C80B75B0061D9DD /* SettingsKit.swift */, C5D7BDA51C80B7620061D9DD /* Supporting Files */, ); + name = Sources; path = SettingsKit; sourceTree = ""; }; @@ -140,6 +141,7 @@ isa = PBXNativeTarget; buildConfigurationList = C5D7BD9B1C80B5FD0061D9DD /* Build configuration list for PBXNativeTarget "SettingsKit" */; buildPhases = ( + C5AA99081C888BB1009577E7 /* Swiftlint */, C5D7BD8E1C80B5FD0061D9DD /* Sources */, C5D7BD8F1C80B5FD0061D9DD /* Frameworks */, C5D7BD901C80B5FD0061D9DD /* Headers */, @@ -158,12 +160,10 @@ isa = PBXNativeTarget; buildConfigurationList = C5D7BDC61C80BA040061D9DD /* Build configuration list for PBXNativeTarget "SettingsKitTests" */; buildPhases = ( - C03216C3FDDE57B4FF66CB57 /* Check Pods Manifest.lock */, C5D7BDB81C80BA040061D9DD /* Sources */, C5D7BDB91C80BA040061D9DD /* Frameworks */, C5D7BDBA1C80BA040061D9DD /* Resources */, - CA76C5286B7C6ADDFE5A3638 /* Embed Pods Frameworks */, - 7AA9D4C150D516C8DC4E73E6 /* Copy Pods Resources */, + 73474DF3A596BE1328F9F3D3 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -229,50 +229,34 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 7AA9D4C150D516C8DC4E73E6 /* Copy Pods Resources */ = { + 73474DF3A596BE1328F9F3D3 /* Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SettingsKitTests/Pods-SettingsKitTests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - C03216C3FDDE57B4FF66CB57 /* Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Check Pods Manifest.lock"; + name = "Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SettingsKitTests/Pods-SettingsKitTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - CA76C5286B7C6ADDFE5A3638 /* Embed Pods Frameworks */ = { + C5AA99081C888BB1009577E7 /* Swiftlint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = Swiftlint; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SettingsKitTests/Pods-SettingsKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/SettingsKit.xcodeproj/xcuserdata/dtrenz.xcuserdatad/xcschemes/SettingsKit.xcscheme b/SettingsKit.xcodeproj/xcshareddata/xcschemes/SettingsKit.xcscheme similarity index 100% rename from SettingsKit.xcodeproj/xcuserdata/dtrenz.xcuserdatad/xcschemes/SettingsKit.xcscheme rename to SettingsKit.xcodeproj/xcshareddata/xcschemes/SettingsKit.xcscheme diff --git a/SettingsKit.xcodeproj/xcshareddata/xcschemes/SettingsKitTests.xcscheme b/SettingsKit.xcodeproj/xcshareddata/xcschemes/SettingsKitTests.xcscheme new file mode 100644 index 0000000..e89ecda --- /dev/null +++ b/SettingsKit.xcodeproj/xcshareddata/xcschemes/SettingsKitTests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SettingsKit.xcodeproj/xcuserdata/dtrenz.xcuserdatad/xcschemes/xcschememanagement.plist b/SettingsKit.xcodeproj/xcuserdata/dtrenz.xcuserdatad/xcschemes/xcschememanagement.plist index 613f3db..8c62454 100644 --- a/SettingsKit.xcodeproj/xcuserdata/dtrenz.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/SettingsKit.xcodeproj/xcuserdata/dtrenz.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,12 +4,12 @@ SchemeUserState - SettingsKit.xcscheme + SettingsKit.xcscheme_^#shared#^_ orderHint 0 - SettingsKitTests.xcscheme + SettingsKitTests.xcscheme_^#shared#^_ orderHint 2 @@ -17,6 +17,11 @@ SuppressBuildableAutocreation + C51AEF471C8241270060E1B9 + + primary + + C5D7BD921C80B5FD0061D9DD primary diff --git a/SettingsKit/SettingsKit_base.xcconfig b/SettingsKit/SettingsKit_base.xcconfig new file mode 100644 index 0000000..a64f020 --- /dev/null +++ b/SettingsKit/SettingsKit_base.xcconfig @@ -0,0 +1,11 @@ +CLANG_ENABLE_MODULES = YES +DEFINES_MODULE = YES +DYLIB_COMPATIBILITY_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +DYLIB_INSTALL_NAME_BASE = @rpath +INFOPLIST_FILE = Sources/Info.plist +INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks +PRODUCT_BUNDLE_IDENTIFIER = com.dtrenz.SettingsKit +PRODUCT_NAME = $(TARGET_NAME) +SKIP_INSTALL = YES \ No newline at end of file diff --git a/SettingsKit/SettingsKit_debug.xcconfig b/SettingsKit/SettingsKit_debug.xcconfig new file mode 100644 index 0000000..b61b4d3 --- /dev/null +++ b/SettingsKit/SettingsKit_debug.xcconfig @@ -0,0 +1,2 @@ +#include "SettingsKit/SettingsKit_base.xcconfig" +SWIFT_OPTIMIZATION_LEVEL = -Onone \ No newline at end of file diff --git a/SettingsKit/SettingsKit_release.xcconfig b/SettingsKit/SettingsKit_release.xcconfig new file mode 100644 index 0000000..232f689 --- /dev/null +++ b/SettingsKit/SettingsKit_release.xcconfig @@ -0,0 +1 @@ +#include "SettingsKit/SettingsKit_base.xcconfig" \ No newline at end of file diff --git a/SettingsKitTests/SettingsKitTests_base.xcconfig b/SettingsKitTests/SettingsKitTests_base.xcconfig new file mode 100644 index 0000000..b275653 --- /dev/null +++ b/SettingsKitTests/SettingsKitTests_base.xcconfig @@ -0,0 +1,4 @@ +INFOPLIST_FILE = Tests/Info.plist +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks +PRODUCT_BUNDLE_IDENTIFIER = com.dtrenz.SettingsKitTests +PRODUCT_NAME = $(TARGET_NAME) \ No newline at end of file diff --git a/SettingsKitTests/SettingsKitTests_debug.xcconfig b/SettingsKitTests/SettingsKitTests_debug.xcconfig new file mode 100644 index 0000000..e5ef641 --- /dev/null +++ b/SettingsKitTests/SettingsKitTests_debug.xcconfig @@ -0,0 +1 @@ +#include "SettingsKitTests/SettingsKitTests_base.xcconfig" \ No newline at end of file diff --git a/SettingsKitTests/SettingsKitTests_release.xcconfig b/SettingsKitTests/SettingsKitTests_release.xcconfig new file mode 100644 index 0000000..e5ef641 --- /dev/null +++ b/SettingsKitTests/SettingsKitTests_release.xcconfig @@ -0,0 +1 @@ +#include "SettingsKitTests/SettingsKitTests_base.xcconfig" \ No newline at end of file diff --git a/Sources/SettingsKit.swift b/Sources/SettingsKit.swift index 9cb9fef..39224e8 100644 --- a/Sources/SettingsKit.swift +++ b/Sources/SettingsKit.swift @@ -6,40 +6,119 @@ // Copyright © 2016 Dan Trenz. All rights reserved. // +/** +* Protocol for the SettingsKit enum +*/ public protocol SettingsKit: CustomStringConvertible { + /// The identifier string for the Settings preference item var identifier: String { get } } +// SettingsKit enum extension (a/k/a "where the magic happens") public extension SettingsKit { - - var description: String { + + /// Convenience typealias for subscribe() onChange closure + public typealias SettingChangeHandler = (newValue: AnyObject?) -> Void + + /// String description of the enum value + public var description: String { guard let value = Self.get(self) else { return "nil" } - + return "\(value)" } - func get() -> AnyObject? { - return Self.get(self) - } + /// Local defaults reference + private var defaults: NSUserDefaults { return NSUserDefaults.standardUserDefaults() } + - func set(value: T) { - Self.set(self, value) + // MARK: - Static Convenience Methods + + /** + Fetch the current value for a given setting. + + - Parameter setting: The setting to fetch + + - Returns: The current setting value + */ + public static func get(setting: Self) -> AnyObject? { + return setting.get() + } + + /** + Update the value of a given setting. + + - Parameters: + - setting: The setting to update + - value: The value to store for the setting + */ + public static func set(setting: Self, _ value: T) { + setting.set(value) } + + /** + Observe a given setting for changes. The `onChange` closure will be called, + with the new setting value, whenever the setting value is changed either + by the user, or progammatically. + + - Parameters: + - setting: The setting to observe + - onChange: The closure to call when the setting's value is updated + */ + public static func subscribe(setting: Self, onChange: SettingChangeHandler) { + setting.subscribe(onChange) + } + + // MARK: - Instance Methods + + /** + Fetch the current value for a given setting. + + __This is the instance method that is called by the static convenience method + in the public API.__ - static func get(setting: Self) -> AnyObject? { - return NSUserDefaults.standardUserDefaults().objectForKey(setting.identifier) + - Returns: The current setting value + */ + private func get() -> AnyObject? { + return defaults.objectForKey(identifier) } - static func set(setting: Self, _ value: T) { - let defaults = NSUserDefaults.standardUserDefaults() - + /** + Update the value of a given setting. + + > This is the instance method that is called by the static convenience method + in the public API. + + - Parameter value: The value to store for the setting + */ + private func set(value: T) { if let boolVal = value as? Bool { - defaults.setBool(boolVal, forKey: setting.identifier) + defaults.setBool(boolVal, forKey: identifier) } else if let intVal = value as? Int { - defaults.setInteger(intVal, forKey: setting.identifier) + defaults.setInteger(intVal, forKey: identifier) } else if let objectVal = value as? AnyObject { - defaults.setObject(objectVal, forKey: setting.identifier) + defaults.setObject(objectVal, forKey: identifier) } } + + /** + Observe a given setting for changes. The `onChange` closure will be called, + with the new setting value, whenever the setting value is changed either + by the user, or progammatically. + + > This is the instance method that is called by the static convenience method + in the public API. + + - Parameter onChange: The closure to call when the setting's value is updated + */ + private func subscribe(onChange: SettingChangeHandler) { + let center = NSNotificationCenter.defaultCenter() + + center.addObserverForName(NSUserDefaultsDidChangeNotification, object: defaults, queue: nil) { (notif) -> Void in + if let defaults = notif.object as? NSUserDefaults { + onChange(newValue: defaults.objectForKey(self.identifier)) + } + } + } + } diff --git a/Tests/SettingsKitSpec.swift b/Tests/SettingsKitSpec.swift index 7864500..5474f41 100644 --- a/Tests/SettingsKitSpec.swift +++ b/Tests/SettingsKitSpec.swift @@ -1,7 +1,7 @@ import Quick import Nimble -import SettingsKit +@testable import SettingsKit enum Settings: SettingsKit { @@ -29,11 +29,27 @@ enum Settings: SettingsKit { class SettingsKitSpec: QuickSpec { override func spec() { - - describe("set()") { + + describe("description") { - context("when storing any supported Settings.bundle preference item value type") { + it("returns a string representation of the setting value") { + Settings.set(.LuckyNumber, 23) + + expect(Settings.LuckyNumber.description) == "23" + } + + it("returns a string representation of a nil setting value") { + NSUserDefaults.standardUserDefaults().removeObjectForKey(Settings.LuckyNumber.identifier) + + expect(Settings.LuckyNumber.description) == "nil" + } + } + + describe("set() — convenience method") { + + context("when storing any supported Settings.bundle preference item value type") { + it("can store an array") { let identifier = Settings.SocialNetworks.identifier let value = [ "facebook", "twitter", "instagram" ] @@ -132,7 +148,7 @@ class SettingsKitSpec: QuickSpec { } - describe("get()") { + describe("get() — convenience method") { context("when fetching any supported Settings.bundle preference item value type") { @@ -219,18 +235,40 @@ class SettingsKitSpec: QuickSpec { } - describe("description") { + + describe("subscribe() — convenience method") { - it("returns a string representation of the setting value") { - Settings.set(.LuckyNumber, 23) + context("when the observed setting changes") { - expect(Settings.LuckyNumber.description) == "23" - } - - it("returns a string representation of a nil setting value") { - NSUserDefaults.standardUserDefaults().removeObjectForKey(Settings.LuckyNumber.identifier) + it("the onChange closure is called") { + var handlerWasCalled = false + + NSUserDefaults.standardUserDefaults().setBool(false, forKey: Settings.EnableAnalytics.identifier) + + Settings.subscribe(.EnableAnalytics) { (newValue) -> Void in + handlerWasCalled = true + } + + NSUserDefaults.standardUserDefaults().setBool(true, forKey: Settings.EnableAnalytics.identifier) + + expect(handlerWasCalled).toEventually(beTrue()) + } - expect(Settings.LuckyNumber.description) == "nil" + it("the new value of the observed setting is passed to the onChange closure") { + var result = false + + NSUserDefaults.standardUserDefaults().setBool(false, forKey: Settings.EnableAnalytics.identifier) + + Settings.subscribe(.EnableAnalytics) { (newValue) -> Void in + if let newValue = newValue as? Bool { + result = newValue + } + } + + NSUserDefaults.standardUserDefaults().setBool(true, forKey: Settings.EnableAnalytics.identifier) + + expect(result).toEventually(beTrue()) + } } } diff --git a/build b/build new file mode 100755 index 0000000..c5af8e0 --- /dev/null +++ b/build @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby + +require "optparse" +require_relative 'cli/settingskit.rb' + + +options = {} + +OptionParser.new do |opts| + opts.banner = "Run script build phase tool for rendering a Settings swift file for use with SettingsKit.\n" + opts.banner += "\n" + opts.banner += "Usage: build [options]" + opts.separator "" + opts.separator "Options:" + + opts.on("-o", "--outfile PATH", "Output location for the generated Settings swift file, relative to the .xcodeproj") do |path| + options[:outfile] = path + end + + opts.on("-p", "--project PATH", "Path to .xcodeproj") do |path| + options[:project] = path + end + + opts.on("-s", "--settings PATH", "Path to Settings.bundle/Root.plist") do |path| + options[:settings] = path + end + + opts.on("-h", "--help", "Prints this help") do + puts opts + exit + end +end.parse! + +def verify(options, option) + unless options[option] + puts "Missing required option: --#{option}" + exit + end +end + +# check for required options +verify(options, :outfile) +verify(options, :project) +verify(options, :settings) + +SettingsKit::Parser.parse(options[:settings]) + .render(options[:outfile]) + .integrate(options[:project]) diff --git a/cli/integrator.rb b/cli/integrator.rb new file mode 100644 index 0000000..f5aed87 --- /dev/null +++ b/cli/integrator.rb @@ -0,0 +1,24 @@ +require "pathname" +require "xcodeproj" + +module SettingsKit + class Integrator + + def initialize(output_filepath) + @output_filepath = Pathname(output_filepath) + end + + def integrate(project_path) + project = Xcodeproj::Project.open(project_path) + + filename = @output_filepath.basename.to_s + + unless project.contains_file(filename) + project_name = Pathname(project_path).basename + puts "SettingsKit: Adding #{filename} to #{project_name}..." + project.add_file_to_main_target(@output_filepath) + end + end + + end +end diff --git a/cli/parser.rb b/cli/parser.rb new file mode 100644 index 0000000..9f16244 --- /dev/null +++ b/cli/parser.rb @@ -0,0 +1,14 @@ +require "xcodeproj" + +module SettingsKit + class Parser + + def self.parse(plist_path) + puts "SettingsKit: Parsing #{plist_path}..." + plist = Xcodeproj::PlistHelper.read(plist_path) + keys = plist["PreferenceSpecifiers"].map { |i| i["Key"] }.compact.sort + Renderer.new(keys) + end + + end +end diff --git a/cli/renderer.rb b/cli/renderer.rb new file mode 100644 index 0000000..353ed91 --- /dev/null +++ b/cli/renderer.rb @@ -0,0 +1,56 @@ +module SettingsKit + class Renderer + + def initialize(settings) + @settings = settings + end + + def render(output_path) + puts "SettingsKit: Rendering Settings.swift..." + swift = generate() + + outfile = File.open(output_path, "w") + outfile.write(swift) + outfile.close + + Integrator.new(output_path) + end + + def generate + enums = @settings.map { |key| " case " + snake_to_studly(key) }.join("\n") + + identifiers = @settings.map { |key| + " case .#{snake_to_studly(key)}:\n return \"#{key}\"" + }.join("\n") + +<<-swift +// +// Settings.swift +// Auto-generated settings manifest file, +// for use with SettingsKit. If you need to make changes, +// edit the Settings.bundle and build the project. +// +// Any manual changes to this file will be overwritten at build time. +// +// Generated by the SettingsKit build tool on 2/29/16. +// +import SettingsKit + +enum Settings: SettingsKit { +#{enums} + + var identifier: String { + switch self { +#{identifiers} + } + } +} +swift + end + + def snake_to_studly(string) + string.split("_").map { |i| i.capitalize }.join + end + + end +end diff --git a/cli/settingskit.rb b/cli/settingskit.rb new file mode 100644 index 0000000..a7c0f1f --- /dev/null +++ b/cli/settingskit.rb @@ -0,0 +1,7 @@ +# SettingsKit build tool component manifest +module SettingsKit + require_relative "xcodeproj+settingskit.rb" + require_relative "integrator.rb" + require_relative "parser.rb" + require_relative "renderer.rb" +end diff --git a/cli/xcodeproj+settingskit.rb b/cli/xcodeproj+settingskit.rb new file mode 100644 index 0000000..94424a0 --- /dev/null +++ b/cli/xcodeproj+settingskit.rb @@ -0,0 +1,25 @@ +require "pathname" +require "xcodeproj" + +class Xcodeproj::Project + + def contains_file(filename) + files.each do |file| + if file.name == filename + return true + end + end + + return false + end + + def add_file_to_main_target(filepath) + file_ref = new_file(filepath, "SOURCE_ROOT") + + main_target = targets.first + main_target.add_file_references([file_ref]) + + save + end + +end diff --git a/publish-docs.sh b/publish-docs.sh new file mode 100755 index 0000000..9c380be --- /dev/null +++ b/publish-docs.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -e # exit with nonzero exit code if anything fails + +# clear and re-create the out directory +rm -rf docs || exit 0; +mkdir docs; + +# generate docs +jazzy \ + --clean \ + --author "Dan Trenz" \ + --github_url https://github.com/dtrenz/SettingsKit \ + --module-version 0.1.0 \ + --xcodebuild-arguments -scheme,SettingsKit \ + --module SettingsKit \ + --output docs + +# go to the out directory and create a *new* Git repo +cd docs +git init + +# The first and only commit to this new Git repo contains all the +# files present with the commit message "Deploy to GitHub Pages". +git add . +git commit -m "Deploy to GitHub Pages" + +# Force push from the current repo's master branch to the remote +# repo's gh-pages branch. (All previous history on the gh-pages branch +# will be lost, since we are overwriting it.) We redirect any output to +# /dev/null to hide any sensitive credential data that might otherwise be exposed. +git push --force --quiet "https://github.com/dtrenz/SettingsKit" master:gh-pages > /dev/null 2>&1