Constraint.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. //
  2. // Snap
  3. //
  4. // Copyright (c) 2011-2015 Masonry Team - https://github.com/Masonry
  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)
  24. import UIKit
  25. #else
  26. import AppKit
  27. #endif
  28. public class Constraint {
  29. public func install() -> [LayoutConstraint] {
  30. return self.installOnView(updateExisting: false)
  31. }
  32. public func uninstall() {
  33. self.uninstallFromView()
  34. }
  35. public func activate() {
  36. if NSLayoutConstraint.respondsToSelector("activateConstraints:") && self.installInfo != nil {
  37. let layoutConstraints = self.installInfo!.layoutConstraints.allObjects as! [LayoutConstraint]
  38. if layoutConstraints.count > 0 {
  39. NSLayoutConstraint.activateConstraints(layoutConstraints)
  40. }
  41. } else {
  42. self.install()
  43. }
  44. }
  45. public func deactivate() {
  46. if NSLayoutConstraint.respondsToSelector("deactivateConstraints:") && self.installInfo != nil {
  47. let layoutConstraints = self.installInfo!.layoutConstraints.allObjects as! [LayoutConstraint]
  48. if layoutConstraints.count > 0 {
  49. NSLayoutConstraint.deactivateConstraints(layoutConstraints)
  50. }
  51. } else {
  52. self.uninstall()
  53. }
  54. }
  55. private let fromItem: ConstraintItem
  56. private let toItem: ConstraintItem
  57. private let relation: ConstraintRelation
  58. private var constant: Any
  59. private var multiplier: Float
  60. private var priority: Float
  61. private var installInfo: ConstraintInstallInfo? = nil
  62. internal init(fromItem: ConstraintItem, toItem: ConstraintItem, relation: ConstraintRelation, constant: Any, multiplier: Float, priority: Float) {
  63. self.fromItem = fromItem
  64. self.toItem = toItem
  65. self.relation = relation
  66. self.constant = constant
  67. self.multiplier = multiplier
  68. self.priority = priority
  69. }
  70. internal func installOnView(updateExisting: Bool = false) -> [LayoutConstraint] {
  71. var installOnView: View? = nil
  72. if self.toItem.view != nil {
  73. installOnView = closestCommonSuperviewBetween(self.fromItem.view, self.toItem.view)
  74. if installOnView == nil {
  75. NSException(name: "Cannot Install Constraint", reason: "No common superview between views", userInfo: nil).raise()
  76. return []
  77. }
  78. } else {
  79. installOnView = self.fromItem.view?.superview
  80. if installOnView == nil {
  81. if self.fromItem.attributes == ConstraintAttributes.Width || self.fromItem.attributes == ConstraintAttributes.Height {
  82. installOnView = self.fromItem.view
  83. }
  84. if installOnView == nil {
  85. NSException(name: "Cannot Install Constraint", reason: "Missing superview", userInfo: nil).raise()
  86. return []
  87. }
  88. }
  89. }
  90. if let installedOnView = self.installInfo?.view {
  91. if installedOnView != installOnView {
  92. NSException(name: "Cannot Install Constraint", reason: "Already installed on different view.", userInfo: nil).raise()
  93. return []
  94. }
  95. return self.installInfo?.layoutConstraints.allObjects as? [LayoutConstraint] ?? []
  96. }
  97. var newLayoutConstraints = [LayoutConstraint]()
  98. let layoutFromAttributes = self.fromItem.attributes.layoutAttributes
  99. let layoutToAttributes = self.toItem.attributes.layoutAttributes
  100. // get layout from
  101. let layoutFrom: View? = self.fromItem.view
  102. // get layout relation
  103. let layoutRelation: NSLayoutRelation = self.relation.layoutRelation
  104. for layoutFromAttribute in layoutFromAttributes {
  105. // get layout to attribute
  106. let layoutToAttribute = (layoutToAttributes.count > 0) ? layoutToAttributes[0] : layoutFromAttribute
  107. // get layout constant
  108. var layoutConstant: CGFloat = layoutToAttribute.snp_constantForValue(self.constant)
  109. // get layout to
  110. var layoutTo: View? = self.toItem.view
  111. if layoutTo == nil && layoutToAttribute != .Width && layoutToAttribute != .Height {
  112. layoutTo = installOnView
  113. }
  114. // create layout constraint
  115. let layoutConstraint = LayoutConstraint(
  116. item: layoutFrom!,
  117. attribute: layoutFromAttribute,
  118. relatedBy: layoutRelation,
  119. toItem: layoutTo,
  120. attribute: layoutToAttribute,
  121. multiplier: CGFloat(self.multiplier),
  122. constant: layoutConstant)
  123. // set priority
  124. layoutConstraint.priority = self.priority
  125. // set constraint
  126. layoutConstraint.snp_constraint = self
  127. newLayoutConstraints.append(layoutConstraint)
  128. }
  129. // special logic for updating
  130. if updateExisting {
  131. // get existing constraints for this view
  132. let existingLayoutConstraints = reverse(layoutFrom!.snp_installedLayoutConstraints)
  133. // array that will contain only new layout constraints to keep
  134. var newLayoutConstraintsToKeep = [LayoutConstraint]()
  135. // begin looping
  136. for layoutConstraint in newLayoutConstraints {
  137. // layout constraint that should be updated
  138. var updateLayoutConstraint: LayoutConstraint? = nil
  139. // loop through existing and check for match
  140. for existingLayoutConstraint in existingLayoutConstraints {
  141. if existingLayoutConstraint == layoutConstraint {
  142. updateLayoutConstraint = existingLayoutConstraint
  143. break
  144. }
  145. }
  146. // if we have existing one lets just update the constant
  147. if updateLayoutConstraint != nil {
  148. updateLayoutConstraint!.constant = layoutConstraint.constant
  149. }
  150. // otherwise add this layout constraint to new keep list
  151. else {
  152. newLayoutConstraintsToKeep.append(layoutConstraint)
  153. }
  154. }
  155. // set constraints to only new ones
  156. newLayoutConstraints = newLayoutConstraintsToKeep
  157. }
  158. // add constraints
  159. installOnView!.addConstraints(newLayoutConstraints)
  160. // set install info
  161. self.installInfo = ConstraintInstallInfo(view: installOnView, layoutConstraints: NSHashTable.weakObjectsHashTable())
  162. // store which layout constraints are installed for this constraint
  163. for layoutConstraint in newLayoutConstraints {
  164. self.installInfo!.layoutConstraints.addObject(layoutConstraint)
  165. }
  166. // store the layout constraints against the layout from view
  167. layoutFrom!.snp_installedLayoutConstraints += newLayoutConstraints
  168. // return the new constraints
  169. return newLayoutConstraints
  170. }
  171. internal func uninstallFromView() {
  172. if let installInfo = self.installInfo,
  173. let installedLayoutConstraints = installInfo.layoutConstraints.allObjects as? [LayoutConstraint] {
  174. if installedLayoutConstraints.count > 0 {
  175. if let installedOnView = installInfo.view {
  176. // remove the constraints from the UIView's storage
  177. installedOnView.removeConstraints(installedLayoutConstraints)
  178. }
  179. // remove the constraints from the from item view
  180. if let fromView = self.fromItem.view {
  181. fromView.snp_installedLayoutConstraints = fromView.snp_installedLayoutConstraints.filter {
  182. return !contains(installedLayoutConstraints, $0)
  183. }
  184. }
  185. }
  186. }
  187. self.installInfo = nil
  188. }
  189. }
  190. private struct ConstraintInstallInfo {
  191. weak var view: View? = nil
  192. let layoutConstraints: NSHashTable
  193. }
  194. private extension NSLayoutAttribute {
  195. private func snp_constantForValue(value: Any?) -> CGFloat {
  196. // Float
  197. if let float = value as? Float {
  198. return CGFloat(float)
  199. }
  200. // Double
  201. else if let double = value as? Double {
  202. return CGFloat(double)
  203. }
  204. // UInt
  205. else if let int = value as? Int {
  206. return CGFloat(int)
  207. }
  208. // Int
  209. else if let uint = value as? UInt {
  210. return CGFloat(uint)
  211. }
  212. // CGFloat
  213. else if let float = value as? CGFloat {
  214. return float
  215. }
  216. // CGSize
  217. else if let size = value as? CGSize {
  218. if self == .Width {
  219. return size.width
  220. } else if self == .Height {
  221. return size.height
  222. }
  223. }
  224. // CGPoint
  225. else if let point = value as? CGPoint {
  226. #if os(iOS)
  227. switch self {
  228. case .Left, .CenterX, .LeftMargin, .CenterXWithinMargins: return point.x
  229. case .Top, .CenterY, .TopMargin, .CenterYWithinMargins, .Baseline, .FirstBaseline: return point.y
  230. case .Right, .RightMargin: return point.x
  231. case .Bottom, .BottomMargin: return point.y
  232. case .Leading, .LeadingMargin: return point.x
  233. case .Trailing, .TrailingMargin: return point.x
  234. case .Width, .Height, .NotAnAttribute: return CGFloat(0)
  235. }
  236. #else
  237. switch self {
  238. case .Left, .CenterX: return point.x
  239. case .Top, .CenterY, .Baseline: return point.y
  240. case .Right: return point.x
  241. case .Bottom: return point.y
  242. case .Leading: return point.x
  243. case .Trailing: return point.x
  244. case .Width, .Height, .NotAnAttribute: return CGFloat(0)
  245. }
  246. #endif
  247. }
  248. // EdgeInsets
  249. else if let insets = value as? EdgeInsets {
  250. #if os(iOS)
  251. switch self {
  252. case .Left, .CenterX, .LeftMargin, .CenterXWithinMargins: return insets.left
  253. case .Top, .CenterY, .TopMargin, .CenterYWithinMargins, .Baseline, .FirstBaseline: return insets.top
  254. case .Right, .RightMargin: return insets.right
  255. case .Bottom, .BottomMargin: return insets.bottom
  256. case .Leading, .LeadingMargin: return (Config.interfaceLayoutDirection == .LeftToRight) ? insets.left : -insets.right
  257. case .Trailing, .TrailingMargin: return (Config.interfaceLayoutDirection == .LeftToRight) ? insets.right : -insets.left
  258. case .Width, .Height, .NotAnAttribute: return CGFloat(0)
  259. }
  260. #else
  261. switch self {
  262. case .Left, .CenterX: return insets.left
  263. case .Top, .CenterY, .Baseline: return insets.top
  264. case .Right: return insets.right
  265. case .Bottom: return insets.bottom
  266. case .Leading, .LeadingMargin: return (Config.interfaceLayoutDirection == .LeftToRight) ? insets.left : -insets.right
  267. case .Trailing, .TrailingMargin: return (Config.interfaceLayoutDirection == .LeftToRight) ? insets.right : -insets.left
  268. case .Width, .Height, .NotAnAttribute: return CGFloat(0)
  269. }
  270. #endif
  271. }
  272. return CGFloat(0);
  273. }
  274. }
  275. private func closestCommonSuperviewBetween(fromView: View?, toView: View?) -> View? {
  276. var views = Set<View>()
  277. var fromView = fromView
  278. var toView = toView
  279. do {
  280. if let view = toView {
  281. if views.contains(view) {
  282. return view
  283. }
  284. views.insert(view)
  285. toView = view.superview
  286. }
  287. if let view = fromView {
  288. if views.contains(view) {
  289. return view
  290. }
  291. views.insert(view)
  292. fromView = view.superview
  293. }
  294. } while (fromView != nil || toView != nil)
  295. return nil
  296. }
  297. private func ==(left: Constraint, right: Constraint) -> Bool {
  298. return (left.fromItem == right.fromItem &&
  299. left.toItem == right.toItem &&
  300. left.relation == right.relation &&
  301. left.multiplier == right.multiplier &&
  302. left.priority == right.priority)
  303. }
  304. //public protocol Constraint: class {
  305. //
  306. // func install() -> [LayoutConstraint]
  307. // func uninstall() -> Void
  308. // func activate() -> Void
  309. // func deactivate() -> Void
  310. //
  311. //}