You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

306 lines
12 KiB

5 years ago
  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. set {
  48. if newValue {
  49. activate()
  50. }
  51. else {
  52. deactivate()
  53. }
  54. }
  55. get {
  56. for layoutConstraint in self.layoutConstraints {
  57. if layoutConstraint.isActive {
  58. return true
  59. }
  60. }
  61. return false
  62. }
  63. }
  64. // MARK: Initialization
  65. internal init(from: ConstraintItem,
  66. to: ConstraintItem,
  67. relation: ConstraintRelation,
  68. sourceLocation: (String, UInt),
  69. label: String?,
  70. multiplier: ConstraintMultiplierTarget,
  71. constant: ConstraintConstantTarget,
  72. priority: ConstraintPriorityTarget) {
  73. self.from = from
  74. self.to = to
  75. self.relation = relation
  76. self.sourceLocation = sourceLocation
  77. self.label = label
  78. self.multiplier = multiplier
  79. self.constant = constant
  80. self.priority = priority
  81. self.layoutConstraints = []
  82. // get attributes
  83. let layoutFromAttributes = self.from.attributes.layoutAttributes
  84. let layoutToAttributes = self.to.attributes.layoutAttributes
  85. // get layout from
  86. let layoutFrom = self.from.layoutConstraintItem!
  87. // get relation
  88. let layoutRelation = self.relation.layoutRelation
  89. for layoutFromAttribute in layoutFromAttributes {
  90. // get layout to attribute
  91. let layoutToAttribute: LayoutAttribute
  92. #if os(iOS) || os(tvOS)
  93. if layoutToAttributes.count > 0 {
  94. if self.from.attributes == .edges && self.to.attributes == .margins {
  95. switch layoutFromAttribute {
  96. case .left:
  97. layoutToAttribute = .leftMargin
  98. case .right:
  99. layoutToAttribute = .rightMargin
  100. case .top:
  101. layoutToAttribute = .topMargin
  102. case .bottom:
  103. layoutToAttribute = .bottomMargin
  104. default:
  105. fatalError()
  106. }
  107. } else if self.from.attributes == .margins && self.to.attributes == .edges {
  108. switch layoutFromAttribute {
  109. case .leftMargin:
  110. layoutToAttribute = .left
  111. case .rightMargin:
  112. layoutToAttribute = .right
  113. case .topMargin:
  114. layoutToAttribute = .top
  115. case .bottomMargin:
  116. layoutToAttribute = .bottom
  117. default:
  118. fatalError()
  119. }
  120. } else if self.from.attributes == self.to.attributes {
  121. layoutToAttribute = layoutFromAttribute
  122. } else {
  123. layoutToAttribute = layoutToAttributes[0]
  124. }
  125. } else {
  126. if self.to.target == nil && (layoutFromAttribute == .centerX || layoutFromAttribute == .centerY) {
  127. layoutToAttribute = layoutFromAttribute == .centerX ? .left : .top
  128. } else {
  129. layoutToAttribute = layoutFromAttribute
  130. }
  131. }
  132. #else
  133. if self.from.attributes == self.to.attributes {
  134. layoutToAttribute = layoutFromAttribute
  135. } else if layoutToAttributes.count > 0 {
  136. layoutToAttribute = layoutToAttributes[0]
  137. } else {
  138. layoutToAttribute = layoutFromAttribute
  139. }
  140. #endif
  141. // get layout constant
  142. let layoutConstant: CGFloat = self.constant.constraintConstantTargetValueFor(layoutAttribute: layoutToAttribute)
  143. // get layout to
  144. var layoutTo: AnyObject? = self.to.target
  145. // use superview if possible
  146. if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height {
  147. layoutTo = layoutFrom.superview
  148. }
  149. // create layout constraint
  150. let layoutConstraint = LayoutConstraint(
  151. item: layoutFrom,
  152. attribute: layoutFromAttribute,
  153. relatedBy: layoutRelation,
  154. toItem: layoutTo,
  155. attribute: layoutToAttribute,
  156. multiplier: self.multiplier.constraintMultiplierTargetValue,
  157. constant: layoutConstant
  158. )
  159. // set label
  160. layoutConstraint.label = self.label
  161. // set priority
  162. layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
  163. // set constraint
  164. layoutConstraint.constraint = self
  165. // append
  166. self.layoutConstraints.append(layoutConstraint)
  167. }
  168. }
  169. // MARK: Public
  170. @available(*, deprecated:3.0, message:"Use activate().")
  171. public func install() {
  172. self.activate()
  173. }
  174. @available(*, deprecated:3.0, message:"Use deactivate().")
  175. public func uninstall() {
  176. self.deactivate()
  177. }
  178. public func activate() {
  179. self.activateIfNeeded()
  180. }
  181. public func deactivate() {
  182. self.deactivateIfNeeded()
  183. }
  184. @discardableResult
  185. public func update(offset: ConstraintOffsetTarget) -> Constraint {
  186. self.constant = offset.constraintOffsetTargetValue
  187. return self
  188. }
  189. @discardableResult
  190. public func update(inset: ConstraintInsetTarget) -> Constraint {
  191. self.constant = inset.constraintInsetTargetValue
  192. return self
  193. }
  194. @discardableResult
  195. public func update(priority: ConstraintPriorityTarget) -> Constraint {
  196. self.priority = priority.constraintPriorityTargetValue
  197. return self
  198. }
  199. @discardableResult
  200. public func update(priority: ConstraintPriority) -> Constraint {
  201. self.priority = priority.value
  202. return self
  203. }
  204. @available(*, deprecated:3.0, message:"Use update(offset: ConstraintOffsetTarget) instead.")
  205. public func updateOffset(amount: ConstraintOffsetTarget) -> Void { self.update(offset: amount) }
  206. @available(*, deprecated:3.0, message:"Use update(inset: ConstraintInsetTarget) instead.")
  207. public func updateInsets(amount: ConstraintInsetTarget) -> Void { self.update(inset: amount) }
  208. @available(*, deprecated:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  209. public func updatePriority(amount: ConstraintPriorityTarget) -> Void { self.update(priority: amount) }
  210. @available(*, obsoleted:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  211. public func updatePriorityRequired() -> Void {}
  212. @available(*, obsoleted:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  213. public func updatePriorityHigh() -> Void { fatalError("Must be implemented by Concrete subclass.") }
  214. @available(*, obsoleted:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  215. public func updatePriorityMedium() -> Void { fatalError("Must be implemented by Concrete subclass.") }
  216. @available(*, obsoleted:3.0, message:"Use update(priority: ConstraintPriorityTarget) instead.")
  217. public func updatePriorityLow() -> Void { fatalError("Must be implemented by Concrete subclass.") }
  218. // MARK: Internal
  219. internal func updateConstantAndPriorityIfNeeded() {
  220. for layoutConstraint in self.layoutConstraints {
  221. let attribute = (layoutConstraint.secondAttribute == .notAnAttribute) ? layoutConstraint.firstAttribute : layoutConstraint.secondAttribute
  222. layoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: attribute)
  223. let requiredPriority = ConstraintPriority.required.value
  224. if (layoutConstraint.priority.rawValue < requiredPriority), (self.priority.constraintPriorityTargetValue != requiredPriority) {
  225. layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
  226. }
  227. }
  228. }
  229. internal func activateIfNeeded(updatingExisting: Bool = false) {
  230. guard let item = self.from.layoutConstraintItem else {
  231. print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
  232. return
  233. }
  234. let layoutConstraints = self.layoutConstraints
  235. if updatingExisting {
  236. var existingLayoutConstraints: [LayoutConstraint] = []
  237. for constraint in item.constraints {
  238. existingLayoutConstraints += constraint.layoutConstraints
  239. }
  240. for layoutConstraint in layoutConstraints {
  241. let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
  242. guard let updateLayoutConstraint = existingLayoutConstraint else {
  243. fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
  244. }
  245. let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
  246. updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
  247. }
  248. } else {
  249. NSLayoutConstraint.activate(layoutConstraints)
  250. item.add(constraints: [self])
  251. }
  252. }
  253. internal func deactivateIfNeeded() {
  254. guard let item = self.from.layoutConstraintItem else {
  255. print("WARNING: SnapKit failed to get from item from constraint. Deactivate will be a no-op.")
  256. return
  257. }
  258. let layoutConstraints = self.layoutConstraints
  259. NSLayoutConstraint.deactivate(layoutConstraints)
  260. item.remove(constraints: [self])
  261. }
  262. }