Constraint.swift 14 KB

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