Constraint.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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. // MARK: Internal
  92. internal func updateConstantAndPriorityIfNeeded() {
  93. guard let installInfo = self.installInfo else {
  94. return
  95. }
  96. for layoutConstraint in installInfo.layoutConstraints.allObjects as! [LayoutConstraint] {
  97. let attribute = (layoutConstraint.secondAttribute == .notAnAttribute) ? layoutConstraint.firstAttribute : layoutConstraint.secondAttribute
  98. layoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: attribute)
  99. layoutConstraint.priority = self.priority.constraintPriorityTargetValue
  100. }
  101. }
  102. internal func installIfNeeded(updateExisting: Bool = false) -> [NSLayoutConstraint] {
  103. let installOnView: ConstraintView?
  104. if let view = self.to.view {
  105. guard let closestSuperview = closestCommonSuperviewFromView(self.from.view, toView: view) else {
  106. fatalError("Cannot Install Constraint. No common superview. (\(self.sourceLocation.0), \(self.sourceLocation.1))")
  107. }
  108. installOnView = closestSuperview
  109. } else if self.from.attributes.isSubset(of: ConstraintAttributes.Width + ConstraintAttributes.Height) {
  110. installOnView = self.from.view
  111. } else {
  112. guard let superview = self.from.view?.superview else {
  113. fatalError("Cannot Install Constraint. No superview. (\(self.sourceLocation.0), \(self.sourceLocation.1))")
  114. }
  115. installOnView = superview
  116. }
  117. guard self.installInfo?.view == nil ||
  118. self.installInfo?.view == installOnView else {
  119. fatalError("Cannot Install Constraint. Already installed on different view. (\(self.sourceLocation.0), \(self.sourceLocation.1))")
  120. }
  121. // setup array to store new layout constraints
  122. var newLayoutConstraints = [LayoutConstraint]()
  123. // get attributes
  124. let layoutFromAttributes = self.from.attributes.layoutAttributes
  125. let layoutToAttributes = self.to.attributes.layoutAttributes
  126. // get layout from
  127. let layoutFrom: ConstraintView = self.from.view!
  128. // get relation
  129. let layoutRelation = self.relation.layoutRelation
  130. for layoutFromAttribute in layoutFromAttributes {
  131. // get layout to attribute
  132. let layoutToAttribute = (layoutToAttributes.count > 0) ? layoutToAttributes[0] : layoutFromAttribute
  133. // get layout constant
  134. let layoutConstant: CGFloat = self.constant.constraintConstantTargetValueFor(layoutAttribute: layoutToAttribute)
  135. // get layout to
  136. #if os(iOS) || os(tvOS)
  137. var layoutTo: AnyObject? = self.to.view ?? self.to.layoutSupport
  138. #else
  139. var layoutTo: AnyObject? = self.to.view
  140. #endif
  141. if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height {
  142. layoutTo = installOnView
  143. }
  144. // create layout constraint
  145. let layoutConstraint = LayoutConstraint(
  146. item: layoutFrom,
  147. attribute: layoutFromAttribute,
  148. relatedBy: layoutRelation,
  149. toItem: layoutTo,
  150. attribute: layoutToAttribute,
  151. multiplier: self.multiplier.constraintMultiplierTargetValue,
  152. constant: layoutConstant
  153. )
  154. // set priority
  155. layoutConstraint.priority = self.priority.constraintPriorityTargetValue
  156. // set constraint
  157. layoutConstraint.constraint = self
  158. // append
  159. newLayoutConstraints.append(layoutConstraint)
  160. }
  161. // updating logic
  162. if updateExisting {
  163. // get existing constraints for this view
  164. let existingLayoutConstraints = layoutFrom.snp.installedLayoutConstraints.reversed()
  165. // array that will contain only new layout constraints to keep
  166. var newLayoutConstraintsToKeep = [LayoutConstraint]()
  167. // begin looping
  168. for layoutConstraint in newLayoutConstraints {
  169. // layout constraint that should be updated
  170. var updateLayoutConstraint: LayoutConstraint? = nil
  171. // loop through existing and check for match
  172. for existingLayoutConstraint in existingLayoutConstraints {
  173. if existingLayoutConstraint == layoutConstraint {
  174. updateLayoutConstraint = existingLayoutConstraint
  175. break
  176. }
  177. }
  178. // if we have existing one lets just update the constant
  179. if updateLayoutConstraint != nil {
  180. updateLayoutConstraint!.constant = layoutConstraint.constant
  181. }
  182. // otherwise add this layout constraint to new keep list
  183. else {
  184. newLayoutConstraintsToKeep.append(layoutConstraint)
  185. }
  186. }
  187. // set constraints to only new ones
  188. newLayoutConstraints = newLayoutConstraintsToKeep
  189. }
  190. // add constraints
  191. #if SNAPKIT_DEPLOYMENT_LEGACY && (os(iOS) || os(tvOS))
  192. if #available(iOS 8.0, *) {
  193. NSLayoutConstraint.activate(newLayoutConstraints)
  194. } else {
  195. installOnView?.addConstraints(newLayoutConstraints)
  196. }
  197. #else
  198. NSLayoutConstraint.activate(newLayoutConstraints)
  199. #endif
  200. // set install info
  201. self.installInfo = ConstraintInstallInfo(view: installOnView, layoutConstraints: NSHashTable.weakObjects())
  202. // store which layout constraints are installed for this constraint
  203. for layoutConstraint in newLayoutConstraints {
  204. self.installInfo!.layoutConstraints.add(layoutConstraint)
  205. }
  206. // store the layout constraints against the layout from view
  207. layoutFrom.snp.appendInstalledLayoutConstraints(newLayoutConstraints)
  208. return newLayoutConstraints
  209. }
  210. internal func uninstallIfNeeded() {
  211. defer {
  212. self.installInfo = nil
  213. }
  214. guard let installedLayoutConstraints = self.installInfo?.layoutConstraints.allObjects as? [LayoutConstraint],
  215. installedLayoutConstraints.count > 0 else {
  216. return
  217. }
  218. #if SNAPKIT_DEPLOYMENT_LEGACY && !os(OSX)
  219. if #available(iOS 8.0, *) {
  220. NSLayoutConstraint.deactivate(installedLayoutConstraints)
  221. } else if let installedOnView = installInfo?.view {
  222. installedOnView.removeConstraints(installedLayoutConstraints)
  223. }
  224. #else
  225. NSLayoutConstraint.deactivate(installedLayoutConstraints)
  226. #endif
  227. // remove the constraints from the from item view
  228. self.from.view?.snp.removeInstalledLayoutConstraints(installedLayoutConstraints)
  229. }
  230. internal func activateIfNeeded() {
  231. guard self.installInfo != nil else {
  232. _ = self.installIfNeeded()
  233. return
  234. }
  235. #if SNAPKIT_DEPLOYMENT_LEGACY
  236. guard #available(iOS 8.0, OSX 10.10, *) else {
  237. _ = self.installIfNeeded()
  238. return
  239. }
  240. #endif
  241. guard let layoutConstraints = self.installInfo?.layoutConstraints.allObjects as? [LayoutConstraint],
  242. layoutConstraints.count > 0 else {
  243. return
  244. }
  245. NSLayoutConstraint.activate(layoutConstraints)
  246. }
  247. internal func deactivateIfNeeded() {
  248. #if SNAPKIT_DEPLOYMENT_LEGACY
  249. guard #available(iOS 8.0, OSX 10.10, *) else {
  250. return
  251. }
  252. #endif
  253. guard let layoutConstraints = self.installInfo?.layoutConstraints.allObjects as? [LayoutConstraint],
  254. layoutConstraints.count > 0 else {
  255. return
  256. }
  257. NSLayoutConstraint.deactivate(layoutConstraints)
  258. }
  259. }
  260. private final class ConstraintInstallInfo {
  261. private weak var view: ConstraintView? = nil
  262. private let layoutConstraints: NSHashTable<AnyObject>
  263. private init(view: ConstraintView?, layoutConstraints: NSHashTable<AnyObject>) {
  264. self.view = view
  265. self.layoutConstraints = layoutConstraints
  266. }
  267. }
  268. private func closestCommonSuperviewFromView(_ fromView: ConstraintView?, toView: ConstraintView?) -> ConstraintView? {
  269. var views = Set<ConstraintView>()
  270. var fromView = fromView
  271. var toView = toView
  272. repeat {
  273. if let view = toView {
  274. if views.contains(view) {
  275. return view
  276. }
  277. views.insert(view)
  278. toView = view.superview
  279. }
  280. if let view = fromView {
  281. if views.contains(view) {
  282. return view
  283. }
  284. views.insert(view)
  285. fromView = view.superview
  286. }
  287. } while (fromView != nil || toView != nil)
  288. return nil
  289. }
  290. private func ==(lhs: Constraint, rhs: Constraint) -> Bool {
  291. return (lhs.from == rhs.from &&
  292. lhs.to == rhs.to &&
  293. lhs.relation == rhs.relation &&
  294. lhs.multiplier.constraintMultiplierTargetValue == rhs.multiplier.constraintMultiplierTargetValue &&
  295. lhs.priority.constraintPriorityTargetValue == rhs.priority.constraintPriorityTargetValue)
  296. }