├── .gitignore ├── LICENSE ├── Readme.markdown ├── SwiftVisualFormat.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SwiftVisualFormat ├── AppDelegate.swift ├── Base.lproj │ └── Main.storyboard ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── Info.plist └── ViewController.swift ├── SwiftVisualFormatTests ├── Info.plist └── SwiftVisualFormatTests.swift └── VisualFormat.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | *.DS_Store 20 | 21 | # CocoaPods 22 | # 23 | # We recommend against adding the Pods directory to your .gitignore. However 24 | # you should judge for yourself, the pros and cons are mentioned at: 25 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 26 | # 27 | # Pods/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Bridger Maxwell 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. -------------------------------------------------------------------------------- /Readme.markdown: -------------------------------------------------------------------------------- 1 | Swift Visual Format Language 2 | === 3 | 4 | This project is an attempt to bring the [Auto Layout Visual Format Language](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage/VisualFormatLanguage.html) to Swift, without requiring strings or dictionaries. It uses some crazy operator overloading instead. Using it looks like this: 5 | 6 | view.addConstraints(horizontalConstraints( |-5-[redView]-0-[greenView]-0-[blueView]-5-| )) 7 | 8 | // Make backgroundView fill its container 9 | view.addConstraints(horizontalConstraints( |[backgroundView]| )) 10 | view.addConstraints(verticalConstraints( |[backgroundView]| )) 11 | 12 | **This project is not finished. Nor is it necessarily a good idea. See the todo section and the drawbacks discussion. Feel free to play around with this code, but I wouldn't use it in a project yet.** 13 | 14 | The purpose of the Visual Format Language is to make code that does layout _look_ like layout. Views are in brackets to look like rectangles `[view]`. Spaces between views are hyphens, like `[view]-5-[view2]`. The containing view is represented by a vertical bar, `|-0-[fullWidthView]-0-|`. 15 | 16 | More Examples 17 | --- 18 | 19 | These examples contrast the visual format with the resulting constraints represented by the equation-based [SwiftAutoLayout package from indragiek](https://github.com/indragiek/SwiftAutoLayout). There are constraints that the visual format can't represent (such as aspect ratios), but the equations are much harder to skim. 20 | 21 | horizontalConstraints( [redView]-10-[greenView] ) 22 | greenView.al_leading == redView.al_trailing + 10 23 | 24 | verticalConstraints( |-5-[redView]-5-| ) 25 | redView.al_top == redView.superview.al_top + 5 26 | redView.superview.al_bottom == redView.al_bottom + 5 27 | 28 | horizontalConstraints( [greenView == redView]-0-[blueView == greenView] ) 29 | greenView.al_width == redView.al_width 30 | blueView.al_leading == greenView.al_trailing 31 | blueView.al_width == greenView.al_width 32 | 33 | // Impossible to represent in the visual format language 34 | greenView.al_width = 0.5 * greenView.al_height 35 | 36 | Okay, so maybe the width constraints are easier to read in the equation format. 37 | 38 | Drawbacks from the Objective-C / String API 39 | --- 40 | 41 | The Objective-C API was string-based, meaning that the visual format language was in a string with the tokens in the string mapped to views or constants via a dictionary. Accessed from Swift, this looked like this: 42 | 43 | let views = ["redView" : redView, "greenView" : greenView, "blueView": blueView] 44 | self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-5-[redView]-0-[greenView]-0-[blueView]-5-|", options: nil, metrics: nil, views: views)) 45 | 46 | The string-based API has *much* better error messages. For example, when you type in the faulty format `|-0-[redView-0-|` you get the error 47 | 48 | Unable to parse constraint format: 49 | Expected a ']' here. That is how you give the end of a view. 50 | |-0-[redView-0-| 51 | ^ 52 | 53 | That same faulty format in Swift gives you the unintelligble error 54 | 55 | Expected ',' separator 56 | 57 | 58 | Because it was string-based, the language didn't have many constraints and could be designed better. For example, for width constraints `"[redView(>=greenView)][greenView]"` looks much better than `[redView >= greenView]-0-[greenView]`, but is not possible (as far as I can tell) with operator overloading. 59 | 60 | The Objectice-C API is also able to do the "standard space" between controls. For example `"[button]-[textField]"` creates a constraint for whatever the correct space between a button and a textField should be. There is no other API for doing this. 61 | 62 | Benefits over Objective-C / String API 63 | --- 64 | 65 | There are some benefits to the Swift approach, mostly due to the fact that the compiler is involved in parsing the constraints intead of parsing a string at runtime. 66 | 67 | If you have an invalid constraint format, it probably won't compile. You don't have to build and run to find that out. There are still some cases where I wasn't able to enforce every rule of the grammar through the type system and operator overloading and you will get a runtime crash instead, but I hope these are encountered rarely. The compiler errors aren't generally useful (see above), but at least they are early. 68 | 69 | The biggest benefit is that you can use the view names directly without putting them into a string and a dictionary mapping them from names. The `NSDictionaryOfVariableBindings` macro isn't available in Swift, so it is a pain to declare a views dictionary. It basically meant that to make one constraint you need to type the view's name three times, with two of those times being in a string not checked by the compiler. 70 | 71 | Another, smaller, benefit is that it is easier to use the same format for either Horizontal or Vertical layout. In the string-based API, I would append either `"H:"` or `"V:"` to the string. Now just use the `toConstraints(.Vertical, [redView]-0-[greenView] )` and change the axis parameter. 72 | 73 | 74 | To Do 75 | --- 76 | - Inequalities like >= or <= aren't supported for spaces between views. It should support `|->=5-[redView]-<=10-[greenView]-==0-|` 77 | - Priorities aren't supported. I am planning to use the ! operator, if it can be infix. For example, to make a high priority constraint it should look like `[redView.al]-10.al!750.al-[greenView.al]` 78 | - Figure out the best way to distribute this. Cocoapods? As a framework? 79 | -------------------------------------------------------------------------------- /SwiftVisualFormat.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AE18C9BC198BCA5600591FA5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE18C9BB198BCA5600591FA5 /* AppDelegate.swift */; }; 11 | AE18C9BE198BCA5600591FA5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE18C9BD198BCA5600591FA5 /* ViewController.swift */; }; 12 | AE18C9C1198BCA5600591FA5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE18C9BF198BCA5600591FA5 /* Main.storyboard */; }; 13 | AE18C9C3198BCA5600591FA5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE18C9C2198BCA5600591FA5 /* Images.xcassets */; }; 14 | AE18C9CF198BCA5600591FA5 /* SwiftVisualFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE18C9CE198BCA5600591FA5 /* SwiftVisualFormatTests.swift */; }; 15 | AE18C9D9198BCA6300591FA5 /* VisualFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE18C9D8198BCA6300591FA5 /* VisualFormat.swift */; }; 16 | AE18C9DD198BF20B00591FA5 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE18C9DC198BF20B00591FA5 /* UIKit.framework */; }; 17 | AE18C9DF198BF22600591FA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE18C9DE198BF22600591FA5 /* Foundation.framework */; }; 18 | AE9486CC199DE327004DEAB2 /* VisualFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE18C9D8198BCA6300591FA5 /* VisualFormat.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | AE18C9C9198BCA5600591FA5 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = AE18C9AE198BCA5600591FA5 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = AE18C9B5198BCA5600591FA5; 27 | remoteInfo = SwiftVisualFormat; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | AE18C9B6198BCA5600591FA5 /* SwiftVisualFormat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftVisualFormat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | AE18C9BA198BCA5600591FA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | AE18C9BB198BCA5600591FA5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | AE18C9BD198BCA5600591FA5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 36 | AE18C9C0198BCA5600591FA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 37 | AE18C9C2198BCA5600591FA5 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 38 | AE18C9C8198BCA5600591FA5 /* SwiftVisualFormatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftVisualFormatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | AE18C9CD198BCA5600591FA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | AE18C9CE198BCA5600591FA5 /* SwiftVisualFormatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftVisualFormatTests.swift; sourceTree = ""; }; 41 | AE18C9D8198BCA6300591FA5 /* VisualFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VisualFormat.swift; path = ../VisualFormat.swift; sourceTree = ""; }; 42 | AE18C9DC198BF20B00591FA5 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 43 | AE18C9DE198BF22600591FA5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | AE18C9B3198BCA5600591FA5 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | AE18C9DF198BF22600591FA5 /* Foundation.framework in Frameworks */, 52 | AE18C9DD198BF20B00591FA5 /* UIKit.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | AE18C9C5198BCA5600591FA5 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | AE18C9AD198BCA5600591FA5 = { 67 | isa = PBXGroup; 68 | children = ( 69 | AE18C9DE198BF22600591FA5 /* Foundation.framework */, 70 | AE18C9DC198BF20B00591FA5 /* UIKit.framework */, 71 | AE18C9B8198BCA5600591FA5 /* SwiftVisualFormat */, 72 | AE18C9CB198BCA5600591FA5 /* SwiftVisualFormatTests */, 73 | AE18C9B7198BCA5600591FA5 /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | AE18C9B7198BCA5600591FA5 /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | AE18C9B6198BCA5600591FA5 /* SwiftVisualFormat.app */, 81 | AE18C9C8198BCA5600591FA5 /* SwiftVisualFormatTests.xctest */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | AE18C9B8198BCA5600591FA5 /* SwiftVisualFormat */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | AE18C9BB198BCA5600591FA5 /* AppDelegate.swift */, 90 | AE18C9BD198BCA5600591FA5 /* ViewController.swift */, 91 | AE18C9D8198BCA6300591FA5 /* VisualFormat.swift */, 92 | AE18C9BF198BCA5600591FA5 /* Main.storyboard */, 93 | AE18C9C2198BCA5600591FA5 /* Images.xcassets */, 94 | AE18C9B9198BCA5600591FA5 /* Supporting Files */, 95 | ); 96 | path = SwiftVisualFormat; 97 | sourceTree = ""; 98 | }; 99 | AE18C9B9198BCA5600591FA5 /* Supporting Files */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | AE18C9BA198BCA5600591FA5 /* Info.plist */, 103 | ); 104 | name = "Supporting Files"; 105 | sourceTree = ""; 106 | }; 107 | AE18C9CB198BCA5600591FA5 /* SwiftVisualFormatTests */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | AE18C9CE198BCA5600591FA5 /* SwiftVisualFormatTests.swift */, 111 | AE18C9CC198BCA5600591FA5 /* Supporting Files */, 112 | ); 113 | path = SwiftVisualFormatTests; 114 | sourceTree = ""; 115 | }; 116 | AE18C9CC198BCA5600591FA5 /* Supporting Files */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | AE18C9CD198BCA5600591FA5 /* Info.plist */, 120 | ); 121 | name = "Supporting Files"; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | AE18C9B5198BCA5600591FA5 /* SwiftVisualFormat */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = AE18C9D2198BCA5600591FA5 /* Build configuration list for PBXNativeTarget "SwiftVisualFormat" */; 130 | buildPhases = ( 131 | AE18C9B2198BCA5600591FA5 /* Sources */, 132 | AE18C9B3198BCA5600591FA5 /* Frameworks */, 133 | AE18C9B4198BCA5600591FA5 /* Resources */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = SwiftVisualFormat; 140 | productName = SwiftVisualFormat; 141 | productReference = AE18C9B6198BCA5600591FA5 /* SwiftVisualFormat.app */; 142 | productType = "com.apple.product-type.application"; 143 | }; 144 | AE18C9C7198BCA5600591FA5 /* SwiftVisualFormatTests */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = AE18C9D5198BCA5600591FA5 /* Build configuration list for PBXNativeTarget "SwiftVisualFormatTests" */; 147 | buildPhases = ( 148 | AE18C9C4198BCA5600591FA5 /* Sources */, 149 | AE18C9C5198BCA5600591FA5 /* Frameworks */, 150 | AE18C9C6198BCA5600591FA5 /* Resources */, 151 | ); 152 | buildRules = ( 153 | ); 154 | dependencies = ( 155 | AE18C9CA198BCA5600591FA5 /* PBXTargetDependency */, 156 | ); 157 | name = SwiftVisualFormatTests; 158 | productName = SwiftVisualFormatTests; 159 | productReference = AE18C9C8198BCA5600591FA5 /* SwiftVisualFormatTests.xctest */; 160 | productType = "com.apple.product-type.bundle.unit-test"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | AE18C9AE198BCA5600591FA5 /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastSwiftUpdateCheck = 0700; 169 | LastUpgradeCheck = 0600; 170 | ORGANIZATIONNAME = "Bridger Maxwell"; 171 | TargetAttributes = { 172 | AE18C9B5198BCA5600591FA5 = { 173 | CreatedOnToolsVersion = 6.0; 174 | }; 175 | AE18C9C7198BCA5600591FA5 = { 176 | CreatedOnToolsVersion = 6.0; 177 | TestTargetID = AE18C9B5198BCA5600591FA5; 178 | }; 179 | }; 180 | }; 181 | buildConfigurationList = AE18C9B1198BCA5600591FA5 /* Build configuration list for PBXProject "SwiftVisualFormat" */; 182 | compatibilityVersion = "Xcode 3.2"; 183 | developmentRegion = English; 184 | hasScannedForEncodings = 0; 185 | knownRegions = ( 186 | en, 187 | Base, 188 | ); 189 | mainGroup = AE18C9AD198BCA5600591FA5; 190 | productRefGroup = AE18C9B7198BCA5600591FA5 /* Products */; 191 | projectDirPath = ""; 192 | projectRoot = ""; 193 | targets = ( 194 | AE18C9B5198BCA5600591FA5 /* SwiftVisualFormat */, 195 | AE18C9C7198BCA5600591FA5 /* SwiftVisualFormatTests */, 196 | ); 197 | }; 198 | /* End PBXProject section */ 199 | 200 | /* Begin PBXResourcesBuildPhase section */ 201 | AE18C9B4198BCA5600591FA5 /* Resources */ = { 202 | isa = PBXResourcesBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | AE18C9C1198BCA5600591FA5 /* Main.storyboard in Resources */, 206 | AE18C9C3198BCA5600591FA5 /* Images.xcassets in Resources */, 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | }; 210 | AE18C9C6198BCA5600591FA5 /* Resources */ = { 211 | isa = PBXResourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXResourcesBuildPhase section */ 218 | 219 | /* Begin PBXSourcesBuildPhase section */ 220 | AE18C9B2198BCA5600591FA5 /* Sources */ = { 221 | isa = PBXSourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | AE18C9BE198BCA5600591FA5 /* ViewController.swift in Sources */, 225 | AE18C9D9198BCA6300591FA5 /* VisualFormat.swift in Sources */, 226 | AE18C9BC198BCA5600591FA5 /* AppDelegate.swift in Sources */, 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | AE18C9C4198BCA5600591FA5 /* Sources */ = { 231 | isa = PBXSourcesBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | AE9486CC199DE327004DEAB2 /* VisualFormat.swift in Sources */, 235 | AE18C9CF198BCA5600591FA5 /* SwiftVisualFormatTests.swift in Sources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXSourcesBuildPhase section */ 240 | 241 | /* Begin PBXTargetDependency section */ 242 | AE18C9CA198BCA5600591FA5 /* PBXTargetDependency */ = { 243 | isa = PBXTargetDependency; 244 | target = AE18C9B5198BCA5600591FA5 /* SwiftVisualFormat */; 245 | targetProxy = AE18C9C9198BCA5600591FA5 /* PBXContainerItemProxy */; 246 | }; 247 | /* End PBXTargetDependency section */ 248 | 249 | /* Begin PBXVariantGroup section */ 250 | AE18C9BF198BCA5600591FA5 /* Main.storyboard */ = { 251 | isa = PBXVariantGroup; 252 | children = ( 253 | AE18C9C0198BCA5600591FA5 /* Base */, 254 | ); 255 | name = Main.storyboard; 256 | sourceTree = ""; 257 | }; 258 | /* End PBXVariantGroup section */ 259 | 260 | /* Begin XCBuildConfiguration section */ 261 | AE18C9D0198BCA5600591FA5 /* Debug */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | ALWAYS_SEARCH_USER_PATHS = NO; 265 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 266 | CLANG_CXX_LIBRARY = "libc++"; 267 | CLANG_ENABLE_MODULES = YES; 268 | CLANG_ENABLE_OBJC_ARC = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_CONSTANT_CONVERSION = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_EMPTY_BODY = YES; 273 | CLANG_WARN_ENUM_CONVERSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 276 | CLANG_WARN_UNREACHABLE_CODE = YES; 277 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 278 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 279 | COPY_PHASE_STRIP = NO; 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | GCC_C_LANGUAGE_STANDARD = gnu99; 282 | GCC_DYNAMIC_NO_PIC = NO; 283 | GCC_OPTIMIZATION_LEVEL = 0; 284 | GCC_PREPROCESSOR_DEFINITIONS = ( 285 | "DEBUG=1", 286 | "$(inherited)", 287 | ); 288 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 289 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 290 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 291 | GCC_WARN_UNDECLARED_SELECTOR = YES; 292 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 293 | GCC_WARN_UNUSED_FUNCTION = YES; 294 | GCC_WARN_UNUSED_VARIABLE = YES; 295 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 296 | MTL_ENABLE_DEBUG_INFO = YES; 297 | ONLY_ACTIVE_ARCH = YES; 298 | SDKROOT = iphoneos; 299 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 300 | TARGETED_DEVICE_FAMILY = "1,2"; 301 | }; 302 | name = Debug; 303 | }; 304 | AE18C9D1198BCA5600591FA5 /* Release */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ALWAYS_SEARCH_USER_PATHS = NO; 308 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 309 | CLANG_CXX_LIBRARY = "libc++"; 310 | CLANG_ENABLE_MODULES = YES; 311 | CLANG_ENABLE_OBJC_ARC = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_CONSTANT_CONVERSION = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 319 | CLANG_WARN_UNREACHABLE_CODE = YES; 320 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 321 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 322 | COPY_PHASE_STRIP = YES; 323 | ENABLE_NS_ASSERTIONS = NO; 324 | ENABLE_STRICT_OBJC_MSGSEND = YES; 325 | GCC_C_LANGUAGE_STANDARD = gnu99; 326 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 327 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 328 | GCC_WARN_UNDECLARED_SELECTOR = YES; 329 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 330 | GCC_WARN_UNUSED_FUNCTION = YES; 331 | GCC_WARN_UNUSED_VARIABLE = YES; 332 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 333 | MTL_ENABLE_DEBUG_INFO = NO; 334 | SDKROOT = iphoneos; 335 | TARGETED_DEVICE_FAMILY = "1,2"; 336 | VALIDATE_PRODUCT = YES; 337 | }; 338 | name = Release; 339 | }; 340 | AE18C9D3198BCA5600591FA5 /* Debug */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 344 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 345 | INFOPLIST_FILE = SwiftVisualFormat/Info.plist; 346 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 347 | PRODUCT_NAME = "$(TARGET_NAME)"; 348 | }; 349 | name = Debug; 350 | }; 351 | AE18C9D4198BCA5600591FA5 /* Release */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 355 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 356 | INFOPLIST_FILE = SwiftVisualFormat/Info.plist; 357 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 358 | PRODUCT_NAME = "$(TARGET_NAME)"; 359 | }; 360 | name = Release; 361 | }; 362 | AE18C9D6198BCA5600591FA5 /* Debug */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SwiftVisualFormat.app/SwiftVisualFormat"; 366 | FRAMEWORK_SEARCH_PATHS = ( 367 | "$(SDKROOT)/Developer/Library/Frameworks", 368 | "$(inherited)", 369 | ); 370 | GCC_PREPROCESSOR_DEFINITIONS = ( 371 | "DEBUG=1", 372 | "$(inherited)", 373 | ); 374 | INFOPLIST_FILE = SwiftVisualFormatTests/Info.plist; 375 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 376 | PRODUCT_NAME = "$(TARGET_NAME)"; 377 | TEST_HOST = "$(BUNDLE_LOADER)"; 378 | }; 379 | name = Debug; 380 | }; 381 | AE18C9D7198BCA5600591FA5 /* Release */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SwiftVisualFormat.app/SwiftVisualFormat"; 385 | FRAMEWORK_SEARCH_PATHS = ( 386 | "$(SDKROOT)/Developer/Library/Frameworks", 387 | "$(inherited)", 388 | ); 389 | INFOPLIST_FILE = SwiftVisualFormatTests/Info.plist; 390 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 391 | PRODUCT_NAME = "$(TARGET_NAME)"; 392 | TEST_HOST = "$(BUNDLE_LOADER)"; 393 | }; 394 | name = Release; 395 | }; 396 | /* End XCBuildConfiguration section */ 397 | 398 | /* Begin XCConfigurationList section */ 399 | AE18C9B1198BCA5600591FA5 /* Build configuration list for PBXProject "SwiftVisualFormat" */ = { 400 | isa = XCConfigurationList; 401 | buildConfigurations = ( 402 | AE18C9D0198BCA5600591FA5 /* Debug */, 403 | AE18C9D1198BCA5600591FA5 /* Release */, 404 | ); 405 | defaultConfigurationIsVisible = 0; 406 | defaultConfigurationName = Release; 407 | }; 408 | AE18C9D2198BCA5600591FA5 /* Build configuration list for PBXNativeTarget "SwiftVisualFormat" */ = { 409 | isa = XCConfigurationList; 410 | buildConfigurations = ( 411 | AE18C9D3198BCA5600591FA5 /* Debug */, 412 | AE18C9D4198BCA5600591FA5 /* Release */, 413 | ); 414 | defaultConfigurationIsVisible = 0; 415 | defaultConfigurationName = Release; 416 | }; 417 | AE18C9D5198BCA5600591FA5 /* Build configuration list for PBXNativeTarget "SwiftVisualFormatTests" */ = { 418 | isa = XCConfigurationList; 419 | buildConfigurations = ( 420 | AE18C9D6198BCA5600591FA5 /* Debug */, 421 | AE18C9D7198BCA5600591FA5 /* Release */, 422 | ); 423 | defaultConfigurationIsVisible = 0; 424 | defaultConfigurationName = Release; 425 | }; 426 | /* End XCConfigurationList section */ 427 | }; 428 | rootObject = AE18C9AE198BCA5600591FA5 /* Project object */; 429 | } 430 | -------------------------------------------------------------------------------- /SwiftVisualFormat.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftVisualFormat/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftVisualFormat 4 | // 5 | // Created by Bridger Maxwell on 8/1/14. 6 | // Copyright (c) 2014 Bridger Maxwell. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | } 15 | 16 | -------------------------------------------------------------------------------- /SwiftVisualFormat/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 | -------------------------------------------------------------------------------- /SwiftVisualFormat/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" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /SwiftVisualFormat/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /SwiftVisualFormat/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.bridgermaxwell.${PRODUCT_NAME:rfc1034identifier} 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 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SwiftVisualFormat/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftVisualFormat 4 | // 5 | // Created by Bridger Maxwell on 8/1/14. 6 | // Copyright (c) 2014 Bridger Maxwell. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | let redView = UIView() 17 | redView.backgroundColor = UIColor.redColor() 18 | redView.translatesAutoresizingMaskIntoConstraints = false 19 | self.view.addSubview(redView) 20 | 21 | let greenView = UIView() 22 | greenView.backgroundColor = UIColor.greenColor() 23 | greenView.translatesAutoresizingMaskIntoConstraints = false 24 | self.view.addSubview(greenView) 25 | 26 | let blueView = UIView() 27 | blueView.backgroundColor = UIColor.blueColor() 28 | blueView.translatesAutoresizingMaskIntoConstraints = false 29 | self.view.addSubview(blueView) 30 | 31 | self.view.addConstraints(horizontalConstraints( |-5-[redView]-0-[greenView]-0-[blueView]-5-| )) 32 | 33 | self.view.addConstraints(horizontalConstraints( [redView == greenView] )) 34 | self.view.addConstraints(horizontalConstraints( [blueView == greenView] )) 35 | 36 | self.view.addConstraints(verticalConstraints( |-5-[redView]-5-| )) 37 | self.view.addConstraints(verticalConstraints( |-5-[greenView]-5-| )) 38 | self.view.addConstraints(verticalConstraints( |-5-[blueView]-5-| )) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /SwiftVisualFormatTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.bridgermaxwell.${PRODUCT_NAME:rfc1034identifier} 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 | -------------------------------------------------------------------------------- /SwiftVisualFormatTests/SwiftVisualFormatTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftVisualFormatTests.swift 3 | // SwiftVisualFormatTests 4 | // 5 | // Created by Bridger Maxwell on 8/1/14. 6 | // Copyright (c) 2014 Bridger Maxwell. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class SwiftVisualFormatTests: XCTestCase { 13 | 14 | var containerView: UIView! 15 | var redView: UIView! 16 | var greenView: UIView! 17 | var blueView: UIView! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | 22 | let containerView = UIView() 23 | containerView.translatesAutoresizingMaskIntoConstraints = false 24 | self.containerView = containerView 25 | 26 | let redView = UIView() 27 | redView.backgroundColor = UIColor.redColor() 28 | redView.translatesAutoresizingMaskIntoConstraints = false 29 | self.containerView.addSubview(redView) 30 | self.redView = redView 31 | 32 | let greenView = UIView() 33 | greenView.backgroundColor = UIColor.greenColor() 34 | greenView.translatesAutoresizingMaskIntoConstraints = false 35 | self.containerView.addSubview(greenView) 36 | self.greenView = greenView 37 | 38 | let blueView = UIView() 39 | blueView.backgroundColor = UIColor.blueColor() 40 | blueView.translatesAutoresizingMaskIntoConstraints = false 41 | self.containerView.addSubview(blueView) 42 | self.blueView = blueView 43 | } 44 | 45 | override func tearDown() { 46 | // Put teardown code here. This method is called after the invocation of each test method in the class. 47 | super.tearDown() 48 | } 49 | 50 | func compareConstraints(visualFormatString: String, checkedConstraints: [NSLayoutConstraint]) { 51 | let views = ["redView" : redView, "blueView" : blueView, "greenView" : greenView] 52 | let referenceConstraints = NSLayoutConstraint.constraintsWithVisualFormat(visualFormatString, options: [], metrics: nil, views: views) as [NSLayoutConstraint] 53 | 54 | XCTAssertEqual(checkedConstraints.count, referenceConstraints.count, "The correct amount of constraints wasn't created") 55 | 56 | for constraint in referenceConstraints { 57 | 58 | var foundMatch = false 59 | for checkConstraint in checkedConstraints { 60 | if (constraint.firstItem === checkConstraint.firstItem && 61 | constraint.firstAttribute == checkConstraint.firstAttribute && 62 | constraint.relation == checkConstraint.relation && 63 | constraint.secondItem === checkConstraint.secondItem && 64 | constraint.secondAttribute == checkConstraint.secondAttribute && 65 | constraint.multiplier == checkConstraint.multiplier && 66 | constraint.constant == checkConstraint.constant && 67 | constraint.priority == checkConstraint.priority) { 68 | 69 | foundMatch = true 70 | break; 71 | } 72 | } 73 | 74 | XCTAssert(foundMatch, "The reference constraint \( constraint ) was not found in the created constraints: \(checkedConstraints)") 75 | } 76 | } 77 | 78 | func compareHorizontalAndVerticalConstraints(formatString: String, constraintAble: [ConstraintAble]) { 79 | compareConstraints("H:" + formatString, checkedConstraints: constraints(.Horizontal, constraintAble: constraintAble)) 80 | compareConstraints("V:" + formatString, checkedConstraints: constraints(.Vertical, constraintAble: constraintAble)) 81 | } 82 | 83 | func testSuperviewSpace() { 84 | compareHorizontalAndVerticalConstraints("|-5-[redView]", constraintAble: |-5-[redView] ) 85 | compareHorizontalAndVerticalConstraints("|[redView]", constraintAble: |[redView] ) 86 | compareHorizontalAndVerticalConstraints("[redView]-5-|", constraintAble: [redView]-5-| ) 87 | compareHorizontalAndVerticalConstraints("[redView]|", constraintAble: [redView]| ) 88 | 89 | compareHorizontalAndVerticalConstraints("|[redView]|", constraintAble: |[redView]| ) 90 | compareHorizontalAndVerticalConstraints("|-5-[redView]|", constraintAble: |-5-[redView]| ) 91 | compareHorizontalAndVerticalConstraints("|[redView]-5-|", constraintAble: |[redView]-5-| ) 92 | } 93 | 94 | func testWidthConstraints() { 95 | compareHorizontalAndVerticalConstraints("[redView(==greenView)]", constraintAble: [redView==greenView] ) 96 | compareHorizontalAndVerticalConstraints("[redView(>=greenView)]", constraintAble: [redView>=greenView] ) 97 | compareHorizontalAndVerticalConstraints("[redView(<=greenView)]", constraintAble: [redView<=greenView] ) 98 | } 99 | 100 | func testSpaceConstraints() { 101 | compareHorizontalAndVerticalConstraints("[redView]-5-[greenView]", constraintAble: [redView]-5-[greenView] ) 102 | compareHorizontalAndVerticalConstraints("[redView]-0-[greenView]", constraintAble: [redView]-0-[greenView] ) 103 | } 104 | 105 | func testCombinedConstraints() { 106 | compareHorizontalAndVerticalConstraints("|-5-[redView(>=blueView)]-10-[greenView]-15-[blueView]-20-|", constraintAble: |-5-[redView >= blueView]-10-[greenView]-15-[blueView]-20-| ) 107 | compareHorizontalAndVerticalConstraints("|[redView(>=blueView)]-10-[greenView]-15-[blueView]-20-|", constraintAble: |[redView >= blueView]-10-[greenView]-15-[blueView]-20-| ) 108 | compareHorizontalAndVerticalConstraints("|-5-[redView(>=blueView)]-10-[greenView]-15-[blueView]|", constraintAble: |-5-[redView >= blueView]-10-[greenView]-15-[blueView]| ) 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /VisualFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisualFormat.swift 3 | // SwiftVisualFormat 4 | // 5 | // Created by Bridger Maxwell on 8/1/14. 6 | // Copyright (c) 2014 Bridger Maxwell. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import AppKit 11 | public typealias ALVFView = NSView 12 | #elseif os(iOS) 13 | import UIKit 14 | public typealias ALVFView = UIView 15 | #endif 16 | 17 | extension ALVFView { 18 | public func addVerticalConstraints(constraintAble: [ConstraintAble]) { 19 | self.addConstraints(verticalConstraints(constraintAble)) 20 | } 21 | 22 | public func addHorizontalConstraints(constraintAble: [ConstraintAble]) { 23 | self.addConstraints(horizontalConstraints(constraintAble)) 24 | } 25 | } 26 | 27 | @objc public protocol ConstraintAble { 28 | func toConstraints(axis: UILayoutConstraintAxis) -> [NSLayoutConstraint]; 29 | } 30 | 31 | public func constraints(axis: UILayoutConstraintAxis, constraintAble: [ConstraintAble]) -> [NSLayoutConstraint] { 32 | return constraintAble[0].toConstraints(axis) 33 | } 34 | 35 | public func horizontalConstraints(constraintAble: [ConstraintAble]) -> [NSLayoutConstraint] { 36 | return constraints(.Horizontal, constraintAble: constraintAble) 37 | } 38 | 39 | public func verticalConstraints(constraintAble: [ConstraintAble]) -> [NSLayoutConstraint] { 40 | return constraints(.Vertical, constraintAble: constraintAble) 41 | } 42 | 43 | 44 | @objc public protocol ViewContainingToken : ConstraintAble { 45 | var firstView: ALVFView? { get } 46 | var lastView: ALVFView? { get } 47 | } 48 | 49 | protocol ConstantToken { 50 | var ALConstant: CGFloat { get } 51 | } 52 | 53 | // This is half of a space constraint, [view]-space 54 | class ViewAndSpaceToken : NSObject { 55 | let view: ViewContainingToken 56 | let space: ConstantToken 57 | let relation: NSLayoutRelation 58 | init(view: ViewContainingToken, space: ConstantToken, relation: NSLayoutRelation) { 59 | self.view = view 60 | self.space = space 61 | self.relation = relation 62 | } 63 | } 64 | 65 | // This is half of a space constraint, |-5 66 | class LeadingSuperviewAndSpaceToken : NSObject { 67 | let space: ConstantToken 68 | let relation: NSLayoutRelation 69 | init(space: ConstantToken, relation: NSLayoutRelation) { 70 | self.space = space 71 | self.relation = relation 72 | } 73 | } 74 | // This is half of a space constraint, 5-| 75 | class TrailingSuperviewAndSpaceToken : NSObject { 76 | let space: ConstantToken 77 | init(space: ConstantToken) { 78 | self.space = space 79 | } 80 | } 81 | 82 | // [view]-5-[view2] 83 | class SpacedViewsConstraintToken: NSObject, ConstraintAble, ViewContainingToken { 84 | let leadingView: ViewContainingToken 85 | let trailingView: ViewContainingToken 86 | let space: ConstantToken 87 | 88 | init(leadingView: ViewContainingToken, trailingView: ViewContainingToken, space: ConstantToken) { 89 | self.leadingView = leadingView 90 | self.trailingView = trailingView 91 | self.space = space 92 | } 93 | 94 | var firstView: UIView? { 95 | get { 96 | return self.leadingView.firstView 97 | } 98 | } 99 | var lastView: UIView? { 100 | get { 101 | return self.trailingView.lastView 102 | } 103 | } 104 | 105 | 106 | func toConstraints(axis: UILayoutConstraintAxis) -> [NSLayoutConstraint] { 107 | if let leadingView = self.leadingView.lastView { 108 | if let trailingView = self.trailingView.firstView { 109 | let space = self.space.ALConstant 110 | 111 | var leadingAttribute: NSLayoutAttribute! 112 | var trailingAttribute: NSLayoutAttribute! 113 | if (axis == .Horizontal) { 114 | leadingAttribute = .Leading 115 | trailingAttribute = .Trailing 116 | } else { 117 | leadingAttribute = .Top 118 | trailingAttribute = .Bottom 119 | } 120 | 121 | var constraints = [NSLayoutConstraint( 122 | item: trailingView, attribute: leadingAttribute, 123 | relatedBy: .Equal, 124 | toItem: leadingView, attribute: trailingAttribute, 125 | multiplier: 1.0, constant: space)] 126 | 127 | constraints += self.leadingView.toConstraints(axis) 128 | constraints += self.trailingView.toConstraints(axis) 129 | 130 | return constraints 131 | } 132 | } 133 | 134 | NSException(name: NSInvalidArgumentException, reason: "This space constraint was between two view items that couldn't fit together. Weird?", userInfo: nil).raise() 135 | return [] // To appease the compiler, which doesn't realize this branch dies 136 | } 137 | } 138 | 139 | // [view == 50] 140 | class SizeConstantConstraintToken: NSObject, ConstraintAble, ViewContainingToken { 141 | let view: ALVFView 142 | let size: ConstantToken 143 | let relation: NSLayoutRelation 144 | init(view: ALVFView, size: ConstantToken, relation: NSLayoutRelation) { 145 | self.view = view 146 | self.size = size 147 | self.relation = relation 148 | } 149 | 150 | var firstView: ALVFView? { 151 | get { 152 | return self.view.firstView 153 | } 154 | } 155 | var lastView: ALVFView? { 156 | get { 157 | return self.view.lastView 158 | } 159 | } 160 | 161 | func toConstraints(axis: UILayoutConstraintAxis) -> [NSLayoutConstraint] { 162 | let constant = self.size.ALConstant 163 | 164 | var attribute: NSLayoutAttribute! 165 | if (axis == .Horizontal) { 166 | attribute = .Width 167 | } else { 168 | attribute = .Height 169 | } 170 | let constraint = NSLayoutConstraint( 171 | item: self.view, attribute: attribute, 172 | relatedBy: self.relation, 173 | toItem: nil, attribute: .NotAnAttribute, 174 | multiplier: 1.0, constant: constant) 175 | 176 | return [constraint] 177 | } 178 | 179 | } 180 | 181 | // [view == view2] 182 | class SizeRelationConstraintToken: NSObject, ConstraintAble, ViewContainingToken { 183 | let view: ALVFView 184 | let relatedView: ALVFView 185 | let relation: NSLayoutRelation 186 | init(view: ALVFView, relatedView: ALVFView, relation: NSLayoutRelation) { 187 | self.view = view 188 | self.relatedView = relatedView 189 | self.relation = relation 190 | } 191 | 192 | var firstView: ALVFView? { 193 | get { 194 | return self.view.firstView 195 | } 196 | } 197 | var lastView: ALVFView? { 198 | get { 199 | return self.view.lastView 200 | } 201 | } 202 | 203 | func toConstraints(axis: UILayoutConstraintAxis) -> [NSLayoutConstraint] { 204 | var attribute: NSLayoutAttribute! 205 | if (axis == .Horizontal) { 206 | attribute = .Width 207 | } else { 208 | attribute = .Height 209 | } 210 | return [ NSLayoutConstraint( 211 | item: self.view, attribute: attribute, 212 | relatedBy: self.relation, 213 | toItem: self.relatedView, attribute: attribute, 214 | multiplier: 1.0, constant: 0) ] 215 | } 216 | } 217 | 218 | // |-5-[view] 219 | public class LeadingSuperviewConstraintToken: NSObject, ConstraintAble, ViewContainingToken { 220 | let viewContainer: ViewContainingToken 221 | let space: ConstantToken 222 | init(viewContainer: ViewContainingToken, space: ConstantToken) { 223 | self.viewContainer = viewContainer 224 | self.space = space 225 | } 226 | public var firstView: UIView? { 227 | get { 228 | return nil // No one can bind to our first view, is the superview 229 | } 230 | } 231 | public var lastView: UIView? { 232 | get { 233 | return self.viewContainer.lastView 234 | } 235 | } 236 | 237 | public func toConstraints(axis: UILayoutConstraintAxis) -> [NSLayoutConstraint] { 238 | if let view = self.viewContainer.firstView { 239 | let constant = self.space.ALConstant 240 | 241 | if let superview = view.superview { 242 | var constraint: NSLayoutConstraint! 243 | 244 | if (axis == .Horizontal) { 245 | constraint = NSLayoutConstraint( 246 | item: view, attribute: .Leading, 247 | relatedBy: .Equal, 248 | toItem: superview, attribute: .Leading, 249 | multiplier: 1.0, constant: constant) 250 | } else { 251 | constraint = NSLayoutConstraint( 252 | item: view, attribute: .Top, 253 | relatedBy: .Equal, 254 | toItem: superview, attribute: .Top, 255 | multiplier: 1.0, constant: constant) 256 | } 257 | 258 | return viewContainer.toConstraints(axis) + [constraint] 259 | } 260 | NSException(name: NSInvalidArgumentException, reason: "You tried to create a constraint to \(view)'s superview, but it has no superview yet!", userInfo: nil).raise() 261 | } 262 | NSException(name: NSInvalidArgumentException, reason: "This superview bar | was before something that doesn't have a view. Weird?", userInfo: nil).raise() 263 | return [] // To appease the compiler, which doesn't realize this branch dies 264 | } 265 | } 266 | 267 | // [view]-5-| 268 | public class TrailingSuperviewConstraintToken: NSObject, ConstraintAble, ViewContainingToken { 269 | let viewContainer: ViewContainingToken 270 | let space: ConstantToken 271 | init(viewContainer: ViewContainingToken, space: ConstantToken) { 272 | self.viewContainer = viewContainer 273 | self.space = space 274 | } 275 | public var firstView: UIView? { 276 | get { 277 | return self.viewContainer.firstView 278 | } 279 | } 280 | public var lastView: UIView? { 281 | get { 282 | return nil // No one can bind to our last view, is the superview 283 | } 284 | } 285 | 286 | public func toConstraints(axis: UILayoutConstraintAxis) -> [NSLayoutConstraint] { 287 | if let view = self.viewContainer.lastView { 288 | let constant = self.space.ALConstant 289 | 290 | if let superview = view.superview { 291 | var constraint: NSLayoutConstraint! 292 | 293 | if (axis == .Horizontal) { 294 | constraint = NSLayoutConstraint( 295 | item: superview, attribute: .Trailing, 296 | relatedBy: .Equal, 297 | toItem: view, attribute: .Trailing, 298 | multiplier: 1.0, constant: constant) 299 | } else { 300 | constraint = NSLayoutConstraint( 301 | item: superview, attribute: .Bottom, 302 | relatedBy: .Equal, 303 | toItem: view, attribute: .Bottom, 304 | multiplier: 1.0, constant: constant) 305 | } 306 | 307 | return viewContainer.toConstraints(axis) + [constraint] 308 | } 309 | NSException(name: NSInvalidArgumentException, reason: "You tried to create a constraint to \(view)'s superview, but it has no superview yet!", userInfo: nil).raise() 310 | } 311 | NSException(name: NSInvalidArgumentException, reason: "This superview bar | was after something that doesn't have a view. Weird?", userInfo: nil).raise() 312 | 313 | return [] // To appease the compiler, which doesn't realize this branch dies 314 | } 315 | } 316 | 317 | let RequiredPriority: Float = 1000 // For some reason, the linker can't find UILayoutPriorityRequired. Not sure what I am doing wrong 318 | 319 | prefix operator | {} 320 | prefix public func | (tokenArray: [ViewContainingToken]) -> [LeadingSuperviewConstraintToken] { 321 | // |[view] 322 | return [LeadingSuperviewConstraintToken(viewContainer: tokenArray[0], space: 0)] 323 | } 324 | 325 | postfix operator | {} 326 | postfix public func | (tokenArray: [ViewContainingToken]) -> [TrailingSuperviewConstraintToken] { 327 | // [view]| 328 | return [TrailingSuperviewConstraintToken(viewContainer: tokenArray[0], space: 0)] 329 | } 330 | 331 | func >= (left: ALVFView, right: ConstantToken) -> SizeConstantConstraintToken { 332 | // [view >= 50] 333 | return SizeConstantConstraintToken(view: left, size: right, relation: .GreaterThanOrEqual) 334 | } 335 | func >= (left: ALVFView, right: ALVFView) -> SizeRelationConstraintToken { 336 | // [view >= view2] 337 | return SizeRelationConstraintToken(view: left, relatedView: right, relation: .GreaterThanOrEqual) 338 | } 339 | 340 | func <= (left: ALVFView, right: ConstantToken) -> SizeConstantConstraintToken { 341 | // [view <= 50] 342 | return SizeConstantConstraintToken(view: left, size: right, relation: .LessThanOrEqual) 343 | } 344 | func <= (left: ALVFView, right: ALVFView) -> SizeRelationConstraintToken { 345 | // [view <= view2] 346 | return SizeRelationConstraintToken(view: left, relatedView: right, relation: .LessThanOrEqual) 347 | } 348 | 349 | func == (left: ALVFView, right: ConstantToken) -> SizeConstantConstraintToken { 350 | // [view == 50] 351 | return SizeConstantConstraintToken(view: left, size: right, relation: .Equal) 352 | } 353 | func == (left: ALVFView, right: ALVFView) -> SizeRelationConstraintToken { 354 | // [view == view2] 355 | return SizeRelationConstraintToken(view: left, relatedView: right, relation: .Equal) 356 | } 357 | 358 | func - (left: [ViewContainingToken], right: ConstantToken) -> ViewAndSpaceToken { 359 | // [view]-5 360 | return ViewAndSpaceToken(view: left[0], space: right, relation: .Equal) 361 | } 362 | 363 | func - (left: ViewAndSpaceToken, right: [ViewContainingToken]) -> [SpacedViewsConstraintToken] { 364 | // [view]-5-[view2] 365 | return [SpacedViewsConstraintToken(leadingView: left.view, trailingView: right[0], space: left.space)] 366 | } 367 | 368 | func - (left: [ViewContainingToken], right: TrailingSuperviewAndSpaceToken) -> [TrailingSuperviewConstraintToken] { 369 | // [view]-5-| 370 | return [TrailingSuperviewConstraintToken(viewContainer: left[0], space: right.space)] 371 | } 372 | 373 | func - (left: LeadingSuperviewAndSpaceToken, right: [ViewContainingToken]) -> [LeadingSuperviewConstraintToken] { 374 | // |-5-[view] 375 | return [LeadingSuperviewConstraintToken(viewContainer: right[0], space: left.space)] 376 | } 377 | 378 | postfix operator -| {} 379 | postfix func -| (constant: ConstantToken) -> TrailingSuperviewAndSpaceToken { 380 | // 5-| 381 | return TrailingSuperviewAndSpaceToken(space: constant) 382 | } 383 | 384 | prefix operator |- {} 385 | prefix func |- (constant: ConstantToken) -> LeadingSuperviewAndSpaceToken { 386 | // |-5 387 | return LeadingSuperviewAndSpaceToken(space: constant, relation: .Equal) 388 | } 389 | 390 | 391 | extension ALVFView: ViewContainingToken { 392 | public var firstView: ALVFView? { 393 | get { 394 | return self 395 | } 396 | } 397 | public var lastView: ALVFView? { 398 | get { 399 | return self 400 | } 401 | } 402 | 403 | public func toConstraints(axis: UILayoutConstraintAxis) -> [NSLayoutConstraint] { 404 | return [] 405 | } 406 | } 407 | 408 | extension CGFloat: ConstantToken { 409 | var ALConstant: CGFloat { 410 | get { 411 | return self 412 | } 413 | } 414 | } 415 | 416 | extension NSInteger: ConstantToken { 417 | var ALConstant: CGFloat { 418 | get { 419 | return CGFloat(self) 420 | } 421 | } 422 | } 423 | 424 | --------------------------------------------------------------------------------