From fed8ab992171f74dd5d0d6bb41ca3b43d63dbd24 Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Sat, 14 Feb 2015 23:02:01 +1300 Subject: [PATCH 1/8] Updated gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3566e86..a729292 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ project.xcworkspace xcuserdata Examples/ -.DS_Store \ No newline at end of file +.DS_Store +Gemfile +Gemfile.lock From f97ec8d1d2f49da103edd447507c9e12c061bf38 Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Sat, 14 Feb 2015 23:37:27 +1300 Subject: [PATCH 2/8] Fixed Swift 1.2 regressions and added some simple tests Conflicts: Snap/ConstraintMaker.swift Snap/View+Snap.swift --- Snap.xcodeproj/project.pbxproj | 4 ++ Snap/ConstraintMaker.swift | 15 +++---- Snap/View+Snap.swift | 13 +++--- SnapTests/SnapTests.swift | 74 ++++++++++++++++++++++++++++++---- 4 files changed, 85 insertions(+), 21 deletions(-) diff --git a/Snap.xcodeproj/project.pbxproj b/Snap.xcodeproj/project.pbxproj index d6151c3..70f4e6f 100644 --- a/Snap.xcodeproj/project.pbxproj +++ b/Snap.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + EEAED5481A8F56A500777EF9 /* Snap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEBCC9D819CC627D0083B827 /* Snap.framework */; }; + EEAED5491A8F56BF00777EF9 /* SnapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE91728C19CB304E007888CF /* SnapTests.swift */; }; EEBCC9F019CC64F80083B827 /* EdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBCC9EF19CC64F70083B827 /* EdgeInsets.swift */; }; EEBCC9F219CC65050083B827 /* View+Snap.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBCC9F119CC65040083B827 /* View+Snap.swift */; }; EEBCC9F419CC65110083B827 /* ConstraintAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBCC9F319CC65110083B827 /* ConstraintAttributes.swift */; }; @@ -46,6 +48,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + EEAED5481A8F56A500777EF9 /* Snap.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -233,6 +236,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EEAED5491A8F56BF00777EF9 /* SnapTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Snap/ConstraintMaker.swift b/Snap/ConstraintMaker.swift index 2449ed9..69aaead 100644 --- a/Snap/ConstraintMaker.swift +++ b/Snap/ConstraintMaker.swift @@ -61,7 +61,7 @@ public class ConstraintMaker { return constraint } - internal class func makeConstraints(view: View, block: (make: ConstraintMaker) -> ()) { + internal class func makeConstraints(view: View, block: (make: ConstraintMaker) -> Void) { #if os(iOS) view.setTranslatesAutoresizingMaskIntoConstraints(false) #else @@ -70,7 +70,7 @@ public class ConstraintMaker { let maker = ConstraintMaker(view: view) block(make: maker) - var layoutConstraints = view.snp_installedLayoutConstraints + var layoutConstraints = Array(view.snp_installedLayoutConstraints) for constraint in maker.constraints { layoutConstraints += constraint.install() } @@ -78,7 +78,7 @@ public class ConstraintMaker { view.snp_installedLayoutConstraints = layoutConstraints } - internal class func remakeConstraints(view: View, block: (make: ConstraintMaker) -> ()) { + internal class func remakeConstraints(view: View, block: (make: ConstraintMaker) -> Void) { #if os(iOS) view.setTranslatesAutoresizingMaskIntoConstraints(false) #else @@ -87,7 +87,7 @@ public class ConstraintMaker { let maker = ConstraintMaker(view: view) block(make: maker) - var layoutConstraints: Array = view.snp_installedLayoutConstraints + var layoutConstraints = Array(view.snp_installedLayoutConstraints) for existingLayoutConstraint in layoutConstraints { existingLayoutConstraint.constraint?.uninstall() } @@ -100,7 +100,7 @@ public class ConstraintMaker { view.snp_installedLayoutConstraints = layoutConstraints } - internal class func updateConstraints(view: View, block: (make: ConstraintMaker) -> ()) { + internal class func updateConstraints(view: View, block: (make: ConstraintMaker) -> Void) { #if os(iOS) view.setTranslatesAutoresizingMaskIntoConstraints(false) #else @@ -109,7 +109,7 @@ public class ConstraintMaker { let maker = ConstraintMaker(view: view) block(make: maker) - var layoutConstraints = view.snp_installedLayoutConstraints + var layoutConstraints = Array(view.snp_installedLayoutConstraints) for constraint in maker.constraints { layoutConstraints += constraint.installOnView(updateExisting: true) } @@ -118,7 +118,8 @@ public class ConstraintMaker { } internal class func removeConstraints(view: View) { - for existingLayoutConstraint in view.snp_installedLayoutConstraints { + let eixsitingLayoutConstraints = Array(view.snp_installedLayoutConstraints) + for existingLayoutConstraint in eixsitingLayoutConstraints { existingLayoutConstraint.constraint?.uninstall() } diff --git a/Snap/View+Snap.swift b/Snap/View+Snap.swift index ebb0c19..7198c0f 100644 --- a/Snap/View+Snap.swift +++ b/Snap/View+Snap.swift @@ -68,15 +68,15 @@ public extension View { public var snp_centerWithinMargins: ConstraintItem { return ConstraintItem(object: self, attributes: ConstraintAttributes.CenterWithinMargins) } #endif - public func snp_makeConstraints(block: (make: ConstraintMaker) -> ()) { + public func snp_makeConstraints(block: (make: ConstraintMaker) -> Void) { ConstraintMaker.makeConstraints(self, block: block) } - public func snp_updateConstraints(block: (make: ConstraintMaker) -> ()) { + public func snp_updateConstraints(block: (make: ConstraintMaker) -> Void) { ConstraintMaker.updateConstraints(self, block: block) } - public func snp_remakeConstraints(block: (make: ConstraintMaker) -> ()) { + public func snp_remakeConstraints(block: (make: ConstraintMaker) -> Void) { ConstraintMaker.remakeConstraints(self, block: block) } @@ -88,11 +88,10 @@ public extension View { internal var snp_installedLayoutConstraints: Array { get { - var constraints = objc_getAssociatedObject(self, &installedLayoutConstraintsKey) as? Array - if constraints != nil { - return constraints! + if let constraints = objc_getAssociatedObject(self, &installedLayoutConstraintsKey) as? Array { + return constraints } - return [] + return Array() } set { objc_setAssociatedObject(self, &installedLayoutConstraintsKey, newValue, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) diff --git a/SnapTests/SnapTests.swift b/SnapTests/SnapTests.swift index 82665ad..bebb1b7 100644 --- a/SnapTests/SnapTests.swift +++ b/SnapTests/SnapTests.swift @@ -8,9 +8,12 @@ import UIKit import XCTest +import Snap class SnapTests: XCTestCase { + let container = UIView() + override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. @@ -21,16 +24,73 @@ class SnapTests: XCTestCase { super.tearDown() } - func testExample() { - // This is an example of a functional test case. - XCTAssert(true, "Pass") + func testMakeConstraints() { + let v1 = UIView() + let v2 = UIView() + self.container.addSubview(v1) + self.container.addSubview(v2) + + v1.snp_makeConstraints { (make) -> Void in + make.top.equalTo(v2.snp_top).offset(50) + make.left.equalTo(v2.snp_top).offset(50) + return + } + v2.snp_makeConstraints { (make) -> Void in + make.edges.equalTo(v1) + return + } + } - func testPerformanceExample() { - // This is an example of a performance test case. - self.measureBlock() { - // Put the code you want to measure the time of here. + func testUpdateConstraints() { + let v1 = UIView() + let v2 = UIView() + self.container.addSubview(v1) + self.container.addSubview(v2) + + v1.snp_makeConstraints { (make) -> Void in + make.top.equalTo(v2.snp_top).offset(50) + make.left.equalTo(v2.snp_top).offset(50) + return } + v1.snp_updateConstraints { (make) -> Void in + make.top.equalTo(v2.snp_top).offset(15) + return + } + + } + + func testRemakeConstraints() { + let v1 = UIView() + let v2 = UIView() + self.container.addSubview(v1) + self.container.addSubview(v2) + + v1.snp_makeConstraints { (make) -> Void in + make.top.equalTo(v2.snp_top).offset(50) + make.left.equalTo(v2.snp_top).offset(50) + return + } + v1.snp_remakeConstraints { (make) -> Void in + make.edges.equalTo(v2) + return + } + + } + + func testRemoveConstraints() { + let v1 = UIView() + let v2 = UIView() + self.container.addSubview(v1) + self.container.addSubview(v2) + + v1.snp_makeConstraints { (make) -> Void in + make.top.equalTo(v2.snp_top).offset(50) + make.left.equalTo(v2.snp_top).offset(50) + return + } + v1.snp_removeConstraints() + } } From 4158cf0d9980d6b666f7736242423ac473c96800 Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Mon, 16 Feb 2015 13:09:18 +1300 Subject: [PATCH 3/8] Further fix remake constraints crash in Swift 1.2 --- Snap/Constraint.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Snap/Constraint.swift b/Snap/Constraint.swift index c9fa8ae..9a07b07 100644 --- a/Snap/Constraint.swift +++ b/Snap/Constraint.swift @@ -402,8 +402,10 @@ public class Constraint { if let view = self.installedOnView { // remove all installed layout constraints var layoutConstraintsToRemove = Array() - if let installedLayoutConstraints = self.installedLayoutConstraints?.allObjects as? Array { - layoutConstraintsToRemove += installedLayoutConstraints + if let allObjects = self.installedLayoutConstraints?.allObjects { + if let installedLayoutConstraints = allObjects as? Array { + layoutConstraintsToRemove += installedLayoutConstraints + } } if layoutConstraintsToRemove.count > 0 { From b7eb62fba017f85f06311f1398837d667149620c Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Tue, 17 Feb 2015 11:11:58 +1300 Subject: [PATCH 4/8] Cleaned up some APIs and added support for prepare constraints. --- Snap/Constraint.swift | 130 ++++++++++++++++++++----------------- Snap/ConstraintMaker.swift | 60 ++++++++--------- Snap/View+Snap.swift | 4 ++ 3 files changed, 103 insertions(+), 91 deletions(-) diff --git a/Snap/Constraint.swift b/Snap/Constraint.swift index 9a07b07..7052ab3 100644 --- a/Snap/Constraint.swift +++ b/Snap/Constraint.swift @@ -31,19 +31,24 @@ import AppKit * Constraint is a single item that defines all the properties for a single ConstraintMaker chain */ public class Constraint { - public var left: Constraint { return addConstraint(ConstraintAttributes.Left) } - public var top: Constraint { return addConstraint(ConstraintAttributes.Top) } - public var right: Constraint { return addConstraint(ConstraintAttributes.Right) } - public var bottom: Constraint { return addConstraint(ConstraintAttributes.Bottom) } - public var leading: Constraint { return addConstraint(ConstraintAttributes.Leading) } - public var trailing: Constraint { return addConstraint(ConstraintAttributes.Trailing) } - public var width: Constraint { return addConstraint(ConstraintAttributes.Width) } - public var height: Constraint { return addConstraint(ConstraintAttributes.Height) } - public var centerX: Constraint { return addConstraint(ConstraintAttributes.CenterX) } - public var centerY: Constraint { return addConstraint(ConstraintAttributes.CenterY) } - public var baseline: Constraint { return addConstraint(ConstraintAttributes.Baseline) } + public var left: Constraint { return self.addConstraint(ConstraintAttributes.Left) } + public var top: Constraint { return self.addConstraint(ConstraintAttributes.Top) } + public var right: Constraint { return self.addConstraint(ConstraintAttributes.Right) } + public var bottom: Constraint { return self.addConstraint(ConstraintAttributes.Bottom) } + public var leading: Constraint { return self.addConstraint(ConstraintAttributes.Leading) } + public var trailing: Constraint { return self.addConstraint(ConstraintAttributes.Trailing) } + public var width: Constraint { return self.addConstraint(ConstraintAttributes.Width) } + public var height: Constraint { return self.addConstraint(ConstraintAttributes.Height) } + public var centerX: Constraint { return self.addConstraint(ConstraintAttributes.CenterX) } + public var centerY: Constraint { return self.addConstraint(ConstraintAttributes.CenterY) } + public var baseline: Constraint { return self.addConstraint(ConstraintAttributes.Baseline) } - public var and: Constraint { return self } + public var and: Constraint { + if self.relation != nil { + fatalError("And is semantic only and can only be used before a relation is set.") + } + return self + } public var with: Constraint { return self } // MARK: initializer @@ -56,115 +61,115 @@ public class Constraint { // MARK: equalTo public func equalTo(other: ConstraintItem) -> Constraint { - return constrainTo(other, relation: .Equal) + return self.constrainTo(other, relation: .Equal) } public func equalTo(other: View) -> Constraint { - return constrainTo(other, relation: .Equal) + return self.constrainTo(other, relation: .Equal) } #if os(iOS) public func equalTo(other: UILayoutSupport) -> Constraint { - return constrainTo(other, relation: .Equal) + return self.constrainTo(other, relation: .Equal) } #endif public func equalTo(other: Float) -> Constraint { - return constrainTo(other, relation: .Equal) + return self.constrainTo(other, relation: .Equal) } public func equalTo(other: Double) -> Constraint { - return constrainTo(Float(other), relation: .Equal) + return self.constrainTo(Float(other), relation: .Equal) } public func equalTo(other: CGFloat) -> Constraint { - return constrainTo(Float(other), relation: .Equal) + return self.constrainTo(Float(other), relation: .Equal) } public func equalTo(other: Int) -> Constraint { - return constrainTo(Float(other), relation: .Equal) + return self.constrainTo(Float(other), relation: .Equal) } public func equalTo(other: UInt) -> Constraint { - return constrainTo(Float(other), relation: .Equal) + return self.constrainTo(Float(other), relation: .Equal) } public func equalTo(other: CGSize) -> Constraint { - return constrainTo(other, relation: .Equal) + return self.constrainTo(other, relation: .Equal) } public func equalTo(other: CGPoint) -> Constraint { - return constrainTo(other, relation: .Equal) + return self.constrainTo(other, relation: .Equal) } public func equalTo(other: EdgeInsets) -> Constraint { - return constrainTo(other, relation: .Equal) + return self.constrainTo(other, relation: .Equal) } // MARK: lessThanOrEqualTo public func lessThanOrEqualTo(other: ConstraintItem) -> Constraint { - return constrainTo(other, relation: .LessThanOrEqualTo) + return self.constrainTo(other, relation: .LessThanOrEqualTo) } public func lessThanOrEqualTo(other: View) -> Constraint { - return constrainTo(other, relation: .LessThanOrEqualTo) + return self.constrainTo(other, relation: .LessThanOrEqualTo) } #if os(iOS) public func lessThanOrEqualTo(other: UILayoutSupport) -> Constraint { - return constrainTo(other, relation: .LessThanOrEqualTo) + return self.constrainTo(other, relation: .LessThanOrEqualTo) } #endif public func lessThanOrEqualTo(other: Float) -> Constraint { - return constrainTo(other, relation: .LessThanOrEqualTo) + return self.constrainTo(other, relation: .LessThanOrEqualTo) } public func lessThanOrEqualTo(other: Double) -> Constraint { - return constrainTo(Float(other), relation: .LessThanOrEqualTo) + return self.constrainTo(Float(other), relation: .LessThanOrEqualTo) } public func lessThanOrEqualTo(other: CGFloat) -> Constraint { - return constrainTo(Float(other), relation: .LessThanOrEqualTo) + return self.constrainTo(Float(other), relation: .LessThanOrEqualTo) } public func lessThanOrEqualTo(other: Int) -> Constraint { - return constrainTo(Float(other), relation: .LessThanOrEqualTo) + return self.constrainTo(Float(other), relation: .LessThanOrEqualTo) } public func lessThanOrEqualTo(other: UInt) -> Constraint { - return constrainTo(Float(other), relation: .LessThanOrEqualTo) + return self.constrainTo(Float(other), relation: .LessThanOrEqualTo) } public func lessThanOrEqualTo(other: CGSize) -> Constraint { - return constrainTo(other, relation: .LessThanOrEqualTo) + return self.constrainTo(other, relation: .LessThanOrEqualTo) } public func lessThanOrEqualTo(other: CGPoint) -> Constraint { - return constrainTo(other, relation: .LessThanOrEqualTo) + return self.constrainTo(other, relation: .LessThanOrEqualTo) } public func lessThanOrEqualTo(other: EdgeInsets) -> Constraint { - return constrainTo(other, relation: .LessThanOrEqualTo) + return self.constrainTo(other, relation: .LessThanOrEqualTo) } // MARK: greaterThanOrEqualTo public func greaterThanOrEqualTo(other: ConstraintItem) -> Constraint { - return constrainTo(other, relation: .GreaterThanOrEqualTo) + return self.constrainTo(other, relation: .GreaterThanOrEqualTo) } public func greaterThanOrEqualTo(other: View) -> Constraint { - return constrainTo(other, relation: .GreaterThanOrEqualTo) + return self.constrainTo(other, relation: .GreaterThanOrEqualTo) } #if os(iOS) public func greaterThanOrEqualTo(other: UILayoutSupport) -> Constraint { - return constrainTo(other, relation: .GreaterThanOrEqualTo) + return self.constrainTo(other, relation: .GreaterThanOrEqualTo) } #endif public func greaterThanOrEqualTo(other: Float) -> Constraint { - return constrainTo(other, relation: .GreaterThanOrEqualTo) + return self.constrainTo(other, relation: .GreaterThanOrEqualTo) } public func greaterThanOrEqualTo(other: Double) -> Constraint { - return constrainTo(Float(other), relation: .GreaterThanOrEqualTo) + return self.constrainTo(Float(other), relation: .GreaterThanOrEqualTo) } public func greaterThanOrEqualTo(other: CGFloat) -> Constraint { - return constrainTo(Float(other), relation: .GreaterThanOrEqualTo) + return self.constrainTo(Float(other), relation: .GreaterThanOrEqualTo) } public func greaterThanOrEqualTo(other: Int) -> Constraint { - return constrainTo(Float(other), relation: .GreaterThanOrEqualTo) + return self.constrainTo(Float(other), relation: .GreaterThanOrEqualTo) } public func greaterThanOrEqualTo(other: UInt) -> Constraint { - return constrainTo(Float(other), relation: .GreaterThanOrEqualTo) + return self.constrainTo(Float(other), relation: .GreaterThanOrEqualTo) } public func greaterThanOrEqualTo(other: CGSize) -> Constraint { - return constrainTo(other, relation: .GreaterThanOrEqualTo) + return self.constrainTo(other, relation: .GreaterThanOrEqualTo) } public func greaterThanOrEqualTo(other: CGPoint) -> Constraint { - return constrainTo(other, relation: .GreaterThanOrEqualTo) + return self.constrainTo(other, relation: .GreaterThanOrEqualTo) } public func greaterThanOrEqualTo(other: EdgeInsets) -> Constraint { - return constrainTo(other, relation: .GreaterThanOrEqualTo) + return self.constrainTo(other, relation: .GreaterThanOrEqualTo) } // MARK: multiplier @@ -308,7 +313,7 @@ public class Constraint { } } - var layoutConstraints: Array = [] + var newLayoutConstraints = Array() let layoutFromAttributes = self.fromItem.attributes.layoutAttributes let layoutToAttributes = self.toItem.attributes.layoutAttributes @@ -348,7 +353,7 @@ public class Constraint { // set constraint layoutConstraint.constraint = self - layoutConstraints.append(layoutConstraint) + newLayoutConstraints.append(layoutConstraint) } // special logic for updating @@ -356,11 +361,11 @@ public class Constraint { // get existing constraints for this view let existingLayoutConstraints = reverse(layoutFrom!.snp_installedLayoutConstraints) - // array that will contain only new layout constraints - var newLayoutConstraints = Array() - + // array that will contain only new layout constraints to keep + var newLayoutConstraintsToKeep = Array() + // begin looping - for layoutConstraint in layoutConstraints { + for layoutConstraint in newLayoutConstraints { // layout constraint that should be updated var updateLayoutConstraint: LayoutConstraint? = nil @@ -376,26 +381,35 @@ public class Constraint { if updateLayoutConstraint != nil { updateLayoutConstraint!.constant = layoutConstraint.constant } - // otherwise add this layout constraint to new list + // otherwise add this layout constraint to new keep list else { - newLayoutConstraints.append(layoutConstraint) + newLayoutConstraintsToKeep.append(layoutConstraint) } } - + // set constraints to only new ones - layoutConstraints = newLayoutConstraints + newLayoutConstraints = newLayoutConstraintsToKeep } // add constraints - installOnView!.addConstraints(layoutConstraints) + installOnView!.addConstraints(newLayoutConstraints) + // store which view this constraint was installed on self.installedOnView = installOnView + + // store which layout constraints are installed for this constraint self.installedLayoutConstraints = NSHashTable.weakObjectsHashTable() - for layoutConstraint in layoutConstraints { + for layoutConstraint in newLayoutConstraints { self.installedLayoutConstraints!.addObject(layoutConstraint) } - return layoutConstraints + // store the layout constraints against the installed on view + var layoutConstraints = Array(layoutFrom!.snp_installedLayoutConstraints) + layoutConstraints += newLayoutConstraints + layoutFrom!.snp_installedLayoutConstraints = layoutConstraints + + // return the new constraints + return newLayoutConstraints } internal func uninstallFromView() { diff --git a/Snap/ConstraintMaker.swift b/Snap/ConstraintMaker.swift index 69aaead..49e03d2 100644 --- a/Snap/ConstraintMaker.swift +++ b/Snap/ConstraintMaker.swift @@ -31,21 +31,21 @@ import AppKit * ConstraintMaker is the maker in snap that gets all constraints kickstarted */ public class ConstraintMaker { - public var left: Constraint { return addConstraint(ConstraintAttributes.Left) } - public var top: Constraint { return addConstraint(ConstraintAttributes.Top) } - public var right: Constraint { return addConstraint(ConstraintAttributes.Right) } - public var bottom: Constraint { return addConstraint(ConstraintAttributes.Bottom) } - public var leading: Constraint { return addConstraint(ConstraintAttributes.Leading) } - public var trailing: Constraint { return addConstraint(ConstraintAttributes.Trailing) } - public var width: Constraint { return addConstraint(ConstraintAttributes.Width) } - public var height: Constraint { return addConstraint(ConstraintAttributes.Height) } - public var centerX: Constraint { return addConstraint(ConstraintAttributes.CenterX) } - public var centerY: Constraint { return addConstraint(ConstraintAttributes.CenterY) } - public var baseline: Constraint { return addConstraint(ConstraintAttributes.Baseline) } + public var left: Constraint { return self.addConstraint(ConstraintAttributes.Left) } + public var top: Constraint { return self.addConstraint(ConstraintAttributes.Top) } + public var right: Constraint { return self.addConstraint(ConstraintAttributes.Right) } + public var bottom: Constraint { return self.addConstraint(ConstraintAttributes.Bottom) } + public var leading: Constraint { return self.addConstraint(ConstraintAttributes.Leading) } + public var trailing: Constraint { return self.addConstraint(ConstraintAttributes.Trailing) } + public var width: Constraint { return self.addConstraint(ConstraintAttributes.Width) } + public var height: Constraint { return self.addConstraint(ConstraintAttributes.Height) } + public var centerX: Constraint { return self.addConstraint(ConstraintAttributes.CenterX) } + public var centerY: Constraint { return self.addConstraint(ConstraintAttributes.CenterY) } + public var baseline: Constraint { return self.addConstraint(ConstraintAttributes.Baseline) } - public var edges: Constraint { return addConstraint(ConstraintAttributes.Edges) } - public var size: Constraint { return addConstraint(ConstraintAttributes.Size) } - public var center: Constraint { return addConstraint(ConstraintAttributes.Center) } + public var edges: Constraint { return self.addConstraint(ConstraintAttributes.Edges) } + public var size: Constraint { return self.addConstraint(ConstraintAttributes.Size) } + public var center: Constraint { return self.addConstraint(ConstraintAttributes.Center) } init(view: View) { self.view = view @@ -61,6 +61,12 @@ public class ConstraintMaker { return constraint } + internal class func prepareConstraints(view: View, block: (make: ConstraintMaker) -> Void) -> Array { + let maker = ConstraintMaker(view: view) + block(make: maker) + return maker.constraints + } + internal class func makeConstraints(view: View, block: (make: ConstraintMaker) -> Void) { #if os(iOS) view.setTranslatesAutoresizingMaskIntoConstraints(false) @@ -69,13 +75,9 @@ public class ConstraintMaker { #endif let maker = ConstraintMaker(view: view) block(make: maker) - - var layoutConstraints = Array(view.snp_installedLayoutConstraints) for constraint in maker.constraints { - layoutConstraints += constraint.install() + constraint.installOnView(updateExisting: false) } - - view.snp_installedLayoutConstraints = layoutConstraints } internal class func remakeConstraints(view: View, block: (make: ConstraintMaker) -> Void) { @@ -89,15 +91,12 @@ public class ConstraintMaker { var layoutConstraints = Array(view.snp_installedLayoutConstraints) for existingLayoutConstraint in layoutConstraints { - existingLayoutConstraint.constraint?.uninstall() + existingLayoutConstraint.constraint?.uninstallFromView() } - layoutConstraints = [] for constraint in maker.constraints { - layoutConstraints += constraint.install() + constraint.installOnView(updateExisting: false) } - - view.snp_installedLayoutConstraints = layoutConstraints } internal class func updateConstraints(view: View, block: (make: ConstraintMaker) -> Void) { @@ -109,20 +108,15 @@ public class ConstraintMaker { let maker = ConstraintMaker(view: view) block(make: maker) - var layoutConstraints = Array(view.snp_installedLayoutConstraints) for constraint in maker.constraints { - layoutConstraints += constraint.installOnView(updateExisting: true) + constraint.installOnView(updateExisting: true) } - - view.snp_installedLayoutConstraints = layoutConstraints } internal class func removeConstraints(view: View) { - let eixsitingLayoutConstraints = Array(view.snp_installedLayoutConstraints) - for existingLayoutConstraint in eixsitingLayoutConstraints { - existingLayoutConstraint.constraint?.uninstall() + let existingLayoutConstraints = Array(view.snp_installedLayoutConstraints) + for existingLayoutConstraint in existingLayoutConstraints { + existingLayoutConstraint.constraint?.uninstallFromView() } - - view.snp_installedLayoutConstraints = [] } } \ No newline at end of file diff --git a/Snap/View+Snap.swift b/Snap/View+Snap.swift index 7198c0f..09d1bab 100644 --- a/Snap/View+Snap.swift +++ b/Snap/View+Snap.swift @@ -68,6 +68,10 @@ public extension View { public var snp_centerWithinMargins: ConstraintItem { return ConstraintItem(object: self, attributes: ConstraintAttributes.CenterWithinMargins) } #endif + public func snp_prepareConstraints(block: (make: ConstraintMaker) -> Void) -> Array { + return ConstraintMaker.prepareConstraints(self, block: block) + } + public func snp_makeConstraints(block: (make: ConstraintMaker) -> Void) { ConstraintMaker.makeConstraints(self, block: block) } From 48eed7ffeb858fcefcdfd731485cdd433eef647f Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Tue, 17 Feb 2015 11:12:03 +1300 Subject: [PATCH 5/8] Updated tests --- SnapTests/SnapTests.swift | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/SnapTests/SnapTests.swift b/SnapTests/SnapTests.swift index bebb1b7..5a82a13 100644 --- a/SnapTests/SnapTests.swift +++ b/SnapTests/SnapTests.swift @@ -35,11 +35,16 @@ class SnapTests: XCTestCase { make.left.equalTo(v2.snp_top).offset(50) return } + + XCTAssertEqual(self.container.constraints().count, 2, "Should have 2 constraints installed") + v2.snp_makeConstraints { (make) -> Void in make.edges.equalTo(v1) return } + XCTAssertEqual(self.container.constraints().count, 6, "Should have 6 constraints installed") + } func testUpdateConstraints() { @@ -53,11 +58,16 @@ class SnapTests: XCTestCase { make.left.equalTo(v2.snp_top).offset(50) return } + + XCTAssertEqual(self.container.constraints().count, 2, "Should have 2 constraints installed") + v1.snp_updateConstraints { (make) -> Void in make.top.equalTo(v2.snp_top).offset(15) return } + XCTAssertEqual(self.container.constraints().count, 2, "Should still have 2 constraints installed") + } func testRemakeConstraints() { @@ -71,11 +81,16 @@ class SnapTests: XCTestCase { make.left.equalTo(v2.snp_top).offset(50) return } + + XCTAssertEqual(self.container.constraints().count, 2, "Should have 2 constraints installed") + v1.snp_remakeConstraints { (make) -> Void in make.edges.equalTo(v2) return } + XCTAssertEqual(self.container.constraints().count, 4, "Should have 4 constraints installed") + } func testRemoveConstraints() { @@ -89,8 +104,40 @@ class SnapTests: XCTestCase { make.left.equalTo(v2.snp_top).offset(50) return } + + XCTAssertEqual(self.container.constraints().count, 2, "Should have 2 constraints installed") + v1.snp_removeConstraints() + XCTAssertEqual(self.container.constraints().count, 0, "Should have 0 constraints installed") + + } + + func testPrepareConstraints() { + let v1 = UIView() + let v2 = UIView() + self.container.addSubview(v1) + self.container.addSubview(v2) + + let constraints = v1.snp_prepareConstraints { (make) -> Void in + make.edges.equalTo(v2) + return + } + + XCTAssertEqual(self.container.constraints().count, 0, "Should have 0 constraints installed") + + for constraint in constraints { + constraint.install() + } + + XCTAssertEqual(self.container.constraints().count, 4, "Should have 4 constraints installed") + + for constraint in constraints { + constraint.uninstall() + } + + XCTAssertEqual(self.container.constraints().count, 0, "Should have 0 constraints installed") + } } From 116002cde754093f33c887d53587a3dd2892260d Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Tue, 17 Feb 2015 11:13:24 +1300 Subject: [PATCH 6/8] Updated changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6b722d..80241ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Beta ======= +# Next Release + +* Re-worked some internal API to allow for future updates +* Added `snp_prepareConstraints -> [Constraint]` which allows pre-building of constraints +* Added a fatal error to `and` when it is used after relation has been set + # 0.0.6 - February 11th, 2015 * Renamed `maker` to `make` in all block APIs \ No newline at end of file From 27012934323e3e3202c405c9b40d2400bf65447f Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Tue, 17 Feb 2015 11:37:43 +1300 Subject: [PATCH 7/8] Improve the closest superview finder algorithm --- Snap/Constraint.swift | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/Snap/Constraint.swift b/Snap/Constraint.swift index 7052ab3..2465af8 100644 --- a/Snap/Constraint.swift +++ b/Snap/Constraint.swift @@ -506,19 +506,27 @@ public class Constraint { } private class func closestCommonSuperviewFromView(fromView: View?, toView: View?) -> View? { - var closestCommonSuperview: View? - var secondViewSuperview: View? = toView - while closestCommonSuperview == nil && secondViewSuperview != nil { - var firstViewSuperview = fromView - while closestCommonSuperview == nil && firstViewSuperview != nil { - if secondViewSuperview == firstViewSuperview { - closestCommonSuperview = secondViewSuperview + var views = NSMutableSet() + var fromView = fromView + var toView = toView + do { + if let view = toView { + if views.containsObject(view) { + return view } - firstViewSuperview = firstViewSuperview?.superview + views.addObject(view) + toView = view.superview } - secondViewSuperview = secondViewSuperview?.superview - } - return closestCommonSuperview + if let view = fromView { + if views.containsObject(view) { + return view + } + views.addObject(view) + fromView = view.superview + } + } while (fromView != nil || toView != nil) + + return nil } } From faf7d43aaf6c46d11d1b1cdbb9ff58392ece6e52 Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Tue, 17 Feb 2015 13:41:45 +1300 Subject: [PATCH 8/8] Allow install and uninstall to be indempotent --- Snap/Constraint.swift | 15 +++++++++------ SnapTests/SnapTests.swift | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/Snap/Constraint.swift b/Snap/Constraint.swift index 2465af8..59676ff 100644 --- a/Snap/Constraint.swift +++ b/Snap/Constraint.swift @@ -287,11 +287,6 @@ public class Constraint { // MARK: internal internal func installOnView(updateExisting: Bool = false) -> Array { - if self.installedOnView != nil { - NSException(name: "Cannot Install Constraint", reason: "Already installed", userInfo: nil).raise() - return [] - } - var installOnView: View? = nil if self.toItem.view != nil { installOnView = Constraint.closestCommonSuperviewFromView(self.fromItem.view, toView: self.toItem.view) @@ -306,13 +301,21 @@ public class Constraint { installOnView = self.fromItem.view } - if installedOnView == nil { + if installOnView == nil { NSException(name: "Cannot Install Constraint", reason: "Missing superview", userInfo: nil).raise() return [] } } } + if self.installedOnView != nil { + if self.installedOnView != installOnView { + NSException(name: "Cannot Install Constraint", reason: "Already installed on different view.", userInfo: nil).raise() + return [] + } + return self.installedLayoutConstraints?.allObjects as Array + } + var newLayoutConstraints = Array() let layoutFromAttributes = self.fromItem.attributes.layoutAttributes let layoutToAttributes = self.toItem.attributes.layoutAttributes diff --git a/SnapTests/SnapTests.swift b/SnapTests/SnapTests.swift index 5a82a13..50b41ca 100644 --- a/SnapTests/SnapTests.swift +++ b/SnapTests/SnapTests.swift @@ -140,4 +140,30 @@ class SnapTests: XCTestCase { } + func testReinstallConstraints() { + let v1 = UIView() + let v2 = UIView() + self.container.addSubview(v1) + self.container.addSubview(v2) + + let constraints = v1.snp_prepareConstraints { (make) -> Void in + make.edges.equalTo(v2) + return + } + + XCTAssertEqual(self.container.constraints().count, 0, "Should have 0 constraints installed") + + for constraint in constraints { + constraint.install() + } + + XCTAssertEqual(self.container.constraints().count, 4, "Should have 4 constraints installed") + + for constraint in constraints { + constraint.install() + } + + XCTAssertEqual(self.container.constraints().count, 4, "Should have 0 constraints installed") + } + }