Constraint.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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 class Constraint {
  29. internal let sourceLocation: (String, UInt)
  30. private let from: ConstraintItem
  31. private let to: ConstraintItem
  32. private let relation: ConstraintRelation
  33. private let multiplier: ConstraintMultiplierTarget
  34. private var constant: ConstraintConstantTarget
  35. private var priority: ConstraintPriorityTarget
  36. private var installInfo: ConstraintInstallInfo? = nil
  37. // MARK: Initialization
  38. internal init(from: ConstraintItem,
  39. to: ConstraintItem,
  40. relation: ConstraintRelation,
  41. sourceLocation: (String, UInt),
  42. multiplier: ConstraintMultiplierTarget,
  43. constant: ConstraintConstantTarget,
  44. priority: ConstraintPriorityTarget) {
  45. self.from = from
  46. self.to = to
  47. self.relation = relation
  48. self.sourceLocation = sourceLocation
  49. self.multiplier = multiplier
  50. self.constant = constant
  51. self.priority = priority
  52. }
  53. // MARK: Public
  54. public func install() -> [NSLayoutConstraint] {
  55. return self.installIfNeeded(updateExisting: false)
  56. }
  57. public func uninstall() {
  58. self.uninstallIfNeeded()
  59. }
  60. @available(iOS 8.0, OSX 10.10, *)
  61. public func activate() {
  62. self.activateIfNeeded()
  63. }
  64. @available(iOS 8.0, OSX 10.10, *)
  65. public func deactivate() {
  66. self.deactivateIfNeeded()
  67. }
  68. // MARK: Internal
  69. internal func installIfNeeded(updateExisting: Bool = false) -> [NSLayoutConstraint] {
  70. let installOnView: ConstraintView?
  71. if let view = self.to.view {
  72. guard let closestSuperview = closestCommonSuperviewFromView(self.from.view, toView: view) else {
  73. fatalError("Cannot Install Constraint. No common superview. (\(self.sourceLocation.0), \(self.sourceLocation.1))")
  74. }
  75. installOnView = closestSuperview
  76. } else if self.from.attributes.isSubset(of: ConstraintAttributes.Width + ConstraintAttributes.Height) {
  77. installOnView = self.from.view
  78. } else {
  79. guard let superview = self.from.view?.superview else {
  80. fatalError("Cannot Install Constraint. No superview. (\(self.sourceLocation.0), \(self.sourceLocation.1))")
  81. }
  82. installOnView = superview
  83. }
  84. guard self.installInfo?.view == nil ||
  85. self.installInfo?.view == installOnView else {
  86. fatalError("Cannot Install Constraint. Already installed on different view. (\(self.sourceLocation.0), \(self.sourceLocation.1))")
  87. }
  88. // setup array to store new layout constraints
  89. var newLayoutConstraints = [LayoutConstraint]()
  90. // get attributes
  91. let layoutFromAttributes = self.from.attributes.layoutAttributes
  92. let layoutToAttributes = self.to.attributes.layoutAttributes
  93. // get layout from
  94. let layoutFrom: ConstraintView = self.from.view!
  95. // get relation
  96. let layoutRelation = self.relation.layoutRelation
  97. for layoutFromAttribute in layoutFromAttributes {
  98. // get layout to attribute
  99. let layoutToAttribute = (layoutToAttributes.count > 0) ? layoutToAttributes[0] : layoutFromAttribute
  100. // get layout constant
  101. let layoutConstant: CGFloat = self.constant.layoutConstantForLayoutAttribute(layoutToAttribute)
  102. // get layout to
  103. #if os(iOS) || os(tvOS)
  104. var layoutTo: AnyObject? = self.to.view ?? self.to.layoutSupport
  105. #else
  106. var layoutTo: AnyObject? = self.to.view
  107. #endif
  108. if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height {
  109. layoutTo = installOnView
  110. }
  111. // create layout constraint
  112. let layoutConstraint = LayoutConstraint(
  113. item: layoutFrom,
  114. attribute: layoutFromAttribute,
  115. relatedBy: layoutRelation,
  116. toItem: layoutTo,
  117. attribute: layoutToAttribute,
  118. multiplier: self.multiplier.constraintMultiplierTargetValue,
  119. constant: layoutConstant
  120. )
  121. // set priority
  122. layoutConstraint.priority = self.priority.constraintPriorityTargetValue
  123. // set constraint
  124. layoutConstraint.constraint = self
  125. // append
  126. newLayoutConstraints.append(layoutConstraint)
  127. }
  128. // updating logic
  129. if updateExisting {
  130. // get existing constraints for this view
  131. let existingLayoutConstraints = layoutFrom.snp.installedLayoutConstraints.reversed()
  132. // array that will contain only new layout constraints to keep
  133. var newLayoutConstraintsToKeep = [LayoutConstraint]()
  134. // begin looping
  135. for layoutConstraint in newLayoutConstraints {
  136. // layout constraint that should be updated
  137. var updateLayoutConstraint: LayoutConstraint? = nil
  138. // loop through existing and check for match
  139. for existingLayoutConstraint in existingLayoutConstraints {
  140. if existingLayoutConstraint == layoutConstraint {
  141. updateLayoutConstraint = existingLayoutConstraint
  142. break
  143. }
  144. }
  145. // if we have existing one lets just update the constant
  146. if updateLayoutConstraint != nil {
  147. updateLayoutConstraint!.constant = layoutConstraint.constant
  148. }
  149. // otherwise add this layout constraint to new keep list
  150. else {
  151. newLayoutConstraintsToKeep.append(layoutConstraint)
  152. }
  153. }
  154. // set constraints to only new ones
  155. newLayoutConstraints = newLayoutConstraintsToKeep
  156. }
  157. // add constraints
  158. #if SNAPKIT_DEPLOYMENT_LEGACY && (os(iOS) || os(tvOS))
  159. if #available(iOS 8.0, *) {
  160. NSLayoutConstraint.activateConstraints(newLayoutConstraints)
  161. } else {
  162. installOnView?.addConstraints(newLayoutConstraints)
  163. }
  164. #else
  165. NSLayoutConstraint.activate(newLayoutConstraints)
  166. #endif
  167. // set install info
  168. self.installInfo = ConstraintInstallInfo(view: installOnView, layoutConstraints: HashTable.weakObjects())
  169. // store which layout constraints are installed for this constraint
  170. for layoutConstraint in newLayoutConstraints {
  171. self.installInfo!.layoutConstraints.add(layoutConstraint)
  172. }
  173. // store the layout constraints against the layout from view
  174. layoutFrom.snp.appendInstalledLayoutConstraints(newLayoutConstraints)
  175. return newLayoutConstraints
  176. }
  177. internal func uninstallIfNeeded() {
  178. defer {
  179. self.installInfo = nil
  180. }
  181. guard let installedLayoutConstraints = self.installInfo?.layoutConstraints.allObjects as? [LayoutConstraint]
  182. where installedLayoutConstraints.count > 0 else {
  183. return
  184. }
  185. #if SNAPKIT_DEPLOYMENT_LEGACY && !os(OSX)
  186. if #available(iOS 8.0, *) {
  187. NSLayoutConstraint.deactivateConstraints(installedLayoutConstraints)
  188. } else if let installedOnView = installInfo.view {
  189. installedOnView.removeConstraints(installedLayoutConstraints)
  190. }
  191. #else
  192. NSLayoutConstraint.deactivate(installedLayoutConstraints)
  193. #endif
  194. // remove the constraints from the from item view
  195. self.from.view?.snp.removeInstalledLayoutConstraints(installedLayoutConstraints)
  196. }
  197. internal func activateIfNeeded() {
  198. guard self.installInfo != nil else {
  199. let _ = self.installIfNeeded()
  200. return
  201. }
  202. #if SNAPKIT_DEPLOYMENT_LEGACY
  203. guard #available(iOS 8.0, OSX 10.10, *) else {
  204. self.installIfNeeded()
  205. return
  206. }
  207. #endif
  208. guard let layoutConstraints = self.installInfo?.layoutConstraints.allObjects as? [LayoutConstraint]
  209. where layoutConstraints.count > 0 else {
  210. return
  211. }
  212. NSLayoutConstraint.activate(layoutConstraints)
  213. }
  214. internal func deactivateIfNeeded() {
  215. #if SNAPKIT_DEPLOYMENT_LEGACY
  216. guard #available(iOS 8.0, OSX 10.10, *) else {
  217. return
  218. }
  219. #endif
  220. guard let layoutConstraints = self.installInfo?.layoutConstraints.allObjects as? [LayoutConstraint]
  221. where layoutConstraints.count > 0 else {
  222. return
  223. }
  224. NSLayoutConstraint.deactivate(layoutConstraints)
  225. }
  226. }
  227. private final class ConstraintInstallInfo {
  228. private weak var view: ConstraintView? = nil
  229. private let layoutConstraints: HashTable<AnyObject>
  230. private init(view: ConstraintView?, layoutConstraints: HashTable<AnyObject>) {
  231. self.view = view
  232. self.layoutConstraints = layoutConstraints
  233. }
  234. }
  235. private func closestCommonSuperviewFromView(_ fromView: ConstraintView?, toView: ConstraintView?) -> ConstraintView? {
  236. var views = Set<ConstraintView>()
  237. var fromView = fromView
  238. var toView = toView
  239. repeat {
  240. if let view = toView {
  241. if views.contains(view) {
  242. return view
  243. }
  244. views.insert(view)
  245. toView = view.superview
  246. }
  247. if let view = fromView {
  248. if views.contains(view) {
  249. return view
  250. }
  251. views.insert(view)
  252. fromView = view.superview
  253. }
  254. } while (fromView != nil || toView != nil)
  255. return nil
  256. }
  257. private func ==(lhs: Constraint, rhs: Constraint) -> Bool {
  258. return (lhs.from == rhs.from &&
  259. lhs.to == rhs.to &&
  260. lhs.relation == rhs.relation &&
  261. lhs.multiplier.constraintMultiplierTargetValue == rhs.multiplier.constraintMultiplierTargetValue &&
  262. lhs.priority.constraintPriorityTargetValue == rhs.priority.constraintPriorityTargetValue)
  263. }