// // SnapKit // // Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit // // 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. #if os(iOS) || os(tvOS) import UIKit #else import AppKit #endif public class Constraint { internal let sourceLocation: (String, UInt) internal let label: String? private let from: ConstraintItem private let to: ConstraintItem private let relation: ConstraintRelation private let multiplier: ConstraintMultiplierTarget private var constant: ConstraintConstantTarget { didSet { self.updateConstantAndPriorityIfNeeded() } } private var priority: ConstraintPriorityTarget { didSet { self.updateConstantAndPriorityIfNeeded() } } private let layoutConstraints: NSHashTable // MARK: Initialization internal init(from: ConstraintItem, to: ConstraintItem, relation: ConstraintRelation, sourceLocation: (String, UInt), label: String?, multiplier: ConstraintMultiplierTarget, constant: ConstraintConstantTarget, priority: ConstraintPriorityTarget) { self.from = from self.to = to self.relation = relation self.sourceLocation = sourceLocation self.label = label self.multiplier = multiplier self.constant = constant self.priority = priority self.layoutConstraints = NSHashTable.weakObjects() // get attributes let layoutFromAttributes = self.from.attributes.layoutAttributes let layoutToAttributes = self.to.attributes.layoutAttributes // get layout from let layoutFrom: ConstraintView = self.from.view! // get relation let layoutRelation = self.relation.layoutRelation for layoutFromAttribute in layoutFromAttributes { // get layout to attribute let layoutToAttribute: NSLayoutAttribute #if os(iOS) || os(tvOS) if layoutToAttributes.count > 1 { if self.from.attributes == .edges && self.to.attributes == .margins { switch layoutFromAttribute { case .left: layoutToAttribute = .leftMargin case .right: layoutToAttribute = .rightMargin case .top: layoutToAttribute = .topMargin case .bottom: layoutToAttribute = .bottomMargin default: fatalError() } } else if self.from.attributes == .margins && self.to.attributes == .edges { switch layoutFromAttribute { case .leftMargin: layoutToAttribute = .left case .rightMargin: layoutToAttribute = .right case .topMargin: layoutToAttribute = .top case .bottomMargin: layoutToAttribute = .bottom default: fatalError() } } else { layoutToAttribute = layoutToAttributes[0] } } else if layoutToAttributes.count == 1 { layoutToAttribute = layoutToAttributes[0] } else { layoutToAttribute = layoutFromAttribute } #else if layoutToAttributes.count > 0 { layoutToAttribute = layoutToAttributes[0] } else { layoutToAttribute = layoutFromAttribute } #endif // get layout constant let layoutConstant: CGFloat = self.constant.constraintConstantTargetValueFor(layoutAttribute: layoutToAttribute) // get layout to var layoutTo: AnyObject? = self.to.target // use superview if possible if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height { layoutTo = layoutFrom.superview } // create layout constraint let layoutConstraint = LayoutConstraint( item: layoutFrom, attribute: layoutFromAttribute, relatedBy: layoutRelation, toItem: layoutTo, attribute: layoutToAttribute, multiplier: self.multiplier.constraintMultiplierTargetValue, constant: layoutConstant ) // set label layoutConstraint.label = self.label // set priority layoutConstraint.priority = self.priority.constraintPriorityTargetValue // set constraint layoutConstraint.constraint = self // append self.layoutConstraints.add(layoutConstraint) } } // MARK: Public public func activate() { self.activateIfNeeded() } public func deactivate() { self.deactivateIfNeeded() } @discardableResult public func update(offset: ConstraintOffsetTarget) -> Constraint { self.constant = offset.constraintOffsetTargetValue return self } @discardableResult public func update(inset: ConstraintInsetTarget) -> Constraint { self.constant = inset.constraintInsetTargetValue return self } @discardableResult public func update(priority: ConstraintPriorityTarget) -> Constraint { self.priority = priority.constraintPriorityTargetValue return self } @available(*, deprecated:0.40.0, message:"Use update(offset: ConstraintOffsetTarget) instead.") public func updateOffset(amount: ConstraintOffsetTarget) -> Void { self.update(offset: amount) } @available(*, deprecated:0.40.0, message:"Use update(inset: ConstraintInsetTarget) instead.") public func updateInsets(amount: ConstraintInsetTarget) -> Void { self.update(inset: amount) } @available(*, deprecated:0.40.0, message:"Use update(priority: ConstraintPriorityTarget) instead.") public func updatePriority(amount: ConstraintPriorityTarget) -> Void { self.update(priority: amount) } @available(*, obsoleted:0.40.0, message:"Use update(priority: ConstraintPriorityTarget) instead.") public func updatePriorityRequired() -> Void {} @available(*, obsoleted:0.40.0, message:"Use update(priority: ConstraintPriorityTarget) instead.") public func updatePriorityHigh() -> Void { fatalError("Must be implemented by Concrete subclass.") } @available(*, obsoleted:0.40.0, message:"Use update(priority: ConstraintPriorityTarget) instead.") public func updatePriorityMedium() -> Void { fatalError("Must be implemented by Concrete subclass.") } @available(*, obsoleted:0.40.0, message:"Use update(priority: ConstraintPriorityTarget) instead.") public func updatePriorityLow() -> Void { fatalError("Must be implemented by Concrete subclass.") } // MARK: Internal internal func updateConstantAndPriorityIfNeeded() { for layoutConstraint in self.layoutConstraints.allObjects { let attribute = (layoutConstraint.secondAttribute == .notAnAttribute) ? layoutConstraint.firstAttribute : layoutConstraint.secondAttribute layoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: attribute) layoutConstraint.priority = self.priority.constraintPriorityTargetValue } } internal func activateIfNeeded(updatingExisting: Bool = false) { let view = self.from.view! let layoutConstraints = self.layoutConstraints.allObjects let existingLayoutConstraints = view.snp.layoutConstraints if updatingExisting && existingLayoutConstraints.count > 0 { for layoutConstraint in layoutConstraints { let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint } guard let updateLayoutConstraint = existingLayoutConstraint else { fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)") } let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute) } } else { NSLayoutConstraint.activate(layoutConstraints) view.snp.add(layoutConstraints: layoutConstraints) } } internal func deactivateIfNeeded() { let view = self.from.view! let layoutConstraints = self.layoutConstraints.allObjects NSLayoutConstraint.deactivate(layoutConstraints) view.snp.remove(layoutConstraints: layoutConstraints) } }