From 9ef9ae6f9f320f391fe43f6002a2bc13ae2bf4a2 Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Thu, 30 Jul 2015 18:49:47 +1200 Subject: [PATCH] Improve debugging and allow width/height constraints to be installed on from view --- Source/Constraint.swift | 26 +++++++++++++++--------- Source/ConstraintMaker.swift | 39 +++++++++++++++++++++++++----------- Source/Debugging.swift | 28 ++++++++++++++++++++++---- Source/View+SnapKit.swift | 18 ++++++++--------- 4 files changed, 76 insertions(+), 35 deletions(-) diff --git a/Source/Constraint.swift b/Source/Constraint.swift index 859e086..79d207e 100644 --- a/Source/Constraint.swift +++ b/Source/Constraint.swift @@ -58,6 +58,9 @@ public class Constraint { public func updatePriorityMedium() -> Void { fatalError("Must be implemented by Concrete subclass.") } public func updatePriorityLow() -> Void { fatalError("Must be implemented by Concrete subclass.") } + internal var makerFile: String = "Unknown" + internal var makerLine: UInt = 0 + } /** @@ -191,23 +194,26 @@ internal class ConcreteConstraint: Constraint { self.priority = priority } - internal func installOnView(updateExisting: Bool = false) -> [LayoutConstraint] { + internal func installOnView(updateExisting: Bool = false, file: String? = nil, line: UInt? = nil) -> [LayoutConstraint] { var installOnView: View? = nil if self.toItem.view != nil { installOnView = closestCommonSuperviewBetween(self.fromItem.view, self.toItem.view) if installOnView == nil { - NSException(name: "Cannot Install Constraint", reason: "No common superview between views", userInfo: nil).raise() + NSException(name: "Cannot Install Constraint", reason: "No common superview between views (@\(self.makerFile)#\(self.makerLine))", userInfo: nil).raise() return [] } } else { - installOnView = self.fromItem.view?.superview - if installOnView == nil { - if self.fromItem.attributes == ConstraintAttributes.Width || self.fromItem.attributes == ConstraintAttributes.Height { - installOnView = self.fromItem.view - } - + + let widthAttr = ConstraintAttributes.Width + let heightAttr = ConstraintAttributes.Height + let sizeAttrs = widthAttr | heightAttr + + if self.fromItem.attributes == widthAttr || self.fromItem.attributes == heightAttr || self.fromItem.attributes == sizeAttrs { + installOnView = self.fromItem.view + } else { + installOnView = self.fromItem.view?.superview if installOnView == nil { - NSException(name: "Cannot Install Constraint", reason: "Missing superview", userInfo: nil).raise() + NSException(name: "Cannot Install Constraint", reason: "Missing superview (@\(self.makerFile)#\(self.makerLine))", userInfo: nil).raise() return [] } } @@ -215,7 +221,7 @@ internal class ConcreteConstraint: Constraint { if let installedOnView = self.installInfo?.view { if installedOnView != installOnView { - NSException(name: "Cannot Install Constraint", reason: "Already installed on different view.", userInfo: nil).raise() + NSException(name: "Cannot Install Constraint", reason: "Already installed on different view. (@\(self.makerFile)#\(self.makerLine))", userInfo: nil).raise() return [] } return self.installInfo?.layoutConstraints.allObjects as? [LayoutConstraint] ?? [] diff --git a/Source/ConstraintMaker.swift b/Source/ConstraintMaker.swift index 746c49f..04ffb02 100644 --- a/Source/ConstraintMaker.swift +++ b/Source/ConstraintMaker.swift @@ -115,10 +115,14 @@ public class ConstraintMaker { #endif - internal init(view: View) { + internal init(view: View, file: String, line: UInt) { self.view = view + self.file = file + self.line = line } + internal let file: String + internal let line: UInt internal let view: View internal var constraintDescriptions = [ConstraintDescription]() @@ -129,60 +133,71 @@ public class ConstraintMaker { return constraintDescription } - internal class func prepareConstraints(view: View, @noescape closure: (make: ConstraintMaker) -> Void) -> [Constraint] { - let maker = ConstraintMaker(view: view) + internal class func prepareConstraints(#view: View, file: String = "Unknown", line: UInt = 0, @noescape closure: (make: ConstraintMaker) -> Void) -> [Constraint] { + let maker = ConstraintMaker(view: view, file: file, line: line) closure(make: maker) - return maker.constraintDescriptions.map { $0.constraint } + let constraints = maker.constraintDescriptions.map { $0.constraint } + for constraint in constraints { + constraint.makerFile = maker.file + constraint.makerLine = maker.line + } + return constraints } - internal class func makeConstraints(view: View, @noescape closure: (make: ConstraintMaker) -> Void) { + internal class func makeConstraints(#view: View, file: String = "Unknown", line: UInt = 0, @noescape closure: (make: ConstraintMaker) -> Void) { #if os(iOS) view.setTranslatesAutoresizingMaskIntoConstraints(false) #else view.translatesAutoresizingMaskIntoConstraints = false #endif - let maker = ConstraintMaker(view: view) + let maker = ConstraintMaker(view: view, file: file, line: line) closure(make: maker) let constraints = maker.constraintDescriptions.map { $0.constraint as! ConcreteConstraint } for constraint in constraints { + constraint.makerFile = maker.file + constraint.makerLine = maker.line constraint.installOnView(updateExisting: false) } } - internal class func remakeConstraints(view: View, @noescape closure: (make: ConstraintMaker) -> Void) { + internal class func remakeConstraints(#view: View, file: String = "Unknown", line: UInt = 0, @noescape closure: (make: ConstraintMaker) -> Void) { #if os(iOS) view.setTranslatesAutoresizingMaskIntoConstraints(false) #else view.translatesAutoresizingMaskIntoConstraints = false #endif - let maker = ConstraintMaker(view: view) + let maker = ConstraintMaker(view: view, file: file, line: line) closure(make: maker) - self.removeConstraints(view) + self.removeConstraints(view: view) let constraints = maker.constraintDescriptions.map { $0.constraint as! ConcreteConstraint } for constraint in constraints { + constraint.makerFile = maker.file + constraint.makerLine = maker.line constraint.installOnView(updateExisting: false) } } - internal class func updateConstraints(view: View, @noescape closure: (make: ConstraintMaker) -> Void) { + internal class func updateConstraints(#view: View, file: String = "Unknown", line: UInt = 0, @noescape closure: (make: ConstraintMaker) -> Void) { #if os(iOS) view.setTranslatesAutoresizingMaskIntoConstraints(false) #else view.translatesAutoresizingMaskIntoConstraints = false #endif - let maker = ConstraintMaker(view: view) + let maker = ConstraintMaker(view: view, file: file, line: line) closure(make: maker) let constraints = maker.constraintDescriptions.map { $0.constraint as! ConcreteConstraint} for constraint in constraints { + constraint.makerFile = maker.file + constraint.makerLine = maker.line constraint.installOnView(updateExisting: true) } } - internal class func removeConstraints(view: View) { + internal class func removeConstraints(#view: View) { for existingLayoutConstraint in view.snp_installedLayoutConstraints { existingLayoutConstraint.snp_constraint?.uninstall() } diff --git a/Source/Debugging.swift b/Source/Debugging.swift index ca2c4ee..c4e356b 100644 --- a/Source/Debugging.swift +++ b/Source/Debugging.swift @@ -100,18 +100,38 @@ public extension LayoutConstraint { return description } + internal var snp_makerFile: String? { + return self.snp_constraint?.makerFile + } + + internal var snp_makerLine: UInt? { + return self.snp_constraint?.makerLine + } + } private var labelKey = "" private func descriptionForObject(object: AnyObject) -> String { - let pointerDescription = NSString(format: "%p", [object]) + let pointerDescription = NSString(format: "%p", ObjectIdentifier(object).uintValue) + var desc = "" + + desc += object.dynamicType.description() + if let object = object as? View { - return "<\(object.dynamicType.description()):\(object.snp_label ?? pointerDescription)>" + desc += ":\(object.snp_label ?? pointerDescription)" } else if let object = object as? LayoutConstraint { - return "<\(object.dynamicType.description()):\(object.snp_label ?? pointerDescription)>" + desc += ":\(object.snp_label ?? pointerDescription)" + } else { + desc += ":\(pointerDescription)" } - return "<\(object.dynamicType.description()):\(pointerDescription)>" + + if let object = object as? LayoutConstraint, let file = object.snp_makerFile, let line = object.snp_makerLine { + desc += "@\(file)#\(line)" + } + + desc += "" + return desc } private extension NSLayoutRelation { diff --git a/Source/View+SnapKit.swift b/Source/View+SnapKit.swift index 5141879..dc47346 100644 --- a/Source/View+SnapKit.swift +++ b/Source/View+SnapKit.swift @@ -124,8 +124,8 @@ public extension View { :returns: the constraints made */ - public func snp_prepareConstraints(@noescape closure: (make: ConstraintMaker) -> Void) -> [Constraint] { - return ConstraintMaker.prepareConstraints(self, closure: closure) + public func snp_prepareConstraints(file: String = __FILE__, line: UInt = __LINE__, @noescape closure: (make: ConstraintMaker) -> Void) -> [Constraint] { + return ConstraintMaker.prepareConstraints(view: self, file: file, line: line, closure: closure) } /** @@ -133,8 +133,8 @@ public extension View { :param: closure that will be passed the `ConstraintMaker` to make the constraints with */ - public func snp_makeConstraints(@noescape closure: (make: ConstraintMaker) -> Void) -> Void { - ConstraintMaker.makeConstraints(self, closure: closure) + public func snp_makeConstraints(file: String = __FILE__, line: UInt = __LINE__, @noescape closure: (make: ConstraintMaker) -> Void) -> Void { + ConstraintMaker.makeConstraints(view: self, file: file, line: line, closure: closure) } /** @@ -144,8 +144,8 @@ public extension View { :param: closure that will be passed the `ConstraintMaker` to update the constraints with */ - public func snp_updateConstraints(@noescape closure: (make: ConstraintMaker) -> Void) -> Void { - ConstraintMaker.updateConstraints(self, closure: closure) + public func snp_updateConstraints(file: String = __FILE__, line: UInt = __LINE__, @noescape closure: (make: ConstraintMaker) -> Void) -> Void { + ConstraintMaker.updateConstraints(view: self, file: file, line: line, closure: closure) } /** @@ -153,15 +153,15 @@ public extension View { :param: closure that will be passed the `ConstraintMaker` to remake the constraints with */ - public func snp_remakeConstraints(@noescape closure: (make: ConstraintMaker) -> Void) -> Void { - ConstraintMaker.remakeConstraints(self, closure: closure) + public func snp_remakeConstraints(file: String = __FILE__, line: UInt = __LINE__, @noescape closure: (make: ConstraintMaker) -> Void) -> Void { + ConstraintMaker.remakeConstraints(view: self, file: file, line: line, closure: closure) } /** Removes all previously made constraints. */ public func snp_removeConstraints() { - ConstraintMaker.removeConstraints(self) + ConstraintMaker.removeConstraints(view: self) } internal var snp_installedLayoutConstraints: [LayoutConstraint] {