├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Examples └── FlowKitExample │ ├── FlowKitExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── filip.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ └── xcuserdata │ │ └── filip.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── FlowKitExample.xcscheme │ │ ├── FlowKitExampleUITests.xcscheme │ │ └── xcschememanagement.plist │ ├── FlowKitExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── README.md │ ├── Stevia+Tap.swift │ ├── flow │ │ └── MainFlow.swift │ └── view │ │ ├── LoginViewController.swift │ │ ├── MainViewController.swift │ │ └── TutorialViewController.swift │ ├── FlowKitExampleTests │ ├── FlowKitExampleTests.swift │ └── Info.plist │ └── FlowKitExampleUITests │ ├── FlowKitExampleUITests.swift │ └── Info.plist ├── FlowKit.podspec ├── FlowKit.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ ├── FlowKit.xcscheme │ └── FlowKitTests.xcscheme ├── FlowKit.xcworkspace └── contents.xcworkspacedata ├── FlowKit ├── DefaultLets.swift ├── FlowKit.swift ├── Info.plist ├── Lets.swift └── LetsFactory.swift ├── FlowKitTests ├── DefaultLetsTests.swift ├── FlowTests.swift ├── Info.plist ├── LetsFactoryTest.swift ├── LetsTests.swift └── stubs │ ├── FlowStub.swift │ ├── LetsFactoryStub.swift │ └── LetsStub.swift ├── LICENSE ├── Podfile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /Pods 3 | Podfile.lock 4 | *.xcuserstate 5 | xcuserdata/ 6 | 7 | 8 | ## GitHub Token 9 | .apitoken 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8 3 | cache: cocoapods 4 | env: 5 | global: 6 | - WORKSPACE=FlowKit.xcworkspace 7 | - SCHEME=FlowKit 8 | - TESTDEVICE="platform=iOS Simulator,name=iPhone 7,OS=10.0" 9 | 10 | script: 11 | - set -o pipefail 12 | - xcodebuild -version 13 | - xcodebuild -showsdks 14 | - xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk iphonesimulator -destination "$TESTDEVICE" build test | xcpretty 15 | 16 | after_success: 17 | - bash <(curl -s https://codecov.io/bash) -J "$SCHEME" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # FlowKit CHANGELOG 2 | 3 | ## 0.2.0 4 | - add `lets.popTo(vc) { $0.configurator }` 5 | 6 | ## 0.1.2 7 | - set minimum deployment target to 8.0 8 | 9 | ## 0.1.1 10 | - add to CocoaPods 11 | 12 | ## 0.1.0 13 | - initial version -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5CEBA1FA01BC0ACB81296C58 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBAD199E8714F188EB84D5 /* LoginViewController.swift */; }; 11 | 5CEBA21711F225AF4EF764AA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBACF493CEC2A3C8597C1E /* AppDelegate.swift */; }; 12 | 5CEBA2EDA85AEEA65375DE7D /* FlowKitExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBAE0C5F6DC127527DF11D /* FlowKitExampleTests.swift */; }; 13 | 5CEBA4318035EB14834562E0 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA9B658BBA10A8C1C2FE2 /* MainViewController.swift */; }; 14 | 5CEBA6FC3EA468E6988507FD /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 5CEBAFD8E6340539E3C8D373 /* README.md */; }; 15 | 5CEBA9EA0B81D0CA7161E0A1 /* TutorialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBACD5C300236616C80BAD /* TutorialViewController.swift */; }; 16 | 5CEBAAADFE48C2E59DEEA912 /* Stevia+Tap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA9F746B778B10B5A8EB8 /* Stevia+Tap.swift */; }; 17 | 5CEBAAE0ABFDB16AC4912023 /* MainFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA6F2E1AA10F5240A4C2D /* MainFlow.swift */; }; 18 | 5CEBAC0CBF0F00874A7CB710 /* FlowKitExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA4BEB950C5637F3C0ED4 /* FlowKitExampleUITests.swift */; }; 19 | 5CEBAFE6CBEBD1FE45E8A9AD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CEBA74087DF4BB31664EE3C /* Assets.xcassets */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 5CEBA8E6D38772F84519FE0D /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 5CEBAF266472B59936E8C358 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 5CEBAFDFDDAEABF9C9A5C38C; 28 | remoteInfo = FlowKitExample; 29 | }; 30 | 5CEBAEBB27ABA68F87B5D041 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 5CEBAF266472B59936E8C358 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 5CEBAFDFDDAEABF9C9A5C38C; 35 | remoteInfo = FlowKitExample; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 5CEBA0EAD12DEA7B8D7159B6 /* FlowKitExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowKitExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 5CEBA3DBD10AA24EA4C3E95A /* FlowKitExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowKitExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 5CEBA4BEB950C5637F3C0ED4 /* FlowKitExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowKitExampleUITests.swift; sourceTree = ""; }; 43 | 5CEBA5A61A0F2FE53C5A0349 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; 44 | 5CEBA65A641093C97E5B466D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; 45 | 5CEBA6F2E1AA10F5240A4C2D /* MainFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainFlow.swift; sourceTree = ""; }; 46 | 5CEBA74087DF4BB31664EE3C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 5CEBA9921EA475013856DA6F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; 48 | 5CEBA9B658BBA10A8C1C2FE2 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 49 | 5CEBA9F746B778B10B5A8EB8 /* Stevia+Tap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Stevia+Tap.swift"; sourceTree = ""; }; 50 | 5CEBACD5C300236616C80BAD /* TutorialViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TutorialViewController.swift; sourceTree = ""; }; 51 | 5CEBACF493CEC2A3C8597C1E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 52 | 5CEBAD199E8714F188EB84D5 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 53 | 5CEBADFF8055BBF7CDD8B60D /* FlowKitExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlowKitExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 5CEBAE0C5F6DC127527DF11D /* FlowKitExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowKitExampleTests.swift; sourceTree = ""; }; 55 | 5CEBAFD8E6340539E3C8D373 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | 5CEBA1AB30297BB21087DE5A /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | 5CEBA2356D77DC0057AF096E /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | 5CEBAE0C3C9DDC7AE0FCBDC4 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | /* End PBXFrameworksBuildPhase section */ 81 | 82 | /* Begin PBXGroup section */ 83 | 5CEBA10815324CDA79DDA419 /* FlowKitExample */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 5CEBA65A641093C97E5B466D /* Info.plist */, 87 | 5CEBA74087DF4BB31664EE3C /* Assets.xcassets */, 88 | 5CEBACF493CEC2A3C8597C1E /* AppDelegate.swift */, 89 | 5CEBA9F746B778B10B5A8EB8 /* Stevia+Tap.swift */, 90 | 5CEBAFD8E6340539E3C8D373 /* README.md */, 91 | 5CEBA295D5BBAFE92576CE6F /* view */, 92 | 5CEBAE10155B53F72AEFEF54 /* flow */, 93 | ); 94 | path = FlowKitExample; 95 | sourceTree = ""; 96 | }; 97 | 5CEBA295D5BBAFE92576CE6F /* view */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 5CEBACD5C300236616C80BAD /* TutorialViewController.swift */, 101 | 5CEBA9B658BBA10A8C1C2FE2 /* MainViewController.swift */, 102 | 5CEBAD199E8714F188EB84D5 /* LoginViewController.swift */, 103 | ); 104 | path = view; 105 | sourceTree = ""; 106 | }; 107 | 5CEBA523604E19CAD64E641D /* FlowKitExampleUITests */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 5CEBA9921EA475013856DA6F /* Info.plist */, 111 | 5CEBA4BEB950C5637F3C0ED4 /* FlowKitExampleUITests.swift */, 112 | ); 113 | path = FlowKitExampleUITests; 114 | sourceTree = ""; 115 | }; 116 | 5CEBAA96DE7259DD59F557B9 = { 117 | isa = PBXGroup; 118 | children = ( 119 | 5CEBAB4C24E7AAA9C89ABEDD /* Products */, 120 | 5CEBA10815324CDA79DDA419 /* FlowKitExample */, 121 | 5CEBAC8A18BCE09EE524EE0C /* FlowKitExampleTests */, 122 | 5CEBA523604E19CAD64E641D /* FlowKitExampleUITests */, 123 | ); 124 | sourceTree = ""; 125 | }; 126 | 5CEBAB4C24E7AAA9C89ABEDD /* Products */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 5CEBADFF8055BBF7CDD8B60D /* FlowKitExample.app */, 130 | 5CEBA0EAD12DEA7B8D7159B6 /* FlowKitExampleTests.xctest */, 131 | 5CEBA3DBD10AA24EA4C3E95A /* FlowKitExampleUITests.xctest */, 132 | ); 133 | name = Products; 134 | sourceTree = ""; 135 | }; 136 | 5CEBAC8A18BCE09EE524EE0C /* FlowKitExampleTests */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 5CEBA5A61A0F2FE53C5A0349 /* Info.plist */, 140 | 5CEBAE0C5F6DC127527DF11D /* FlowKitExampleTests.swift */, 141 | ); 142 | path = FlowKitExampleTests; 143 | sourceTree = ""; 144 | }; 145 | 5CEBAE10155B53F72AEFEF54 /* flow */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 5CEBA6F2E1AA10F5240A4C2D /* MainFlow.swift */, 149 | ); 150 | path = flow; 151 | sourceTree = ""; 152 | }; 153 | /* End PBXGroup section */ 154 | 155 | /* Begin PBXNativeTarget section */ 156 | 5CEBA6A8131443A826317EE6 /* FlowKitExampleTests */ = { 157 | isa = PBXNativeTarget; 158 | buildConfigurationList = 5CEBA966C9A82F3C59A9209C /* Build configuration list for PBXNativeTarget "FlowKitExampleTests" */; 159 | buildPhases = ( 160 | 5CEBA0A9605315744CA37386 /* Sources */, 161 | 5CEBA1AB30297BB21087DE5A /* Frameworks */, 162 | 5CEBA90AFDC982286C363B16 /* Resources */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | 5CEBA17B2AB627C7BB2C46FE /* PBXTargetDependency */, 168 | ); 169 | name = FlowKitExampleTests; 170 | productName = FlowKitExampleTests; 171 | productReference = 5CEBA0EAD12DEA7B8D7159B6 /* FlowKitExampleTests.xctest */; 172 | productType = "com.apple.product-type.bundle.unit-test"; 173 | }; 174 | 5CEBAD8D517F3B044BE77BE9 /* FlowKitExampleUITests */ = { 175 | isa = PBXNativeTarget; 176 | buildConfigurationList = 5CEBAB2F1216B5A98A9BC9B3 /* Build configuration list for PBXNativeTarget "FlowKitExampleUITests" */; 177 | buildPhases = ( 178 | 5CEBA29582CABA5366E2DA62 /* Sources */, 179 | 5CEBAE0C3C9DDC7AE0FCBDC4 /* Frameworks */, 180 | 5CEBABEFBAD864EC910DF898 /* Resources */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | 5CEBAD47B20BF51315C201AB /* PBXTargetDependency */, 186 | ); 187 | name = FlowKitExampleUITests; 188 | productName = FlowKitExampleUITests; 189 | productReference = 5CEBA3DBD10AA24EA4C3E95A /* FlowKitExampleUITests.xctest */; 190 | productType = "com.apple.product-type.bundle.ui-testing"; 191 | }; 192 | 5CEBAFDFDDAEABF9C9A5C38C /* FlowKitExample */ = { 193 | isa = PBXNativeTarget; 194 | buildConfigurationList = 5CEBA7743005A82713BEB207 /* Build configuration list for PBXNativeTarget "FlowKitExample" */; 195 | buildPhases = ( 196 | 5CEBA2A42302B6770C64845C /* Sources */, 197 | 5CEBA2356D77DC0057AF096E /* Frameworks */, 198 | 5CEBA9D797FAA1F398F025DF /* Resources */, 199 | ); 200 | buildRules = ( 201 | ); 202 | dependencies = ( 203 | ); 204 | name = FlowKitExample; 205 | productName = FlowKitExample; 206 | productReference = 5CEBADFF8055BBF7CDD8B60D /* FlowKitExample.app */; 207 | productType = "com.apple.product-type.application"; 208 | }; 209 | /* End PBXNativeTarget section */ 210 | 211 | /* Begin PBXProject section */ 212 | 5CEBAF266472B59936E8C358 /* Project object */ = { 213 | isa = PBXProject; 214 | attributes = { 215 | LastUpgradeCheck = 0800; 216 | ORGANIZATIONNAME = "Filip Zawada"; 217 | TargetAttributes = { 218 | 5CEBAFDFDDAEABF9C9A5C38C = { 219 | DevelopmentTeam = T379HXD2U2; 220 | }; 221 | }; 222 | }; 223 | buildConfigurationList = 5CEBACE097415F2D93EAD225 /* Build configuration list for PBXProject "FlowKitExample" */; 224 | compatibilityVersion = "Xcode 3.2"; 225 | developmentRegion = English; 226 | hasScannedForEncodings = 0; 227 | knownRegions = ( 228 | en, 229 | ); 230 | mainGroup = 5CEBAA96DE7259DD59F557B9; 231 | productRefGroup = 5CEBAB4C24E7AAA9C89ABEDD /* Products */; 232 | projectDirPath = ""; 233 | projectRoot = ""; 234 | targets = ( 235 | 5CEBAFDFDDAEABF9C9A5C38C /* FlowKitExample */, 236 | 5CEBA6A8131443A826317EE6 /* FlowKitExampleTests */, 237 | 5CEBAD8D517F3B044BE77BE9 /* FlowKitExampleUITests */, 238 | ); 239 | }; 240 | /* End PBXProject section */ 241 | 242 | /* Begin PBXResourcesBuildPhase section */ 243 | 5CEBA90AFDC982286C363B16 /* Resources */ = { 244 | isa = PBXResourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | 5CEBA9D797FAA1F398F025DF /* Resources */ = { 251 | isa = PBXResourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | 5CEBAFE6CBEBD1FE45E8A9AD /* Assets.xcassets in Resources */, 255 | 5CEBA6FC3EA468E6988507FD /* README.md in Resources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | 5CEBABEFBAD864EC910DF898 /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXResourcesBuildPhase section */ 267 | 268 | /* Begin PBXSourcesBuildPhase section */ 269 | 5CEBA0A9605315744CA37386 /* Sources */ = { 270 | isa = PBXSourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | 5CEBA2EDA85AEEA65375DE7D /* FlowKitExampleTests.swift in Sources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | 5CEBA29582CABA5366E2DA62 /* Sources */ = { 278 | isa = PBXSourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | 5CEBAC0CBF0F00874A7CB710 /* FlowKitExampleUITests.swift in Sources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | 5CEBA2A42302B6770C64845C /* Sources */ = { 286 | isa = PBXSourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 5CEBA21711F225AF4EF764AA /* AppDelegate.swift in Sources */, 290 | 5CEBAAADFE48C2E59DEEA912 /* Stevia+Tap.swift in Sources */, 291 | 5CEBA9EA0B81D0CA7161E0A1 /* TutorialViewController.swift in Sources */, 292 | 5CEBA4318035EB14834562E0 /* MainViewController.swift in Sources */, 293 | 5CEBA1FA01BC0ACB81296C58 /* LoginViewController.swift in Sources */, 294 | 5CEBAAE0ABFDB16AC4912023 /* MainFlow.swift in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXSourcesBuildPhase section */ 299 | 300 | /* Begin PBXTargetDependency section */ 301 | 5CEBA17B2AB627C7BB2C46FE /* PBXTargetDependency */ = { 302 | isa = PBXTargetDependency; 303 | target = 5CEBAFDFDDAEABF9C9A5C38C /* FlowKitExample */; 304 | targetProxy = 5CEBA8E6D38772F84519FE0D /* PBXContainerItemProxy */; 305 | }; 306 | 5CEBAD47B20BF51315C201AB /* PBXTargetDependency */ = { 307 | isa = PBXTargetDependency; 308 | target = 5CEBAFDFDDAEABF9C9A5C38C /* FlowKitExample */; 309 | targetProxy = 5CEBAEBB27ABA68F87B5D041 /* PBXContainerItemProxy */; 310 | }; 311 | /* End PBXTargetDependency section */ 312 | 313 | /* Begin XCBuildConfiguration section */ 314 | 5CEBA0586971B06BC430A649 /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/FlowKitExample.app/FlowKitExample"; 318 | INFOPLIST_FILE = FlowKitExampleTests/Info.plist; 319 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 320 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKitExampleTests; 321 | PRODUCT_NAME = "$(TARGET_NAME)"; 322 | SWIFT_VERSION = 3.0; 323 | TEST_HOST = "$(BUNDLE_LOADER)"; 324 | }; 325 | name = Debug; 326 | }; 327 | 5CEBA13C18689421A5665082 /* Release */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/FlowKitExample.app/FlowKitExample"; 331 | INFOPLIST_FILE = FlowKitExampleTests/Info.plist; 332 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 333 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKitExampleTests; 334 | PRODUCT_NAME = "$(TARGET_NAME)"; 335 | SWIFT_VERSION = 3.0; 336 | TEST_HOST = "$(BUNDLE_LOADER)"; 337 | }; 338 | name = Release; 339 | }; 340 | 5CEBA7C4EF43BD9EC20A203D /* Debug */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | INFOPLIST_FILE = FlowKitExampleUITests/Info.plist; 344 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 345 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKitExampleUITests; 346 | PRODUCT_NAME = "$(TARGET_NAME)"; 347 | SWIFT_VERSION = 3.0; 348 | TEST_TARGET_NAME = FlowKitExample; 349 | }; 350 | name = Debug; 351 | }; 352 | 5CEBA7F73E95D311DCA96323 /* Debug */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | ALWAYS_SEARCH_USER_PATHS = NO; 356 | CLANG_ANALYZER_NONNULL = YES; 357 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 358 | CLANG_CXX_LIBRARY = "libc++"; 359 | CLANG_ENABLE_MODULES = YES; 360 | CLANG_ENABLE_OBJC_ARC = YES; 361 | CLANG_WARN_BOOL_CONVERSION = YES; 362 | CLANG_WARN_CONSTANT_CONVERSION = YES; 363 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 364 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 365 | CLANG_WARN_EMPTY_BODY = YES; 366 | CLANG_WARN_ENUM_CONVERSION = YES; 367 | CLANG_WARN_INFINITE_RECURSION = YES; 368 | CLANG_WARN_INT_CONVERSION = YES; 369 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 370 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 371 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 372 | CLANG_WARN_UNREACHABLE_CODE = YES; 373 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 374 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 375 | COPY_PHASE_STRIP = NO; 376 | DEBUG_INFORMATION_FORMAT = dwarf; 377 | ENABLE_STRICT_OBJC_MSGSEND = YES; 378 | ENABLE_TESTABILITY = YES; 379 | GCC_C_LANGUAGE_STANDARD = gnu99; 380 | GCC_DYNAMIC_NO_PIC = NO; 381 | GCC_NO_COMMON_BLOCKS = YES; 382 | GCC_OPTIMIZATION_LEVEL = 0; 383 | GCC_PREPROCESSOR_DEFINITIONS = ( 384 | "DEBUG=1", 385 | "$(inherited)", 386 | ); 387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 389 | GCC_WARN_UNDECLARED_SELECTOR = YES; 390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 391 | GCC_WARN_UNUSED_FUNCTION = YES; 392 | GCC_WARN_UNUSED_VARIABLE = YES; 393 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 394 | MTL_ENABLE_DEBUG_INFO = YES; 395 | ONLY_ACTIVE_ARCH = YES; 396 | SDKROOT = iphoneos; 397 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 398 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 399 | }; 400 | name = Debug; 401 | }; 402 | 5CEBA8F4E3A922D9A8632AF8 /* Release */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 406 | DEVELOPMENT_TEAM = T379HXD2U2; 407 | INFOPLIST_FILE = FlowKitExample/Info.plist; 408 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 409 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKitExample; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | SWIFT_VERSION = 3.0; 412 | }; 413 | name = Release; 414 | }; 415 | 5CEBA944F183EC8FB20AFF55 /* Release */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | ALWAYS_SEARCH_USER_PATHS = NO; 419 | CLANG_ANALYZER_NONNULL = YES; 420 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 421 | CLANG_CXX_LIBRARY = "libc++"; 422 | CLANG_ENABLE_MODULES = YES; 423 | CLANG_ENABLE_OBJC_ARC = YES; 424 | CLANG_WARN_BOOL_CONVERSION = YES; 425 | CLANG_WARN_CONSTANT_CONVERSION = YES; 426 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 427 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 428 | CLANG_WARN_EMPTY_BODY = YES; 429 | CLANG_WARN_ENUM_CONVERSION = YES; 430 | CLANG_WARN_INFINITE_RECURSION = YES; 431 | CLANG_WARN_INT_CONVERSION = YES; 432 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 433 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 434 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 435 | CLANG_WARN_UNREACHABLE_CODE = YES; 436 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 437 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 438 | COPY_PHASE_STRIP = NO; 439 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 440 | ENABLE_NS_ASSERTIONS = NO; 441 | ENABLE_STRICT_OBJC_MSGSEND = YES; 442 | GCC_C_LANGUAGE_STANDARD = gnu99; 443 | GCC_NO_COMMON_BLOCKS = YES; 444 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 445 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 446 | GCC_WARN_UNDECLARED_SELECTOR = YES; 447 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 448 | GCC_WARN_UNUSED_FUNCTION = YES; 449 | GCC_WARN_UNUSED_VARIABLE = YES; 450 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 451 | MTL_ENABLE_DEBUG_INFO = NO; 452 | SDKROOT = iphoneos; 453 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 454 | VALIDATE_PRODUCT = YES; 455 | }; 456 | name = Release; 457 | }; 458 | 5CEBAB828F88D96A7C11BAD6 /* Debug */ = { 459 | isa = XCBuildConfiguration; 460 | buildSettings = { 461 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 462 | DEVELOPMENT_TEAM = T379HXD2U2; 463 | INFOPLIST_FILE = FlowKitExample/Info.plist; 464 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 465 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKitExample; 466 | PRODUCT_NAME = "$(TARGET_NAME)"; 467 | SWIFT_VERSION = 3.0; 468 | }; 469 | name = Debug; 470 | }; 471 | 5CEBAFA75B8CED6C0E08B2D0 /* Release */ = { 472 | isa = XCBuildConfiguration; 473 | buildSettings = { 474 | INFOPLIST_FILE = FlowKitExampleUITests/Info.plist; 475 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 476 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKitExampleUITests; 477 | PRODUCT_NAME = "$(TARGET_NAME)"; 478 | SWIFT_VERSION = 3.0; 479 | TEST_TARGET_NAME = FlowKitExample; 480 | }; 481 | name = Release; 482 | }; 483 | /* End XCBuildConfiguration section */ 484 | 485 | /* Begin XCConfigurationList section */ 486 | 5CEBA7743005A82713BEB207 /* Build configuration list for PBXNativeTarget "FlowKitExample" */ = { 487 | isa = XCConfigurationList; 488 | buildConfigurations = ( 489 | 5CEBAB828F88D96A7C11BAD6 /* Debug */, 490 | 5CEBA8F4E3A922D9A8632AF8 /* Release */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | defaultConfigurationName = Release; 494 | }; 495 | 5CEBA966C9A82F3C59A9209C /* Build configuration list for PBXNativeTarget "FlowKitExampleTests" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | 5CEBA0586971B06BC430A649 /* Debug */, 499 | 5CEBA13C18689421A5665082 /* Release */, 500 | ); 501 | defaultConfigurationIsVisible = 0; 502 | defaultConfigurationName = Release; 503 | }; 504 | 5CEBAB2F1216B5A98A9BC9B3 /* Build configuration list for PBXNativeTarget "FlowKitExampleUITests" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | 5CEBA7C4EF43BD9EC20A203D /* Debug */, 508 | 5CEBAFA75B8CED6C0E08B2D0 /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | 5CEBACE097415F2D93EAD225 /* Build configuration list for PBXProject "FlowKitExample" */ = { 514 | isa = XCConfigurationList; 515 | buildConfigurations = ( 516 | 5CEBA7F73E95D311DCA96323 /* Debug */, 517 | 5CEBA944F183EC8FB20AFF55 /* Release */, 518 | ); 519 | defaultConfigurationIsVisible = 0; 520 | defaultConfigurationName = Release; 521 | }; 522 | /* End XCConfigurationList section */ 523 | }; 524 | rootObject = 5CEBAF266472B59936E8C358 /* Project object */; 525 | } 526 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample.xcodeproj/project.xcworkspace/xcuserdata/filip.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FilipZawada/FlowKit/e9cbbda01152bf57ecf394a826f9e52652f87b27/Examples/FlowKitExample/FlowKitExample.xcodeproj/project.xcworkspace/xcuserdata/filip.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample.xcodeproj/project.xcworkspace/xcuserdata/filip.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample.xcodeproj/xcuserdata/filip.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample.xcodeproj/xcuserdata/filip.xcuserdatad/xcschemes/FlowKitExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample.xcodeproj/xcuserdata/filip.xcuserdatad/xcschemes/FlowKitExampleUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample.xcodeproj/xcuserdata/filip.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FlowKitExample.xcscheme 8 | 9 | orderHint 10 | 3 11 | 12 | FlowKitExampleUITests.xcscheme 13 | 14 | orderHint 15 | 4 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 5CEBA6A8131443A826317EE6 21 | 22 | primary 23 | 24 | 25 | 5CEBAD8D517F3B044BE77BE9 26 | 27 | primary 28 | 29 | 30 | 5CEBAFDFDDAEABF9C9A5C38C 31 | 32 | primary 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FlowKitExample 4 | // 5 | // Created by Filip on 10/27/16. 6 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | var flow: MainFlow? 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 19 | 20 | window = UIWindow(frame: UIScreen.main.bounds) 21 | 22 | flow = MainFlow() 23 | 24 | let nav = UINavigationController(rootViewController: flow!.tutorialScreen.viewController) 25 | 26 | window?.rootViewController = nav 27 | window?.makeKeyAndVisible() 28 | return true 29 | } 30 | 31 | 32 | func applicationWillResignActive(_ application: UIApplication) { 33 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 34 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 35 | 36 | } 37 | 38 | 39 | func applicationDidEnterBackground(_ application: UIApplication) { 40 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 41 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 42 | 43 | } 44 | 45 | 46 | func applicationWillEnterForeground(_ application: UIApplication) { 47 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 48 | } 49 | 50 | 51 | func applicationDidBecomeActive(_ application: UIApplication) { 52 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 53 | } 54 | 55 | 56 | func applicationWillTerminate(_ application: UIApplication) { 57 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 58 | } 59 | 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/README.md: -------------------------------------------------------------------------------- 1 | * Loosely couples view controllers. View control 2 | * Keeps Flow in a dedicated place, not coupled with other things your app do 3 | * Flow only takes care of flow, i.e what view causes other view to display and how views are created. 4 | You don't write wiring code (controllerA.a = controllerB.b) 5 | * Provides methods for testing flow 6 | * Integration with RxSwift (coming soon) 7 | * integration with PromiseKit (coming soon) 8 | * ViewController doesn't know anything about FlowKit (yay!) 9 | 10 | 1. Testing 11 | * vaniila testing 12 | * using simon matchers 13 | 14 | 2. RxSwift version 15 | 16 | 17 | 18 | 1. Naive way of writing navigation code 19 | - it has many pitfalls, e.g.: 20 | - MassiveViewController 21 | - Tightly Coupled Views 22 | - Krzysztof Zablocki proposed a better solution 23 | 2. Improved version 24 | - it's x100 better than the old approach, however we can still improve it 25 | - mediators take care of setting flow & configuring view controllers 26 | - there's a lot of boilerplate in the code making it less readable 27 | - it's not easy to test this stuff -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/Stevia+Tap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Tap.swift 3 | // LoginStevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 01/10/15. 6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | typealias ActionBlock = (() -> Void)? 12 | 13 | class ClosureWrapper { 14 | var closure: ActionBlock 15 | 16 | init(_ closure: ActionBlock) { 17 | self.closure = closure 18 | } 19 | } 20 | 21 | private var kButtonBlockAssociationKey: UInt8 = 0 22 | public extension UIButton { 23 | 24 | internal var testButtonBlock: ActionBlock { 25 | get { 26 | if let cw = objc_getAssociatedObject(self, 27 | &kButtonBlockAssociationKey) as? ClosureWrapper { 28 | return cw.closure 29 | } 30 | return nil 31 | } 32 | set(newValue) { 33 | objc_setAssociatedObject(self, 34 | &kButtonBlockAssociationKey, 35 | ClosureWrapper(newValue), 36 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 37 | } 38 | } 39 | 40 | 41 | /** Links UIButton tap (TouchUpInside) event to a block. 42 | 43 | Example Usage: 44 | 45 | ``` 46 | button.tap { 47 | // do something 48 | } 49 | ``` 50 | 51 | Or 52 | ``` 53 | button.tap(doSomething) 54 | 55 | // later 56 | func doSomething() { 57 | // ... 58 | } 59 | ``` 60 | 61 | - Returns: Itself for chaining purposes 62 | 63 | */ 64 | @discardableResult public func tap(_ block:@escaping () -> Void) -> UIButton { 65 | #if swift(>=2.2) 66 | addTarget(self, action: #selector(UIButton.tapped), for: .touchUpInside) 67 | #else 68 | addTarget(self, action: "tapped", forControlEvents: .TouchUpInside) 69 | #endif 70 | testButtonBlock = block 71 | return self 72 | } 73 | 74 | /** */ 75 | func tapped() { 76 | testButtonBlock?() 77 | } 78 | } -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/flow/MainFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 10/25/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import FlowKit 7 | 8 | class MainFlow { 9 | // todo: provide a link to a radar about `lazy var` type required: 10 | // - https://twitter.com/filip_zawada/status/791732757177200640 11 | // - https://twitter.com/filip_zawada/status/791732993769500672 12 | lazy var tutorialScreen: Flow = Flow { [unowned self] lets in 13 | let screen = TutorialViewController() 14 | 15 | // we forward 3 args from TutorialVC::onContinue(String, Int, Bool) to LoginVC::prepare(String, Int, Bool) 16 | screen.onContinue = lets.push(self.loginScreen) { $0.prepare } 17 | 18 | return screen 19 | } 20 | 21 | lazy var mainScreen: Flow = Flow { [unowned self] lets in 22 | let screen = MainViewController() 23 | 24 | screen.onBack = lets.pop() 25 | screen.onLogOut = lets.popTo(self.loginScreen) { $0.receive } 26 | screen.onExit = lets.popToRoot() 27 | 28 | return screen 29 | } 30 | 31 | lazy var loginScreen: Flow = Flow { [unowned self] lets in 32 | let screen = LoginViewController() 33 | 34 | screen.onLogin = lets.push(self.mainScreen) 35 | screen.onBack = lets.pop() 36 | screen.onSayHello = { print("Howdy?") } 37 | 38 | return screen 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/view/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/10/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | class LoginViewController: UIViewController { 9 | var onLogin: () -> Void = {} 10 | var onBack: () -> Void = {} 11 | var onSayHello: () -> Void = {} 12 | 13 | let loginButton = UIButton(frame: CGRect(x: 50, y: 100, width: 100, height: 100)) 14 | let backButton = UIButton(frame: CGRect(x: 50, y: 200, width: 100, height: 100)) 15 | let sayHelloButton = UIButton(frame: CGRect(x: 50, y: 300, width: 100, height: 100)) 16 | let titleLabel = UIButton(frame: CGRect(x: 50, y: 400, width: 100, height: 100)) 17 | 18 | func prepare(string: String, int: Int, bool: Bool) { 19 | print("received \(string) \(int) \(bool)") 20 | } 21 | 22 | func receive(_ string: String) { 23 | print("received \(string)") 24 | } 25 | 26 | override func viewDidLoad() { 27 | view.backgroundColor = .green 28 | 29 | loginButton.setTitle("login", for: .normal) 30 | loginButton.setTitleColor(.black, for: .normal) 31 | backButton.setTitle("back", for: .normal) 32 | backButton.setTitleColor(.black, for: .normal) 33 | sayHelloButton.setTitle("hello", for: .normal) 34 | sayHelloButton.setTitleColor(.black, for: .normal) 35 | 36 | view.addSubview(loginButton) 37 | view.addSubview(backButton) 38 | view.addSubview(sayHelloButton) 39 | 40 | loginButton.tap(onLogin) 41 | backButton.tap(onBack) 42 | sayHelloButton.tap(onSayHello) 43 | } 44 | 45 | deinit { 46 | print("Deinit LoginViewController") 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/view/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/10/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | class MainViewController: UIViewController { 9 | 10 | let logOutButton = UIButton(frame: CGRect(x: 50, y: 100, width: 100, height: 100)) 11 | let backButton = UIButton(frame: CGRect(x: 50, y: 200, width: 100, height: 100)) 12 | let exitButton = UIButton(frame: CGRect(x: 50, y: 300, width: 100, height: 100)) 13 | 14 | override func viewDidLoad() { 15 | view.backgroundColor = .yellow 16 | 17 | logOutButton.setTitle("log out", for: .normal) 18 | logOutButton.setTitleColor(.black, for: .normal) 19 | backButton.setTitle("back", for: .normal) 20 | backButton.setTitleColor(.black, for: .normal) 21 | exitButton.setTitle("exit", for: .normal) 22 | exitButton.setTitleColor(.black, for: .normal) 23 | 24 | view.addSubview(logOutButton) 25 | view.addSubview(backButton) 26 | view.addSubview(exitButton) 27 | 28 | logOutButton.tap { [unowned self] in 29 | self.onLogOut("dummy") 30 | } 31 | 32 | backButton.tap(onBack) 33 | exitButton.tap(onExit) 34 | } 35 | 36 | var onBack: () -> Void = {} 37 | var onLogOut: (String) -> Void = { _ in } 38 | var onExit: () -> Void = {} 39 | 40 | deinit { 41 | print("Deinit MainViewController") 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExample/view/TutorialViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/10/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | class TutorialViewController: UIViewController { 9 | 10 | var continueButton = UIButton(frame: CGRect(x: 200, y: 200, width: 100, height: 100)) 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | view.backgroundColor = .red 16 | 17 | continueButton.setTitle("continue", for: .normal) 18 | 19 | view.addSubview(continueButton) 20 | 21 | continueButton.tap { 22 | self.onContinue("AAA", 123, true) 23 | } 24 | } 25 | 26 | var onContinue = { (_: String, _: Int, _: Bool) in 27 | print("noop") // throw? 28 | } 29 | 30 | deinit { 31 | print("Deinit TutorialViewController") 32 | } 33 | } -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExampleTests/FlowKitExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlowKitExampleTests.swift 3 | // FlowKitExampleTests 4 | // 5 | // Created by Filip on 10/27/16. 6 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import FlowKitExample 11 | 12 | class FlowKitExampleTests: XCTestCase { 13 | 14 | // func testLetStubs() { 15 | // let loginScreen = Flow { LoginViewController() } 16 | // let mainScreen = Flow { lets in 17 | // let screen = MainViewController() 18 | // 19 | // screen.onBack = lets.pop() 20 | // screen.onLogOut = lets.popTo(self.loginScreen) 21 | // screen.onExit = lets.popToRoot() 22 | // 23 | // return screen 24 | // } 25 | // 26 | // let letsFactory = LetsSpyFactory() 27 | // 28 | // mainScreen.letsFactory = letsFactory 29 | // 30 | // // `LetsSpyFactory` always return the same item, so `factory.makeLets() === factoryMakeLets()` is always true 31 | // let spy = letsFactory.makeLets() as! LetsSpy 32 | // 33 | // let mainScreenViewController = mainScreen.viewController 34 | // 35 | // // a) - lets.pop(), lets.popTo(self.loginScreen), lets.popToRoot() - were called 36 | // // b) - no other lets.method() was called 37 | // // c) - check if screen `onBack`, `onLogOut` & `onExit` handlers were properly set. 38 | // XCTAssertEqual(spy.pushCalled, 0) 39 | // XCTAssertEqual(spy.popCalled, 1) 40 | // XCTAssertEqual(spy.popToCalled, 1) 41 | // XCTAssertEqual(spy.popToRootCalled, 1) 42 | // XCTAssertEqual(spy.presentCalled, 0) 43 | // XCTAssertEqual(spy.dismissCalled, 0) 44 | // XCTAssertEqual(spy.popToCalledWithArgument, loginScreen) 45 | // 46 | // XCTAssertEqual(mainScreenViewController.onBack, spy.pop()) 47 | // XCTAssertEqual(mainScreenViewController.onLogOut, spy.popTo(self.loginScreen)) 48 | // XCTAssertEqual(mainScreenViewController.popToRoot, spy.popToRoot()) 49 | // } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExampleUITests/FlowKitExampleUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlowKitExampleUITests.swift 3 | // FlowKitExampleUITests 4 | // 5 | // Created by Filip on 10/27/16. 6 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FlowKitExampleUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Examples/FlowKitExample/FlowKitExampleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlowKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "FlowKit" 3 | s.version = "0.2.0" 4 | s.summary = "Screenflow management for iOS Edit" 5 | 6 | s.description = <<-DESC 7 | Define screen flows easily with FlowKit. 8 | Elegant syntax, clear separation of concerns and testability makes it 9 | a perfect add-on for your current MV* setup. 10 | DESC 11 | 12 | s.homepage = "https://github.com/FilipZawada/FlowKit" 13 | s.license = { :type => "MIT", :file => "./LICENSE" } 14 | s.author = { "Filip Zawada" => "" } 15 | s.social_media_url = "https://twitter.com/Filip_Zawada" 16 | 17 | 18 | s.source = { 19 | :git => "https://github.com/FilipZawada/FlowKit.git", 20 | :tag => s.version 21 | } 22 | 23 | s.ios.deployment_target = '8.0' 24 | s.frameworks = 'UIKit' 25 | s.source_files = "FlowKit/*.swift" 26 | s.requires_arc = true 27 | end 28 | -------------------------------------------------------------------------------- /FlowKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0B72D680EE6C2893398FDF0F /* Pods_FlowKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF669258F2B067AA6082BC7D /* Pods_FlowKitTests.framework */; }; 11 | 5CEBA0379A6DAC0B66898102 /* LetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBAC7617BBDB1FE5E81BF9 /* LetsTests.swift */; }; 12 | 5CEBA0AA7B78DD8F54600D0B /* Lets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBAE5884A1632E2DDEABFD /* Lets.swift */; }; 13 | 5CEBA0D107B188156C5966AA /* FlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBABA9AB02E5CFDEC0BACA /* FlowTests.swift */; }; 14 | 5CEBA196A53EC19ABB648AE1 /* LetsFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA68505DF675C5EA07EEB /* LetsFactoryTest.swift */; }; 15 | 5CEBA24D1A2E5BDFD7AD2CE4 /* LetsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBAFA20E1FE3821230985A /* LetsFactory.swift */; }; 16 | 5CEBA33360B349B8FB331363 /* FlowKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA193B69D744D45F527E6 /* FlowKit.swift */; }; 17 | 5CEBA430A29790814302C6E8 /* LetsStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA976FF6D44E056F5872B /* LetsStub.swift */; }; 18 | 5CEBA6C7FBB16AB678CCC550 /* DefaultLetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBAB25C45231DE99E026F9 /* DefaultLetsTests.swift */; }; 19 | 5CEBAB1DA2E72C23710DB2A0 /* LetsFactoryStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA7DC2B4155824F12E7C7 /* LetsFactoryStub.swift */; }; 20 | 5CEBAD24475A9368E60D3173 /* DefaultLets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBAD595212F33FD3403A9E /* DefaultLets.swift */; }; 21 | 5CEBAD5AC67935C028B1C8D6 /* FlowKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEBA46BB1C85A6DC5A308EF /* FlowKit.framework */; }; 22 | 5CEBAEF19C70AAB7E4BE5662 /* FlowStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA09F329E6FEF42518DA8 /* FlowStub.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 5CEBA4DEE9953DFBF6E0C506 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 5CEBA568CB5AABA3353E80A1 /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 5CEBA58D6F1C5FD51A72DDF5; 31 | remoteInfo = FlowKit; 32 | }; 33 | 5CEBA51AC9BB9A19D120B1C9 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 5CEBA2FBF74D6020219C0DA6 /* FlowKitExample.xcodeproj */; 36 | proxyType = 2; 37 | remoteGlobalIDString = 5CEBADFF8055BBF7CDD8B60D; 38 | remoteInfo = FlowKitExample; 39 | }; 40 | 5CEBA53B967A7D51AEEEDEC3 /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = 5CEBA2FBF74D6020219C0DA6 /* FlowKitExample.xcodeproj */; 43 | proxyType = 2; 44 | remoteGlobalIDString = 5CEBA0EAD12DEA7B8D7159B6; 45 | remoteInfo = FlowKitExampleTests; 46 | }; 47 | 5CEBAEB6D694E7D0E8FA905C /* PBXContainerItemProxy */ = { 48 | isa = PBXContainerItemProxy; 49 | containerPortal = 5CEBA2FBF74D6020219C0DA6 /* FlowKitExample.xcodeproj */; 50 | proxyType = 2; 51 | remoteGlobalIDString = 5CEBA3DBD10AA24EA4C3E95A; 52 | remoteInfo = FlowKitExampleUITests; 53 | }; 54 | /* End PBXContainerItemProxy section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | 1A56CEB6CEA20B0DD3CFDD18 /* Pods-FlowKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FlowKitTests/Pods-FlowKitTests.release.xcconfig"; sourceTree = ""; }; 58 | 5CEBA09F329E6FEF42518DA8 /* FlowStub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlowStub.swift; sourceTree = ""; }; 59 | 5CEBA193B69D744D45F527E6 /* FlowKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlowKit.swift; sourceTree = ""; }; 60 | 5CEBA25931008B65516817C0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; 61 | 5CEBA2FBF74D6020219C0DA6 /* FlowKitExample.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FlowKitExample.xcodeproj; path = Examples/FlowKitExample/FlowKitExample.xcodeproj; sourceTree = ""; }; 62 | 5CEBA46BB1C85A6DC5A308EF /* FlowKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FlowKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 5CEBA68505DF675C5EA07EEB /* LetsFactoryTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LetsFactoryTest.swift; sourceTree = ""; }; 64 | 5CEBA6EC1CA699733B9C805E /* FlowKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | 5CEBA7DC2B4155824F12E7C7 /* LetsFactoryStub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LetsFactoryStub.swift; sourceTree = ""; }; 66 | 5CEBA976FF6D44E056F5872B /* LetsStub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LetsStub.swift; sourceTree = ""; }; 67 | 5CEBAB25C45231DE99E026F9 /* DefaultLetsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultLetsTests.swift; sourceTree = ""; }; 68 | 5CEBABA9AB02E5CFDEC0BACA /* FlowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlowTests.swift; sourceTree = ""; }; 69 | 5CEBABC746BB9542FA89E4A8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; 70 | 5CEBAC7617BBDB1FE5E81BF9 /* LetsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LetsTests.swift; sourceTree = ""; }; 71 | 5CEBAD595212F33FD3403A9E /* DefaultLets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultLets.swift; sourceTree = ""; }; 72 | 5CEBAE5884A1632E2DDEABFD /* Lets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lets.swift; sourceTree = ""; }; 73 | 5CEBAFA20E1FE3821230985A /* LetsFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LetsFactory.swift; sourceTree = ""; }; 74 | 95B19017E9F577BF9B1BC253 /* Pods-FlowKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FlowKitTests/Pods-FlowKitTests.debug.xcconfig"; sourceTree = ""; }; 75 | AF669258F2B067AA6082BC7D /* Pods_FlowKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FlowKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | 5CEBA11EE73E9FD2978ECF0C /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | 5CEBA969731333EA3A8ED150 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | 5CEBAD5AC67935C028B1C8D6 /* FlowKit.framework in Frameworks */, 91 | 0B72D680EE6C2893398FDF0F /* Pods_FlowKitTests.framework in Frameworks */, 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXFrameworksBuildPhase section */ 96 | 97 | /* Begin PBXGroup section */ 98 | 37AE3FD74F44165514C576EC /* Frameworks */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | AF669258F2B067AA6082BC7D /* Pods_FlowKitTests.framework */, 102 | ); 103 | name = Frameworks; 104 | sourceTree = ""; 105 | }; 106 | 5CEBA03CD3BA8D494D3EFDD4 /* FlowKitTests */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 5CEBABC746BB9542FA89E4A8 /* Info.plist */, 110 | 5CEBAB25C45231DE99E026F9 /* DefaultLetsTests.swift */, 111 | 5CEBABA9AB02E5CFDEC0BACA /* FlowTests.swift */, 112 | 5CEBA6C7CBC9CA81828BC6B7 /* stubs */, 113 | 5CEBA68505DF675C5EA07EEB /* LetsFactoryTest.swift */, 114 | 5CEBAC7617BBDB1FE5E81BF9 /* LetsTests.swift */, 115 | ); 116 | path = FlowKitTests; 117 | sourceTree = ""; 118 | }; 119 | 5CEBA15E90D6A9E009917D95 /* FlowKit */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 5CEBA25931008B65516817C0 /* Info.plist */, 123 | 5CEBA193B69D744D45F527E6 /* FlowKit.swift */, 124 | 5CEBAE5884A1632E2DDEABFD /* Lets.swift */, 125 | 5CEBAFA20E1FE3821230985A /* LetsFactory.swift */, 126 | 5CEBAD595212F33FD3403A9E /* DefaultLets.swift */, 127 | ); 128 | path = FlowKit; 129 | sourceTree = ""; 130 | }; 131 | 5CEBA2F91915AA7811F93860 /* Products */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 5CEBA46BB1C85A6DC5A308EF /* FlowKit.framework */, 135 | 5CEBA6EC1CA699733B9C805E /* FlowKitTests.xctest */, 136 | ); 137 | name = Products; 138 | sourceTree = ""; 139 | }; 140 | 5CEBA526287726532737DBA2 /* Products */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 5CEBA2103CB8F5EA201487D5 /* FlowKitExample.app */, 144 | 5CEBAC9B8DFAAB567FAE4B85 /* FlowKitExampleTests.xctest */, 145 | 5CEBA5B2DDD9F9F3E8C6D8DC /* FlowKitExampleUITests.xctest */, 146 | ); 147 | name = Products; 148 | sourceTree = ""; 149 | }; 150 | 5CEBA6C7CBC9CA81828BC6B7 /* stubs */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 5CEBA976FF6D44E056F5872B /* LetsStub.swift */, 154 | 5CEBA7DC2B4155824F12E7C7 /* LetsFactoryStub.swift */, 155 | 5CEBA09F329E6FEF42518DA8 /* FlowStub.swift */, 156 | ); 157 | path = stubs; 158 | sourceTree = ""; 159 | }; 160 | 5CEBA985EDDFA27EB1EB8F5F = { 161 | isa = PBXGroup; 162 | children = ( 163 | 5CEBA2F91915AA7811F93860 /* Products */, 164 | 5CEBA15E90D6A9E009917D95 /* FlowKit */, 165 | 5CEBA03CD3BA8D494D3EFDD4 /* FlowKitTests */, 166 | 89F7F29B69D9414FD5083DDA /* Pods */, 167 | 37AE3FD74F44165514C576EC /* Frameworks */, 168 | 5CEBADDB2F769432998EC999 /* Examples */, 169 | 5CEBA2FBF74D6020219C0DA6 /* FlowKitExample.xcodeproj */, 170 | ); 171 | sourceTree = ""; 172 | }; 173 | 5CEBADDB2F769432998EC999 /* Examples */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | ); 177 | path = Examples; 178 | sourceTree = ""; 179 | }; 180 | 89F7F29B69D9414FD5083DDA /* Pods */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | 95B19017E9F577BF9B1BC253 /* Pods-FlowKitTests.debug.xcconfig */, 184 | 1A56CEB6CEA20B0DD3CFDD18 /* Pods-FlowKitTests.release.xcconfig */, 185 | ); 186 | name = Pods; 187 | sourceTree = ""; 188 | }; 189 | /* End PBXGroup section */ 190 | 191 | /* Begin PBXHeadersBuildPhase section */ 192 | 5CEBA5A919590FC1687B115F /* Headers */ = { 193 | isa = PBXHeadersBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXHeadersBuildPhase section */ 200 | 201 | /* Begin PBXNativeTarget section */ 202 | 5CEBA58D6F1C5FD51A72DDF5 /* FlowKit */ = { 203 | isa = PBXNativeTarget; 204 | buildConfigurationList = 5CEBA6D2AB10C5C0043EF22A /* Build configuration list for PBXNativeTarget "FlowKit" */; 205 | buildPhases = ( 206 | 5CEBA0A0486D1B039270AC9D /* Sources */, 207 | 5CEBA11EE73E9FD2978ECF0C /* Frameworks */, 208 | 5CEBA5A919590FC1687B115F /* Headers */, 209 | 5CEBA9E700C23EB23BDDDDD7 /* Resources */, 210 | ); 211 | buildRules = ( 212 | ); 213 | dependencies = ( 214 | ); 215 | name = FlowKit; 216 | productName = FlowKit; 217 | productReference = 5CEBA46BB1C85A6DC5A308EF /* FlowKit.framework */; 218 | productType = "com.apple.product-type.framework"; 219 | }; 220 | 5CEBA85B24211FB6D3637984 /* FlowKitTests */ = { 221 | isa = PBXNativeTarget; 222 | buildConfigurationList = 5CEBA2F55A747F696508FE2A /* Build configuration list for PBXNativeTarget "FlowKitTests" */; 223 | buildPhases = ( 224 | 86296D68679AC9FFEA09458D /* [CP] Check Pods Manifest.lock */, 225 | 5CEBAE6BE100C79130A695E0 /* Sources */, 226 | 5CEBA969731333EA3A8ED150 /* Frameworks */, 227 | 5CEBAFE4DB31183C87BB3F48 /* Resources */, 228 | 18858786AC4F092A8F08CB04 /* [CP] Embed Pods Frameworks */, 229 | BF73304DA34F34EC244FDBFE /* [CP] Copy Pods Resources */, 230 | ); 231 | buildRules = ( 232 | ); 233 | dependencies = ( 234 | 5CEBA0C2572BCBD744E0241E /* PBXTargetDependency */, 235 | ); 236 | name = FlowKitTests; 237 | productName = FlowKitTests; 238 | productReference = 5CEBA6EC1CA699733B9C805E /* FlowKitTests.xctest */; 239 | productType = "com.apple.product-type.bundle.unit-test"; 240 | }; 241 | /* End PBXNativeTarget section */ 242 | 243 | /* Begin PBXProject section */ 244 | 5CEBA568CB5AABA3353E80A1 /* Project object */ = { 245 | isa = PBXProject; 246 | attributes = { 247 | LastUpgradeCheck = 0810; 248 | ORGANIZATIONNAME = "Filip Zawada"; 249 | TargetAttributes = { 250 | 5CEBA58D6F1C5FD51A72DDF5 = { 251 | DevelopmentTeam = T379HXD2U2; 252 | ProvisioningStyle = Automatic; 253 | }; 254 | 5CEBA85B24211FB6D3637984 = { 255 | ProvisioningStyle = Manual; 256 | }; 257 | }; 258 | }; 259 | buildConfigurationList = 5CEBA08D13B346A45290E2C7 /* Build configuration list for PBXProject "FlowKit" */; 260 | compatibilityVersion = "Xcode 3.2"; 261 | developmentRegion = English; 262 | hasScannedForEncodings = 0; 263 | knownRegions = ( 264 | en, 265 | ); 266 | mainGroup = 5CEBA985EDDFA27EB1EB8F5F; 267 | productRefGroup = 5CEBA2F91915AA7811F93860 /* Products */; 268 | projectDirPath = ""; 269 | projectReferences = ( 270 | { 271 | ProductGroup = 5CEBA526287726532737DBA2 /* Products */; 272 | ProjectRef = 5CEBA2FBF74D6020219C0DA6 /* FlowKitExample.xcodeproj */; 273 | }, 274 | ); 275 | projectRoot = ""; 276 | targets = ( 277 | 5CEBA58D6F1C5FD51A72DDF5 /* FlowKit */, 278 | 5CEBA85B24211FB6D3637984 /* FlowKitTests */, 279 | ); 280 | }; 281 | /* End PBXProject section */ 282 | 283 | /* Begin PBXReferenceProxy section */ 284 | 5CEBA2103CB8F5EA201487D5 /* FlowKitExample.app */ = { 285 | isa = PBXReferenceProxy; 286 | fileType = wrapper.application; 287 | path = FlowKitExample.app; 288 | remoteRef = 5CEBA51AC9BB9A19D120B1C9 /* PBXContainerItemProxy */; 289 | sourceTree = BUILT_PRODUCTS_DIR; 290 | }; 291 | 5CEBA5B2DDD9F9F3E8C6D8DC /* FlowKitExampleUITests.xctest */ = { 292 | isa = PBXReferenceProxy; 293 | fileType = wrapper.cfbundle; 294 | path = FlowKitExampleUITests.xctest; 295 | remoteRef = 5CEBAEB6D694E7D0E8FA905C /* PBXContainerItemProxy */; 296 | sourceTree = BUILT_PRODUCTS_DIR; 297 | }; 298 | 5CEBAC9B8DFAAB567FAE4B85 /* FlowKitExampleTests.xctest */ = { 299 | isa = PBXReferenceProxy; 300 | fileType = wrapper.cfbundle; 301 | path = FlowKitExampleTests.xctest; 302 | remoteRef = 5CEBA53B967A7D51AEEEDEC3 /* PBXContainerItemProxy */; 303 | sourceTree = BUILT_PRODUCTS_DIR; 304 | }; 305 | /* End PBXReferenceProxy section */ 306 | 307 | /* Begin PBXResourcesBuildPhase section */ 308 | 5CEBA9E700C23EB23BDDDDD7 /* Resources */ = { 309 | isa = PBXResourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | 5CEBAFE4DB31183C87BB3F48 /* Resources */ = { 316 | isa = PBXResourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | ); 320 | runOnlyForDeploymentPostprocessing = 0; 321 | }; 322 | /* End PBXResourcesBuildPhase section */ 323 | 324 | /* Begin PBXShellScriptBuildPhase section */ 325 | 18858786AC4F092A8F08CB04 /* [CP] Embed Pods Frameworks */ = { 326 | isa = PBXShellScriptBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | ); 330 | inputPaths = ( 331 | ); 332 | name = "[CP] Embed Pods Frameworks"; 333 | outputPaths = ( 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | shellPath = /bin/sh; 337 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FlowKitTests/Pods-FlowKitTests-frameworks.sh\"\n"; 338 | showEnvVarsInLog = 0; 339 | }; 340 | 86296D68679AC9FFEA09458D /* [CP] Check Pods Manifest.lock */ = { 341 | isa = PBXShellScriptBuildPhase; 342 | buildActionMask = 2147483647; 343 | files = ( 344 | ); 345 | inputPaths = ( 346 | ); 347 | name = "[CP] Check Pods Manifest.lock"; 348 | outputPaths = ( 349 | ); 350 | runOnlyForDeploymentPostprocessing = 0; 351 | shellPath = /bin/sh; 352 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 353 | showEnvVarsInLog = 0; 354 | }; 355 | BF73304DA34F34EC244FDBFE /* [CP] Copy Pods Resources */ = { 356 | isa = PBXShellScriptBuildPhase; 357 | buildActionMask = 2147483647; 358 | files = ( 359 | ); 360 | inputPaths = ( 361 | ); 362 | name = "[CP] Copy Pods Resources"; 363 | outputPaths = ( 364 | ); 365 | runOnlyForDeploymentPostprocessing = 0; 366 | shellPath = /bin/sh; 367 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FlowKitTests/Pods-FlowKitTests-resources.sh\"\n"; 368 | showEnvVarsInLog = 0; 369 | }; 370 | /* End PBXShellScriptBuildPhase section */ 371 | 372 | /* Begin PBXSourcesBuildPhase section */ 373 | 5CEBA0A0486D1B039270AC9D /* Sources */ = { 374 | isa = PBXSourcesBuildPhase; 375 | buildActionMask = 2147483647; 376 | files = ( 377 | 5CEBA33360B349B8FB331363 /* FlowKit.swift in Sources */, 378 | 5CEBA0AA7B78DD8F54600D0B /* Lets.swift in Sources */, 379 | 5CEBA24D1A2E5BDFD7AD2CE4 /* LetsFactory.swift in Sources */, 380 | 5CEBAD24475A9368E60D3173 /* DefaultLets.swift in Sources */, 381 | ); 382 | runOnlyForDeploymentPostprocessing = 0; 383 | }; 384 | 5CEBAE6BE100C79130A695E0 /* Sources */ = { 385 | isa = PBXSourcesBuildPhase; 386 | buildActionMask = 2147483647; 387 | files = ( 388 | 5CEBA6C7FBB16AB678CCC550 /* DefaultLetsTests.swift in Sources */, 389 | 5CEBA0D107B188156C5966AA /* FlowTests.swift in Sources */, 390 | 5CEBA430A29790814302C6E8 /* LetsStub.swift in Sources */, 391 | 5CEBAB1DA2E72C23710DB2A0 /* LetsFactoryStub.swift in Sources */, 392 | 5CEBA196A53EC19ABB648AE1 /* LetsFactoryTest.swift in Sources */, 393 | 5CEBA0379A6DAC0B66898102 /* LetsTests.swift in Sources */, 394 | 5CEBAEF19C70AAB7E4BE5662 /* FlowStub.swift in Sources */, 395 | ); 396 | runOnlyForDeploymentPostprocessing = 0; 397 | }; 398 | /* End PBXSourcesBuildPhase section */ 399 | 400 | /* Begin PBXTargetDependency section */ 401 | 5CEBA0C2572BCBD744E0241E /* PBXTargetDependency */ = { 402 | isa = PBXTargetDependency; 403 | target = 5CEBA58D6F1C5FD51A72DDF5 /* FlowKit */; 404 | targetProxy = 5CEBA4DEE9953DFBF6E0C506 /* PBXContainerItemProxy */; 405 | }; 406 | /* End PBXTargetDependency section */ 407 | 408 | /* Begin XCBuildConfiguration section */ 409 | 5CEBA0C9319AC3397A9A333A /* Release */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | CODE_SIGN_IDENTITY = ""; 413 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 414 | DEFINES_MODULE = YES; 415 | DEVELOPMENT_TEAM = T379HXD2U2; 416 | DYLIB_COMPATIBILITY_VERSION = 1; 417 | DYLIB_CURRENT_VERSION = 1; 418 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 419 | INFOPLIST_FILE = FlowKit/Info.plist; 420 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 421 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 422 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKit; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SKIP_INSTALL = YES; 425 | SWIFT_VERSION = 3.0; 426 | }; 427 | name = Release; 428 | }; 429 | 5CEBA22047EA67527CF82BC3 /* Release */ = { 430 | isa = XCBuildConfiguration; 431 | baseConfigurationReference = 1A56CEB6CEA20B0DD3CFDD18 /* Pods-FlowKitTests.release.xcconfig */; 432 | buildSettings = { 433 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 434 | DEVELOPMENT_TEAM = ""; 435 | INFOPLIST_FILE = FlowKitTests/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKitTests; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_VERSION = 3.0; 440 | USER_HEADER_SEARCH_PATHS = "\"${PROJECT_DIR}/Examples/FlowKitExample\"/**"; 441 | }; 442 | name = Release; 443 | }; 444 | 5CEBA2D0E5A046AA7AC12E14 /* Debug */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | CODE_SIGN_IDENTITY = ""; 448 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 449 | DEFINES_MODULE = YES; 450 | DEVELOPMENT_TEAM = T379HXD2U2; 451 | DYLIB_COMPATIBILITY_VERSION = 1; 452 | DYLIB_CURRENT_VERSION = 1; 453 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 454 | INFOPLIST_FILE = FlowKit/Info.plist; 455 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 456 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 457 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKit; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SKIP_INSTALL = YES; 460 | SWIFT_VERSION = 3.0; 461 | }; 462 | name = Debug; 463 | }; 464 | 5CEBA578889435F09E0EA237 /* Debug */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ALWAYS_SEARCH_USER_PATHS = NO; 468 | CLANG_ANALYZER_NONNULL = YES; 469 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 470 | CLANG_CXX_LIBRARY = "libc++"; 471 | CLANG_ENABLE_MODULES = YES; 472 | CLANG_ENABLE_OBJC_ARC = YES; 473 | CLANG_WARN_BOOL_CONVERSION = YES; 474 | CLANG_WARN_CONSTANT_CONVERSION = YES; 475 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 476 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 477 | CLANG_WARN_EMPTY_BODY = YES; 478 | CLANG_WARN_ENUM_CONVERSION = YES; 479 | CLANG_WARN_INFINITE_RECURSION = YES; 480 | CLANG_WARN_INT_CONVERSION = YES; 481 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 482 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 483 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 484 | CLANG_WARN_UNREACHABLE_CODE = YES; 485 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 486 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 487 | COPY_PHASE_STRIP = NO; 488 | CURRENT_PROJECT_VERSION = 1; 489 | DEBUG_INFORMATION_FORMAT = dwarf; 490 | ENABLE_STRICT_OBJC_MSGSEND = YES; 491 | ENABLE_TESTABILITY = YES; 492 | GCC_C_LANGUAGE_STANDARD = gnu99; 493 | GCC_DYNAMIC_NO_PIC = NO; 494 | GCC_NO_COMMON_BLOCKS = YES; 495 | GCC_OPTIMIZATION_LEVEL = 0; 496 | GCC_PREPROCESSOR_DEFINITIONS = ( 497 | "DEBUG=1", 498 | "$(inherited)", 499 | ); 500 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 501 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 502 | GCC_WARN_UNDECLARED_SELECTOR = YES; 503 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 504 | GCC_WARN_UNUSED_FUNCTION = YES; 505 | GCC_WARN_UNUSED_VARIABLE = YES; 506 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 507 | MTL_ENABLE_DEBUG_INFO = YES; 508 | ONLY_ACTIVE_ARCH = YES; 509 | SDKROOT = iphoneos; 510 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 511 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 512 | TARGETED_DEVICE_FAMILY = "1,2"; 513 | VERSIONING_SYSTEM = "apple-generic"; 514 | VERSION_INFO_PREFIX = ""; 515 | }; 516 | name = Debug; 517 | }; 518 | 5CEBA65507A3475F6CD28407 /* Release */ = { 519 | isa = XCBuildConfiguration; 520 | buildSettings = { 521 | ALWAYS_SEARCH_USER_PATHS = NO; 522 | CLANG_ANALYZER_NONNULL = YES; 523 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 524 | CLANG_CXX_LIBRARY = "libc++"; 525 | CLANG_ENABLE_MODULES = YES; 526 | CLANG_ENABLE_OBJC_ARC = YES; 527 | CLANG_WARN_BOOL_CONVERSION = YES; 528 | CLANG_WARN_CONSTANT_CONVERSION = YES; 529 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 530 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 531 | CLANG_WARN_EMPTY_BODY = YES; 532 | CLANG_WARN_ENUM_CONVERSION = YES; 533 | CLANG_WARN_INFINITE_RECURSION = YES; 534 | CLANG_WARN_INT_CONVERSION = YES; 535 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 536 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 537 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 538 | CLANG_WARN_UNREACHABLE_CODE = YES; 539 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 540 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 541 | COPY_PHASE_STRIP = NO; 542 | CURRENT_PROJECT_VERSION = 1; 543 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 544 | ENABLE_NS_ASSERTIONS = NO; 545 | ENABLE_STRICT_OBJC_MSGSEND = YES; 546 | GCC_C_LANGUAGE_STANDARD = gnu99; 547 | GCC_NO_COMMON_BLOCKS = YES; 548 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 549 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 550 | GCC_WARN_UNDECLARED_SELECTOR = YES; 551 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 552 | GCC_WARN_UNUSED_FUNCTION = YES; 553 | GCC_WARN_UNUSED_VARIABLE = YES; 554 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 555 | MTL_ENABLE_DEBUG_INFO = NO; 556 | SDKROOT = iphoneos; 557 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 558 | TARGETED_DEVICE_FAMILY = "1,2"; 559 | VALIDATE_PRODUCT = YES; 560 | VERSIONING_SYSTEM = "apple-generic"; 561 | VERSION_INFO_PREFIX = ""; 562 | }; 563 | name = Release; 564 | }; 565 | 5CEBACB5B61F129804D138CE /* Debug */ = { 566 | isa = XCBuildConfiguration; 567 | baseConfigurationReference = 95B19017E9F577BF9B1BC253 /* Pods-FlowKitTests.debug.xcconfig */; 568 | buildSettings = { 569 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 570 | DEVELOPMENT_TEAM = ""; 571 | INFOPLIST_FILE = FlowKitTests/Info.plist; 572 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 573 | PRODUCT_BUNDLE_IDENTIFIER = com.filimanjaro.FlowKitTests; 574 | PRODUCT_NAME = "$(TARGET_NAME)"; 575 | SWIFT_VERSION = 3.0; 576 | USER_HEADER_SEARCH_PATHS = "\"${PROJECT_DIR}/Examples/FlowKitExample\"/**"; 577 | }; 578 | name = Debug; 579 | }; 580 | /* End XCBuildConfiguration section */ 581 | 582 | /* Begin XCConfigurationList section */ 583 | 5CEBA08D13B346A45290E2C7 /* Build configuration list for PBXProject "FlowKit" */ = { 584 | isa = XCConfigurationList; 585 | buildConfigurations = ( 586 | 5CEBA578889435F09E0EA237 /* Debug */, 587 | 5CEBA65507A3475F6CD28407 /* Release */, 588 | ); 589 | defaultConfigurationIsVisible = 0; 590 | defaultConfigurationName = Release; 591 | }; 592 | 5CEBA2F55A747F696508FE2A /* Build configuration list for PBXNativeTarget "FlowKitTests" */ = { 593 | isa = XCConfigurationList; 594 | buildConfigurations = ( 595 | 5CEBACB5B61F129804D138CE /* Debug */, 596 | 5CEBA22047EA67527CF82BC3 /* Release */, 597 | ); 598 | defaultConfigurationIsVisible = 0; 599 | defaultConfigurationName = Release; 600 | }; 601 | 5CEBA6D2AB10C5C0043EF22A /* Build configuration list for PBXNativeTarget "FlowKit" */ = { 602 | isa = XCConfigurationList; 603 | buildConfigurations = ( 604 | 5CEBA2D0E5A046AA7AC12E14 /* Debug */, 605 | 5CEBA0C9319AC3397A9A333A /* Release */, 606 | ); 607 | defaultConfigurationIsVisible = 0; 608 | defaultConfigurationName = Release; 609 | }; 610 | /* End XCConfigurationList section */ 611 | }; 612 | rootObject = 5CEBA568CB5AABA3353E80A1 /* Project object */; 613 | } 614 | -------------------------------------------------------------------------------- /FlowKit.xcodeproj/xcshareddata/xcschemes/FlowKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /FlowKit.xcodeproj/xcshareddata/xcschemes/FlowKitTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /FlowKit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FlowKit/DefaultLets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/9/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | // todo: shouldn't be public 9 | public class DefaultLets: Lets { 10 | 11 | let flow: Flow 12 | 13 | var nav: UINavigationController? { 14 | get { 15 | return flow.viewController.navigationController 16 | } 17 | } 18 | 19 | public init(flow: Flow) { 20 | self.flow = flow 21 | } 22 | 23 | public func push(_ flow: Flow, 24 | animated: Bool = true, 25 | with getConfigure: ((ViewController) -> ((Args) -> Void))? = nil) 26 | -> (Args) -> Void where ViewController: UIViewController 27 | { 28 | return { args in 29 | let viewController = flow.viewController 30 | 31 | if let configureViewController = getConfigure?(viewController) { 32 | _ = configureViewController(args) 33 | } 34 | 35 | self.nav?.pushViewController(viewController, animated: animated) 36 | } 37 | } 38 | 39 | public func pop(animated: Bool = true) -> () -> Void { 40 | return { 41 | _ = self.nav?.popViewController(animated: animated) 42 | } 43 | } 44 | 45 | public func popTo(_ flow: Flow, 46 | animated: Bool = true, 47 | with getConfigure: ((ViewController) -> ((Args) -> Void))? = nil) 48 | -> (Args) -> Void where ViewController: UIViewController 49 | { 50 | return { args in 51 | let viewController = flow.viewController 52 | 53 | _ = self.nav?.popToViewController(viewController, animated: animated) 54 | 55 | if let configureViewController = getConfigure?(viewController) { 56 | _ = configureViewController(args) 57 | } 58 | } 59 | } 60 | 61 | public func popToRoot(animated: Bool = true) -> () -> Void { 62 | return { 63 | _ = self.nav?.popToRootViewController(animated: animated) 64 | } 65 | } 66 | 67 | public func present(_ flow: Flow, animated: Bool = true) -> () -> Void { 68 | return { 69 | self.nav?.present(flow.viewController, animated: animated) 70 | } 71 | } 72 | 73 | public func dismiss(animated: Bool = true) -> () -> Void { 74 | return { 75 | self.nav?.dismiss(animated: animated) 76 | } 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /FlowKit/FlowKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 10/25/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | 10 | public class Flow { 11 | private weak var _viewController: T? 12 | 13 | private let letsFactory:LetsFactory 14 | 15 | public var viewController: T { 16 | get { 17 | if let _viewController = self._viewController { 18 | return _viewController 19 | } 20 | let lets = letsFactory.makeLets(flow: self) 21 | let vc = self.configuration(lets) 22 | self._viewController = vc 23 | return vc 24 | } 25 | } 26 | 27 | private var configuration: (Lets) -> T 28 | 29 | public init(_ configuration: @escaping (Lets) -> T) { 30 | letsFactory = DefaultLetsFactory() 31 | self.configuration = configuration 32 | } 33 | 34 | public init(with viewController: @autoclosure @escaping () -> T) { 35 | letsFactory = DefaultLetsFactory() 36 | self.configuration = { _ in viewController() } 37 | } 38 | 39 | public init(with viewController: @autoclosure @escaping () -> T, _ configuration: @escaping (T, Lets) -> Void) { 40 | letsFactory = DefaultLetsFactory() 41 | self.configuration = { lets in 42 | let vc = viewController() 43 | configuration(vc, lets) 44 | return vc 45 | } 46 | } 47 | 48 | public init(factory: LetsFactory, configuration: @escaping (Lets) -> T) { 49 | letsFactory = factory 50 | self.configuration = configuration 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /FlowKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.1.1 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /FlowKit/Lets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/9/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | public protocol Lets { 10 | func push(_ flow: Flow, animated: Bool, with getConfigure: ((ViewController) -> ((Args) -> Void))?) 11 | -> (Args) -> Void where ViewController: UIViewController 12 | func pop(animated: Bool) -> () -> Void 13 | func popTo(_ flow: Flow, animated: Bool, with getConfigure: ((ViewController) -> ((Args) -> Void))?) 14 | -> (Args) -> Void where ViewController: UIViewController 15 | func popToRoot(animated: Bool) -> () -> Void 16 | func present(_ flow: Flow, animated: Bool) -> () -> Void 17 | func dismiss(animated: Bool) -> () -> Void 18 | } 19 | 20 | // add default values 21 | public extension Lets { 22 | func push(_ flow: Flow, animated: Bool = true, with getConfigure: ((ViewController) -> ((Args) -> Void))? = nil) 23 | -> (Args) -> Void where ViewController: UIViewController { 24 | return self.push(flow, animated: animated, with: getConfigure) 25 | } 26 | 27 | func pop(animated: Bool = true) -> () -> Void { 28 | return self.pop(animated: animated) 29 | } 30 | 31 | func popTo(_ flow: Flow, animated: Bool = true, with getConfigure: ((ViewController) -> ((Args) -> Void))? = nil) 32 | -> (Args) -> Void where ViewController: UIViewController { 33 | return self.popTo(flow, animated: animated, with: getConfigure) 34 | } 35 | 36 | func popToRoot(animated: Bool = true) -> () -> Void { 37 | return self.popToRoot(animated: animated) 38 | } 39 | 40 | func present(_ flow: Flow, animated: Bool = true) -> () -> Void { 41 | return self.present(flow, animated: animated) 42 | } 43 | 44 | func dismiss(animated: Bool = true) -> () -> Void { 45 | return self.dismiss(animated: true) 46 | } 47 | } -------------------------------------------------------------------------------- /FlowKit/LetsFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/9/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | // todo: figure out, how to do this as a protocol 10 | open class LetsFactory { 11 | 12 | public init() {} 13 | 14 | open func makeLets(flow: Flow) -> Lets { 15 | 16 | fatalError("Needs to be overridden") 17 | 18 | } 19 | } 20 | 21 | public class DefaultLetsFactory: LetsFactory { 22 | 23 | public override func makeLets(flow: Flow) -> Lets { 24 | return DefaultLets(flow: flow) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /FlowKitTests/DefaultLetsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/9/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | import Nimble 8 | import FlowKit 9 | 10 | fileprivate class SpyNavigation: UINavigationController { 11 | var popCount = 0 12 | var popToRootCount = 0 13 | var presentCount = 0 14 | var popToCount = 0 15 | var pushCount = 0 16 | var dismissCount = 0 17 | 18 | override func pushViewController(_ viewController: UIViewController, animated: Bool) { 19 | pushCount += 1 20 | } 21 | 22 | override func popViewController(animated: Bool) -> UIViewController? { 23 | popCount += 1 24 | return nil 25 | } 26 | 27 | override func popToRootViewController(animated: Bool) -> [UIViewController]? { 28 | popToRootCount += 1 29 | return nil 30 | } 31 | 32 | override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { 33 | presentCount += 1 34 | } 35 | 36 | override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { 37 | popToCount += 1 38 | return nil 39 | } 40 | 41 | override func dismiss(animated flag: Bool, completion: (() -> Void)?) { 42 | dismissCount += 1 43 | } 44 | } 45 | 46 | fileprivate class DummyViewController: UIViewController { 47 | var nav: UINavigationController? 48 | 49 | override var navigationController: UINavigationController? { 50 | return nav 51 | } 52 | 53 | var onTouch: (() -> Void)? 54 | } 55 | 56 | class DefaultLetsTests: XCTestCase { 57 | 58 | fileprivate var spy = SpyNavigation() 59 | private var flow = Flow(with: UIViewController()) 60 | 61 | override func setUp() { 62 | spy = SpyNavigation() 63 | flow = Flow(with: UIViewController()) 64 | } 65 | 66 | private func prepare(configure: @escaping (Lets) -> (() -> Void)) { 67 | let flow = Flow { [weak self] lets in 68 | let vc = DummyViewController() 69 | vc.nav = self?.spy 70 | 71 | vc.onTouch = configure(lets) 72 | 73 | return vc 74 | } 75 | 76 | let vc = flow.viewController 77 | vc.onTouch?() 78 | 79 | } 80 | 81 | func testPush() { 82 | prepare { $0.push(self.flow) } 83 | 84 | expect(self.spy.pushCount).to(equal(1)) 85 | } 86 | 87 | func testPop() { 88 | prepare { $0.pop() } 89 | 90 | expect(self.spy.popCount).to(equal(1)) 91 | } 92 | 93 | func testPopTo() { 94 | prepare { $0.popTo(self.flow) } 95 | 96 | expect(self.spy.popToCount).to(equal(1)) 97 | } 98 | 99 | func testPopToRoot() { 100 | prepare { $0.popToRoot() } 101 | 102 | expect(self.spy.popToRootCount).to(equal(1)) 103 | } 104 | 105 | func testPresent() { 106 | prepare { $0.present(self.flow) } 107 | 108 | expect(self.spy.presentCount).to(equal(1)) 109 | } 110 | 111 | func testDismiss() { 112 | prepare { $0.dismiss() } 113 | 114 | expect(self.spy.dismissCount).to(equal(1)) 115 | } 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /FlowKitTests/FlowTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/9/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | import Nimble 8 | @testable import FlowKit 9 | 10 | fileprivate class DummyViewController: UIViewController { 11 | var configured: (String, Bool, Int)? 12 | 13 | func configure(string: String, bool: Bool, int: Int) { 14 | configured = (string, bool, int) 15 | } 16 | } 17 | fileprivate class BogusViewController: UIViewController { 18 | var onSth: ((String, Bool, Int) -> Void)? 19 | } 20 | class FlowTests: XCTestCase { 21 | 22 | func testFlowInit_closure() { 23 | let vc = DummyViewController() 24 | let flow = Flow { _ in vc } 25 | 26 | let viewController = flow.viewController 27 | 28 | expect(viewController).to(equal(vc)) 29 | } 30 | 31 | func testFlowInit_with() { 32 | let vc = DummyViewController() 33 | let flow = Flow(with: vc) 34 | 35 | let viewController = flow.viewController 36 | 37 | expect(viewController).to(equal(vc)) 38 | } 39 | 40 | func testFlowInit_withAndClosure() { 41 | let vc = DummyViewController() 42 | var receivedViewController: DummyViewController? 43 | var receivedLets: Lets? 44 | 45 | let flow = Flow(with: vc) { vc, lets in 46 | receivedViewController = vc 47 | receivedLets = lets 48 | } 49 | let viewController = flow.viewController 50 | 51 | expect(receivedViewController).to(equal(vc)) 52 | expect(viewController).to(equal(vc)) 53 | expect(type(of: receivedLets!)).to(be(DefaultLets.self)) 54 | } 55 | 56 | func testFlowGetViewController_returnsSameInstance() { 57 | var callCount = 0 58 | let flow = Flow { _ in 59 | callCount += 1 60 | return DummyViewController() 61 | } 62 | 63 | let vc1 = flow.viewController 64 | let vc2 = flow.viewController 65 | 66 | expect(vc1).to(equal(vc2)) 67 | expect(callCount).to(equal(1)) 68 | } 69 | 70 | func testFlowPassArguments() { 71 | let dummyScreen = Flow { _ in DummyViewController() } 72 | let bogusScreen = Flow { lets in 73 | let vc = BogusViewController() 74 | 75 | vc.onSth = lets.push(dummyScreen, animated: true) { $0.configure } 76 | 77 | return vc 78 | } 79 | 80 | let bogusViewController = bogusScreen.viewController 81 | let dummyScreenViewController = dummyScreen.viewController 82 | bogusViewController.onSth?("A", true, 1) 83 | 84 | let received = dummyScreenViewController.configured 85 | let expected = ("A", true, 1) 86 | expect(received!.0).to(equal(expected.0)) 87 | expect(received!.1).to(equal(expected.1)) 88 | expect(received!.2).to(equal(expected.2)) 89 | } 90 | 91 | func testFlowLetsFactory() { 92 | var receivedLets: Lets? 93 | let factory = LetsFactoryStub() 94 | let flow = Flow(factory: factory) { lets in 95 | receivedLets = lets 96 | return DummyViewController() 97 | } 98 | 99 | _ = flow.viewController 100 | 101 | expect(type(of: receivedLets!)).to(be(LetsStub.self)) 102 | } 103 | 104 | func testFlowReturnsDefaultLets() { 105 | var receivedLets: Lets? 106 | let flow = Flow() { lets in 107 | receivedLets = lets 108 | return DummyViewController() 109 | } 110 | 111 | // triggers closure above 112 | _ = flow.viewController 113 | 114 | expect(type(of: receivedLets!)).to(be(DefaultLets.self)) 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /FlowKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlowKitTests/LetsFactoryTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/16/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import FlowKit 7 | import XCTest 8 | import Nimble 9 | 10 | class LetsTest: XCTestCase { 11 | 12 | func testMakeLets() { 13 | let factory = LetsFactory() 14 | expect { () -> Void in 15 | _ = factory.makeLets(flow: sampleFlow) 16 | }.to(throwAssertion()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FlowKitTests/LetsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/17/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | import FlowKit 8 | import Nimble 9 | 10 | class LetsImpl: Lets { 11 | var pushCount = 0 12 | var popCount = 0 13 | var popToCount = 0 14 | var popToRootCount = 0 15 | var presentCount = 0 16 | var dismissCount = 0 17 | 18 | func push(_ flow: Flow, animated: Bool = true, with getConfigure: ((ViewController) -> ((Args) -> Void))? = nil) 19 | -> (Args) -> Void where ViewController: UIViewController { 20 | pushCount += 1 21 | return { _ in } 22 | } 23 | 24 | func pop(animated: Bool = true) -> () -> Void { 25 | popCount += 1 26 | return {} 27 | } 28 | 29 | func popTo(_ flow: Flow, animated: Bool = true, with getConfigure: ((ViewController) -> ((Args) -> Void))? = nil) 30 | -> (Args) -> Void where ViewController: UIViewController { 31 | popToCount += 1 32 | return { _ in } 33 | } 34 | 35 | func popToRoot(animated: Bool = true) -> () -> Void { 36 | popToRootCount += 1 37 | return {} 38 | } 39 | 40 | func present(_ flow: Flow, animated: Bool = true) -> () -> Void { 41 | presentCount += 1 42 | return {} 43 | } 44 | 45 | func dismiss(animated: Bool = true) -> () -> Void { 46 | dismissCount += 1 47 | return {} 48 | } 49 | 50 | } 51 | 52 | class LetsTests: XCTestCase { 53 | 54 | func testLetsProtocolForwarding() { 55 | let letsImpl = LetsImpl() 56 | 57 | // specify type as `Lets`, so we use `Lets` default extension methods, rather than `LetsImpl` 58 | let lets: Lets = letsImpl 59 | 60 | // use `click` so type can be inferred 61 | let click: () -> Void = lets.push(sampleFlow) 62 | click() 63 | expect(letsImpl.pushCount).to(equal(1)) 64 | 65 | lets.pop()() 66 | expect(letsImpl.popCount).to(equal(1)) 67 | 68 | // todo: we should also check if `sampleFlow` is forwarded correctly 69 | // use `click` so type can be inferred 70 | let click2: () -> Void = lets.popTo(sampleFlow) 71 | click2() 72 | expect(letsImpl.popToCount).to(equal(1)) 73 | 74 | lets.popToRoot()() 75 | expect(letsImpl.popToRootCount).to(equal(1)) 76 | 77 | // todo: we should also check if `flow` is forwarded correctly 78 | lets.present(sampleFlow)() 79 | expect(letsImpl.presentCount).to(equal(1)) 80 | 81 | lets.dismiss()() 82 | expect(letsImpl.dismissCount).to(equal(1)) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /FlowKitTests/stubs/FlowStub.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/19/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import FlowKit 8 | 9 | var sampleFlow: Flow { 10 | let flow = Flow(with: UIViewController()) 11 | 12 | // run `UIViewController()` 13 | _ = flow.viewController 14 | 15 | return flow 16 | } -------------------------------------------------------------------------------- /FlowKitTests/stubs/LetsFactoryStub.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/9/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import FlowKit 8 | import UIKit 9 | 10 | class LetsFactoryStub: LetsFactory { 11 | 12 | public override func makeLets(flow: Flow) -> Lets { 13 | return LetsStub() 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /FlowKitTests/stubs/LetsStub.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Filip on 12/9/16. 3 | // Copyright (c) 2016 Filip Zawada. All rights reserved. 4 | // 5 | 6 | import FlowKit 7 | import UIKit 8 | 9 | class LetsStub: Lets {} 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Filip Zawada 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Podfile 2 | 3 | use_frameworks! 4 | 5 | def testing_pods 6 | # pod 'Quick' 7 | pod 'Nimble', '5.1.1' 8 | end 9 | 10 | target 'FlowKitTests' do 11 | testing_pods 12 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/FilipZawada/FlowKit.svg?branch=master)](https://travis-ci.org/FilipZawada/FlowKit) 2 | [![codecov](https://codecov.io/gh/FilipZawada/FlowKit/branch/master/graph/badge.svg)](https://codecov.io/gh/FilipZawada/FlowKit) 3 | 4 | # Flow Kit 5 | ![FlowKit](https://cloud.githubusercontent.com/assets/1159454/21260746/c2069212-c387-11e6-9181-17c998547975.png) 6 | _iOS/Swift_ 7 | 8 | Define screen flows easily with FlowKit. Elegant syntax, clear separation of concerns and testability makes it a perfect add-on for your current MV* setup. 9 | 10 | ```swift 11 | let tutorialScreen = Flow(with: TutorialViewController()) { vc, lets in 12 | vc.onContinue = lets.push(loginScreen) 13 | } 14 | ``` 15 | 16 | ```swift 17 | let loginScreen = Flow(with: LoginViewController()) { vc, lets in 18 | vc.onLogin = lets.push(dashboardScreen) 19 | vc.onBack = lets.pop() 20 | } 21 | ``` 22 | 23 | ```swift 24 | let dashboardScreen = Flow(with: DashboardViewController()) { vc, lets in 25 | vc.onBack = lets.pop() 26 | vc.onLogOut = lets.popToRoot() 27 | } 28 | ``` 29 | 30 | This supports following flow 31 | ```text 32 | ____________ _________ _____________ 33 | | | | | | | 34 | | TutorialVC | onContinue() | LoginVC | onLogin() | DashboardVC | 35 | | | -----------> | | --------> | | 36 | |____________| |_________| |_____________| 37 | ``` 38 | 39 | This is how any of our view controllers may look like: 40 | ```swift 41 | class DashboardViewController: UIViewController { 42 | var onBack: () -> Void = {} 43 | var onLogOut: () -> Void = {} 44 | 45 | @IBAction func backButtonTapped(button: UIButton) { 46 | onBack() 47 | } 48 | 49 | @IBAction func logOutButtonTapped(button: UIButton) { 50 | onLogOut() 51 | } 52 | } 53 | ``` 54 | ## Defining a flow 55 | 56 | `Flow` is a wrapper around any `UIViewController`. Through `Flow` you can define 57 | how your `ViewController` interacts with other view controllers. Main interactions possible are: 58 | * `push(otherViewController)` 59 | * `present(otherViewController)` 60 | * `pop()` 61 | * `dismiss()` 62 | 63 | This approach has a few major advantages, since your `ViewController`: 64 | - doesn't need to know other view controllers in the town _#loosely-coupling_ 65 | - focuses on managing it's own view, rather than managing apps navigation _#single-responsibility_ 66 | - is easier to test _#testability_ 67 | - code becomes more readable, since navigation-related pieces can be put in one place _#readability_ 68 | - entrance and exit points of your `ViewController` are clearly defined _#clear-api_ 69 | 70 | There are multiple ways how to initialize the flow: 71 | 72 | 1. Without interactions: 73 | 74 | ``` 75 | let yourScreen = Flow(with: YourViewController()) 76 | 77 | // or e.g. with a custom xib 78 | let yourScreen = Flow(with: YourViewController(nibName: "YourView", bundle: nil)) 79 | ``` 80 | 81 | _Note that thanks to `@autoclosure` `YourViewController()` is instantiated lazily, i.e. only when needed. #swiftmagic_ 82 | 83 | 2. With interactions (short version) 84 | ``` 85 | let yourScreen = Flow(with: YourViewController()) { vc, lets in 86 | vc.onBack = lets.pop() 87 | vc.onAbout = lets.present(otherScreen) 88 | } 89 | ``` 90 | 91 | 3. With interactions (long version) 92 | ``` 93 | let yourScreen = Flow { lets in 94 | // let's initialize our ViewController from a storyboard 95 | let storyboard = UIStoryboard(name: "YourView", bundle: nil) 96 | let vc = storyboard.instantiateViewController(withIdentifier: "YourView") as! YourViewController 97 | 98 | vc.onBack = lets.pop() 99 | vc.onAbout = lets.present(otherScreen) 100 | 101 | return vc 102 | } 103 | ``` 104 | 105 | ## Passing arguments 106 | So, let's assume we've got a shopping app. `ItemViewController` presents our GreatProduct™. 107 | If a user decides to purchase it, `CheckoutViewController` is pushed to the screen to guide user through the checkout process. 108 | Now, how can `CheckoutViewController` know which product is actually being purchased? 109 | Obviously, it should receive that info from `ItemViewController`. This is how to do this: 110 | 111 | ```Swift 112 | class ItemViewController: UIViewController { 113 | var item: Item? // = GreatProduct™ 114 | var quantity = 0 115 | 116 | var onCheckout: (Item, Int) -> Void? 117 | 118 | @IBAction func checkout(button: UIButton) { 119 | if let item = item, onCheckout = onCheckout { 120 | onCheckout(item, quantity) 121 | } 122 | } 123 | } 124 | 125 | class CheckoutViewController: UIViewController { 126 | func prepare(item: Item, quantity: Int) { 127 | print("User wants to purchase \(item) × \(quantity)") 128 | } 129 | } 130 | 131 | // our flow 132 | 133 | let checkoutScreen = Flow(with: CheckoutViewController(nibName: "CheckoutView", bundle: nil)) 134 | 135 | let itemScreen = Flow(with: ItemViewController()) { vc, lets in 136 | 137 | vc.onCheckout = lets.push(checkoutScreen) { $0.prepare } 138 | 139 | } 140 | 141 | ``` 142 | 143 | The trick here is to forward arguments from `onCheckout()` to `prepare()` function. 144 | This is done exactly here `vc.onCheckout = lets.push(checkoutScreen) { $0.prepare }` 145 | In plain words we'd say: 146 | ```plain 147 | vc. onCheckout= lets.push( checkoutScreen){$0.prepare } 148 | hey itemViewController, on checkout lets push checkout screen and prepare it 149 | 150 | ``` 151 | 152 | Important thing to note here is that the signature of `onCheckout` has to be identical with the signature of `prepare`, so arguments can be passed successfully. 153 | 154 | # Grouping Flows 155 | In a usual scenario it's convenient to group your flows in a separate classes. 156 | For example you can have a `LoginFlow`, `SignUpFlow`, `CheckoutFlow` etc... 157 | If your app is small, it may be enough to have one `MainFlow`. 158 | 159 | ```swift 160 | class MainFlow { 161 | lazy var tutorialScreen: Flow = Flow { [unowned self] lets in 162 | let screen = TutorialViewController() 163 | 164 | screen.onContinue = lets.push(self.loginScreen) { $0.prepare } 165 | 166 | return screen 167 | } 168 | 169 | lazy var dashboardScreen: Flow = Flow { [unowned self] lets in 170 | let screen = DashboardViewController() 171 | 172 | screen.onBack = lets.pop() 173 | screen.onLogOut = lets.popTo(self.loginScreen) 174 | screen.onExit = lets.popToRoot() 175 | 176 | return screen 177 | } 178 | 179 | lazy var loginScreen: Flow = Flow { [unowned self] lets in 180 | let screen = LoginViewController() 181 | 182 | screen.onLogin = lets.push(self.dashboardScreen) 183 | screen.onBack = lets.pop() 184 | 185 | return screen 186 | } 187 | } 188 | ``` 189 | 190 | _note 1. We had to use `lazy var` to allow `dashboardScreen` to reference `loginScreen` and vice versa. 191 | With regular `let` compiler wouldn't allow us to use `loginScreen` in `dashboardScreen`._ 192 | 193 | _note 2. Unfortunately due to compiler bug we have declare variable type, otherwise we can't use `self.`._ 194 | 195 | # Testability 196 | 197 | _under construction_ 198 | 199 | I'd like to create a custom nimble matchers, so testing our flows would be as easy as writing them: 200 | ```swift 201 | let mainScreen = Flow(with: MainViewController()) 202 | mainScreen.letsFactory = LetsSpyFactory() 203 | let spy = mainScreen.letsFactory.makeSpy() 204 | 205 | let vc = mainScreen.viewController 206 | 207 | expect(vc.onBack).to(haveBeenBackedWith(spy.pop)) 208 | expect(vc.onLogOut).to(haveBeenBackedWith(spy.popTo(loginScreen))) 209 | expect(vc.onExit).to(haveBeenBackedWith(spy.popToRoot())) 210 | ``` 211 | 212 | # Extensions 213 | 214 | _under construction_ 215 | 216 | FlowKit shall integrate well with: 217 | 218 | - RxSwift (in preparation) 219 | 220 | ```swift 221 | let tutorialScreen = Flow(with: TutorialViewController()) { vc, lets in 222 | vc.onContinue 223 | .asDriver() 224 | .drive(lets.push(loginScreen)) 225 | .addToDisposeBag(disposeBag) 226 | } 227 | ``` 228 | This is just an illustration, I'm not yet sure how this is going to look like. 229 | 230 | # About 231 | This project is created and maintained by Filip Zawada. It was created as a remedy for navigation problems in my last apps. 232 | 233 | This projects is the next iteration over the idea of Flow Controllers, [described](http://merowing.info/2016/01/improve-your-ios-architecture-with-flowcontrollers/) by Krzysztof Zabłocki. 234 | # License 235 | To be chosen soon. 236 | 237 |

238 | designed in Poland, assembled in Swift 🙃 239 |

240 | 241 | --------------------------------------------------------------------------------