├── .bundle └── config ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Example ├── OutletsExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── OutletsExample.xcscheme ├── OutletsExample.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── OutletsExample │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ └── ViewController.swift ├── Podfile ├── Podfile.lock ├── Tests │ ├── Info.plist │ ├── OutletsExampleTests-Bridging-Header.h │ └── ViewControllerSpec.swift └── build │ └── Pods.build │ └── Release-iphoneos │ ├── Nimble.build │ └── dgph │ ├── Outlets.build │ └── dgph │ ├── Pods-OutletsExample.build │ └── dgph │ ├── Pods-OutletsExampleTests.build │ └── dgph │ └── Quick.build │ └── dgph ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── Outlets.podspec ├── Outlets.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Outlets.xcscheme ├── Outlets.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Outlets ├── Info.plist └── Source │ ├── Action.swift │ ├── Outlet.swift │ ├── Outlets.h │ └── Validation.swift ├── OutletsTests ├── Info.plist └── OutletsTests.swift ├── README.md └── Rakefile /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: ".rubygems" 3 | BUNDLE_DISABLE_SHARED_GEMS: "true" 4 | BUNDLE_CLEAN: "true" 5 | BUNDLE_BIN: "bin" 6 | BUNDLE_JOBS: "8" 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.yml] 13 | indent_size = 2 14 | 15 | # Use 2 spaces for the Ruby files 16 | [{Podfile,Rakefile,*.{rb,podspec}}] 17 | indent_size = 2 18 | indent_style = space 19 | max_line_length = 80 20 | 21 | # Use tabs for property lists 22 | [*.plist] 23 | indent_style = tab 24 | 25 | # Makefiles always use tabs for indentation 26 | [Makefile] 27 | indent_style = tab 28 | 29 | [*.md] 30 | trim_trailing_whitespace = false 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .rubygems 3 | Pods/ 4 | Carthage/ 5 | *.xccheckout 6 | *.xcscmblueprint 7 | xcuserdata 8 | bin 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # 2 | # .travis.yml 3 | # Outlets 4 | # 5 | # https://travis-ci.com/mas-cli/mas 6 | # https://docs.travis-ci.com/user/reference/osx/ 7 | # https://docs.travis-ci.com/user/languages/objective-c/ 8 | # http://www.objc.io/issue-6/travis-ci.html 9 | # https://github.com/supermarin/xcpretty#usage 10 | # 11 | 12 | # https://docs.travis-ci.com/user/build-config-validation 13 | version: ~> 1.0 14 | 15 | # https://docs.travis-ci.com/user/reference/osx/#macos-version 16 | os: osx 17 | osx_image: xcode11.3 # macOS 10.14 18 | 19 | env: 20 | global: 21 | - LANG=en_US.UTF-8 22 | - LC_ALL=en_US.UTF-8 23 | - LANGUAGE=en_US.UTF-8 24 | matrix: 25 | - TASK=carthage 26 | - TASK=xcodebuild 27 | - TASK=podlint 28 | 29 | # Bundler 2 30 | # https://docs.travis-ci.com/user/languages/ruby/#bundler-20 31 | before_install: 32 | - gem install bundler 33 | - bundle install 34 | 35 | install: 36 | - bundle exec pod install --project-directory=Example 37 | 38 | script: 39 | - bundle exec rake ci:$TASK 40 | 41 | after_success: 42 | - bash <(curl -s https://codecov.io/bash) 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Outlets CHANGELOG 2 | 3 | # 0.1.0 4 | 5 | Initial release. 6 | Read about the idea in [Testing IBOutlets and IBActions With Curried Functions in Swift](http://phatbl.at/2016/04/29/testing-iboutlets-and-ibactions-with-curried-functions-in-swift.html) 7 | -------------------------------------------------------------------------------- /Example/OutletsExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 03D0B7091305DFC5DC175C9D /* Pods_OutletsExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EF37ED6AA70750D8E3625207 /* Pods_OutletsExampleTests.framework */; }; 11 | 3F14D25CF8123B9FCB93E7FF /* Pods_OutletsExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98F5CC0994EBAD348D994EB7 /* Pods_OutletsExample.framework */; }; 12 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 13 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 14 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 15 | F8BDCC021CD6F262005F046D /* ViewControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BDCC011CD6F262005F046D /* ViewControllerSpec.swift */; }; 16 | F8BDCC031CD6F364005F046D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 17 | F8BDCC041CD6F366005F046D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 26 | remoteInfo = OutletsPod; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 2B653E52269D3BE67798E162 /* Pods-OutletsExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OutletsExampleTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-OutletsExampleTests/Pods-OutletsExampleTests.release.xcconfig"; sourceTree = ""; }; 32 | 544764B8DFF3472DB3E8E858 /* Pods-OutletsExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OutletsExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OutletsExample/Pods-OutletsExample.debug.xcconfig"; sourceTree = ""; }; 33 | 607FACD01AFB9204008FA782 /* OutletsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OutletsExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = OutletsExample/Info.plist; sourceTree = ""; }; 35 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 37 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 39 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 40 | 607FACE51AFB9204008FA782 /* OutletsExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OutletsExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 98F5CC0994EBAD348D994EB7 /* Pods_OutletsExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OutletsExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | C34503E5C205F1ED76D7DC0B /* Pods-OutletsExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OutletsExampleTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OutletsExampleTests/Pods-OutletsExampleTests.debug.xcconfig"; sourceTree = ""; }; 44 | C859811AE05A247E55EEC36B /* Pods-OutletsExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OutletsExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-OutletsExample/Pods-OutletsExample.release.xcconfig"; sourceTree = ""; }; 45 | D20B255CC067B6966168AE34 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 46 | EF37ED6AA70750D8E3625207 /* Pods_OutletsExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OutletsExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | F8325C561CD68AAD001189AF /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = LICENSE.md; path = ../LICENSE.md; sourceTree = ""; }; 48 | F8325C571CD68AAD001189AF /* Outlets.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Outlets.podspec; path = ../Outlets.podspec; sourceTree = ""; }; 49 | F8BDCC001CD6F262005F046D /* OutletsExampleTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OutletsExampleTests-Bridging-Header.h"; sourceTree = ""; }; 50 | F8BDCC011CD6F262005F046D /* ViewControllerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerSpec.swift; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | 3F14D25CF8123B9FCB93E7FF /* Pods_OutletsExample.framework in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | 03D0B7091305DFC5DC175C9D /* Pods_OutletsExampleTests.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 607FACC71AFB9204008FA782 = { 74 | isa = PBXGroup; 75 | children = ( 76 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 77 | 607FACD21AFB9204008FA782 /* Example for Outlets */, 78 | 607FACE81AFB9204008FA782 /* Tests */, 79 | 607FACD11AFB9204008FA782 /* Products */, 80 | A223269B147105C4BC0F850B /* Pods */, 81 | 77F5EA3AE84A655791310B16 /* Frameworks */, 82 | ); 83 | sourceTree = ""; 84 | }; 85 | 607FACD11AFB9204008FA782 /* Products */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 607FACD01AFB9204008FA782 /* OutletsExample.app */, 89 | 607FACE51AFB9204008FA782 /* OutletsExampleTests.xctest */, 90 | ); 91 | name = Products; 92 | sourceTree = ""; 93 | }; 94 | 607FACD21AFB9204008FA782 /* Example for Outlets */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 98 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 99 | 607FACD91AFB9204008FA782 /* Main.storyboard */, 100 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 101 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 102 | 607FACD31AFB9204008FA782 /* Supporting Files */, 103 | ); 104 | name = "Example for Outlets"; 105 | path = OutletsExample; 106 | sourceTree = ""; 107 | }; 108 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 607FACD41AFB9204008FA782 /* Info.plist */, 112 | ); 113 | name = "Supporting Files"; 114 | sourceTree = ""; 115 | }; 116 | 607FACE81AFB9204008FA782 /* Tests */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | F8BDCC001CD6F262005F046D /* OutletsExampleTests-Bridging-Header.h */, 120 | 607FACE91AFB9204008FA782 /* Supporting Files */, 121 | F8BDCC011CD6F262005F046D /* ViewControllerSpec.swift */, 122 | ); 123 | path = Tests; 124 | sourceTree = ""; 125 | }; 126 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 607FACEA1AFB9204008FA782 /* Info.plist */, 130 | ); 131 | name = "Supporting Files"; 132 | sourceTree = ""; 133 | }; 134 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | F8325C571CD68AAD001189AF /* Outlets.podspec */, 138 | D20B255CC067B6966168AE34 /* README.md */, 139 | F8325C561CD68AAD001189AF /* LICENSE.md */, 140 | ); 141 | name = "Podspec Metadata"; 142 | sourceTree = ""; 143 | }; 144 | 77F5EA3AE84A655791310B16 /* Frameworks */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 98F5CC0994EBAD348D994EB7 /* Pods_OutletsExample.framework */, 148 | EF37ED6AA70750D8E3625207 /* Pods_OutletsExampleTests.framework */, 149 | ); 150 | name = Frameworks; 151 | sourceTree = ""; 152 | }; 153 | A223269B147105C4BC0F850B /* Pods */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 544764B8DFF3472DB3E8E858 /* Pods-OutletsExample.debug.xcconfig */, 157 | C859811AE05A247E55EEC36B /* Pods-OutletsExample.release.xcconfig */, 158 | C34503E5C205F1ED76D7DC0B /* Pods-OutletsExampleTests.debug.xcconfig */, 159 | 2B653E52269D3BE67798E162 /* Pods-OutletsExampleTests.release.xcconfig */, 160 | ); 161 | name = Pods; 162 | sourceTree = ""; 163 | }; 164 | /* End PBXGroup section */ 165 | 166 | /* Begin PBXNativeTarget section */ 167 | 607FACCF1AFB9204008FA782 /* OutletsExample */ = { 168 | isa = PBXNativeTarget; 169 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "OutletsExample" */; 170 | buildPhases = ( 171 | 38A9A40EB8EED031B298216D /* [CP] Check Pods Manifest.lock */, 172 | 607FACCC1AFB9204008FA782 /* Sources */, 173 | 607FACCD1AFB9204008FA782 /* Frameworks */, 174 | 607FACCE1AFB9204008FA782 /* Resources */, 175 | 2DCAE16C33853FAF7F0D658F /* [CP] Embed Pods Frameworks */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | ); 181 | name = OutletsExample; 182 | productName = OutletsPod; 183 | productReference = 607FACD01AFB9204008FA782 /* OutletsExample.app */; 184 | productType = "com.apple.product-type.application"; 185 | }; 186 | 607FACE41AFB9204008FA782 /* OutletsExampleTests */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "OutletsExampleTests" */; 189 | buildPhases = ( 190 | 121BFDD26645CB51CACA44D2 /* [CP] Check Pods Manifest.lock */, 191 | 607FACE11AFB9204008FA782 /* Sources */, 192 | 607FACE21AFB9204008FA782 /* Frameworks */, 193 | 607FACE31AFB9204008FA782 /* Resources */, 194 | 8774D26E8E0AB04814A12231 /* [CP] Embed Pods Frameworks */, 195 | ); 196 | buildRules = ( 197 | ); 198 | dependencies = ( 199 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 200 | ); 201 | name = OutletsExampleTests; 202 | productName = Tests; 203 | productReference = 607FACE51AFB9204008FA782 /* OutletsExampleTests.xctest */; 204 | productType = "com.apple.product-type.bundle.unit-test"; 205 | }; 206 | /* End PBXNativeTarget section */ 207 | 208 | /* Begin PBXProject section */ 209 | 607FACC81AFB9204008FA782 /* Project object */ = { 210 | isa = PBXProject; 211 | attributes = { 212 | LastSwiftUpdateCheck = 0730; 213 | LastUpgradeCheck = 1140; 214 | ORGANIZATIONNAME = "Ben Chatelain"; 215 | TargetAttributes = { 216 | 607FACCF1AFB9204008FA782 = { 217 | CreatedOnToolsVersion = 6.3.1; 218 | DevelopmentTeam = MTGSZH8QM4; 219 | LastSwiftMigration = 0830; 220 | }; 221 | 607FACE41AFB9204008FA782 = { 222 | CreatedOnToolsVersion = 6.3.1; 223 | LastSwiftMigration = 0830; 224 | TestTargetID = 607FACCF1AFB9204008FA782; 225 | }; 226 | }; 227 | }; 228 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "OutletsExample" */; 229 | compatibilityVersion = "Xcode 3.2"; 230 | developmentRegion = English; 231 | hasScannedForEncodings = 0; 232 | knownRegions = ( 233 | English, 234 | en, 235 | Base, 236 | ); 237 | mainGroup = 607FACC71AFB9204008FA782; 238 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 239 | projectDirPath = ""; 240 | projectRoot = ""; 241 | targets = ( 242 | 607FACCF1AFB9204008FA782 /* OutletsExample */, 243 | 607FACE41AFB9204008FA782 /* OutletsExampleTests */, 244 | ); 245 | }; 246 | /* End PBXProject section */ 247 | 248 | /* Begin PBXResourcesBuildPhase section */ 249 | 607FACCE1AFB9204008FA782 /* Resources */ = { 250 | isa = PBXResourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 254 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 255 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | 607FACE31AFB9204008FA782 /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXResourcesBuildPhase section */ 267 | 268 | /* Begin PBXShellScriptBuildPhase section */ 269 | 121BFDD26645CB51CACA44D2 /* [CP] Check Pods Manifest.lock */ = { 270 | isa = PBXShellScriptBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | inputPaths = ( 275 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 276 | "${PODS_ROOT}/Manifest.lock", 277 | ); 278 | name = "[CP] Check Pods Manifest.lock"; 279 | outputPaths = ( 280 | "$(DERIVED_FILE_DIR)/Pods-OutletsExampleTests-checkManifestLockResult.txt", 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | shellPath = /bin/sh; 284 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/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# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 285 | showEnvVarsInLog = 0; 286 | }; 287 | 2DCAE16C33853FAF7F0D658F /* [CP] Embed Pods Frameworks */ = { 288 | isa = PBXShellScriptBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | ); 292 | inputPaths = ( 293 | "${PODS_ROOT}/Target Support Files/Pods-OutletsExample/Pods-OutletsExample-frameworks.sh", 294 | "${BUILT_PRODUCTS_DIR}/Outlets/Outlets.framework", 295 | ); 296 | name = "[CP] Embed Pods Frameworks"; 297 | outputPaths = ( 298 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Outlets.framework", 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | shellPath = /bin/sh; 302 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OutletsExample/Pods-OutletsExample-frameworks.sh\"\n"; 303 | showEnvVarsInLog = 0; 304 | }; 305 | 38A9A40EB8EED031B298216D /* [CP] Check Pods Manifest.lock */ = { 306 | isa = PBXShellScriptBuildPhase; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | ); 310 | inputPaths = ( 311 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 312 | "${PODS_ROOT}/Manifest.lock", 313 | ); 314 | name = "[CP] Check Pods Manifest.lock"; 315 | outputPaths = ( 316 | "$(DERIVED_FILE_DIR)/Pods-OutletsExample-checkManifestLockResult.txt", 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | shellPath = /bin/sh; 320 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/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# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 321 | showEnvVarsInLog = 0; 322 | }; 323 | 8774D26E8E0AB04814A12231 /* [CP] Embed Pods Frameworks */ = { 324 | isa = PBXShellScriptBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | ); 328 | inputPaths = ( 329 | "${PODS_ROOT}/Target Support Files/Pods-OutletsExampleTests/Pods-OutletsExampleTests-frameworks.sh", 330 | "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", 331 | "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", 332 | ); 333 | name = "[CP] Embed Pods Frameworks"; 334 | outputPaths = ( 335 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", 336 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", 337 | ); 338 | runOnlyForDeploymentPostprocessing = 0; 339 | shellPath = /bin/sh; 340 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OutletsExampleTests/Pods-OutletsExampleTests-frameworks.sh\"\n"; 341 | showEnvVarsInLog = 0; 342 | }; 343 | /* End PBXShellScriptBuildPhase section */ 344 | 345 | /* Begin PBXSourcesBuildPhase section */ 346 | 607FACCC1AFB9204008FA782 /* Sources */ = { 347 | isa = PBXSourcesBuildPhase; 348 | buildActionMask = 2147483647; 349 | files = ( 350 | F8BDCC041CD6F366005F046D /* ViewController.swift in Sources */, 351 | F8BDCC031CD6F364005F046D /* AppDelegate.swift in Sources */, 352 | ); 353 | runOnlyForDeploymentPostprocessing = 0; 354 | }; 355 | 607FACE11AFB9204008FA782 /* Sources */ = { 356 | isa = PBXSourcesBuildPhase; 357 | buildActionMask = 2147483647; 358 | files = ( 359 | F8BDCC021CD6F262005F046D /* ViewControllerSpec.swift in Sources */, 360 | ); 361 | runOnlyForDeploymentPostprocessing = 0; 362 | }; 363 | /* End PBXSourcesBuildPhase section */ 364 | 365 | /* Begin PBXTargetDependency section */ 366 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 367 | isa = PBXTargetDependency; 368 | target = 607FACCF1AFB9204008FA782 /* OutletsExample */; 369 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 370 | }; 371 | /* End PBXTargetDependency section */ 372 | 373 | /* Begin PBXVariantGroup section */ 374 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = { 375 | isa = PBXVariantGroup; 376 | children = ( 377 | 607FACDA1AFB9204008FA782 /* Base */, 378 | ); 379 | name = Main.storyboard; 380 | sourceTree = ""; 381 | }; 382 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 383 | isa = PBXVariantGroup; 384 | children = ( 385 | 607FACDF1AFB9204008FA782 /* Base */, 386 | ); 387 | name = LaunchScreen.xib; 388 | sourceTree = ""; 389 | }; 390 | /* End PBXVariantGroup section */ 391 | 392 | /* Begin XCBuildConfiguration section */ 393 | 607FACED1AFB9204008FA782 /* Debug */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ALWAYS_SEARCH_USER_PATHS = NO; 397 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 398 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 399 | CLANG_CXX_LIBRARY = "libc++"; 400 | CLANG_ENABLE_MODULES = YES; 401 | CLANG_ENABLE_OBJC_ARC = YES; 402 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 403 | CLANG_WARN_BOOL_CONVERSION = YES; 404 | CLANG_WARN_COMMA = YES; 405 | CLANG_WARN_CONSTANT_CONVERSION = YES; 406 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 407 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 408 | CLANG_WARN_EMPTY_BODY = YES; 409 | CLANG_WARN_ENUM_CONVERSION = YES; 410 | CLANG_WARN_INFINITE_RECURSION = YES; 411 | CLANG_WARN_INT_CONVERSION = YES; 412 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 414 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 416 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 417 | CLANG_WARN_STRICT_PROTOTYPES = YES; 418 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 419 | CLANG_WARN_UNREACHABLE_CODE = YES; 420 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 421 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 422 | COPY_PHASE_STRIP = NO; 423 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 424 | ENABLE_STRICT_OBJC_MSGSEND = YES; 425 | ENABLE_TESTABILITY = YES; 426 | GCC_C_LANGUAGE_STANDARD = gnu99; 427 | GCC_DYNAMIC_NO_PIC = NO; 428 | GCC_NO_COMMON_BLOCKS = YES; 429 | GCC_OPTIMIZATION_LEVEL = 0; 430 | GCC_PREPROCESSOR_DEFINITIONS = ( 431 | "DEBUG=1", 432 | "$(inherited)", 433 | ); 434 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 435 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 436 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 437 | GCC_WARN_UNDECLARED_SELECTOR = YES; 438 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 439 | GCC_WARN_UNUSED_FUNCTION = YES; 440 | GCC_WARN_UNUSED_VARIABLE = YES; 441 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 442 | MTL_ENABLE_DEBUG_INFO = YES; 443 | ONLY_ACTIVE_ARCH = YES; 444 | SDKROOT = iphoneos; 445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 446 | SWIFT_VERSION = 5.0; 447 | }; 448 | name = Debug; 449 | }; 450 | 607FACEE1AFB9204008FA782 /* Release */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | ALWAYS_SEARCH_USER_PATHS = NO; 454 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 455 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 456 | CLANG_CXX_LIBRARY = "libc++"; 457 | CLANG_ENABLE_MODULES = YES; 458 | CLANG_ENABLE_OBJC_ARC = YES; 459 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 460 | CLANG_WARN_BOOL_CONVERSION = YES; 461 | CLANG_WARN_COMMA = YES; 462 | CLANG_WARN_CONSTANT_CONVERSION = YES; 463 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 464 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 465 | CLANG_WARN_EMPTY_BODY = YES; 466 | CLANG_WARN_ENUM_CONVERSION = YES; 467 | CLANG_WARN_INFINITE_RECURSION = YES; 468 | CLANG_WARN_INT_CONVERSION = YES; 469 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 470 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 471 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 472 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 473 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 474 | CLANG_WARN_STRICT_PROTOTYPES = YES; 475 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 476 | CLANG_WARN_UNREACHABLE_CODE = YES; 477 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 478 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 479 | COPY_PHASE_STRIP = NO; 480 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 481 | ENABLE_NS_ASSERTIONS = NO; 482 | ENABLE_STRICT_OBJC_MSGSEND = YES; 483 | GCC_C_LANGUAGE_STANDARD = gnu99; 484 | GCC_NO_COMMON_BLOCKS = YES; 485 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 486 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 487 | GCC_WARN_UNDECLARED_SELECTOR = YES; 488 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 489 | GCC_WARN_UNUSED_FUNCTION = YES; 490 | GCC_WARN_UNUSED_VARIABLE = YES; 491 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 492 | MTL_ENABLE_DEBUG_INFO = NO; 493 | SDKROOT = iphoneos; 494 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 495 | SWIFT_VERSION = 5.0; 496 | VALIDATE_PRODUCT = YES; 497 | }; 498 | name = Release; 499 | }; 500 | 607FACF01AFB9204008FA782 /* Debug */ = { 501 | isa = XCBuildConfiguration; 502 | baseConfigurationReference = 544764B8DFF3472DB3E8E858 /* Pods-OutletsExample.debug.xcconfig */; 503 | buildSettings = { 504 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 505 | DEVELOPMENT_TEAM = MTGSZH8QM4; 506 | INFOPLIST_FILE = OutletsExample/Info.plist; 507 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 508 | PRODUCT_BUNDLE_IDENTIFIER = at.phatbl.OutletsExample; 509 | PRODUCT_NAME = OutletsExample; 510 | }; 511 | name = Debug; 512 | }; 513 | 607FACF11AFB9204008FA782 /* Release */ = { 514 | isa = XCBuildConfiguration; 515 | baseConfigurationReference = C859811AE05A247E55EEC36B /* Pods-OutletsExample.release.xcconfig */; 516 | buildSettings = { 517 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 518 | DEVELOPMENT_TEAM = MTGSZH8QM4; 519 | INFOPLIST_FILE = OutletsExample/Info.plist; 520 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 521 | PRODUCT_BUNDLE_IDENTIFIER = at.phatbl.OutletsExample; 522 | PRODUCT_NAME = OutletsExample; 523 | }; 524 | name = Release; 525 | }; 526 | 607FACF31AFB9204008FA782 /* Debug */ = { 527 | isa = XCBuildConfiguration; 528 | baseConfigurationReference = C34503E5C205F1ED76D7DC0B /* Pods-OutletsExampleTests.debug.xcconfig */; 529 | buildSettings = { 530 | CLANG_ENABLE_MODULES = YES; 531 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 532 | GCC_PREPROCESSOR_DEFINITIONS = ( 533 | "DEBUG=1", 534 | "$(inherited)", 535 | ); 536 | INFOPLIST_FILE = Tests/Info.plist; 537 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 538 | PRODUCT_BUNDLE_IDENTIFIER = "at.phatbl.$(PRODUCT_NAME:rfc1034identifier)"; 539 | PRODUCT_NAME = OutletsExampleTests; 540 | SWIFT_OBJC_BRIDGING_HEADER = "Tests/OutletsExampleTests-Bridging-Header.h"; 541 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 542 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OutletsExample.app/OutletsExample"; 543 | }; 544 | name = Debug; 545 | }; 546 | 607FACF41AFB9204008FA782 /* Release */ = { 547 | isa = XCBuildConfiguration; 548 | baseConfigurationReference = 2B653E52269D3BE67798E162 /* Pods-OutletsExampleTests.release.xcconfig */; 549 | buildSettings = { 550 | CLANG_ENABLE_MODULES = YES; 551 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 552 | INFOPLIST_FILE = Tests/Info.plist; 553 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 554 | PRODUCT_BUNDLE_IDENTIFIER = "at.phatbl.$(PRODUCT_NAME:rfc1034identifier)"; 555 | PRODUCT_NAME = OutletsExampleTests; 556 | SWIFT_OBJC_BRIDGING_HEADER = "Tests/OutletsExampleTests-Bridging-Header.h"; 557 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OutletsExample.app/OutletsExample"; 558 | }; 559 | name = Release; 560 | }; 561 | /* End XCBuildConfiguration section */ 562 | 563 | /* Begin XCConfigurationList section */ 564 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "OutletsExample" */ = { 565 | isa = XCConfigurationList; 566 | buildConfigurations = ( 567 | 607FACED1AFB9204008FA782 /* Debug */, 568 | 607FACEE1AFB9204008FA782 /* Release */, 569 | ); 570 | defaultConfigurationIsVisible = 0; 571 | defaultConfigurationName = Release; 572 | }; 573 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "OutletsExample" */ = { 574 | isa = XCConfigurationList; 575 | buildConfigurations = ( 576 | 607FACF01AFB9204008FA782 /* Debug */, 577 | 607FACF11AFB9204008FA782 /* Release */, 578 | ); 579 | defaultConfigurationIsVisible = 0; 580 | defaultConfigurationName = Release; 581 | }; 582 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "OutletsExampleTests" */ = { 583 | isa = XCConfigurationList; 584 | buildConfigurations = ( 585 | 607FACF31AFB9204008FA782 /* Debug */, 586 | 607FACF41AFB9204008FA782 /* Release */, 587 | ); 588 | defaultConfigurationIsVisible = 0; 589 | defaultConfigurationName = Release; 590 | }; 591 | /* End XCConfigurationList section */ 592 | }; 593 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 594 | } 595 | -------------------------------------------------------------------------------- /Example/OutletsExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/OutletsExample.xcodeproj/xcshareddata/xcschemes/OutletsExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 46 | 47 | 53 | 54 | 55 | 56 | 62 | 63 | 64 | 65 | 67 | 73 | 74 | 75 | 76 | 77 | 87 | 89 | 95 | 96 | 97 | 98 | 104 | 106 | 112 | 113 | 114 | 115 | 117 | 118 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /Example/OutletsExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/OutletsExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/OutletsExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // OutletsPod 4 | // 5 | // Created by Ben Chatelain on 05/01/2016. 6 | // Copyright (c) 2016 Ben Chatelain. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Example/OutletsExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/OutletsExample/Base.lproj/Main.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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Example/OutletsExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/OutletsExample/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/OutletsExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // OutletsPod 4 | // 5 | // Created by Ben Chatelain on 05/01/2016. 6 | // Copyright (c) 2016 Ben Chatelain. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | @IBOutlet fileprivate var leftButton: UIBarButtonItem! 13 | @IBOutlet fileprivate var rightButton: UIBarButtonItem! 14 | @IBOutlet fileprivate var segmentedControl: UISegmentedControl! 15 | } 16 | 17 | // MARK: - IBAction Methods 18 | extension ViewController { 19 | @IBAction func didTapLeftButton(_ button: UIBarButtonItem) { 20 | print("didTapLeftButton:") 21 | } 22 | 23 | @IBAction func didTapRightButton(_ button: UIBarButtonItem) { 24 | print("didTapRightButton:") 25 | } 26 | 27 | @IBAction func segmentedControlValueDidChange(_ segmentedControl: UISegmentedControl) { 28 | print("segmentedControlValueDidChange:") 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | 3 | workspace 'OutletsExample' 4 | 5 | use_frameworks! 6 | inhibit_all_warnings! 7 | 8 | target 'OutletsExample' do 9 | pod 'Outlets', path: '../', inhibit_warnings: false 10 | 11 | target 'OutletsExampleTests' do 12 | inherit! :search_paths 13 | pod 'Quick' 14 | pod 'Nimble' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Nimble (8.0.5) 3 | - Outlets (0.1.0) 4 | - Quick (2.2.0) 5 | 6 | DEPENDENCIES: 7 | - Nimble 8 | - Outlets (from `../`) 9 | - Quick 10 | 11 | SPEC REPOS: 12 | trunk: 13 | - Nimble 14 | - Quick 15 | 16 | EXTERNAL SOURCES: 17 | Outlets: 18 | :path: "../" 19 | 20 | SPEC CHECKSUMS: 21 | Nimble: 4ab1aeb9b45553c75b9687196b0fa0713170a332 22 | Outlets: a0b6ca144bcee81b1ce0da02e7d905f5e5425ba1 23 | Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e 24 | 25 | PODFILE CHECKSUM: 3f3275226fe32a78f75a20a88d5ccdf1ca351dcf 26 | 27 | COCOAPODS: 1.9.0 28 | -------------------------------------------------------------------------------- /Example/Tests/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/OutletsExampleTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /Example/Tests/ViewControllerSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerSpec.swift 3 | // OutletsExample 4 | // 5 | // Created by Ben Chatelain on 5/1/16. 6 | // Copyright © 2016 Ben Chatelain. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @testable import OutletsExample 12 | import Outlets 13 | import Quick 14 | import Nimble 15 | 16 | class ViewControllerSpec: QuickSpec { 17 | override func spec() { 18 | setupFailHandler { message in 19 | if let message = message { 20 | fail(message) 21 | } else { 22 | fail() 23 | } 24 | } 25 | 26 | var viewController: UIViewController! 27 | 28 | var hasBarButtonItemOutlet: BarButtonItemOutletAssertion! 29 | var hasSegmentedControlOutlet: SegmentedControlOutletAssertion! 30 | var receivesAction: ActionAssertion! 31 | 32 | describe("view controller") { 33 | beforeEach { 34 | viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") 35 | viewController.loadView() 36 | expect(viewController.view).toNot(beNil()) 37 | 38 | setupActionValidator { target, action, expectedAction in 39 | expect(target) === viewController 40 | expect(action).toNot(beNil()) 41 | if let action = action { 42 | expect(action) == expectedAction 43 | } 44 | } 45 | 46 | // Capture the new viewController instance for each test 47 | hasBarButtonItemOutlet = outlet(viewController) 48 | hasSegmentedControlOutlet = outlet(viewController) 49 | receivesAction = action(viewController) 50 | } 51 | 52 | // MARK: - Outlets 53 | it("has a leftButton outlet") { 54 | _ = hasBarButtonItemOutlet("leftButton") 55 | } 56 | it("has a rightButton outlet") { 57 | _ = hasBarButtonItemOutlet("rightButton") 58 | } 59 | it("has a segmentedControl outlet") { 60 | _ = hasSegmentedControlOutlet("segmentedControl") 61 | } 62 | 63 | // MARK: - Actions 64 | it("receives a didTapLeftButton: action from leftButton") { 65 | receivesAction("didTapLeftButton:", "leftButton") 66 | } 67 | it("receives a didTapRightButton: action from rightButton") { 68 | receivesAction("didTapRightButton:", "rightButton") 69 | } 70 | it("receives a segmentedControlValueDidChange: action from segmentedControl") { 71 | receivesAction("segmentedControlValueDidChange:", "segmentedControl") 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Example/build/Pods.build/Release-iphoneos/Nimble.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Apr 11 201618:39:16 /UsersphatblatdeviospodsOutletsExamplePods -------------------------------------------------------------------------------- /Example/build/Pods.build/Release-iphoneos/Outlets.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Apr 11 201618:39:16 /UsersphatblatdeviospodsOutletsExamplePods -------------------------------------------------------------------------------- /Example/build/Pods.build/Release-iphoneos/Pods-OutletsExample.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Apr 11 201618:39:16 /UsersphatblatdeviospodsOutletsExamplePods -------------------------------------------------------------------------------- /Example/build/Pods.build/Release-iphoneos/Pods-OutletsExampleTests.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Apr 11 201618:39:16 /UsersphatblatdeviospodsOutletsExamplePods -------------------------------------------------------------------------------- /Example/build/Pods.build/Release-iphoneos/Quick.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Apr 11 201618:39:16 /UsersphatblatdeviospodsOutletsExamplePods -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '~> 1.9' 4 | gem 'cocoapods-deintegrate' 5 | gem 'rake', ">= 12.3.3" 6 | gem 'xcpretty' 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | activesupport (4.2.11.1) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | algoliasearch (1.27.1) 11 | httpclient (~> 2.8, >= 2.8.3) 12 | json (>= 1.5.1) 13 | atomos (0.1.3) 14 | claide (1.0.3) 15 | cocoapods (1.9.0) 16 | activesupport (>= 4.0.2, < 5) 17 | claide (>= 1.0.2, < 2.0) 18 | cocoapods-core (= 1.9.0) 19 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 20 | cocoapods-downloader (>= 1.2.2, < 2.0) 21 | cocoapods-plugins (>= 1.0.0, < 2.0) 22 | cocoapods-search (>= 1.0.0, < 2.0) 23 | cocoapods-stats (>= 1.0.0, < 2.0) 24 | cocoapods-trunk (>= 1.4.0, < 2.0) 25 | cocoapods-try (>= 1.1.0, < 2.0) 26 | colored2 (~> 3.1) 27 | escape (~> 0.0.4) 28 | fourflusher (>= 2.3.0, < 3.0) 29 | gh_inspector (~> 1.0) 30 | molinillo (~> 0.6.6) 31 | nap (~> 1.0) 32 | ruby-macho (~> 1.4) 33 | xcodeproj (>= 1.14.0, < 2.0) 34 | cocoapods-core (1.9.0) 35 | activesupport (>= 4.0.2, < 6) 36 | algoliasearch (~> 1.0) 37 | concurrent-ruby (~> 1.1) 38 | fuzzy_match (~> 2.0.4) 39 | nap (~> 1.0) 40 | netrc (~> 0.11) 41 | typhoeus (~> 1.0) 42 | cocoapods-deintegrate (1.0.4) 43 | cocoapods-downloader (1.3.0) 44 | cocoapods-plugins (1.0.0) 45 | nap 46 | cocoapods-search (1.0.0) 47 | cocoapods-stats (1.1.0) 48 | cocoapods-trunk (1.4.1) 49 | nap (>= 0.8, < 2.0) 50 | netrc (~> 0.11) 51 | cocoapods-try (1.1.0) 52 | colored2 (3.1.2) 53 | concurrent-ruby (1.1.6) 54 | escape (0.0.4) 55 | ethon (0.12.0) 56 | ffi (>= 1.3.0) 57 | ffi (1.12.2) 58 | fourflusher (2.3.1) 59 | fuzzy_match (2.0.4) 60 | gh_inspector (1.1.3) 61 | httpclient (2.8.3) 62 | i18n (0.9.5) 63 | concurrent-ruby (~> 1.0) 64 | json (2.3.0) 65 | minitest (5.14.0) 66 | molinillo (0.6.6) 67 | nanaimo (0.2.6) 68 | nap (1.1.0) 69 | netrc (0.11.0) 70 | rake (13.0.1) 71 | rouge (2.0.7) 72 | ruby-macho (1.4.0) 73 | thread_safe (0.3.6) 74 | typhoeus (1.3.1) 75 | ethon (>= 0.9.0) 76 | tzinfo (1.2.6) 77 | thread_safe (~> 0.1) 78 | xcodeproj (1.15.0) 79 | CFPropertyList (>= 2.3.3, < 4.0) 80 | atomos (~> 0.1.3) 81 | claide (>= 1.0.2, < 2.0) 82 | colored2 (~> 3.1) 83 | nanaimo (~> 0.2.6) 84 | xcpretty (0.3.0) 85 | rouge (~> 2.0.7) 86 | 87 | PLATFORMS 88 | ruby 89 | 90 | DEPENDENCIES 91 | cocoapods (~> 1.9) 92 | cocoapods-deintegrate 93 | rake (>= 12.3.3) 94 | xcpretty 95 | 96 | BUNDLED WITH 97 | 2.1.4 98 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ben Chatelain 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. 22 | -------------------------------------------------------------------------------- /Outlets.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Outlets.podspec 3 | # Outlets 4 | # 5 | 6 | Pod::Spec.new do |s| 7 | s.name = 'Outlets' 8 | s.version = '0.1.0' 9 | s.summary = 'Utility functions for validating IBOutlet and IBAction connections.' 10 | 11 | # This description is used to generate tags and improve search results. 12 | # * Think: What does it do? Why did you write it? What is the focus? 13 | # * Try to keep it short, snappy and to the point. 14 | # * Write the description between the DESC delimiters below. 15 | # * Finally, don't worry about the indent, CocoaPods strips it! 16 | 17 | s.description = <<-DESC 18 | Outlets provides a set of functions which validate that `IBOutlets` are 19 | correctly connected between your Storyboard/XIB file and view controller properties. It 20 | can also validate that `IBAction` methods are connected correctly as well. 21 | 22 | For an explanation of the approach and how these assertions work see 23 | [Testing IBOutlets and IBActions With Curried Functions in Swift](http://phatbl.at/2016/04/29/testing-iboutlets-and-ibactions-with-curried-functions-in-swift.html) 24 | 25 | The standalone 26 | [OutletActionAssertion](https://github.com/phatblat/OutletActionAssertion) 27 | project demonstrates how they work. 28 | DESC 29 | 30 | s.homepage = 'https://github.com/phatblat/Outlets' 31 | s.screenshots = 'http://phatbl.at/images/outlet-action-tests-pass.png' 32 | s.license = 'MIT' 33 | s.author = { 'Ben Chatelain' => 'ben@octop.ad' } 34 | s.source = { git: 'https://github.com/phatblat/Outlets.git', tag: s.version.to_s } 35 | s.social_media_url = 'https://twitter.com/phatblat' 36 | 37 | s.ios.deployment_target = '8.0' 38 | # s.ios.deployment_target = '12.1' 39 | s.swift_version = "5.1" 40 | 41 | s.source_files = 'Outlets/Source/**/*' 42 | 43 | # s.resource_bundles = { 44 | # 'OutletsPod' => ['OutletsPod/Assets/*.png'] 45 | # } 46 | 47 | # s.public_header_files = 'Pod/Classes/**/*.h' 48 | s.frameworks = 'UIKit' 49 | end 50 | -------------------------------------------------------------------------------- /Outlets.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B585CE051CD7EAFF001A701E /* Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B585CE041CD7EAFF001A701E /* Validation.swift */; }; 11 | F8325C331CD67E78001189AF /* Outlets.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8325C281CD67E77001189AF /* Outlets.framework */; }; 12 | F8325C381CD67E78001189AF /* OutletsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8325C371CD67E78001189AF /* OutletsTests.swift */; }; 13 | F8325C511CD688E7001189AF /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8325C4E1CD688E7001189AF /* Action.swift */; }; 14 | F8325C531CD688E7001189AF /* Outlet.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8325C501CD688E7001189AF /* Outlet.swift */; }; 15 | F8325C551CD68915001189AF /* Outlets.h in Headers */ = {isa = PBXBuildFile; fileRef = F8325C541CD68915001189AF /* Outlets.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | F8325C341CD67E78001189AF /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = F8325C1F1CD67E77001189AF /* Project object */; 22 | proxyType = 1; 23 | remoteGlobalIDString = F8325C271CD67E77001189AF; 24 | remoteInfo = Outlets; 25 | }; 26 | /* End PBXContainerItemProxy section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | B585CE041CD7EAFF001A701E /* Validation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Validation.swift; sourceTree = ""; }; 30 | F8325C281CD67E77001189AF /* Outlets.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Outlets.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | F8325C2D1CD67E78001189AF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | F8325C321CD67E78001189AF /* OutletsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OutletsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | F8325C371CD67E78001189AF /* OutletsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutletsTests.swift; sourceTree = ""; }; 34 | F8325C391CD67E78001189AF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | F8325C4E1CD688E7001189AF /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; 36 | F8325C501CD688E7001189AF /* Outlet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Outlet.swift; sourceTree = ""; }; 37 | F8325C541CD68915001189AF /* Outlets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Outlets.h; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | F8325C241CD67E77001189AF /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | F8325C2F1CD67E78001189AF /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | F8325C331CD67E78001189AF /* Outlets.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | F8325C1E1CD67E77001189AF = { 60 | isa = PBXGroup; 61 | children = ( 62 | F8325C2A1CD67E78001189AF /* Outlets */, 63 | F8325C361CD67E78001189AF /* OutletsTests */, 64 | F8325C291CD67E77001189AF /* Products */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | F8325C291CD67E77001189AF /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | F8325C281CD67E77001189AF /* Outlets.framework */, 72 | F8325C321CD67E78001189AF /* OutletsTests.xctest */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | F8325C2A1CD67E78001189AF /* Outlets */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | F8325C491CD688C0001189AF /* Source */, 81 | F8325C2D1CD67E78001189AF /* Info.plist */, 82 | ); 83 | path = Outlets; 84 | sourceTree = ""; 85 | }; 86 | F8325C361CD67E78001189AF /* OutletsTests */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | F8325C371CD67E78001189AF /* OutletsTests.swift */, 90 | F8325C391CD67E78001189AF /* Info.plist */, 91 | ); 92 | path = OutletsTests; 93 | sourceTree = ""; 94 | }; 95 | F8325C491CD688C0001189AF /* Source */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | F8325C4E1CD688E7001189AF /* Action.swift */, 99 | F8325C501CD688E7001189AF /* Outlet.swift */, 100 | F8325C541CD68915001189AF /* Outlets.h */, 101 | B585CE041CD7EAFF001A701E /* Validation.swift */, 102 | ); 103 | path = Source; 104 | sourceTree = ""; 105 | }; 106 | /* End PBXGroup section */ 107 | 108 | /* Begin PBXHeadersBuildPhase section */ 109 | F8325C251CD67E77001189AF /* Headers */ = { 110 | isa = PBXHeadersBuildPhase; 111 | buildActionMask = 2147483647; 112 | files = ( 113 | F8325C551CD68915001189AF /* Outlets.h in Headers */, 114 | ); 115 | runOnlyForDeploymentPostprocessing = 0; 116 | }; 117 | /* End PBXHeadersBuildPhase section */ 118 | 119 | /* Begin PBXNativeTarget section */ 120 | F8325C271CD67E77001189AF /* Outlets */ = { 121 | isa = PBXNativeTarget; 122 | buildConfigurationList = F8325C3C1CD67E78001189AF /* Build configuration list for PBXNativeTarget "Outlets" */; 123 | buildPhases = ( 124 | F8325C231CD67E77001189AF /* Sources */, 125 | F8325C241CD67E77001189AF /* Frameworks */, 126 | F8325C251CD67E77001189AF /* Headers */, 127 | F8325C261CD67E77001189AF /* Resources */, 128 | ); 129 | buildRules = ( 130 | ); 131 | dependencies = ( 132 | ); 133 | name = Outlets; 134 | productName = Outlets; 135 | productReference = F8325C281CD67E77001189AF /* Outlets.framework */; 136 | productType = "com.apple.product-type.framework"; 137 | }; 138 | F8325C311CD67E78001189AF /* OutletsTests */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = F8325C3F1CD67E78001189AF /* Build configuration list for PBXNativeTarget "OutletsTests" */; 141 | buildPhases = ( 142 | F8325C2E1CD67E78001189AF /* Sources */, 143 | F8325C2F1CD67E78001189AF /* Frameworks */, 144 | F8325C301CD67E78001189AF /* Resources */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | F8325C351CD67E78001189AF /* PBXTargetDependency */, 150 | ); 151 | name = OutletsTests; 152 | productName = OutletsTests; 153 | productReference = F8325C321CD67E78001189AF /* OutletsTests.xctest */; 154 | productType = "com.apple.product-type.bundle.unit-test"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | F8325C1F1CD67E77001189AF /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | LastSwiftUpdateCheck = 0730; 163 | LastUpgradeCheck = 1140; 164 | ORGANIZATIONNAME = "Ben Chatelain"; 165 | TargetAttributes = { 166 | F8325C271CD67E77001189AF = { 167 | CreatedOnToolsVersion = 7.3.1; 168 | LastSwiftMigration = 0800; 169 | }; 170 | F8325C311CD67E78001189AF = { 171 | CreatedOnToolsVersion = 7.3.1; 172 | LastSwiftMigration = 0800; 173 | }; 174 | }; 175 | }; 176 | buildConfigurationList = F8325C221CD67E77001189AF /* Build configuration list for PBXProject "Outlets" */; 177 | compatibilityVersion = "Xcode 3.2"; 178 | developmentRegion = en; 179 | hasScannedForEncodings = 0; 180 | knownRegions = ( 181 | en, 182 | Base, 183 | ); 184 | mainGroup = F8325C1E1CD67E77001189AF; 185 | productRefGroup = F8325C291CD67E77001189AF /* Products */; 186 | projectDirPath = ""; 187 | projectRoot = ""; 188 | targets = ( 189 | F8325C271CD67E77001189AF /* Outlets */, 190 | F8325C311CD67E78001189AF /* OutletsTests */, 191 | ); 192 | }; 193 | /* End PBXProject section */ 194 | 195 | /* Begin PBXResourcesBuildPhase section */ 196 | F8325C261CD67E77001189AF /* Resources */ = { 197 | isa = PBXResourcesBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | F8325C301CD67E78001189AF /* Resources */ = { 204 | isa = PBXResourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | }; 210 | /* End PBXResourcesBuildPhase section */ 211 | 212 | /* Begin PBXSourcesBuildPhase section */ 213 | F8325C231CD67E77001189AF /* Sources */ = { 214 | isa = PBXSourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | F8325C531CD688E7001189AF /* Outlet.swift in Sources */, 218 | F8325C511CD688E7001189AF /* Action.swift in Sources */, 219 | B585CE051CD7EAFF001A701E /* Validation.swift in Sources */, 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | F8325C2E1CD67E78001189AF /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | F8325C381CD67E78001189AF /* OutletsTests.swift in Sources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXSourcesBuildPhase section */ 232 | 233 | /* Begin PBXTargetDependency section */ 234 | F8325C351CD67E78001189AF /* PBXTargetDependency */ = { 235 | isa = PBXTargetDependency; 236 | target = F8325C271CD67E77001189AF /* Outlets */; 237 | targetProxy = F8325C341CD67E78001189AF /* PBXContainerItemProxy */; 238 | }; 239 | /* End PBXTargetDependency section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | F8325C3A1CD67E78001189AF /* Debug */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 247 | CLANG_ANALYZER_NONNULL = YES; 248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 249 | CLANG_CXX_LIBRARY = "libc++"; 250 | CLANG_ENABLE_MODULES = YES; 251 | CLANG_ENABLE_OBJC_ARC = YES; 252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_COMMA = YES; 255 | CLANG_WARN_CONSTANT_CONVERSION = YES; 256 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 272 | COPY_PHASE_STRIP = NO; 273 | CURRENT_PROJECT_VERSION = 1; 274 | DEBUG_INFORMATION_FORMAT = dwarf; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | ENABLE_TESTABILITY = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu99; 278 | GCC_DYNAMIC_NO_PIC = NO; 279 | GCC_NO_COMMON_BLOCKS = YES; 280 | GCC_OPTIMIZATION_LEVEL = 0; 281 | GCC_PREPROCESSOR_DEFINITIONS = ( 282 | "DEBUG=1", 283 | "$(inherited)", 284 | ); 285 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 286 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 287 | GCC_WARN_UNDECLARED_SELECTOR = YES; 288 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 289 | GCC_WARN_UNUSED_FUNCTION = YES; 290 | GCC_WARN_UNUSED_VARIABLE = YES; 291 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 292 | MTL_ENABLE_DEBUG_INFO = YES; 293 | ONLY_ACTIVE_ARCH = YES; 294 | SDKROOT = iphoneos; 295 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 296 | SWIFT_VERSION = 5.0; 297 | TARGETED_DEVICE_FAMILY = "1,2"; 298 | VERSIONING_SYSTEM = "apple-generic"; 299 | VERSION_INFO_PREFIX = ""; 300 | }; 301 | name = Debug; 302 | }; 303 | F8325C3B1CD67E78001189AF /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ALWAYS_SEARCH_USER_PATHS = NO; 307 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 308 | CLANG_ANALYZER_NONNULL = YES; 309 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 310 | CLANG_CXX_LIBRARY = "libc++"; 311 | CLANG_ENABLE_MODULES = YES; 312 | CLANG_ENABLE_OBJC_ARC = YES; 313 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 314 | CLANG_WARN_BOOL_CONVERSION = YES; 315 | CLANG_WARN_COMMA = YES; 316 | CLANG_WARN_CONSTANT_CONVERSION = YES; 317 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 318 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 319 | CLANG_WARN_EMPTY_BODY = YES; 320 | CLANG_WARN_ENUM_CONVERSION = YES; 321 | CLANG_WARN_INFINITE_RECURSION = YES; 322 | CLANG_WARN_INT_CONVERSION = YES; 323 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 325 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 327 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 328 | CLANG_WARN_STRICT_PROTOTYPES = YES; 329 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 330 | CLANG_WARN_UNREACHABLE_CODE = YES; 331 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 332 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 333 | COPY_PHASE_STRIP = NO; 334 | CURRENT_PROJECT_VERSION = 1; 335 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 336 | ENABLE_NS_ASSERTIONS = NO; 337 | ENABLE_STRICT_OBJC_MSGSEND = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_NO_COMMON_BLOCKS = YES; 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 347 | MTL_ENABLE_DEBUG_INFO = NO; 348 | SDKROOT = iphoneos; 349 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 350 | SWIFT_VERSION = 5.0; 351 | TARGETED_DEVICE_FAMILY = "1,2"; 352 | VALIDATE_PRODUCT = YES; 353 | VERSIONING_SYSTEM = "apple-generic"; 354 | VERSION_INFO_PREFIX = ""; 355 | }; 356 | name = Release; 357 | }; 358 | F8325C3D1CD67E78001189AF /* Debug */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | CLANG_ENABLE_MODULES = YES; 362 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 363 | DEFINES_MODULE = YES; 364 | DYLIB_COMPATIBILITY_VERSION = 1; 365 | DYLIB_CURRENT_VERSION = 1; 366 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 367 | INFOPLIST_FILE = Outlets/Info.plist; 368 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 370 | PRODUCT_BUNDLE_IDENTIFIER = at.phatbl.Outlets; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | SKIP_INSTALL = YES; 373 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 374 | }; 375 | name = Debug; 376 | }; 377 | F8325C3E1CD67E78001189AF /* Release */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | CLANG_ENABLE_MODULES = YES; 381 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 382 | DEFINES_MODULE = YES; 383 | DYLIB_COMPATIBILITY_VERSION = 1; 384 | DYLIB_CURRENT_VERSION = 1; 385 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 386 | INFOPLIST_FILE = Outlets/Info.plist; 387 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 388 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 389 | PRODUCT_BUNDLE_IDENTIFIER = at.phatbl.Outlets; 390 | PRODUCT_NAME = "$(TARGET_NAME)"; 391 | SKIP_INSTALL = YES; 392 | }; 393 | name = Release; 394 | }; 395 | F8325C401CD67E78001189AF /* Debug */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | INFOPLIST_FILE = OutletsTests/Info.plist; 399 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 400 | PRODUCT_BUNDLE_IDENTIFIER = at.phatbl.OutletsTests; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | }; 403 | name = Debug; 404 | }; 405 | F8325C411CD67E78001189AF /* Release */ = { 406 | isa = XCBuildConfiguration; 407 | buildSettings = { 408 | INFOPLIST_FILE = OutletsTests/Info.plist; 409 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 410 | PRODUCT_BUNDLE_IDENTIFIER = at.phatbl.OutletsTests; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | }; 413 | name = Release; 414 | }; 415 | /* End XCBuildConfiguration section */ 416 | 417 | /* Begin XCConfigurationList section */ 418 | F8325C221CD67E77001189AF /* Build configuration list for PBXProject "Outlets" */ = { 419 | isa = XCConfigurationList; 420 | buildConfigurations = ( 421 | F8325C3A1CD67E78001189AF /* Debug */, 422 | F8325C3B1CD67E78001189AF /* Release */, 423 | ); 424 | defaultConfigurationIsVisible = 0; 425 | defaultConfigurationName = Release; 426 | }; 427 | F8325C3C1CD67E78001189AF /* Build configuration list for PBXNativeTarget "Outlets" */ = { 428 | isa = XCConfigurationList; 429 | buildConfigurations = ( 430 | F8325C3D1CD67E78001189AF /* Debug */, 431 | F8325C3E1CD67E78001189AF /* Release */, 432 | ); 433 | defaultConfigurationIsVisible = 0; 434 | defaultConfigurationName = Release; 435 | }; 436 | F8325C3F1CD67E78001189AF /* Build configuration list for PBXNativeTarget "OutletsTests" */ = { 437 | isa = XCConfigurationList; 438 | buildConfigurations = ( 439 | F8325C401CD67E78001189AF /* Debug */, 440 | F8325C411CD67E78001189AF /* Release */, 441 | ); 442 | defaultConfigurationIsVisible = 0; 443 | defaultConfigurationName = Release; 444 | }; 445 | /* End XCConfigurationList section */ 446 | }; 447 | rootObject = F8325C1F1CD67E77001189AF /* Project object */; 448 | } 449 | -------------------------------------------------------------------------------- /Outlets.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Outlets.xcodeproj/xcshareddata/xcschemes/Outlets.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Outlets.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Outlets.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Outlets/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 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Outlets/Source/Action.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Action.swift 3 | // Outlets 4 | // 5 | // Created by Ben Chatelain on 5/1/16. 6 | // Copyright © 2016 Ben Chatelain. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | import UIKit 28 | 29 | // MARK: - Actions 30 | /// Full signature of the `action` curried function. 31 | typealias FullActionAssertion = (UIViewController) -> (String, _ from: String) -> Void 32 | 33 | /// Asserts that the `from` outlet. 34 | public typealias ActionAssertion = (String, _ from: String) -> Void 35 | 36 | /// Asserts that `viewController` contains an action invoked from a known outlet. 37 | /// The Nimble `expect` function is used for validation and `fail` is called if 38 | /// action type is not supported. 39 | /// 40 | /// - parameter viewController: `UIViewController` to inspect. 41 | /// 42 | /// - returns: Function which validates `expectedAction`. 43 | /// 44 | /// - parameter expectedAction: Name of action to look up. 45 | /// 46 | /// - parameter expectedOutlet: Name of outlet to look up. 47 | /// 48 | /// - returns: Object bound to `outlet` if found; nil otherwise. 49 | public func action(_ viewController: UIViewController) -> (String, _ from: String) -> Void { 50 | return { (expectedAction: String, expectedOutlet: String) in 51 | let optionalControl = outlet(viewController)(expectedOutlet) 52 | 53 | var target: Any? 54 | var action: String? 55 | 56 | if let control = optionalControl { 57 | switch control { 58 | case let button as UIBarButtonItem: 59 | target = button.target 60 | action = button.action?.description 61 | case let control as UIControl: 62 | target = control.allTargets.first! 63 | var allActions: [String] = [] 64 | for event: UIControl.Event in [.touchUpInside, .valueChanged] { 65 | allActions += control.actions(forTarget: target!, forControlEvent: event) ?? [] 66 | } 67 | 68 | // Filter down to the expected action 69 | action = allActions.filter{$0 == expectedAction}.first 70 | default: 71 | fail("Unhandled control type: \(type(of: control))") 72 | } 73 | } 74 | 75 | validate(target, action: action, expectedAction: expectedAction) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Outlets/Source/Outlet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Outlet.swift 3 | // Outlets 4 | // 5 | // Created by Ben Chatelain on 5/1/16. 6 | // Copyright © 2016 Ben Chatelain. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | import UIKit 28 | 29 | // MARK: - Outlets 30 | /// Full signature of the `outlet` curried function. 31 | private typealias FullOutletTest = (UIViewController) -> (String) -> AnyObject? 32 | 33 | /// Asserts that the named outlet is bound, but does not care about the type of object. 34 | typealias AnyOutletAssertion = (String) -> AnyObject? 35 | 36 | /// Asserts that the named outlet is bound to a `UIButton`. 37 | typealias ButtonOutletAssertion = (String) -> UIButton? 38 | 39 | /// Asserts that the named outlet is bound to a `UIBarButtonItem`. 40 | public typealias BarButtonItemOutletAssertion = (String) -> UIBarButtonItem? 41 | 42 | /// Asserts that the named outlet is bound to a `UISegmentedControl`. 43 | public typealias SegmentedControlOutletAssertion = (String) -> UISegmentedControl? 44 | 45 | /// Asserts that the named outlet is bound to a `UILabel`. 46 | typealias LabelOutletAssertion = (String) -> UILabel? 47 | 48 | /// Asserts that the named outlet is bound to a `UIImageView`. 49 | typealias ImageOutletAssertion = (String) -> UIImageView? 50 | 51 | /// Asserts that `viewController` has an outlet with matching name. The Nimble 52 | /// `fail` function is called if outlet is not found. 53 | /// 54 | /// - parameter viewController: `UIViewController` to inspect. 55 | /// 56 | /// - returns: Function which validates `outlet`. 57 | /// 58 | /// - parameter outlet: Name of outlet to look up. 59 | /// 60 | /// - returns: Object bound to `outlet` if found; nil otherwise. 61 | /// 62 | /// - note: Does not need an explicit call to test framework fail as runtime will catch exception: 63 | /// Assertions: failed: caught "NSUnknownKeyException", "[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key leftButton1." 64 | func outlet(_ viewController: UIViewController) -> (String) -> Any? { 65 | return { (outlet: String) -> Any? in 66 | guard let object = viewController.value(forKey: outlet) 67 | else { return nil } 68 | 69 | return object 70 | } 71 | } 72 | 73 | /// Asserts that `viewController` has an outlet with matching name. The Nimble 74 | /// `fail` function is called if outlet is not found. 75 | /// 76 | /// - parameter viewController: `UIViewController` to inspect. 77 | /// 78 | /// - returns: Function which validates `outlet`. 79 | /// 80 | /// - parameter outlet: Name of outlet to look up. 81 | /// 82 | /// - returns: Object bound to `outlet` if found; nil otherwise. 83 | public func outlet(_ viewController: UIViewController) -> (String) -> T? { 84 | return { (expectedOutlet: String) -> T? in 85 | guard let object = outlet(viewController)(expectedOutlet) 86 | else { return nil } 87 | 88 | debugPrint(type(of: object)) 89 | 90 | guard let objectOfType = object as? T 91 | else { fail("\(object) outlet was not a \(T.self)"); return nil } 92 | 93 | return objectOfType 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Outlets/Source/Outlets.h: -------------------------------------------------------------------------------- 1 | // 2 | // Outlets.h 3 | // Outlets 4 | // 5 | // Created by Ben Chatelain on 5/1/16. 6 | // Copyright © 2016 Ben Chatelain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Outlets. 12 | FOUNDATION_EXPORT double OutletsVersionNumber; 13 | 14 | //! Project version string for Outlets. 15 | FOUNDATION_EXPORT const unsigned char OutletsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Outlets/Source/Validation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Validation.swift 3 | // Outlets 4 | // 5 | // Created by Ben Chatelain on 5/1/16. 6 | // Copyright © 2016 Ben Chatelain. All rights reserved. 7 | // 8 | 9 | // MARK: - Failure Handling 10 | 11 | /// Closure called when a test has failed. Used to map to testing framework's 12 | /// fail function. 13 | public typealias FailHandler = (String?) -> Void 14 | 15 | private var failHandler: FailHandler? 16 | 17 | /// Stores the given `FailHandler` for use when a built-in validation fails. 18 | /// Example of calling this with trailing closure syntax and mapping to the 19 | /// Nimble `fail` function: 20 | /// 21 | /// setupFailHandler { message in 22 | /// if let message = message { 23 | /// fail(message) 24 | /// } else { 25 | /// fail() 26 | /// } 27 | /// } 28 | /// 29 | /// - parameter handler: Optional `FailHandler` to handle failures. 30 | public func setupFailHandler(_ handler: FailHandler?) { 31 | failHandler = handler 32 | } 33 | 34 | /// Passthrough function which calls the `FailHandler` provided by the caller. 35 | /// This allows for tests to call XCTFail() or Nimble's fail() function without 36 | /// any knowledge or dependencies in this framework. 37 | /// 38 | /// - parameter message: Optional description of failure. 39 | func fail(_ message: String?) { 40 | guard let handler = failHandler else { print("ERROR: failHandler has not been set up."); return } 41 | handler(message) 42 | } 43 | 44 | // MARK: - Action Validation 45 | public typealias ActionValidation = (_ target: Any?, _ action: String?, _ expectedAction: String) -> Void 46 | 47 | private var actionValidator: ActionValidation? 48 | 49 | /// Stores the given `ActionValidation` for use in validating IBAction methods. 50 | /// Example of calling this with trailing closure syntax and mapping to the 51 | /// Nimble `expect` function: 52 | /// 53 | /// setupActionValidator { target, action, expectedAction in 54 | /// expect(target) === viewController 55 | /// expect(action).toNot(beNil()) 56 | /// if let action = action { 57 | /// expect(action) == expectedAction 58 | /// } 59 | /// } 60 | /// 61 | /// - parameter validator: Optional `ActionValidation` to perform validation. 62 | /// - Note: The `validator` should be passed the same `UIViewController` instance that will be 63 | /// used in your tests. If you reinitialize this for each text/example method (e.g. in 64 | /// `setUp` or `beforeEach`) you will need to call `setupActionValidator` again with 65 | /// the new instance. 66 | public func setupActionValidator(_ validator: ActionValidation?) { 67 | actionValidator = validator 68 | } 69 | 70 | /// Passthrough function which calls the `ActionValidation` provided by the caller. 71 | /// 72 | /// - parameter target: Target of the action, expected to be the given view controller. 73 | /// - parameter action: Action found (will be nil if not found). 74 | /// - parameter expectedAction: String name of expected action. 75 | func validate(_ target: Any?, action: String?, expectedAction: String) { 76 | guard let validator = actionValidator else { print("ERROR: actionValidator has not been set up."); return } 77 | validator(target, action, expectedAction) 78 | } 79 | -------------------------------------------------------------------------------- /OutletsTests/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /OutletsTests/OutletsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutletsTests.swift 3 | // OutletsTests 4 | // 5 | // Created by Ben Chatelain on 5/1/16. 6 | // Copyright © 2016 Ben Chatelain. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Outlets 11 | 12 | class OutletsTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Outlets logo](http://phatbl.at/images/outlets-logo.png "Outlets logo showing electrical sockets from various contries") 2 | 3 | # Outlets 4 | 5 | _Utility functions for validating `IBOutlet` and `IBAction` connections._ 6 | 7 | [![Version](https://img.shields.io/cocoapods/v/Outlets.svg?style=flat)](http://cocoapods.org/pods/Outlets) 8 | [![License](https://img.shields.io/cocoapods/l/Outlets.svg?style=flat)](http://cocoapods.org/pods/Outlets) 9 | [![Language Swift 2](https://img.shields.io/badge/Language-Swift%202-orange.svg)](https://developer.apple.com/swift) 10 | [![Platform](https://img.shields.io/cocoapods/p/Outlets.svg?style=flat)](http://cocoapods.org/pods/Outlets) 11 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 12 | [![Build Status](https://travis-ci.org/phatblat/Outlets.svg?branch=master)](https://travis-ci.org/phatblat/Outlets) 13 | [![codecov](https://codecov.io/gh/phatblat/Outlets/branch/master/graph/badge.svg)](https://codecov.io/gh/phatblat/Outlets) 14 | 15 | # About 16 | 17 | Outlets provides a set of functions which validate that `IBOutlets` are 18 | correctly connected between your Storyboard/XIB file and view controller properties. It 19 | can also validate that `IBAction` methods are connected correctly as well. 20 | 21 | > This micro-library is based on the following post: 22 | > [Testing IBOutlets and IBActions With Curried Functions in Swift](http://phatbl.at/2016/04/29/testing-iboutlets-and-ibactions-with-curried-functions-in-swift.html) 23 | 24 | 25 | ## Requirements 26 | 27 | - Xcode 7.3+ 28 | - Swift 2.2+ 29 | - iOS 8.0+ 30 | 31 | ## Installation 32 | 33 | ### [CocoaPods](http://cocoapods.org) (recommended) 34 | 35 | ```ruby 36 | target 'AppTests' do 37 | use_frameworks! 38 | pod 'Outlets' 39 | end 40 | ``` 41 | 42 | ### [Carthage](https://github.com/Carthage/Carthage) 43 | 44 | ``` 45 | github "phatblat/Outlets" 46 | ``` 47 | 48 | ## Example 49 | 50 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 51 | 52 | Or run `pod try Outlets` from the command line. 53 | 54 | ## Getting Started 55 | 56 | Here is an example of using Outlets with Quick and Nimble: 57 | 58 | ```swift 59 | class ViewControllerSpec: QuickSpec { 60 | override func spec() { 61 | setupFailHandler { message in 62 | if let message = message { 63 | fail(message) 64 | } else { 65 | fail() 66 | } 67 | } 68 | 69 | var viewController: UIViewController! 70 | 71 | var hasBarButtonItemOutlet: BarButtonItemOutletAssertion! 72 | var hasSegmentedControlOutlet: SegmentedControlOutletAssertion! 73 | var receivesAction: ActionAssertion! 74 | 75 | describe("view controller") { 76 | beforeEach { 77 | viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController") 78 | viewController.loadView() 79 | expect(viewController.view).toNot(beNil()) 80 | 81 | setupActionValidator { target, action, expectedAction in 82 | expect(target) === viewController 83 | expect(action).toNot(beNil()) 84 | if let action = action { 85 | expect(action) == expectedAction 86 | } 87 | } 88 | 89 | // Capture the new viewController instance for each test 90 | hasBarButtonItemOutlet = outlet(viewController) 91 | hasSegmentedControlOutlet = outlet(viewController) 92 | receivesAction = action(viewController) 93 | } 94 | 95 | // MARK: - Outlets 96 | it("has a leftButton outlet") { 97 | hasBarButtonItemOutlet("leftButton") 98 | } 99 | it("has a rightButton outlet") { 100 | hasBarButtonItemOutlet("rightButton") 101 | } 102 | it("has a segmentedControl outlet") { 103 | hasSegmentedControlOutlet("segmentedControl") 104 | } 105 | 106 | // MARK: - Actions 107 | it("receives a didTapLeftButton: action from leftButton") { 108 | receivesAction("didTapLeftButton:", from: "leftButton") 109 | } 110 | it("receives a didTapRightButton: action from rightButton") { 111 | receivesAction("didTapRightButton:", from: "rightButton") 112 | } 113 | it("receives a segmentedControlValueDidChange: action from segmentedControl") { 114 | receivesAction("segmentedControlValueDidChange:", from: "segmentedControl") 115 | } 116 | } 117 | } 118 | } 119 | ``` 120 | 121 | ## Author 122 | 123 | Ben Chatelain, [@phatblat](https://twitter.com/phatblat) 124 | 125 | ## License 126 | 127 | Outlets is released under the [MIT License](http://opensource.org/licenses/MIT). See the [LICENSE](LICENSE.md) file for details. 128 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # 2 | # Rakefile 3 | # Outlets 4 | # 5 | 6 | def run(command) 7 | system(command) or raise "RAKE TASK FAILED: #{command}" 8 | end 9 | 10 | namespace "ci" do 11 | desc "Builds the Outlets framework using Carthage" 12 | task :carthage do |t| 13 | run "carthage build --no-skip-current --verbose" 14 | end 15 | 16 | desc "Builds and tests the OutletsExample project using xcodebuild" 17 | task :xcodebuild do |t| 18 | run "set -o pipefail && xcodebuild test -workspace Example/OutletsExample.xcworkspace -scheme OutletsExample -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty" 19 | end 20 | 21 | desc "Lints the CocoaPod" 22 | task :podlint do |t| 23 | run "bundle exec pod lib lint" 24 | end 25 | end 26 | --------------------------------------------------------------------------------