123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- //
- // 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 final 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()
- }
- }
- public var layoutConstraints: [LayoutConstraint]
-
- public var isActive: Bool {
- set {
- if newValue {
- activate()
- }
- else {
- deactivate()
- }
- }
-
- get {
- for layoutConstraint in self.layoutConstraints {
- if layoutConstraint.isActive {
- return true
- }
- }
- return false
- }
- }
-
- // 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 = []
- // get attributes
- let layoutFromAttributes = self.from.attributes.layoutAttributes
- let layoutToAttributes = self.to.attributes.layoutAttributes
- // get layout from
- let layoutFrom = self.from.layoutConstraintItem!
- // get relation
- let layoutRelation = self.relation.layoutRelation
- for layoutFromAttribute in layoutFromAttributes {
- // get layout to attribute
- let layoutToAttribute: LayoutAttribute
- #if os(iOS) || os(tvOS)
- if layoutToAttributes.count > 0 {
- 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 if self.from.attributes == .directionalEdges && self.to.attributes == .directionalMargins {
- switch layoutFromAttribute {
- case .leading:
- layoutToAttribute = .leadingMargin
- case .trailing:
- layoutToAttribute = .trailingMargin
- case .top:
- layoutToAttribute = .topMargin
- case .bottom:
- layoutToAttribute = .bottomMargin
- default:
- fatalError()
- }
- } else if self.from.attributes == .directionalMargins && self.to.attributes == .directionalEdges {
- switch layoutFromAttribute {
- case .leadingMargin:
- layoutToAttribute = .leading
- case .trailingMargin:
- layoutToAttribute = .trailing
- case .topMargin:
- layoutToAttribute = .top
- case .bottomMargin:
- layoutToAttribute = .bottom
- default:
- fatalError()
- }
- } else if self.from.attributes == self.to.attributes {
- layoutToAttribute = layoutFromAttribute
- } else {
- layoutToAttribute = layoutToAttributes[0]
- }
- } else {
- if self.to.target == nil && (layoutFromAttribute == .centerX || layoutFromAttribute == .centerY) {
- layoutToAttribute = layoutFromAttribute == .centerX ? .left : .top
- } else {
- layoutToAttribute = layoutFromAttribute
- }
- }
- #else
- if self.from.attributes == self.to.attributes {
- 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 = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
- // set constraint
- layoutConstraint.constraint = self
- // append
- self.layoutConstraints.append(layoutConstraint)
- }
- }
- // MARK: Public
- @available(*, deprecated, renamed:"activate()")
- public func install() {
- self.activate()
- }
- @available(*, deprecated, renamed:"deactivate()")
- public func uninstall() {
- self.deactivate()
- }
- 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
- }
- #if os(iOS) || os(tvOS)
- @discardableResult
- @available(iOS 11.0, tvOS 11.0, *)
- public func update(inset: ConstraintDirectionalInsetTarget) -> Constraint {
- self.constant = inset.constraintDirectionalInsetTargetValue
- return self
- }
- #endif
- @discardableResult
- public func update(priority: ConstraintPriorityTarget) -> Constraint {
- self.priority = priority.constraintPriorityTargetValue
- return self
- }
- @discardableResult
- public func update(priority: ConstraintPriority) -> Constraint {
- self.priority = priority.value
- return self
- }
- @available(*, deprecated, renamed:"update(offset:)")
- public func updateOffset(amount: ConstraintOffsetTarget) -> Void { self.update(offset: amount) }
- @available(*, deprecated, renamed:"update(inset:)")
- public func updateInsets(amount: ConstraintInsetTarget) -> Void { self.update(inset: amount) }
- @available(*, deprecated, renamed:"update(priority:)")
- public func updatePriority(amount: ConstraintPriorityTarget) -> Void { self.update(priority: amount) }
- @available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
- public func updatePriorityRequired() -> Void {}
- @available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
- public func updatePriorityHigh() -> Void { fatalError("Must be implemented by Concrete subclass.") }
- @available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
- public func updatePriorityMedium() -> Void { fatalError("Must be implemented by Concrete subclass.") }
- @available(*, deprecated, 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 {
- let attribute = (layoutConstraint.secondAttribute == .notAnAttribute) ? layoutConstraint.firstAttribute : layoutConstraint.secondAttribute
- layoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: attribute)
- let requiredPriority = ConstraintPriority.required.value
- if (layoutConstraint.priority.rawValue < requiredPriority), (self.priority.constraintPriorityTargetValue != requiredPriority) {
- layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
- }
- }
- }
- internal func activateIfNeeded(updatingExisting: Bool = false) {
- guard let item = self.from.layoutConstraintItem else {
- print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
- return
- }
- let layoutConstraints = self.layoutConstraints
- if updatingExisting {
- var existingLayoutConstraints: [LayoutConstraint] = []
- for constraint in item.constraints {
- existingLayoutConstraints += constraint.layoutConstraints
- }
- 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)
- item.add(constraints: [self])
- }
- }
- internal func deactivateIfNeeded() {
- guard let item = self.from.layoutConstraintItem else {
- print("WARNING: SnapKit failed to get from item from constraint. Deactivate will be a no-op.")
- return
- }
- let layoutConstraints = self.layoutConstraints
- NSLayoutConstraint.deactivate(layoutConstraints)
- item.remove(constraints: [self])
- }
- }
|