Constraint.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. //
  2. // SnapKit
  3. //
  4. // Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. #if os(iOS) || os(tvOS)
  24. import UIKit
  25. #else
  26. import AppKit
  27. #endif
  28. public final class Constraint {
  29. internal let sourceLocation: (String, UInt)
  30. internal let label: String?
  31. private let from: ConstraintItem
  32. private let to: ConstraintItem
  33. private let relation: ConstraintRelation
  34. private let multiplier: ConstraintMultiplierTarget
  35. private var constant: ConstraintConstantTarget {
  36. didSet {
  37. self.updateConstantAndPriorityIfNeeded()
  38. }
  39. }
  40. private var priority: ConstraintPriorityTarget {
  41. didSet {
  42. self.updateConstantAndPriorityIfNeeded()
  43. }
  44. }
  45. public var layoutConstraints: [LayoutConstraint]
  46. public var isActive: Bool {
  47. for layoutConstraint in self.layoutConstraints {
  48. if layoutConstraint.isActive {
  49. return true
  50. }
  51. }
  52. return false
  53. }
  54. // MARK: Initialization
  55. internal init(from: ConstraintItem,
  56. to: ConstraintItem,
  57. relation: ConstraintRelation,
  58. sourceLocation: (String, UInt),
  59. label: String?,
  60. multiplier: ConstraintMultiplierTarget,
  61. constant: ConstraintConstantTarget,
  62. priority: ConstraintPriorityTarget) {
  63. self.from = from
  64. self.to = to
  65. self.relation = relation
  66. self.sourceLocation = sourceLocation
  67. self.label = label
  68. self.multiplier = multiplier
  69. self.constant = constant
  70. self.priority = priority
  71. self.layoutConstraints = []
  72. // get attributes
  73. let layoutFromAttributes = self.from.attributes.layoutAttributes
  74. let layoutToAttributes = self.to.attributes.layoutAttributes
  75. // get layout from
  76. let layoutFrom = self.from.layoutConstraintItem!
  77. // get relation
  78. let layoutRelation = self.relation.layoutRelation
  79. for layoutFromAttribute in layoutFromAttributes {
  80. // get layout to attribute
  81. let layoutToAttribute: LayoutAttribute
  82. #if os(iOS) || os(tvOS)
  83. if layoutToAttributes.count > 0 {
  84. if self.from.attributes == .edges && self.to.attributes == .margins {
  85. switch layoutFromAttribute {
  86. case .left:
  87. layoutToAttribute = .leftMargin
  88. case .right:
  89. layoutToAttribute = .rightMargin
  90. case .top:
  91. layoutToAttribute = .topMargin
  92. case .bottom:
  93. layoutToAttribute = .bottomMargin
  94. default:
  95. fatalError()
  96. }
  97. } else if self.from.attributes == .margins && self.to.attributes == .edges {
  98. switch layoutFromAttribute {
  99. case .leftMargin:
  100. layoutToAttribute = .left
  101. case .rightMargin:
  102. layoutToAttribute = .right
  103. case .topMargin:
  104. layoutToAttribute = .top
  105. case .bottomMargin:
  106. layoutToAttribute = .bottom
  107. default:
  108. fatalError()
  109. }
  110. } else if self.from.attributes == self.to.attributes {
  111. layoutToAttribute = layoutFromAttribute
  112. } else {
  113. layoutToAttribute = layoutToAttributes[0]
  114. }
  115. } else {
  116. if self.to.target == nil && (layoutFromAttribute == .centerX || layoutFromAttribute == .centerY) {
  117. layoutToAttribute = layoutFromAttribute == .centerX ? .left : .top
  118. } else {
  119. layoutToAttribute = layoutFromAttribute
  120. }
  121. }
  122. #else
  123. if self.from.attributes == self.to.attributes {
  124. layoutToAttribute = layoutFromAttribute
  125. } else if layoutToAttributes.count > 0 {
  126. layoutToAttribute = layoutToAttributes[0]
  127. } else {
  128. layoutToAttribute = layoutFromAttribute
  129. }
  130. #endif
  131. // get layout constant
  132. let layoutConstant: CGFloat = self.constant.constraintConstantTargetValueFor(layoutAttribute: layoutToAttribute)
  133. // get layout to
  134. var layoutTo: AnyObject? = self.to.target
  135. // use superview if possible
  136. if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height {
  137. layoutTo = layoutFrom.superview
  138. }
  139. // create layout constraint
  140. let layoutConstraint = LayoutConstraint(
  141. item: layoutFrom,
  142. attribute: layoutFromAttribute,
  143. relatedBy: layoutRelation,
  144. toItem: layoutTo,
  145. attribute: layoutToAttribute,
  146. multiplier: self.multiplier.constraintMultiplierTargetValue,
  147. constant: layoutConstant
  148. )
  149. // set label
  150. layoutConstraint.label = self.label
  151. // set priority
  152. layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
  153. // set constraint
  154. layoutConstraint.constraint = self
  155. // append
  156. self.layoutConstraints.append(layoutConstraint)
  157. }
  158. }
  159. // MARK: Public
  160. @available(*, deprecated:3.0, message:"Use activate().")
  161. public func install() {
  162. self.activate()
  163. }
  164. @available(*, deprecated:3.0, message:"Use deactivate().")
  165. public func uninstall() {
  166. self.deactivate()
  167. }
  168. public func activate() {
  169. self.activateIfNeeded()
  170. }
  171. public func deactivate() {
  172. self.deactivateIfNeeded()
  173. }
  174. @discardableResult
  175. public func update(offset: ConstraintOffsetTarget) -> Constraint {
  176. self.constant = offset.constraintOffsetTargetValue
  177. return self
  178. }
  179. @discardableResult
  180. public func update(inset: ConstraintInsetTarget) -> Constraint {
  181. self.constant = inset.constraintInsetTargetValue
  182. return self
  183. }
  184. @discardableResult
  185. public func update(priority: ConstraintPriorityTarget) -> Constraint {
  186. self.priority = priority.constraintPriorityTargetValue
  187. return self
  188. }
  189. @available(*, deprecated:3.0, message:"Use update(offset: ConstraintOffsetTarget) instead.")
  190. public func updateOffset(amount: ConstraintOffsetTarget) -> Void { self.update(offset: amount) }
  191. @available(*, deprecated:3.0, message:"Use update(inset: ConstraintInsetTarget) instead.")
  192. public func updateInsets(amount: ConstraintInsetTarget) -> Void { self.update(inset: amount) }
  193. @available(*, deprecated:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  194. public func updatePriority(amount: ConstraintPriorityTarget) -> Void { self.update(priority: amount) }
  195. @available(*, obsoleted:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  196. public func updatePriorityRequired() -> Void {}
  197. @available(*, obsoleted:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  198. public func updatePriorityHigh() -> Void { fatalError("Must be implemented by Concrete subclass.") }
  199. @available(*, obsoleted:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  200. public func updatePriorityMedium() -> Void { fatalError("Must be implemented by Concrete subclass.") }
  201. @available(*, obsoleted:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  202. public func updatePriorityLow() -> Void { fatalError("Must be implemented by Concrete subclass.") }
  203. // MARK: Internal
  204. internal func updateConstantAndPriorityIfNeeded() {
  205. for layoutConstraint in self.layoutConstraints {
  206. let attribute = (layoutConstraint.secondAttribute == .notAnAttribute) ? layoutConstraint.firstAttribute : layoutConstraint.secondAttribute
  207. layoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: attribute)
  208. let requiredPriority = ConstraintPriority.required.value
  209. if (layoutConstraint.priority.rawValue < requiredPriority), (self.priority.constraintPriorityTargetValue != requiredPriority) {
  210. layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
  211. }
  212. }
  213. }
  214. internal func activateIfNeeded(updatingExisting: Bool = false) {
  215. guard let item = self.from.layoutConstraintItem else {
  216. print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
  217. return
  218. }
  219. let layoutConstraints = self.layoutConstraints
  220. if updatingExisting {
  221. var existingLayoutConstraints: [LayoutConstraint] = []
  222. for constraint in item.constraints {
  223. existingLayoutConstraints += constraint.layoutConstraints
  224. }
  225. for layoutConstraint in layoutConstraints {
  226. let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
  227. guard let updateLayoutConstraint = existingLayoutConstraint else {
  228. fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
  229. }
  230. let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
  231. updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
  232. }
  233. } else {
  234. NSLayoutConstraint.activate(layoutConstraints)
  235. item.add(constraints: [self])
  236. }
  237. }
  238. internal func deactivateIfNeeded() {
  239. guard let item = self.from.layoutConstraintItem else {
  240. print("WARNING: SnapKit failed to get from item from constraint. Deactivate will be a no-op.")
  241. return
  242. }
  243. let layoutConstraints = self.layoutConstraints
  244. NSLayoutConstraint.deactivate(layoutConstraints)
  245. item.remove(constraints: [self])
  246. }
  247. }