diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3485c8b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +Preview +======= + +Snappy is currently in preview and regular Changelogs will be provided as it comes out of preview into production quality code. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..15f29e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +// +// Constraint.swift +// Snappy +// +// Copyright (c) 2011-2014 Masonry Team - https://github.com/Masonry +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a890d14 --- /dev/null +++ b/README.md @@ -0,0 +1,267 @@ +# Snappy + +Snappy is a light-weight layout framework which wraps AutoLayout with a nicer syntax. Snappy has its own layout DSL which provides a chainable way of describing your NSLayoutConstraints which results in layout code that is more concise and readable. +Snappy supports iOS and Mac OS X. + +## What's wrong with NSLayoutConstraints? + +Under the hood Auto Layout is a powerful and flexible way of organising and laying out your views. However creating constraints from code is verbose and not very descriptive. +Imagine a simple example in which you want to have a view fill its superview but inset by 10 pixels on every side +```swift +let superview = self; + +let view1 = UIView() +view1.setTranslatesAutoresizingMaskIntoConstraints(false) +view1.backgroundColor = UIColor.greenColor() +superview.addSubview(view1) + +let padding = UIEdgeInsetsMake(10, 10, 10, 10) + +superview.addConstraints([ + NSLayoutConstraint( + item: view1, + attribute: NSLayoutAttribute.Top, + relatedBy: NSLayoutRelation.Equal, + toItem: superview, + attribute: NSLayoutAttribute.Top, + multiplier: 1.0, + constant: padding.top + ), + NSLayoutConstraint( + item: view1, + attribute: NSLayoutAttribute.Left, + relatedBy: NSLayoutRelation.Equal, + toItem: superview, + attribute: NSLayoutAttribute.Left, + multiplier: 1.0, + constant: padding.left + ), + NSLayoutConstraint( + item: view1, + attribute: NSLayoutAttribute.Bottom, + relatedBy: NSLayoutRelation.Equal, + toItem: superview, + attribute: NSLayoutAttribute.Bottom, + multiplier: 1.0, + constant: -padding.bottom + ), + NSLayoutConstraint( + item: view1, + attribute: NSLayoutAttribute.Right, + relatedBy: NSLayoutRelation.Equal, + toItem: superview, + attribute: NSLayoutAttribute.Right, + multiplier: 1.0, + constant: -padding.right + ) +]) +``` +Even with such a simple example the code needed is quite verbose and quickly becomes unreadable when you have more than 2 or 3 views. +Another option is to use Visual Format Language (VFL), which is a bit less long winded. +However the ASCII type syntax has its own pitfalls and its also a bit harder to animate as `NSLayoutConstraint.constraintsWithVisualFormat` returns an array. + +## Prepare to meet your Maker! + +Heres the same constraints created using ConstraintMaker + +```swift +let padding = UIEdgeInsetsMake(10, 10, 10, 10) + +view1.snp_makeConstraints { make in + make.top.equalTo(superview.snp_top).with.offset(padding.top) // with is an optional semantic filler + make.left.equalTo(superview.snp_left).with.offset(padding.left) + make.bottom.equalTo(superview.snp_bottom).with.offset(-padding.bottom) + make.right.equalTo(superview.snp_right).with.offset(-padding.right) +} +``` +Or even shorter + +```swift +view1.snp_makeConstraints { make in + make.edges.equalTo(superview).with.insets(padding) + return // this return is a fix for implicit returns in Swift and is only required for single line constraints +} +``` + +Also note in the first example we had to add the constraints to the superview `superview.addConstraints`. +Snappy however will automagically add constraints to the appropriate view. + +Snappy will also call `view1.setTranslatesAutoresizingMaskIntoConstraints(false)` for you. + +## Not all things are created equal + +> `.equalTo` equivalent to **NSLayoutRelation.Equal** + +> `.lessThanOrEqualTo` equivalent to **NSLayoutRelation.LessThanOrEqual** + +> `.greaterThanOrEqualTo` equivalent to **NSLayoutRelation.GreaterThanOrEqual** + +These three equality constraints accept one argument which can be any of the following: + +#### 1. ViewAttribute + +```swift +make.centerX.lessThanOrEqualTo(view2.snp_left) +``` + +ViewAttribute | NSLayoutAttribute +------------------------- | -------------------------- +view.snp_left | NSLayoutAttribute.Left +view.snp_right | NSLayoutAttribute.Right +view.snp_top | NSLayoutAttribute.Top +view.snp_bottom | NSLayoutAttribute.Bottom +view.snp_leading | NSLayoutAttribute.Leading +view.snp_trailing | NSLayoutAttribute.Trailing +view.snp_width | NSLayoutAttribute.Width +view.snp_height | NSLayoutAttribute.Height +view.snp_centerX | NSLayoutAttribute.CenterX +view.snp_centerY | NSLayoutAttribute.CenterY +view.snp_baseline | NSLayoutAttribute.Baseline + +#### 2. UIView/NSView + +if you want view.left to be greater than or equal to label.left : +```swift +// these two constraints are exactly the same +make.left.greaterThanOrEqualTo(label) +make.left.greaterThanOrEqualTo(label.snp_left) +``` + +#### 3. Strict Checks + +Auto Layout allows width and height to be set to constant values. +if you want to set view to have a minimum and maximum width you could pass a primitive to the equality blocks: +```swift +// width >= 200 && width <= 400 +make.width.greaterThanOrEqualTo(200) +make.width.lessThanOrEqualTo(400) +``` + +However Auto Layout does not allow alignment attributes such as left, right, centerY etc to be set to constant values. +So if you pass a primitive for these attributes Snappy will turn these into constraints relative to the view’s superview ie: +```swift +// creates view.left = view.superview.left + 10 +make.left.lessThanOrEqualTo(10) +``` + +You can also use other primitives and structs to build your constraints, like so: +```swift +make.top.snp_equalTo(42) +make.height.snp_equalTo(20) +make.size.snp_equalTo(CGSizeMake(50, 100)) +make.edges.snp_equalTo(UIEdgeInsetsMake(10, 0, 10, 0)) +make.left.snp_equalTo(view).offset(UIEdgeInsetsMake(10, 0, 10, 0)) +``` + +```` + +## Learn to prioritize + +> `.prority` allows you to specify an exact priority + +> `.priorityHigh` equivalent to **UILayoutPriority.DefaultHigh** + +> `.priorityMedium` is half way between high and low + +> `.priorityLow` equivalent to **UILayoutPriority.DefaultLow** + +Priorities are can be tacked on to the end of a constraint chain like so: +```swift +make.left.greaterThanOrEqualTo(label.snp_left).with.priorityLow(); + +make.top.equalTo(label.snp_top).with.priority(600); +``` + +## Composition, composition, composition + +Snappy also gives you a few convenience methods which create multiple constraints at the same time. + +#### edges + +```swift +// make top, left, bottom, right equal view2 +make.edges.equalTo(view2); + +// make top = superview.top + 5, left = superview.left + 10, +// bottom = superview.bottom - 15, right = superview.right - 20 +make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20)) +``` + +#### size + +```swift +// make width and height greater than or equal to titleLabel +make.size.greaterThanOrEqualTo(titleLabel) + +// make width = superview.width + 100, height = superview.height - 50 +make.size.equalTo(superview).offset(CGSizeMake(100, -50)) +``` + +#### center + +```swift +// make centerX and centerY = button1 +make.center.equalTo(button1) + +// make centerX = superview.centerX - 5, centerY = superview.centerY + 10 +make.center.equalTo(superview).offset(CGPointMake(-5, 10)) +``` + +You can chain view attributes for increased readability: + +```swift +// All edges but the top should equal those of the superview +make.left.right.and.bottom.equalTo(superview) +make.top.equalTo(otherView) +``` + +## Hold on for dear life + +Sometimes you need modify existing constraints in order to animate or remove/replace constraints. +In Snappy there are a few different approaches to updating constraints. + +#### 1. References +You can hold on to a reference of a particular constraint by assigning the result of a constraint make expression to a local variable or a class property. +You could also reference multiple constraints by storing them away in an array. + +```swift + +var topConstraint: Constraint? = nil + +... + +// when making constraints +view1.snp_makeConstraints { make in + self.topConstraint = make.top.equalTo(superview).with.offset(padding.top) + make.left.equalTo(superview).with.offset(padding.left) +} + +... +// then later you can call +self.topConstraint.uninstall() +``` + +### 2. snp_remakeConstraints + +`snp_remakeConstraints` is similar to `snp_makeConstraints`, but will first remove all existing constraints installed by Snappy. + +```swift +func changeButtonPosition() { + self.button.snp_remakeConstraints { make in + make.size.equalTo(self.buttonSize) + + if topLeft { + make.top.left.equalTo(10) + } else { + make.bottom.equalTo(self.view).offset(-10) + make.right.equalTo(self.view).offset(-10) + } + } +} +``` + +## TODO + +* Eye candy +* Example projects +* Tests diff --git a/Snappy.xcodeproj/project.pbxproj b/Snappy.xcodeproj/project.pbxproj index 1de8b6e..e8aa53d 100644 --- a/Snappy.xcodeproj/project.pbxproj +++ b/Snappy.xcodeproj/project.pbxproj @@ -10,13 +10,11 @@ DD03E3151981B52F00E0DE94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD03E30F1981B52F00E0DE94 /* AppDelegate.swift */; }; DD03E3171981B52F00E0DE94 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DD03E3121981B52F00E0DE94 /* Images.xcassets */; }; DD03E3191981B52F00E0DE94 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD03E3141981B52F00E0DE94 /* ViewController.swift */; }; - DD03E3201981B70D00E0DE94 /* CompositeConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD03E31F1981B70D00E0DE94 /* CompositeConstraint.swift */; }; DD03E3221981B71C00E0DE94 /* Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD03E3211981B71C00E0DE94 /* Constraint.swift */; }; DD03E3241981B72A00E0DE94 /* ConstraintMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD03E3231981B72A00E0DE94 /* ConstraintMaker.swift */; }; - DD03E3261981B78D00E0DE94 /* ViewAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD03E3251981B78D00E0DE94 /* ViewAttribute.swift */; }; - DD03E3281981B79C00E0DE94 /* ViewConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD03E3271981B79C00E0DE94 /* ViewConstraint.swift */; }; DD03E32A1981B7BF00E0DE94 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD03E3291981B7BF00E0DE94 /* View.swift */; }; DDC9FDAE1981B4DD009612C7 /* SnappyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC9FDAD1981B4DD009612C7 /* SnappyTests.swift */; }; + EEC6EB4E1985370500C27B12 /* LayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC6EB4D1985370500C27B12 /* LayoutConstraint.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -48,15 +46,13 @@ DD03E3121981B52F00E0DE94 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; DD03E3131981B52F00E0DE94 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DD03E3141981B52F00E0DE94 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - DD03E31F1981B70D00E0DE94 /* CompositeConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositeConstraint.swift; sourceTree = ""; }; DD03E3211981B71C00E0DE94 /* Constraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constraint.swift; sourceTree = ""; }; DD03E3231981B72A00E0DE94 /* ConstraintMaker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstraintMaker.swift; sourceTree = ""; }; - DD03E3251981B78D00E0DE94 /* ViewAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewAttribute.swift; sourceTree = ""; }; - DD03E3271981B79C00E0DE94 /* ViewConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewConstraint.swift; sourceTree = ""; }; DD03E3291981B7BF00E0DE94 /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; DDC9FD951981B4DD009612C7 /* Snappy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Snappy.app; sourceTree = BUILT_PRODUCTS_DIR; }; DDC9FDA71981B4DD009612C7 /* SnappyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SnappyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DDC9FDAD1981B4DD009612C7 /* SnappyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnappyTests.swift; sourceTree = ""; }; + EEC6EB4D1985370500C27B12 /* LayoutConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutConstraint.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -91,11 +87,9 @@ DD03E31A1981B62D00E0DE94 /* Snappy */ = { isa = PBXGroup; children = ( - DD03E31F1981B70D00E0DE94 /* CompositeConstraint.swift */, + EEC6EB4D1985370500C27B12 /* LayoutConstraint.swift */, DD03E3211981B71C00E0DE94 /* Constraint.swift */, DD03E3231981B72A00E0DE94 /* ConstraintMaker.swift */, - DD03E3251981B78D00E0DE94 /* ViewAttribute.swift */, - DD03E3271981B79C00E0DE94 /* ViewConstraint.swift */, DD03E3291981B7BF00E0DE94 /* View.swift */, ); path = Snappy; @@ -230,12 +224,10 @@ files = ( DD03E32A1981B7BF00E0DE94 /* View.swift in Sources */, DD03E3221981B71C00E0DE94 /* Constraint.swift in Sources */, + EEC6EB4E1985370500C27B12 /* LayoutConstraint.swift in Sources */, DD03E3241981B72A00E0DE94 /* ConstraintMaker.swift in Sources */, DD03E3191981B52F00E0DE94 /* ViewController.swift in Sources */, - DD03E3281981B79C00E0DE94 /* ViewConstraint.swift in Sources */, - DD03E3201981B70D00E0DE94 /* CompositeConstraint.swift in Sources */, DD03E3151981B52F00E0DE94 /* AppDelegate.swift in Sources */, - DD03E3261981B78D00E0DE94 /* ViewAttribute.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Snappy/CompositeConstraint.swift b/Snappy/CompositeConstraint.swift deleted file mode 100644 index ad7a81d..0000000 --- a/Snappy/CompositeConstraint.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// CompositeConstraint.swift -// Snappy -// -// Created by Jonas Budelmann on 25/07/14. -// Copyright (c) 2014 Jonas Budelmann. All rights reserved. -// - -import UIKit - -class CompositeConstraint: Constraint, ConstraintDelegate { - var children = [Constraint]() - weak var delegate: ConstraintDelegate? - - init(children: [Constraint]) { - self.children = children - for constraint in children { - constraint.delegate = self - } - } - - func constraint(constraint: Constraint, shouldBeReplacedWithConstraint replacementConstraint: Constraint) { - var index: Int? - for (i, c) in enumerate(self.children) { - if (c === constraint) { - index = i - } - } - - if (index) { - self.children[index!] = replacementConstraint - } - } - - func constraint(constraint: Constraint?, addConstraintWithLayoutAttribute layoutAttribute: NSLayoutAttribute) -> Constraint { - var newConstraint = self.delegate!.constraint(self, addConstraintWithLayoutAttribute: layoutAttribute) - newConstraint.delegate = self - self.children.append(newConstraint) - return newConstraint - } - - var left: Constraint { return addConstraint(.Left) } - var top: Constraint { return addConstraint(.Top) } - var right: Constraint { return addConstraint(.Right) } - var bottom: Constraint { return addConstraint(.Bottom) } - var leading: Constraint { return addConstraint(.Leading) } - var trailing: Constraint { return addConstraint(.Trailing) } - var width: Constraint { return addConstraint(.Width) } - var height: Constraint { return addConstraint(.Height) } - var centerX: Constraint { return addConstraint(.CenterX) } - var centerY: Constraint { return addConstraint(.CenterY) } - var baseline: Constraint { return addConstraint(.Baseline) } - - var and: Constraint { return self } - var with: Constraint { return self } - - func addConstraint(NSLayoutAttribute) -> Constraint { - return self; - } - - func equalTo(attr: Any) -> Constraint { - return self - } - - func greaterThanOrEqualTo(attr: Any) -> Constraint { - return self - } - - func lessThanOrEqualTo(attr: Any) -> Constraint { - return self - } - - func offset(offset: Any) -> Constraint { - return self - } - - func insets(insets: UIEdgeInsets) -> Constraint { - return self; - } - - func multipliedBy(multiplier: Float) -> Constraint { - return self - } - - func dividedBy(divider: Float) -> Constraint { - return self - } - - func priority(priority: UILayoutPriority) -> Constraint { - return self - } - - func priorityLow() -> Constraint { - return self - } - - func priorityMedium() -> Constraint { - return self - } - - func priorityHigh() -> Constraint { - return self - } - - func install() { - } -} \ No newline at end of file diff --git a/Snappy/Constraint.swift b/Snappy/Constraint.swift index e48ea0f..d54dc42 100644 --- a/Snappy/Constraint.swift +++ b/Snappy/Constraint.swift @@ -2,60 +2,549 @@ // Constraint.swift // Snappy // -// Created by Jonas Budelmann on 25/07/14. -// Copyright (c) 2014 Jonas Budelmann. All rights reserved. +// Copyright (c) 2011-2014 Masonry Team - https://github.com/Masonry // +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. import UIKit -@class_protocol protocol ConstraintDelegate { - func constraint(constraint: Constraint, shouldBeReplacedWithConstraint replacementConstraint: Constraint) - func constraint(constraint: Constraint?, addConstraintWithLayoutAttribute layoutAttribute: NSLayoutAttribute) -> Constraint +/** + * ConstraintAttributes is an options set that maps to NSLayoutAttributes. + */ +struct ConstraintAttributes: RawOptionSet { + + var value: UInt + + init(_ value: UInt) { + self.value = value + } + func toRaw() -> UInt { return self.value } + func getLogicValue() -> Bool { return self.value != 0 } + static func fromRaw(raw: UInt) -> ConstraintAttributes? { return self(raw) } + static func fromMask(raw: UInt) -> ConstraintAttributes { return self(raw) } + static func convertFromNilLiteral() -> ConstraintAttributes { return self(0) } + + static var None: ConstraintAttributes { return self(0) } + static var Left: ConstraintAttributes { return self(1) } + static var Top: ConstraintAttributes { return self(2) } + static var Right: ConstraintAttributes { return self(4) } + static var Bottom: ConstraintAttributes { return self(8) } + static var Leading: ConstraintAttributes { return self(16) } + static var Trailing: ConstraintAttributes { return self(32) } + static var Width: ConstraintAttributes { return self(64) } + static var Height: ConstraintAttributes { return self(128) } + static var CenterX: ConstraintAttributes { return self(256) } + static var CenterY: ConstraintAttributes { return self(512) } + static var Baseline: ConstraintAttributes { return self(1024) } + + static var Edges: ConstraintAttributes { return self(15) } + static var Size: ConstraintAttributes { return self(192) } + static var Center: ConstraintAttributes { return self(768) } + + var layoutAttributes:Array { + var attrs: Array = [] + if (self & ConstraintAttributes.Left) { + attrs.append(.Left) + } + if (self & ConstraintAttributes.Top) { + attrs.append(.Top) + } + if (self & ConstraintAttributes.Right) { + attrs.append(.Right) + } + if (self & ConstraintAttributes.Bottom) { + attrs.append(.Bottom) + } + if (self & ConstraintAttributes.Leading) { + attrs.append(.Leading) + } + if (self & ConstraintAttributes.Trailing) { + attrs.append(.Trailing) + } + if (self & ConstraintAttributes.Width) { + attrs.append(.Width) + } + if (self & ConstraintAttributes.Height ){ + attrs.append(.Height) + } + if (self & ConstraintAttributes.CenterX) { + attrs.append(.CenterX) + } + if (self & ConstraintAttributes.CenterY) { + attrs.append(.CenterY) + } + if (self & ConstraintAttributes.Baseline) { + attrs.append(.Baseline) + } + return attrs + } +} +@assignment func += (inout left: ConstraintAttributes, right: ConstraintAttributes) { + left = (left | right) +} +@assignment func -= (inout left: ConstraintAttributes, right: ConstraintAttributes) { + left = ConstraintAttributes(left.toRaw() & ~right.toRaw()) } -typealias Delegate = ConstraintDelegate? - -@class_protocol protocol Constraint { - weak var delegate: Delegate { get set } - - var left: Constraint { get } - var top: Constraint { get } - var right: Constraint { get } - var bottom: Constraint { get } - var leading: Constraint { get } - var trailing: Constraint { get } - var width: Constraint { get } - var height: Constraint { get } - var centerX: Constraint { get } - var centerY: Constraint { get } - var baseline: Constraint { get } +/** + * ConstraintRelation is an Int enum that maps to NSLayoutRelation. + */ +enum ConstraintRelation: Int { + case Equal = 1, LessThanOrEqualTo, GreaterThanOrEqualTo - var and: Constraint { get } - var with: Constraint { get } - - func addConstraint(NSLayoutAttribute) -> Constraint - - func equalTo(attr: Any) -> Constraint - - func greaterThanOrEqualTo(attr: Any) -> Constraint - - func lessThanOrEqualTo(attr: Any) -> Constraint - - func insets(insets: UIEdgeInsets) -> Constraint - - func offset(offset: Any) -> Constraint - - func multipliedBy(multiplier: Float) -> Constraint - - func dividedBy(divider: Float) -> Constraint - - func priority(priority: UILayoutPriority) -> Constraint - - func priorityLow() -> Constraint - - func priorityMedium() -> Constraint - - func priorityHigh() -> Constraint - - func install() + var layoutRelation: NSLayoutRelation { + get { + switch(self) { + case .LessThanOrEqualTo: + return .LessThanOrEqual + case .GreaterThanOrEqualTo: + return .GreaterThanOrEqual + default: + return .Equal + } + } + } +} + +/** + * ConstraintItem is a class that is used while building constraints. + */ +class ConstraintItem { + + init(view: View?, attributes: ConstraintAttributes) { + self.view = view + self.attributes = attributes + } + + internal weak var view: View? + internal var attributes: ConstraintAttributes +} + +/** + * Constraint is a single item that defines all the properties for a single ConstraintMaker chain + */ +class Constraint { + var left: Constraint { return addConstraint(ConstraintAttributes.Left) } + var top: Constraint { return addConstraint(ConstraintAttributes.Top) } + var right: Constraint { return addConstraint(ConstraintAttributes.Right) } + var bottom: Constraint { return addConstraint(ConstraintAttributes.Bottom) } + var leading: Constraint { return addConstraint(ConstraintAttributes.Leading) } + var trailing: Constraint { return addConstraint(ConstraintAttributes.Trailing) } + var width: Constraint { return addConstraint(ConstraintAttributes.Width) } + var height: Constraint { return addConstraint(ConstraintAttributes.Height) } + var centerX: Constraint { return addConstraint(ConstraintAttributes.CenterX) } + var centerY: Constraint { return addConstraint(ConstraintAttributes.CenterY) } + var baseline: Constraint { return addConstraint(ConstraintAttributes.Baseline) } + + var and: Constraint { return self } + var with: Constraint { return self } + + // MARK: initializer + + init(fromItem: ConstraintItem) { + self.fromItem = fromItem + self.toItem = ConstraintItem(view: nil, attributes: ConstraintAttributes.None) + } + + // MARK: equalTo + + func equalTo(other: ConstraintItem) -> Constraint { + return constrainTo(other, relation: .Equal) + } + func equalTo(other: View) -> Constraint { + return constrainTo(other, relation: .Equal) + } + func equalTo(other: Float) -> Constraint { + return constrainTo(other, relation: .Equal) + } + func equalTo(other: Int) -> Constraint { + return constrainTo(Float(other), relation: .Equal) + } + func equalTo(other: CGSize) -> Constraint { + return constrainTo(other, relation: .Equal) + } + func equalTo(other: CGPoint) -> Constraint { + return constrainTo(other, relation: .Equal) + } + func equalTo(other: UIEdgeInsets) -> Constraint { + return constrainTo(other, relation: .Equal) + } + + // MARK: lessThanOrEqualTo + + func lessThanOrEqualTo(other: ConstraintItem) -> Constraint { + return constrainTo(other, relation: .LessThanOrEqualTo) + } + func lessThanOrEqualTo(other: View) -> Constraint { + return constrainTo(other, relation: .LessThanOrEqualTo) + } + func lessThanOrEqualTo(other: Float) -> Constraint { + return constrainTo(other, relation: .LessThanOrEqualTo) + } + func lessThanOrEqualTo(other: Int) -> Constraint { + return constrainTo(Float(other), relation: .LessThanOrEqualTo) + } + func lessThanOrEqualTo(other: CGSize) -> Constraint { + return constrainTo(other, relation: .LessThanOrEqualTo) + } + func lessThanOrEqualTo(other: CGPoint) -> Constraint { + return constrainTo(other, relation: .LessThanOrEqualTo) + } + func lessThanOrEqualTo(other: UIEdgeInsets) -> Constraint { + return constrainTo(other, relation: .LessThanOrEqualTo) + } + + // MARK: greaterThanOrEqualTo + + func greaterThanOrEqualTo(other: ConstraintItem) -> Constraint { + return constrainTo(other, relation: .GreaterThanOrEqualTo) + } + func greaterThanOrEqualTo(other: View) -> Constraint { + return constrainTo(other, relation: .GreaterThanOrEqualTo) + } + func greaterThanOrEqualTo(other: Float) -> Constraint { + return constrainTo(other, relation: .GreaterThanOrEqualTo) + } + func greaterThanOrEqualTo(other: Int) -> Constraint { + return constrainTo(Float(other), relation: .GreaterThanOrEqualTo) + } + func greaterThanOrEqualTo(other: CGSize) -> Constraint { + return constrainTo(other, relation: .GreaterThanOrEqualTo) + } + func greaterThanOrEqualTo(other: CGPoint) -> Constraint { + return constrainTo(other, relation: .GreaterThanOrEqualTo) + } + func greaterThanOrEqualTo(other: UIEdgeInsets) -> Constraint { + return constrainTo(other, relation: .GreaterThanOrEqualTo) + } + + // MARK: multiplier + + func multipliedBy(amount: Float) -> Constraint { + self.multiplier = amount + return self + } + func dividedBy(amount: Float) -> Constraint { + self.multiplier = 1.0 / amount; + return self + } + + // MARK: priority + + func priority(priority: Float) -> Constraint { + self.priority = priority + return self + } + func priorityRequired() -> Constraint { + return priority(1000.0) + } + func priorityHigh() -> Constraint { + return priority(750.0) + } + func priorityLow() -> Constraint { + return priority(250.0) + } + + // MARK: offset + + func offset(amount: Float) -> Constraint { + self.offset = amount + return self + } + func offset(amount: Int) -> Constraint { + self.offset = amount + return self + } + func offset(amount: CGPoint) -> Constraint { + self.offset = amount + return self + } + func offset(amount: CGSize) -> Constraint { + self.offset = amount + return self + } + func offset(amount: UIEdgeInsets) -> Constraint { + self.offset = amount + return self + } + + // MARK: insets + + func insets(amount: UIEdgeInsets) -> Constraint { + self.insets = amount + return self + } + + // MARK: install + + func install() -> Array { + var installOnView: View? = nil + if self.toItem.view { + installOnView = Constraint.closestCommonSuperviewFromView(self.fromItem.view, toView: self.toItem.view) + if !installOnView { + NSException(name: "Cannot Install Constraint", reason: "No common superview between views", userInfo: nil).raise() + return [] + } + } else { + installOnView = self.fromItem.view?.superview + if !installOnView { + NSException(name: "Cannot Install Constraint", reason: "Missing superview", userInfo: nil).raise() + return [] + } + } + + var layoutConstraints: Array = [] + let layoutFromAttributes = self.fromItem.attributes.layoutAttributes + let layoutToAttributes = self.toItem.attributes.layoutAttributes + + // get layout from + let layoutFrom: View? = self.fromItem.view + + // get layout to + let layoutTo: View? = self.toItem.view + + // get layout relation + let layoutRelation: NSLayoutRelation = (self.relation) ? self.relation!.layoutRelation : .Equal + + for layoutFromAttribute in layoutFromAttributes { + // get layout to attribute + let layoutToAttribute = (layoutToAttributes.count > 0) ? layoutToAttributes[0] : layoutFromAttribute + + // get layout constant + var layoutConstant: CGFloat = layoutToAttribute.snp_constantForValue(self.constant) + layoutConstant += layoutToAttribute.snp_offsetForValue(self.offset) + + // create layout constraint + let layoutConstraint = LayoutConstraint( + item: layoutFrom, + attribute: layoutFromAttribute, + relatedBy: layoutRelation, + toItem: layoutTo, + attribute: layoutToAttribute, + multiplier: CGFloat(self.multiplier), + constant: layoutConstant) + + // set priority + layoutConstraint.priority = self.priority + + // set constraint + layoutConstraint.constraint = self + + layoutConstraints.append(layoutConstraint) + } + + installOnView?.addConstraints(layoutConstraints) + + self.installedOnView = installOnView + return layoutConstraints + } + + // MARK: uninstall + + func uninstall() { + if let view = self.installedOnView { + var installedConstraints = view.constraints() + var constraintsToRemove: Array = [] + for installedConstraint in installedConstraints { + if let layoutConstraint = installedConstraint as? LayoutConstraint { + if layoutConstraint.constraint === self { + constraintsToRemove.append(layoutConstraint) + } + } + } + if constraintsToRemove.count > 0 { + view.removeConstraints(constraintsToRemove) + } + } + self.installedOnView = nil + } + + // MARK: private + + private let fromItem: ConstraintItem + private var toItem: ConstraintItem + private var relation: ConstraintRelation? + private var constant: Any? + private var multiplier: Float = 1.0 + private var priority: Float = 1000.0 + private var offset: Any? + private var insets: UIEdgeInsets = UIEdgeInsetsZero + + private weak var installedOnView: View? + + private func addConstraint(attributes: ConstraintAttributes) -> Constraint { + if !self.relation { + self.fromItem.attributes += attributes + } + return self + } + + private func constrainTo(other: ConstraintItem, relation: ConstraintRelation) -> Constraint { + if other.attributes != ConstraintAttributes.None { + var toLayoutAttributes = other.attributes.layoutAttributes + if toLayoutAttributes.count > 1 { + var fromLayoutAttributes = self.fromItem.attributes.layoutAttributes + if toLayoutAttributes != fromLayoutAttributes { + NSException(name: "Invalid Constraint", reason: "Cannot constrain to multiple non identical attributes", userInfo: nil).raise() + return self + } + other.attributes = ConstraintAttributes.None + } + } + self.toItem = other + self.relation = relation + return self + } + private func constrainTo(other: View, relation: ConstraintRelation) -> Constraint { + return constrainTo(ConstraintItem(view: other, attributes: ConstraintAttributes.None), relation: relation) + } + private func constrainTo(other: Float, relation: ConstraintRelation) -> Constraint { + self.constant = other + return constrainTo(ConstraintItem(view: nil, attributes: ConstraintAttributes.None), relation: relation) + } + private func constrainTo(other: CGSize, relation: ConstraintRelation) -> Constraint { + self.constant = other + return constrainTo(ConstraintItem(view: nil, attributes: ConstraintAttributes.None), relation: relation) + } + private func constrainTo(other: CGPoint, relation: ConstraintRelation) -> Constraint { + self.constant = other + return constrainTo(ConstraintItem(view: nil, attributes: ConstraintAttributes.None), relation: relation) + } + private func constrainTo(other: UIEdgeInsets, relation: ConstraintRelation) -> Constraint { + self.constant = other + return constrainTo(ConstraintItem(view: nil, attributes: ConstraintAttributes.None), relation: relation) + } + + private class func closestCommonSuperviewFromView(fromView: View?, toView: View?) -> View? { + var closestCommonSuperview: View? + var secondViewSuperview: View? = toView + while !closestCommonSuperview && secondViewSuperview { + var firstViewSuperview = fromView + while !closestCommonSuperview && firstViewSuperview { + if secondViewSuperview == firstViewSuperview { + closestCommonSuperview = secondViewSuperview + } + firstViewSuperview = firstViewSuperview?.superview + } + secondViewSuperview = secondViewSuperview?.superview + } + return closestCommonSuperview + } +} + +private extension NSLayoutAttribute { + + func snp_offsetForValue(value: Any?) -> CGFloat { + // Float + if let float = value as? Float { + return CGFloat(float) + } + // Int + else if let int = value as? Int { + return CGFloat(int) + } + // CGFloat + else if let float = value as? CGFloat { + return float + } + // CGSize + else if let size = value as? CGSize { + if self == .Width { + return size.width + } else if self == .Height { + return size.height + } + } + // CGPoint + else if let point = value as? CGPoint { + if self == .Left || self == .CenterX { + return point.x + } else if self == .Top || self == .CenterY { + return point.y + } else if self == .Right { + return -point.x + } else if self == .Bottom { + return -point.y + } + } + // UIEdgeInsets + else if let insets = value as? UIEdgeInsets { + if self == .Left { + return insets.left + } else if self == .Top { + return insets.top + } else if self == .Right { + return -insets.right + } else if self == .Bottom { + return -insets.bottom + } + } + + return CGFloat(0) + } + + func snp_constantForValue(value: Any?) -> CGFloat { + // Float + if let float = value as? Float { + return CGFloat(float) + } + // Int + else if let int = value as? Int { + return CGFloat(int) + } + // CGFloat + else if let float = value as? CGFloat { + return float + } + // CGSize + else if let size = value as? CGSize { + if self == .Width { + return size.width + } else if self == .Height { + return size.height + } + } + // CGPoint + else if let point = value as? CGPoint { + if self == .Left || self == .CenterX { + return point.x + } else if self == .Top || self == .CenterY { + return point.y + } else if self == .Right { + return point.x + } else if self == .Bottom { + return point.y + } + } + // UIEdgeInsets + else if let insets = value as? UIEdgeInsets { + if self == .Left { + return insets.left + } else if self == .Top { + return insets.top + } else if self == .Right { + return insets.right + } else if self == .Bottom { + return insets.bottom + } + } + + return CGFloat(0); + } } diff --git a/Snappy/ConstraintMaker.swift b/Snappy/ConstraintMaker.swift index 5d20e8e..0e4a959 100644 --- a/Snappy/ConstraintMaker.swift +++ b/Snappy/ConstraintMaker.swift @@ -2,91 +2,89 @@ // ConstraintMaker.swift // Snappy // -// Created by Jonas Budelmann on 25/07/14. -// Copyright (c) 2014 Jonas Budelmann. All rights reserved. +// Copyright (c) 2011-2014 Masonry Team - https://github.com/Masonry // +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. import UIKit -class ConstraintMaker: ConstraintDelegate { - var constraints = [Constraint]() - weak var view: View? +/** + * ConstraintMaker is the maker in snappy that gets all constraints kickstarted + */ +class ConstraintMaker { + var left: Constraint { return addConstraint(ConstraintAttributes.Left) } + var top: Constraint { return addConstraint(ConstraintAttributes.Top) } + var right: Constraint { return addConstraint(ConstraintAttributes.Right) } + var bottom: Constraint { return addConstraint(ConstraintAttributes.Bottom) } + var leading: Constraint { return addConstraint(ConstraintAttributes.Leading) } + var trailing: Constraint { return addConstraint(ConstraintAttributes.Trailing) } + var width: Constraint { return addConstraint(ConstraintAttributes.Width) } + var height: Constraint { return addConstraint(ConstraintAttributes.Height) } + var centerX: Constraint { return addConstraint(ConstraintAttributes.CenterX) } + var centerY: Constraint { return addConstraint(ConstraintAttributes.CenterY) } + var baseline: Constraint { return addConstraint(ConstraintAttributes.Baseline) } + + var edges: Constraint { return addConstraint(ConstraintAttributes.Edges) } + var size: Constraint { return addConstraint(ConstraintAttributes.Size) } + var center: Constraint { return addConstraint(ConstraintAttributes.Center) } init(view: View) { self.view = view } - var left: Constraint { return addConstraint(.Left) } - var top: Constraint { return addConstraint(.Top) } - var right: Constraint { return addConstraint(.Right) } - var bottom: Constraint { return addConstraint(.Bottom) } - var leading: Constraint { return addConstraint(.Leading) } - var trailing: Constraint { return addConstraint(.Trailing) } - var width: Constraint { return addConstraint(.Width) } - var height: Constraint { return addConstraint(.Height) } - var centerX: Constraint { return addConstraint(.CenterX) } - var centerY: Constraint { return addConstraint(.CenterY) } - var baseline: Constraint { return addConstraint(.Baseline) } + internal weak var view: View? + internal var constraints = Array() - //TODO - var edges: Constraint { return addConstraints(.Top, .Left, .Bottom, .Right) } - var size: Constraint { return addConstraints(.Width, .Height) } - var center: Constraint { return addConstraints(.CenterX, .CenterY) } - - func install() { - for constraint in constraints { - constraint.install() - } - self.constraints.removeAll(keepCapacity: true) - } - - func addConstraint(layoutAttribute: NSLayoutAttribute) -> Constraint { - return constraint(nil, addConstraintWithLayoutAttribute: layoutAttribute) - } - - - func addConstraints(layoutAttributes: NSLayoutAttribute...) -> Constraint { - var children = layoutAttributes.map({ (attr: NSLayoutAttribute) -> Constraint in - var viewAttribute = ViewAttribute(view: self.view, layoutAttribute: attr) - return ViewConstraint(view: self.view!, firstViewAttribute: viewAttribute) - }) - - var constraint = CompositeConstraint(children: children) - constraint.delegate = self + internal func addConstraint(attributes: ConstraintAttributes) -> Constraint { + let item = ConstraintItem(view: self.view, attributes: attributes) + let constraint = Constraint(fromItem: item) self.constraints.append(constraint) - return constraint } - func constraint(constraint: Constraint, shouldBeReplacedWithConstraint replacementConstraint: Constraint) { - var index: Int? - for (i, c) in enumerate(self.constraints) { - if (c === constraint) { - index = i - } - } + internal class func makeConstraints(view: View, block: (make: ConstraintMaker) -> ()) { + view.setTranslatesAutoresizingMaskIntoConstraints(false) + let maker = ConstraintMaker(view: view) + block(make: maker) - if (index) { - self.constraints[index!] = replacementConstraint + var layoutConstraints: Array = [] + for constraint in maker.constraints { + layoutConstraints += constraint.install() } + LayoutConstraint.setLayoutConstraints(layoutConstraints, installedOnView: view) } - func constraint(constraint: Constraint?, addConstraintWithLayoutAttribute layoutAttribute: NSLayoutAttribute) -> Constraint { - var viewAttribute = ViewAttribute(view: self.view, layoutAttribute: layoutAttribute) - var newConstraint = ViewConstraint(view: self.view!, firstViewAttribute: viewAttribute) + internal class func remakeConstraints(view: View, block: (make: ConstraintMaker) -> ()) { + view.setTranslatesAutoresizingMaskIntoConstraints(false) + let maker = ConstraintMaker(view: view) + block(make: maker) - if let viewConstraint = constraint as? ViewConstraint { - //replace with composite constraint - var children = [viewConstraint, newConstraint] - var compositeConstraint = CompositeConstraint(children: children) - compositeConstraint.delegate = self - self.constraint(viewConstraint, shouldBeReplacedWithConstraint:compositeConstraint); - return compositeConstraint + var layoutConstraints: Array = LayoutConstraint.layoutConstraintsInstalledOnView(view) + for existingLayoutConstraint in layoutConstraints { + existingLayoutConstraint.constraint?.uninstall() } - if (!constraint) { - newConstraint.delegate = self - self.constraints.append(newConstraint) + layoutConstraints = [] + + for constraint in maker.constraints { + layoutConstraints += constraint.install() } - return newConstraint + LayoutConstraint.setLayoutConstraints(layoutConstraints, installedOnView: view) } -} \ No newline at end of file +} + diff --git a/Snappy/LayoutConstraint.swift b/Snappy/LayoutConstraint.swift new file mode 100644 index 0000000..936e250 --- /dev/null +++ b/Snappy/LayoutConstraint.swift @@ -0,0 +1,48 @@ +// +// LayoutConstraint.swift +// Snappy +// +// Copyright (c) 2011-2014 Masonry Team - https://github.com/Masonry +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +/** + * LayoutConstraint is a subclass of NSLayoutConstraint to assist Snappy and also provide better debugging + */ +class LayoutConstraint: NSLayoutConstraint { + + // internal + + internal var constraint: Constraint? + + internal class func layoutConstraintsInstalledOnView(view: View) -> Array { + var constraints = objc_getAssociatedObject(view, &layoutConstraintsInstalledOnViewKey) as? Array + if constraints { + return constraints! + } + return [] + } + internal class func setLayoutConstraints(layoutConstraints: Array, installedOnView view: View) { + objc_setAssociatedObject(view, &layoutConstraintsInstalledOnViewKey, layoutConstraints, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + } +} + +private var layoutConstraintsInstalledOnViewKey = "" \ No newline at end of file diff --git a/Snappy/View.swift b/Snappy/View.swift index fec4b3d..7ed61b9 100644 --- a/Snappy/View.swift +++ b/Snappy/View.swift @@ -2,50 +2,80 @@ // View.swift // Snappy // -// Created by Jonas Budelmann on 25/07/14. -// Copyright (c) 2014 Jonas Budelmann. All rights reserved. +// Copyright (c) 2011-2014 Masonry Team - https://github.com/Masonry // - -import Foundation +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. import UIKit -typealias View = UIView +typealias View = UIView extension View { - var mas_left: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .Left) } - var mas_top: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .Top) } - var mas_right: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .Right) } - var mas_bottom: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .Bottom) } - var mas_leading: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .Leading) } - var mas_trailing: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .Trailing) } - var mas_width: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .Width) } - var mas_height: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .Height) } - var mas_centerX: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .CenterX) } - var mas_centerY: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .CenterY) } - var mas_baseline: ViewAttribute { return ViewAttribute(view: self, layoutAttribute: .Baseline) } +#if SNP_SHORTHAND + var left: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Left) } + var top: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Top) } + var right: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Right) } + var bottom: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Bottom) } + var leading: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Leading) } + var trailing: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Trailing) } + var width: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Width) } + var height: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Height) } + var centerX: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.CenterX) } + var centerY: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.CenterY) } + var baseline: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Baseline) } - func mas_makeConstraints(block: (make: ConstraintMaker) -> ()) { - self.setTranslatesAutoresizingMaskIntoConstraints(false) - let constraintMaker: ConstraintMaker = ConstraintMaker(view: self) - block(make: constraintMaker) - constraintMaker.install() + var edges: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Edges) } + var size: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Size) } + var center: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Center) } + + func makeConstraints(block: (maker: ConstraintMaker) -> ()) { + ConstraintMaker.makeConstraints(self, block: block) } - func mas_closestCommonSuperview(view: View) -> View { - var closestCommonSuperview: View? = nil - - var secondViewSuperview: View = view - while (!closestCommonSuperview && secondViewSuperview != nil) { - var firstViewSuperview: View = self - while (!closestCommonSuperview && firstViewSuperview != nil) { - if (secondViewSuperview == firstViewSuperview) { - closestCommonSuperview = secondViewSuperview - } - firstViewSuperview = firstViewSuperview.superview - } - secondViewSuperview = secondViewSuperview.superview - } - return closestCommonSuperview! + func remakeConstraints(block: (maker: ConstraintMaker) -> ()) { + ConstraintMaker.remakeConstraints(self, block: block) } + +#else + var snp_left: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Left) } + var snp_top: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Top) } + var snp_right: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Right) } + var snp_bottom: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Bottom) } + var snp_leading: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Leading) } + var snp_trailing: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Trailing) } + var snp_width: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Width) } + var snp_height: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Height) } + var snp_centerX: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.CenterX) } + var snp_centerY: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.CenterY) } + var snp_baseline: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Baseline) } + + var snp_edges: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Edges) } + var snp_size: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Size) } + var snp_center: ConstraintItem { return ConstraintItem(view: self, attributes: ConstraintAttributes.Center) } + + func snp_makeConstraints(block: (maker: ConstraintMaker) -> ()) { + ConstraintMaker.makeConstraints(self, block: block) + } + + func snp_remakeConstraints(block: (maker: ConstraintMaker) -> ()) { + ConstraintMaker.remakeConstraints(self, block: block) + } + +#endif } diff --git a/Snappy/ViewAttribute.swift b/Snappy/ViewAttribute.swift deleted file mode 100644 index 6504ab0..0000000 --- a/Snappy/ViewAttribute.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// ViewAttribute.swift -// Snappy -// -// Created by Jonas Budelmann on 25/07/14. -// Copyright (c) 2014 Jonas Budelmann. All rights reserved. -// - -import UIKit - -struct ViewAttribute { - weak var view: View? - var layoutAttribute: NSLayoutAttribute -} \ No newline at end of file diff --git a/Snappy/ViewConstraint.swift b/Snappy/ViewConstraint.swift deleted file mode 100644 index fe1f3f6..0000000 --- a/Snappy/ViewConstraint.swift +++ /dev/null @@ -1,191 +0,0 @@ -// -// ViewConstraint.swift -// Snappy -// -// Created by Jonas Budelmann on 25/07/14. -// Copyright (c) 2014 Jonas Budelmann. All rights reserved. -// - -import UIKit - -class ViewConstraint: Constraint { - weak var view: View? - - var firstViewAttribute: ViewAttribute - var secondViewAttribute: ViewAttribute? - - var layoutPriority = 1000.0// UILayoutPriorityRequired gives error?! - var layoutMultiplier = 1.0 - var layoutConstraint: NSLayoutConstraint? - var layoutRelation: NSLayoutRelation? - var layoutConstant: Float - - weak var delegate: ConstraintDelegate? - - init(view: View, firstViewAttribute: ViewAttribute) { - self.view = view; - self.firstViewAttribute = firstViewAttribute; - - self.layoutPriority = 1000.0// UILayoutPriorityRequired gives error?! - self.layoutMultiplier = 1.0 - self.layoutConstant = 0.0 - } - - var left: Constraint { return addConstraint(.Left) } - var top: Constraint { return addConstraint(.Top) } - var right: Constraint { return addConstraint(.Right) } - var bottom: Constraint { return addConstraint(.Bottom) } - var leading: Constraint { return addConstraint(.Leading) } - var trailing: Constraint { return addConstraint(.Trailing) } - var width: Constraint { return addConstraint(.Width) } - var height: Constraint { return addConstraint(.Height) } - var centerX: Constraint { return addConstraint(.CenterX) } - var centerY: Constraint { return addConstraint(.CenterY) } - var baseline: Constraint { return addConstraint(.Baseline) } - - var and: Constraint { return self } - var with: Constraint { return self } - - func addConstraint(attr: NSLayoutAttribute) -> Constraint { - if (self.layoutRelation) { - //TODO use assert - println("Attributes should be chained before defining the constraint relation") - } - - return self.delegate!.constraint(self, addConstraintWithLayoutAttribute:attr) - } - - func equality(relation: NSLayoutRelation, attr: Any) -> Constraint { - layoutRelation = relation - - switch attr { - case let view as View: - secondViewAttribute = ViewAttribute(view: firstViewAttribute.view, layoutAttribute: firstViewAttribute.layoutAttribute) - case let offset as Float: - layoutConstant = offset - case let number as NSNumber: - layoutConstant = number - case let viewAttribute as ViewAttribute: - secondViewAttribute = viewAttribute - case let size as CGSize: - sizeOffset(size) - case let point as CGPoint: - centerOffset(point) - case let inset as UIEdgeInsets: - insets(inset) - default: - println("unsupported value: \(attr)") - } - -// } else if (strcmp(value.objCType, @encode(CGPoint)) == 0) { -// CGPoint point; -// [value getValue:&point]; -// self.centerOffset = point; -// } else if (strcmp(value.objCType, @encode(CGSize)) == 0) { -// CGSize size; -// [value getValue:&size]; -// self.sizeOffset = size; -// } else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) { -// MASEdgeInsets insets; -// [value getValue:&insets]; -// self.insets = insets; -// } else { -// NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value); -// } - - return self; - } - - private func sizeOffset(size: CGSize) { - switch (firstViewAttribute.layoutAttribute) { - case .Width: - layoutConstant = Float(size.width); - break; - case .Height: - layoutConstant = Float(size.height); - break; - default: - break; - } - } - - private func centerOffset(point: CGPoint) { - switch (firstViewAttribute.layoutAttribute) { - case .CenterX: - layoutConstant = Float(point.x); - break; - case .CenterY: - layoutConstant = Float(point.y); - break; - default: - break; - } - } - - func insets(insets: UIEdgeInsets) -> Constraint { - switch (firstViewAttribute.layoutAttribute) { - case .Left: - layoutConstant = Float(insets.left); - break; - case .Top: - layoutConstant = Float(insets.top); - break; - case .Bottom: - layoutConstant = Float(-insets.bottom); - break; - case .Right: - layoutConstant = Float(-insets.right); - break; - default: - break; - } - return self; - } - - func equalTo(attr: Any) -> Constraint { - return equality(.Equal, attr: attr) - } - - func greaterThanOrEqualTo(attr: Any) -> Constraint { - return equality(.GreaterThanOrEqual, attr: attr) - } - - func lessThanOrEqualTo(attr: Any) -> Constraint { - return equality(.LessThanOrEqual, attr: attr) - } - - func insets(insets: Any) -> Constraint { - return self - } - - func offset(offset: Any) -> Constraint { - return self - } - - func multipliedBy(multiplier: Float) -> Constraint { - return self - } - - func dividedBy(divider: Float) -> Constraint { - return self - } - - func priority(priority: UILayoutPriority) -> Constraint { - return self - } - - func priorityLow() -> Constraint { - return self - } - - func priorityMedium() -> Constraint { - return self - } - - func priorityHigh() -> Constraint { - return self - } - - func install() { - } -} \ No newline at end of file diff --git a/SnappyExample/ViewController.swift b/SnappyExample/ViewController.swift index 839faf1..1def895 100644 --- a/SnappyExample/ViewController.swift +++ b/SnappyExample/ViewController.swift @@ -15,7 +15,7 @@ class ViewController: UIViewController { let superview: UIView = self.view - let view1 = UIView() + let view1 = UIView(frame: CGRectZero) view1.backgroundColor = UIColor.greenColor() view1.layer.borderColor = UIColor.blackColor().CGColor view1.layer.borderWidth = 2 @@ -35,34 +35,23 @@ class ViewController: UIViewController { let padding = UIEdgeInsets(top: 15, left: 10, bottom: 15, right: 10) - view1.mas_makeConstraints { make in - make.top.and.left.greaterThanOrEqualTo(superview).insets(padding) - - make.bottom.equalTo(view3.mas_top).insets(padding) - make.right.equalTo(view2.mas_left).insets(padding) - make.width.equalTo(view2.mas_width) - - make.height.equalTo([view2, view3]) + + view1.snp_makeConstraints { make in + make.top.and.left.equalTo(CGPointZero).insets(padding) + make.size.equalTo(CGSizeMake(100, 50)) } - view2.mas_makeConstraints { make in - // chain attributes - make.top.and.right.equalTo(superview).insets(padding) - - make.left.equalTo(view1.mas_right).insets(padding) - make.bottom.equalTo(view3.mas_top).insets(padding) - make.width.equalTo(view1.mas_width) - - make.height.equalTo([view1, view3]) + view2.snp_makeConstraints { make in + make.centerX.equalTo(view1.snp_centerX).offset(CGPointMake(50, 0)) + make.top.equalTo(view1.snp_bottom).offset(50) + make.width.equalTo(view1.snp_height) + make.height.equalTo(view1.snp_width) } - view3.mas_makeConstraints { make in - make.top.equalTo(view1.mas_bottom).insets(padding) - - // chain attributes - make.left.right.and.bottom.equalTo(superview).insets(padding) - - make.height.equalTo([view1, view2]) + view3.snp_makeConstraints { make in + make.width.height.greaterThanOrEqualTo(view1) + make.width.height.greaterThanOrEqualTo(view2) + make.center.equalTo(superview) } }