flip_the_switch 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.travis.yml +3 -1
  4. data/CHANGELOG.md +10 -0
  5. data/Classes/FlipTheSwitch/Shared/FTSFeature.h +11 -0
  6. data/Classes/FlipTheSwitch/Shared/FTSFeature.m +67 -0
  7. data/Classes/FlipTheSwitch/Shared/FTSFlipTheSwitch.h +21 -0
  8. data/Classes/FlipTheSwitch/Shared/FTSFlipTheSwitch.m +144 -0
  9. data/Classes/FlipTheSwitch/iOS/FTSFeatureCell.h +17 -0
  10. data/Classes/FlipTheSwitch/iOS/FTSFeatureCell.m +12 -0
  11. data/Classes/FlipTheSwitch/iOS/FTSFeatureConfigurationViewController.h +5 -0
  12. data/Classes/FlipTheSwitch/iOS/FTSFeatureConfigurationViewController.m +142 -0
  13. data/Classes/FlipTheSwitch/iOS/FTSFeatureDescriptionLabel.h +5 -0
  14. data/Classes/FlipTheSwitch/iOS/FTSFeatureDescriptionLabel.m +13 -0
  15. data/Example/Colors-OSX/AppDelegate.h +7 -0
  16. data/Example/Colors-OSX/AppDelegate.m +17 -0
  17. data/Example/Colors-OSX/Base.lproj/Main.storyboard +744 -0
  18. data/Example/Colors-OSX/ColoredViews/GreenView.h +5 -0
  19. data/Example/Colors-OSX/ColoredViews/GreenView.m +12 -0
  20. data/Example/Colors-OSX/ColoredViews/PurpleView.h +5 -0
  21. data/Example/Colors-OSX/ColoredViews/PurpleView.m +12 -0
  22. data/Example/Colors-OSX/ColoredViews/RedView.h +5 -0
  23. data/Example/Colors-OSX/ColoredViews/RedView.m +12 -0
  24. data/Example/Colors-OSX/ColoredViews/YellowView.h +5 -0
  25. data/Example/Colors-OSX/ColoredViews/YellowView.m +12 -0
  26. data/Example/Colors-OSX/FTSFlipTheSwitch+Features.h +45 -0
  27. data/Example/Colors-OSX/FTSFlipTheSwitch+Features.m +165 -0
  28. data/Example/Colors-OSX/Features.plist +39 -0
  29. data/Example/Colors-OSX/Images.xcassets/AppIcon.appiconset/Contents.json +58 -0
  30. data/Example/Colors-OSX/Info.plist +32 -0
  31. data/Example/Colors-OSX/Settings.bundle/Features.plist +59 -0
  32. data/Example/Colors-OSX/Settings.bundle/Root.plist +23 -0
  33. data/Example/Colors-OSX/ViewController.h +14 -0
  34. data/Example/Colors-OSX/ViewController.m +152 -0
  35. data/Example/Colors-OSX/main.m +5 -0
  36. data/Example/Colors-iOS/Base.lproj/Main.storyboard +111 -0
  37. data/Example/Colors-iOS/Colors-iOS-Info.plist +2 -2
  38. data/Example/Colors-iOS/FTSFlipTheSwitch+Features.h +45 -0
  39. data/Example/Colors-iOS/FTSFlipTheSwitch+Features.m +165 -0
  40. data/Example/Colors-iOS/Images.xcassets/AppIcon.appiconset/Contents.json +15 -0
  41. data/Example/Colors-iOS/ViewControlleriOS.m +55 -20
  42. data/Example/Colors.xcodeproj/project.pbxproj +273 -25
  43. data/Example/Colors.xcodeproj/xcshareddata/xcschemes/Colors-OSX.xcscheme +115 -0
  44. data/Example/Colors.xcodeproj/xcshareddata/xcschemes/Colors-iOS.xcscheme +9 -15
  45. data/Example/FTSFlipTheSwitch+Features.h +45 -0
  46. data/Example/FTSFlipTheSwitch+Features.m +165 -0
  47. data/Example/Features.plist +39 -0
  48. data/Example/Gemfile +4 -0
  49. data/Example/Podfile +11 -2
  50. data/Example/README.md +13 -0
  51. data/Example/Rakefile +5 -1
  52. data/Example/features.json +23 -0
  53. data/FlipTheSwitch.podspec +9 -4
  54. data/Gemfile +2 -1
  55. data/README.md +101 -21
  56. data/Resources/FlipTheSwitch/FlipTheSwitch.storyboard +208 -0
  57. data/Tests/FlipTheSwitchSpec-iOS/Features.plist +6 -1
  58. data/Tests/{FlipTheSwitchSpec-Mac/Features.plist → FlipTheSwitchSpec-iOS/OtherFeatures.plist} +5 -2
  59. data/Tests/FlipTheSwitchSpec.xcodeproj/project.pbxproj +101 -38
  60. data/Tests/FlipTheSwitchSpec.xcodeproj/xcshareddata/xcschemes/FlipTheSwitchSpec-Mac.xcscheme +9 -7
  61. data/Tests/FlipTheSwitchSpec.xcodeproj/xcshareddata/xcschemes/FlipTheSwitchSpec-iOS.xcscheme +9 -7
  62. data/Tests/Rakefile +1 -1
  63. data/Tests/Spec/Classes/FlipTheSwitch/FlipTheSwitchSpec.m +134 -28
  64. data/flip_the_switch.gemspec +2 -1
  65. data/images/feature_configuration_screen.png +0 -0
  66. data/lib/flip_the_switch.rb +2 -0
  67. data/lib/flip_the_switch/cli.rb +16 -24
  68. data/lib/flip_the_switch/environment.rb +11 -0
  69. data/lib/flip_the_switch/feature.rb +11 -0
  70. data/lib/flip_the_switch/generator/base.rb +3 -3
  71. data/lib/flip_the_switch/generator/category.rb +13 -5
  72. data/lib/flip_the_switch/generator/header.h.erb +15 -8
  73. data/lib/flip_the_switch/generator/implementation.m.erb +36 -12
  74. data/lib/flip_the_switch/generator/plist.rb +19 -1
  75. data/lib/flip_the_switch/generator/settings.rb +6 -6
  76. data/lib/flip_the_switch/reader/features.rb +136 -22
  77. data/spec/flip_the_switch/cli_spec.rb +12 -12
  78. data/spec/flip_the_switch/environment_spec.rb +20 -0
  79. data/spec/flip_the_switch/feature_spec.rb +20 -0
  80. data/spec/flip_the_switch/generator/category_spec.rb +14 -8
  81. data/spec/flip_the_switch/generator/plist_spec.rb +23 -7
  82. data/spec/flip_the_switch/generator/settings_spec.rb +6 -3
  83. data/spec/flip_the_switch/reader/defaults_spec.rb +0 -18
  84. data/spec/flip_the_switch/reader/features_spec.rb +74 -17
  85. data/spec/resources/ExpectedFeatures.plist +10 -2
  86. data/spec/resources/expected_header.h +8 -3
  87. data/spec/resources/expected_implementation.m +37 -10
  88. data/spec/resources/invalid_layout/features.json +7 -0
  89. data/spec/resources/invalid_type/features.json +23 -0
  90. data/spec/resources/real/features.json +38 -0
  91. data/tmp/.keep +0 -0
  92. metadata +74 -16
  93. data/Classes/FlipTheSwitch/FlipTheSwitch.h +0 -9
  94. data/Classes/FlipTheSwitch/FlipTheSwitch.m +0 -88
  95. data/Example/Colors-iOS/Base.lproj/Main_iPhone.storyboard +0 -76
  96. data/Example/features.yml +0 -3
  97. data/Tests/Spec/Helpers/GcovTestObserver.h +0 -4
  98. data/Tests/Spec/Helpers/GcovTestObserver.m +0 -12
  99. data/spec/resources/invalid_layout/features.yml +0 -4
  100. data/spec/resources/invalid_type/features.yml +0 -7
  101. data/spec/resources/real/features.yml +0 -6
data/Example/README.md ADDED
@@ -0,0 +1,13 @@
1
+ ## Colors
2
+
3
+ In order to run the project, be sure to run:
4
+
5
+ $ bundle install
6
+
7
+ $ bundle exec pod install
8
+
9
+ $ bundle exec flip-the-switch category -o Colors-iOS
10
+
11
+ $ bundle exec flip-the-switch plist -o Colors-iOS
12
+
13
+ $ bundle exec flip-the-switch settings -o Colors-iOS
data/Example/Rakefile CHANGED
@@ -20,5 +20,9 @@ task :test => podfile_lock do
20
20
  sh 'bundle exec ../bin/flip-the-switch plist -o Colors-iOS'
21
21
  sh 'bundle exec ../bin/flip-the-switch category -o Colors-iOS'
22
22
  sh 'bundle exec ../bin/flip-the-switch settings -o Colors-iOS'
23
- sh "xctool build -scheme #{workspace}-iOS -workspace #{workspace}.xcworkspace -sdk iphonesimulator OBJROOT=#{build} SHARED_PRECOMPS_DIR=#{build}"
23
+ sh 'bundle exec ../bin/flip-the-switch plist -o Colors-OSX'
24
+ sh 'bundle exec ../bin/flip-the-switch category -o Colors-OSX'
25
+ sh 'bundle exec ../bin/flip-the-switch settings -o Colors-OSX'
26
+ sh "xcodebuild build -scheme #{workspace}-OSX -workspace #{workspace}.xcworkspace -sdk macosx OBJROOT=#{build} SHARED_PRECOMPS_DIR=#{build}"
27
+ sh "xcodebuild build -scheme #{workspace}-iOS -workspace #{workspace}.xcworkspace -sdk iphonesimulator OBJROOT=#{build} SHARED_PRECOMPS_DIR=#{build}"
24
28
  end
@@ -0,0 +1,23 @@
1
+ {
2
+ "default": {
3
+ "features_controller" : {
4
+ "enabled": true,
5
+ "description": "Enable feature switching UI"
6
+ },
7
+ "purple_color": {
8
+ "enabled": true,
9
+ "description": "The color purple"
10
+ },
11
+ "red_color": {
12
+ "enabled": false,
13
+ "description": "The color red"
14
+ },
15
+ "no_description_feature": {
16
+ "enabled": false
17
+ },
18
+ "long_description_feature": {
19
+ "enabled": false,
20
+ "description": "The path of the righteous man is beset on all sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who, in the name of charity and good will, shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who would attempt to poison and destroy My brothers. And you will know My name is the Lord when I lay My vengeance upon thee."
21
+ }
22
+ }
23
+ }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'FlipTheSwitch'
3
- s.version = '0.4.0'
3
+ s.version = '1.0.0'
4
4
  s.summary = 'A simple library to help enabling/disabling features on iOS/Mac applications.'
5
5
  s.authors = {
6
6
  'Michael England' => 'mg.england@gmail.com',
@@ -9,8 +9,13 @@ Pod::Spec.new do |s|
9
9
  s.homepage = 'https://github.com/michaelengland/FlipTheSwitch'
10
10
 
11
11
  s.license = {:type => 'MIT', :file => 'LICENSE'}
12
- s.source = {:git => 'git@github.com:michaelengland/FlipTheSwitch.git', :tag => s.version.to_s}
13
- s.source_files = 'Classes/FlipTheSwitch/*.{h,m}'
14
- s.public_header_files = 'Classes/FlipTheSwitch/*.h'
12
+ s.source = {:git => 'https://github.com/michaelengland/FlipTheSwitch.git', :tag => s.version.to_s}
13
+ s.osx.source_files = 'Classes/FlipTheSwitch/Shared/*.{h,m}'
14
+ s.osx.public_header_files = 'Classes/FlipTheSwitch/Shared/*.h'
15
+ s.osx.deployment_target = '10.8'
16
+ s.ios.source_files = 'Classes/FlipTheSwitch/**/*.{h,m}'
17
+ s.ios.public_header_files = 'Classes/FlipTheSwitch/**/*.h'
18
+ s.ios.resource_bundles = { 'FlipTheSwitch' => 'Resources/FlipTheSwitch/*.storyboard' }
19
+ s.ios.deployment_target = '7.0'
15
20
  s.requires_arc = true
16
21
  end
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'travis', '~> 1.6.11'
4
- gem 'cocoapods', '~> 0.33.1'
4
+ gem 'cocoapods', '~> 0.37.2'
5
5
  gem 'rake', '~> 10.3.2'
6
6
 
7
7
  gemspec
@@ -14,4 +14,5 @@ group :development, :test do
14
14
  gem 'guard-bundler', '~> 2.0.0'
15
15
  gem 'guard-spork', '~> 1.5.1'
16
16
  gem 'guard-rspec', '~> 4.2.10'
17
+ gem 'json-schema', '2.5.1'
17
18
  end
data/README.md CHANGED
@@ -4,7 +4,7 @@ A feature switching/toggling/flipping library for ObjectiveC.
4
4
 
5
5
  # flip_the_switch [![Build Status](https://travis-ci.org/michaelengland/FlipTheSwitch.svg?branch=master)](https://travis-ci.org/michaelengland/FlipTheSwitch) [![Code Climate](https://codeclimate.com/github/michaelengland/FlipTheSwitch.png)](https://codeclimate.com/github/michaelengland/FlipTheSwitch) [![Code Climate](https://codeclimate.com/github/michaelengland/FlipTheSwitch/coverage.png)](https://codeclimate.com/github/michaelengland/FlipTheSwitch) [![Gem Version](https://badge.fury.io/rb/flip_the_switch.svg)](http://badge.fury.io/rb/flip_the_switch)
6
6
 
7
- A gem command line tool for generating the `Features.plist` & `FlipTheSwitch+Features.{h,m}` categories to help with the corresponding Pod.
7
+ A gem command line tool for generating the `Features.plist` & `FTSFlipTheSwitch+Features.{h,m}` categories to help with the corresponding Pod.
8
8
 
9
9
  ## The Problem
10
10
 
@@ -19,24 +19,26 @@ How can we get the benefits of working on `master` branch, while still not worry
19
19
 
20
20
  ## Introducing FlipTheSwitch
21
21
 
22
- With FlipTheSwitch, we can choose different code paths at runtime:
22
+ ### Auto-Generated Files:
23
+
24
+ With 'FTSFlipTheSwitch', we can choose different code paths at runtime:
23
25
 
24
26
  ```objective-c
25
- self.newFeatureButton.hidden = [[FlipTheSwitch sharedInstance] isFeatureEnabled:@"new_feature"];
27
+ self.newFeatureButton.hidden = [[FTSFlipTheSwitch sharedInstance] isFeatureEnabled:@"new_feature"];
26
28
  ```
27
29
 
28
30
  The features can be enabled/disabled at runtime:
29
31
 
30
32
  ```objective-c
31
- [[FlipTheSwitch sharedInstance] enableFeature:@"new_feature"];
33
+ [[FTSFlipTheSwitch sharedInstance] enableFeature:@"new_feature"];
32
34
  ```
33
35
 
34
36
  ```objective-c
35
- [[FlipTheSwitch sharedInstance] disableFeature:@"new_feature"];
37
+ [[FTSFlipTheSwitch sharedInstance] disableFeature:@"new_feature"];
36
38
  ```
37
39
 
38
40
  ```objective-c
39
- [[FlipTheSwitch sharedInstance] setFeature:@"new_feature" enabled:YES];
41
+ [[FTSFlipTheSwitch sharedInstance] setFeature:@"new_feature" enabled:YES];
40
42
  ```
41
43
 
42
44
  All enabled features will persist between app loads through `NSUserDefaults`.
@@ -45,47 +47,123 @@ The features can defaulted to enabled/disabled via a plist file `Features.plist`
45
47
 
46
48
  ```xml
47
49
  <?xml version="1.0" encoding="UTF-8"?>
48
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
50
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
49
51
  <plist version="1.0">
50
52
  <dict>
51
- <key>enabled_feature</key>
52
- <true/>
53
- <key>disabled_feature</key>
54
- <false/>
53
+ <key>disabled_feature</key>
54
+ <dict>
55
+ <key>description</key>
56
+ <string>is disabled description</string>
57
+ <key>enabled</key>
58
+ <false/>
59
+ </dict>
60
+ <key>enabled_feature</key>
61
+ <dict>
62
+ <key>enabled</key>
63
+ <true/>
64
+ </dict>
55
65
  </dict>
56
66
  </plist>
67
+
68
+ ```
69
+
70
+ ### Subfeatures
71
+
72
+ You can add as many subfeatures to a feature recursively. Resulting in a plist as e.g. :
73
+
74
+ ```xml
75
+ <?xml version="1.0" encoding="UTF-8"?>
76
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
77
+ <plist version="1.0">
78
+ <dict>
79
+ <key>description</key>
80
+ <string>is disabled description</string>
81
+ <key>enabled</key>
82
+ <false/>
83
+ <key>subfeature</key>
84
+ <dict>
85
+ <key>enabled</key>
86
+ <true/>
87
+ </dict>
88
+ </dict>
89
+ </plist>
57
90
  ```
58
91
 
92
+ ### Configuration Screen
93
+
94
+ ![Screenshot of configuration screen](images/feature_configuration_screen.png)
95
+
96
+ * shows all features, their description texts and enabled state
97
+ * easily switching features on and off
98
+ * reset all feature back to the original setting from the plist (based on the json)
99
+
100
+ ### Notifications
101
+
102
+ When a feature status changes, you can find out about it by listening for the notification titled `FTSFeatureStatusChangedNotification`, which contains:
103
+
104
+ * `FTSFeatureStatusChangedNotificationFeatureKey` - the key of the feature being enabled/disabled
105
+ * `FTSFeatureStatusChangedNotificationEnabledKey` - the `NSNumber` of whether the feature was enabled or disabled
106
+
107
+ ### Plattform Support
108
+
109
+ * iOS
110
+ * example project
111
+ * full test coverage
112
+ * configuration screen
113
+ * Mac OSX
114
+ * example project
115
+ * full test coverage
116
+
59
117
  ## Command-Line-Interface
60
118
 
61
119
  If you install the gem, you will be able to use the Command-Line-Interface.
62
120
 
63
- The CLI consists of 2 commands:
121
+ The CLI consists of 3 commands:
64
122
 
65
- - `plist` - creates a `Features.plist` file for enabled/disabled features like that mentioned above.
123
+ - `plist` - creates a `Features.plist` file for enabled/disabled features including their description (optional) like that mentioned above.
66
124
  - `settings` - creates a `Settings.bundle` used by the OS settings. These can then be used to enable/disable the features at runtime.
67
- - `category` - creates `FlipTheSwitch+Features.{h,m}` files for features, thus giving compile-time checks for adding/removal of new features.
125
+ - `category` - creates `FTSFlipTheSwitch+Features.{h,m}` files for features, thus giving compile-time checks for adding/removal of new features.
68
126
  e.g:
69
127
 
70
128
  ```objective-c
71
129
  /* AUTO-GENERATED. DO NOT ALTER */
72
- #import <FlipTheSwitch/FlipTheSwitch.h>
130
+ #import <FlipTheSwitch/FTSFlipTheSwitch.h>
73
131
 
74
- @interface FlipTheSwitch (Features)
132
+ @interface FTSFlipTheSwitch (Features)
75
133
 
76
134
  + (BOOL)isAwesomeFeatureEnabled;
77
135
  + (void)enableAwesomeFeature;
78
136
  + (void)disableAwesomeFeature;
79
137
  + (void)setAwesomeFeatureEnabled:(BOOL)enabled;
138
+ + (void)resetAwesomeFeatureController;
139
+ + (NSString *)awesomeFeatureControllerKey;
80
140
 
81
141
  @end
82
142
  ```
83
143
 
84
- The features, along with their default enabled/disabled state, are read from a `features.yml` file. e.g.:
85
-
86
- ```yaml
87
- default:
88
- awesome_feature: Yes
144
+ ### Define Features and subfeatures
145
+
146
+ The features and subfeatures, along with their default enabled/disabled state, are read from a `features.json` file. e.g.:
147
+
148
+ ```json
149
+ {
150
+ "default": {
151
+ "awesome_feature": {
152
+ "enabled": true,
153
+ "description": "Makes this project awesome",
154
+ "sub_feature": {
155
+ "enabled": true,
156
+ "description": "Makes this project even more awesome"
157
+ }
158
+ }
159
+ },
160
+ "beta": {
161
+ "awesome_feature": {
162
+ "enabled": false
163
+ }
164
+ }
165
+ }
166
+
89
167
  ```
90
168
 
91
169
  In order to avoid typing in the same options all the time, you can create a `.flip.yml` file for the default options, e.g.:
@@ -108,6 +186,8 @@ Add `gem 'flip_the_switch'` to your Gemfile
108
186
 
109
187
  - [Michael England](https://github.com/michaelengland) @ [SoundCloud](https://github.com/soundcloud)
110
188
  - [Rob Siwek](https://github.com/nerdsRob) @ [SoundCloud](https://github.com/soundcloud)
189
+ - [Kristina Roddewig](https://github.com/FrauR) @ [SoundCloud](https://github.com/soundcloud)
190
+ - [Vincent Garrigues](https://github.com/garriguv) @ [SoundCloud](https://github.com/soundcloud)
111
191
 
112
192
  ## License
113
193
 
@@ -0,0 +1,208 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14F27" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="7Me-Uf-CYC">
3
+ <dependencies>
4
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
5
+ <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
6
+ </dependencies>
7
+ <scenes>
8
+ <!--Features-->
9
+ <scene sceneID="HmN-ks-p58">
10
+ <objects>
11
+ <tableViewController storyboardIdentifier="FeatureConfigurationScreen" useStoryboardIdentifierAsRestorationIdentifier="YES" id="MfP-lZ-lJZ" sceneMemberID="viewController">
12
+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="qox-Cy-iKL">
13
+ <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
14
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
15
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
16
+ <prototypes>
17
+ <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="featureCell" rowHeight="80" id="D45-mi-SWk" customClass="FTSFeatureCell">
18
+ <rect key="frame" x="0.0" y="22" width="320" height="80"/>
19
+ <autoresizingMask key="autoresizingMask"/>
20
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="D45-mi-SWk" id="SUw-kE-nVw">
21
+ <rect key="frame" x="0.0" y="0.0" width="320" height="79"/>
22
+ <autoresizingMask key="autoresizingMask"/>
23
+ <subviews>
24
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="249" text="FeatureDescription" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="243" translatesAutoresizingMaskIntoConstraints="NO" id="WCn-j6-JQJ" customClass="FTSFeatureDescriptionLabel">
25
+ <rect key="frame" x="10" y="38" width="243" height="31"/>
26
+ <fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
27
+ <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
28
+ <nil key="highlightedColor"/>
29
+ </label>
30
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="FeatureName" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="243" translatesAutoresizingMaskIntoConstraints="NO" id="JrP-11-dyn">
31
+ <rect key="frame" x="10" y="10" width="243" height="20"/>
32
+ <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
33
+ <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
34
+ <nil key="highlightedColor"/>
35
+ </label>
36
+ <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FXx-pu-gXY">
37
+ <rect key="frame" x="261" y="10" width="51" height="31"/>
38
+ </switch>
39
+ </subviews>
40
+ <constraints>
41
+ <constraint firstAttribute="bottom" secondItem="WCn-j6-JQJ" secondAttribute="bottom" constant="10" id="1VP-zB-7id"/>
42
+ <constraint firstItem="WCn-j6-JQJ" firstAttribute="leading" secondItem="SUw-kE-nVw" secondAttribute="leading" constant="10" id="1ha-yE-GKY"/>
43
+ <constraint firstItem="JrP-11-dyn" firstAttribute="trailing" secondItem="WCn-j6-JQJ" secondAttribute="trailing" id="9Bo-eH-7qf"/>
44
+ <constraint firstItem="JrP-11-dyn" firstAttribute="top" secondItem="SUw-kE-nVw" secondAttribute="top" constant="10" id="axL-pe-FFk"/>
45
+ <constraint firstItem="JrP-11-dyn" firstAttribute="leading" secondItem="SUw-kE-nVw" secondAttribute="leading" constant="10" id="iuu-aZ-hyf"/>
46
+ <constraint firstItem="FXx-pu-gXY" firstAttribute="leading" secondItem="WCn-j6-JQJ" secondAttribute="trailing" constant="8" id="jq5-8Y-uyk"/>
47
+ <constraint firstAttribute="trailingMargin" secondItem="FXx-pu-gXY" secondAttribute="trailing" constant="2" id="nsd-Sy-5Xy"/>
48
+ <constraint firstItem="JrP-11-dyn" firstAttribute="top" secondItem="FXx-pu-gXY" secondAttribute="top" id="y4U-Do-c8a"/>
49
+ <constraint firstItem="WCn-j6-JQJ" firstAttribute="top" secondItem="JrP-11-dyn" secondAttribute="bottom" constant="8" id="ywP-47-8cN"/>
50
+ </constraints>
51
+ </tableViewCellContentView>
52
+ </tableViewCell>
53
+ </prototypes>
54
+ <connections>
55
+ <outlet property="dataSource" destination="MfP-lZ-lJZ" id="WKz-RW-2Ip"/>
56
+ <outlet property="delegate" destination="MfP-lZ-lJZ" id="TeA-Cq-pD2"/>
57
+ </connections>
58
+ </tableView>
59
+ <navigationItem key="navigationItem" title="Features" id="CmQ-ny-cRX">
60
+ <barButtonItem key="rightBarButtonItem" systemItem="done" id="Fxb-5D-mkV">
61
+ <connections>
62
+ <action selector="doneButtonTapped:" destination="MfP-lZ-lJZ" id="l1S-0q-bey"/>
63
+ </connections>
64
+ </barButtonItem>
65
+ </navigationItem>
66
+ </tableViewController>
67
+ <placeholder placeholderIdentifier="IBFirstResponder" id="TNm-zL-vN8" userLabel="First Responder" sceneMemberID="firstResponder"/>
68
+ </objects>
69
+ <point key="canvasLocation" x="286.39999999999998" y="381.50666666666666"/>
70
+ </scene>
71
+ <!--Features-->
72
+ <scene sceneID="d4s-di-gRP">
73
+ <objects>
74
+ <viewController id="VNR-A9-bVe" customClass="FTSFeatureConfigurationViewController" sceneMemberID="viewController">
75
+ <layoutGuides>
76
+ <viewControllerLayoutGuide type="top" id="CZV-QH-phi"/>
77
+ <viewControllerLayoutGuide type="bottom" id="Hl8-DJ-f5M"/>
78
+ </layoutGuides>
79
+ <view key="view" contentMode="scaleToFill" id="FK5-Sw-fHL">
80
+ <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
81
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
82
+ <subviews>
83
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jTG-0P-p33" userLabel="button view">
84
+ <rect key="frame" x="0.0" y="64" width="320" height="30"/>
85
+ <subviews>
86
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ak9-OY-hUu">
87
+ <rect key="frame" x="0.0" y="0.0" width="304" height="30"/>
88
+ <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
89
+ <state key="normal" title="reset features">
90
+ <color key="titleColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
91
+ </state>
92
+ <connections>
93
+ <action selector="resetFeatures:" destination="VNR-A9-bVe" eventType="touchUpInside" id="Wtx-Jk-Rnw"/>
94
+ </connections>
95
+ </button>
96
+ </subviews>
97
+ <color key="backgroundColor" red="0.83727904040000001" green="0.23504479617310975" blue="0.046994104183399457" alpha="1" colorSpace="calibratedRGB"/>
98
+ <constraints>
99
+ <constraint firstItem="Ak9-OY-hUu" firstAttribute="height" secondItem="jTG-0P-p33" secondAttribute="height" id="4P0-bd-VAF"/>
100
+ <constraint firstItem="Ak9-OY-hUu" firstAttribute="leading" secondItem="jTG-0P-p33" secondAttribute="leading" id="aS8-Zg-5nf"/>
101
+ <constraint firstAttribute="trailing" secondItem="Ak9-OY-hUu" secondAttribute="trailing" constant="16" id="l9R-D4-Gtg"/>
102
+ <constraint firstAttribute="height" constant="30" id="pY4-gc-j0P"/>
103
+ <constraint firstAttribute="centerY" secondItem="Ak9-OY-hUu" secondAttribute="centerY" id="q85-e1-Msa"/>
104
+ </constraints>
105
+ </view>
106
+ <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="CAV-UP-hYJ">
107
+ <rect key="frame" x="0.0" y="96" width="320" height="472"/>
108
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
109
+ <prototypes>
110
+ <tableViewCell contentMode="scaleToFill" selectionStyle="default" hidesAccessoryWhenEditing="NO" indentationWidth="10" reuseIdentifier="featureCell" rowHeight="80" id="WYa-NK-09p" customClass="FTSFeatureCell">
111
+ <rect key="frame" x="0.0" y="22" width="320" height="80"/>
112
+ <autoresizingMask key="autoresizingMask"/>
113
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="WYa-NK-09p" id="rl5-2d-YAl">
114
+ <rect key="frame" x="0.0" y="0.0" width="320" height="79"/>
115
+ <autoresizingMask key="autoresizingMask"/>
116
+ <subviews>
117
+ <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T5e-hB-sUM">
118
+ <rect key="frame" x="255" y="18" width="51" height="31"/>
119
+ <connections>
120
+ <action selector="featureToggled:" destination="WYa-NK-09p" eventType="valueChanged" id="eed-Vv-qb7"/>
121
+ </connections>
122
+ </switch>
123
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="FeatureName" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="243" translatesAutoresizingMaskIntoConstraints="NO" id="kZ7-Ia-TXE">
124
+ <rect key="frame" x="18" y="18" width="102" height="20"/>
125
+ <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
126
+ <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
127
+ <nil key="highlightedColor"/>
128
+ </label>
129
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="249" text="FeatureDescription" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="243" translatesAutoresizingMaskIntoConstraints="NO" id="MwL-gA-zbI" customClass="FTSFeatureDescriptionLabel">
130
+ <rect key="frame" x="18" y="46" width="229" height="15"/>
131
+ <fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
132
+ <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
133
+ <nil key="highlightedColor"/>
134
+ </label>
135
+ </subviews>
136
+ <constraints>
137
+ <constraint firstItem="T5e-hB-sUM" firstAttribute="top" secondItem="kZ7-Ia-TXE" secondAttribute="top" id="4sp-vs-EGn"/>
138
+ <constraint firstItem="MwL-gA-zbI" firstAttribute="leading" secondItem="kZ7-Ia-TXE" secondAttribute="leading" id="KLd-Mh-rhV"/>
139
+ <constraint firstItem="kZ7-Ia-TXE" firstAttribute="leading" secondItem="rl5-2d-YAl" secondAttribute="leadingMargin" constant="10" id="Own-CZ-DEv"/>
140
+ <constraint firstAttribute="bottomMargin" secondItem="MwL-gA-zbI" secondAttribute="bottom" constant="10" id="VHJ-KA-U0P"/>
141
+ <constraint firstItem="kZ7-Ia-TXE" firstAttribute="top" secondItem="rl5-2d-YAl" secondAttribute="topMargin" constant="10" id="afa-9Z-kyZ"/>
142
+ <constraint firstItem="MwL-gA-zbI" firstAttribute="leading" secondItem="kZ7-Ia-TXE" secondAttribute="leading" id="bHw-Lq-K8w"/>
143
+ <constraint firstAttribute="trailingMargin" secondItem="T5e-hB-sUM" secondAttribute="trailing" constant="8" id="fib-2g-StW"/>
144
+ <constraint firstItem="kZ7-Ia-TXE" firstAttribute="top" secondItem="T5e-hB-sUM" secondAttribute="top" id="pt9-Lw-QSL"/>
145
+ <constraint firstItem="MwL-gA-zbI" firstAttribute="top" secondItem="kZ7-Ia-TXE" secondAttribute="bottom" constant="8" id="tgd-Hc-Aoy"/>
146
+ <constraint firstItem="T5e-hB-sUM" firstAttribute="leading" secondItem="MwL-gA-zbI" secondAttribute="trailing" constant="8" id="xWp-xU-WYT"/>
147
+ </constraints>
148
+ </tableViewCellContentView>
149
+ <connections>
150
+ <outlet property="featureDescriptionLabel" destination="MwL-gA-zbI" id="d62-i0-CsK"/>
151
+ <outlet property="featureNameLabel" destination="kZ7-Ia-TXE" id="qYa-fX-kfb"/>
152
+ <outlet property="toggle" destination="T5e-hB-sUM" id="7AF-V8-nFY"/>
153
+ </connections>
154
+ </tableViewCell>
155
+ </prototypes>
156
+ </tableView>
157
+ </subviews>
158
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
159
+ <constraints>
160
+ <constraint firstAttribute="trailingMargin" secondItem="jTG-0P-p33" secondAttribute="trailing" constant="-16" id="7XB-JS-ZUB"/>
161
+ <constraint firstItem="jTG-0P-p33" firstAttribute="leading" secondItem="FK5-Sw-fHL" secondAttribute="leadingMargin" constant="-16" id="Ccp-Z5-hK7"/>
162
+ <constraint firstItem="jTG-0P-p33" firstAttribute="top" secondItem="CZV-QH-phi" secondAttribute="bottom" id="N8Y-lp-UcX"/>
163
+ <constraint firstItem="CAV-UP-hYJ" firstAttribute="top" secondItem="jTG-0P-p33" secondAttribute="bottom" constant="2" id="TpK-0d-Kg8"/>
164
+ <constraint firstAttribute="bottomMargin" secondItem="CAV-UP-hYJ" secondAttribute="bottom" id="Vwk-5q-UCu"/>
165
+ <constraint firstAttribute="trailingMargin" secondItem="CAV-UP-hYJ" secondAttribute="trailing" constant="-16" id="ZbZ-ha-pGQ"/>
166
+ <constraint firstItem="CAV-UP-hYJ" firstAttribute="leading" secondItem="FK5-Sw-fHL" secondAttribute="leadingMargin" constant="-16" id="xyC-iY-xjJ"/>
167
+ </constraints>
168
+ </view>
169
+ <navigationItem key="navigationItem" title="Features" id="3uf-ZL-7jI">
170
+ <barButtonItem key="rightBarButtonItem" systemItem="done" id="RdH-Dv-eEC">
171
+ <connections>
172
+ <action selector="doneButtonTapped:" destination="VNR-A9-bVe" id="9gW-r3-wTk"/>
173
+ </connections>
174
+ </barButtonItem>
175
+ </navigationItem>
176
+ <connections>
177
+ <outlet property="tableView" destination="CAV-UP-hYJ" id="dGn-6y-wnj"/>
178
+ </connections>
179
+ </viewController>
180
+ <placeholder placeholderIdentifier="IBFirstResponder" id="SNb-iL-KWt" userLabel="First Responder" sceneMemberID="firstResponder"/>
181
+ </objects>
182
+ <point key="canvasLocation" x="1217" y="981"/>
183
+ </scene>
184
+ <!--Navigation Controller-->
185
+ <scene sceneID="QDo-Id-00w">
186
+ <objects>
187
+ <navigationController automaticallyAdjustsScrollViewInsets="NO" id="7Me-Uf-CYC" sceneMemberID="viewController">
188
+ <toolbarItems/>
189
+ <navigationBar key="navigationBar" contentMode="scaleToFill" id="Atc-Ob-RNj">
190
+ <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
191
+ <autoresizingMask key="autoresizingMask"/>
192
+ </navigationBar>
193
+ <nil name="viewControllers"/>
194
+ <connections>
195
+ <segue destination="VNR-A9-bVe" kind="relationship" relationship="rootViewController" id="tmG-Lz-Fw1"/>
196
+ </connections>
197
+ </navigationController>
198
+ <placeholder placeholderIdentifier="IBFirstResponder" id="Uty-I5-CH8" userLabel="First Responder" sceneMemberID="firstResponder"/>
199
+ </objects>
200
+ <point key="canvasLocation" x="826" y="981"/>
201
+ </scene>
202
+ </scenes>
203
+ <simulatedMetricsContainer key="defaultSimulatedMetrics">
204
+ <simulatedStatusBarMetrics key="statusBar"/>
205
+ <simulatedOrientationMetrics key="orientation"/>
206
+ <simulatedScreenMetrics key="destination" type="retina4"/>
207
+ </simulatedMetricsContainer>
208
+ </document>