ConstraintDescription.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. //
  2. // SnapKit
  3. //
  4. // Copyright (c) 2011-2015 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 protocol RelationTarget {
  29. var constraintItem: ConstraintItem { get }
  30. }
  31. public protocol FloatConvertible: RelationTarget {
  32. var floatValue: Float { get }
  33. }
  34. extension FloatConvertible {
  35. public var constraintItem: ConstraintItem {
  36. return ConstraintItem(object: nil, attributes: ConstraintAttributes.None)
  37. }
  38. }
  39. extension Float: FloatConvertible, RelationTarget {
  40. public var floatValue: Float {
  41. return self
  42. }
  43. }
  44. extension Int: FloatConvertible, RelationTarget {
  45. public var floatValue: Float {
  46. return Float(self)
  47. }
  48. }
  49. extension UInt: FloatConvertible, RelationTarget {
  50. public var floatValue: Float {
  51. return Float(self)
  52. }
  53. }
  54. extension Double: FloatConvertible, RelationTarget {
  55. public var floatValue: Float {
  56. return Float(self)
  57. }
  58. }
  59. extension CGFloat: FloatConvertible, RelationTarget {
  60. public var floatValue: Float {
  61. return Float(self)
  62. }
  63. }
  64. @available(iOS 9.0, OSX 10.11, *)
  65. extension NSLayoutAnchor: RelationTarget {
  66. public var constraintItem: ConstraintItem {
  67. return ConstraintItem(object: self, attributes: ConstraintAttributes.None)
  68. }
  69. }
  70. extension CGPoint: RelationTarget {
  71. public var constraintItem: ConstraintItem {
  72. return ConstraintItem(object: nil, attributes: ConstraintAttributes.None)
  73. }
  74. }
  75. extension CGSize: RelationTarget {
  76. public var constraintItem: ConstraintItem {
  77. return ConstraintItem(object: nil, attributes: ConstraintAttributes.None)
  78. }
  79. }
  80. extension EdgeInsets: RelationTarget {
  81. public var constraintItem: ConstraintItem {
  82. return ConstraintItem(object: nil, attributes: ConstraintAttributes.None)
  83. }
  84. }
  85. extension View: RelationTarget {
  86. public var constraintItem: ConstraintItem {
  87. return ConstraintItem(object: self, attributes: ConstraintAttributes.None)
  88. }
  89. }
  90. extension ConstraintItem: RelationTarget {
  91. public var constraintItem: ConstraintItem {
  92. return self
  93. }
  94. }
  95. /**
  96. Used to expose the final API of a `ConstraintDescription` which allows getting a constraint from it
  97. */
  98. public class ConstraintDescriptionFinalizable {
  99. private let backing: ConstraintDescription
  100. internal init(_ backing: ConstraintDescription) {
  101. self.backing = backing
  102. }
  103. public var constraint: Constraint {
  104. return backing.constraint
  105. }
  106. }
  107. /**
  108. Used to expose priority APIs
  109. */
  110. public class ConstraintDescriptionPriortizable: ConstraintDescriptionFinalizable {
  111. public func priority(priority: FloatConvertible) -> ConstraintDescriptionFinalizable {
  112. return ConstraintDescriptionFinalizable(self.backing.priority(priority))
  113. }
  114. public func priorityRequired() -> ConstraintDescriptionFinalizable {
  115. return ConstraintDescriptionFinalizable(self.backing.priorityRequired())
  116. }
  117. public func priorityHigh() -> ConstraintDescriptionFinalizable {
  118. return ConstraintDescriptionFinalizable(self.backing.priorityHigh())
  119. }
  120. public func priorityMedium() -> ConstraintDescriptionFinalizable {
  121. return ConstraintDescriptionFinalizable(self.backing.priorityMedium())
  122. }
  123. public func priorityLow() -> ConstraintDescriptionFinalizable {
  124. return ConstraintDescriptionFinalizable(self.backing.priorityLow())
  125. }
  126. }
  127. /**
  128. Used to expose multiplier & constant APIs
  129. */
  130. public class ConstraintDescriptionEditable: ConstraintDescriptionPriortizable {
  131. public func multipliedBy(amount: FloatConvertible) -> ConstraintDescriptionEditable {
  132. return ConstraintDescriptionEditable(self.backing.multipliedBy(amount))
  133. }
  134. public func dividedBy(amount: FloatConvertible) -> ConstraintDescriptionEditable {
  135. return self.multipliedBy(1 / amount.floatValue)
  136. }
  137. public func offset(amount: FloatConvertible) -> ConstraintDescriptionEditable {
  138. return ConstraintDescriptionEditable(self.backing.offset(amount))
  139. }
  140. public func offset(amount: CGPoint) -> ConstraintDescriptionEditable {
  141. return ConstraintDescriptionEditable(self.backing.offset(amount))
  142. }
  143. public func offset(amount: CGSize) -> ConstraintDescriptionEditable {
  144. return ConstraintDescriptionEditable(self.backing.offset(amount))
  145. }
  146. public func offset(amount: EdgeInsets) -> ConstraintDescriptionEditable {
  147. return ConstraintDescriptionEditable(self.backing.offset(amount))
  148. }
  149. public func inset(amount: FloatConvertible) -> ConstraintDescriptionEditable {
  150. return ConstraintDescriptionEditable(self.backing.inset(amount))
  151. }
  152. public func inset(amount: EdgeInsets) -> ConstraintDescriptionEditable {
  153. return ConstraintDescriptionEditable(self.backing.inset(amount))
  154. }
  155. }
  156. /**
  157. Used to expose relation APIs
  158. */
  159. public class ConstraintDescriptionRelatable {
  160. private let backing: ConstraintDescription
  161. init(_ backing: ConstraintDescription) {
  162. self.backing = backing
  163. }
  164. public func equalTo(other: RelationTarget, file: String = __FILE__, line: UInt = __LINE__) -> ConstraintDescriptionEditable {
  165. let location = SourceLocation(file: file, line: line)
  166. return ConstraintDescriptionEditable(self.backing.constrainTo(other, relation: .Equal, location: location))
  167. }
  168. public func equalTo(other: LayoutSupport, file: String = __FILE__, line: UInt = __LINE__) -> ConstraintDescriptionEditable {
  169. let location = SourceLocation(file: file, line: line)
  170. return ConstraintDescriptionEditable(self.backing.constrainTo(other, relation: .Equal, location: location))
  171. }
  172. public func lessThanOrEqualTo(other: RelationTarget, file: String = __FILE__, line: UInt = __LINE__) -> ConstraintDescriptionEditable {
  173. let location = SourceLocation(file: file, line: line)
  174. return ConstraintDescriptionEditable(self.backing.constrainTo(other, relation: .LessThanOrEqualTo, location: location))
  175. }
  176. public func lessThanOrEqualTo(other: LayoutSupport, file: String = __FILE__, line: UInt = __LINE__) -> ConstraintDescriptionEditable {
  177. let location = SourceLocation(file: file, line: line)
  178. return ConstraintDescriptionEditable(self.backing.constrainTo(other, relation: .LessThanOrEqualTo, location: location))
  179. }
  180. public func greaterThanOrEqualTo(other: RelationTarget, file: String = __FILE__, line: UInt = __LINE__) -> ConstraintDescriptionEditable {
  181. let location = SourceLocation(file: file, line: line)
  182. return ConstraintDescriptionEditable(self.backing.constrainTo(other, relation: .GreaterThanOrEqualTo, location: location))
  183. }
  184. public func greaterThanOrEqualTo(other: LayoutSupport, file: String = __FILE__, line: UInt = __LINE__) -> ConstraintDescriptionEditable {
  185. let location = SourceLocation(file: file, line: line)
  186. return ConstraintDescriptionEditable(self.backing.constrainTo(other, relation: .GreaterThanOrEqualTo, location: location))
  187. }
  188. }
  189. /**
  190. Used to expose chaining APIs
  191. */
  192. public class ConstraintDescriptionExtendable: ConstraintDescriptionRelatable {
  193. public var left: ConstraintDescriptionExtendable {
  194. return ConstraintDescriptionExtendable(self.backing.left)
  195. }
  196. public var top: ConstraintDescriptionExtendable {
  197. return ConstraintDescriptionExtendable(self.backing.top)
  198. }
  199. public var bottom: ConstraintDescriptionExtendable {
  200. return ConstraintDescriptionExtendable(self.backing.bottom)
  201. }
  202. public var right: ConstraintDescriptionExtendable {
  203. return ConstraintDescriptionExtendable(self.backing.right)
  204. }
  205. public var leading: ConstraintDescriptionExtendable {
  206. return ConstraintDescriptionExtendable(self.backing.leading)
  207. }
  208. public var trailing: ConstraintDescriptionExtendable {
  209. return ConstraintDescriptionExtendable(self.backing.trailing)
  210. }
  211. public var width: ConstraintDescriptionExtendable {
  212. return ConstraintDescriptionExtendable(self.backing.width)
  213. }
  214. public var height: ConstraintDescriptionExtendable {
  215. return ConstraintDescriptionExtendable(self.backing.height)
  216. }
  217. public var centerX: ConstraintDescriptionExtendable {
  218. return ConstraintDescriptionExtendable(self.backing.centerX)
  219. }
  220. public var centerY: ConstraintDescriptionExtendable {
  221. return ConstraintDescriptionExtendable(self.backing.centerY)
  222. }
  223. public var baseline: ConstraintDescriptionExtendable {
  224. return ConstraintDescriptionExtendable(self.backing.baseline)
  225. }
  226. @available(iOS 8.0, *)
  227. public var firstBaseline: ConstraintDescriptionExtendable {
  228. return ConstraintDescriptionExtendable(self.backing.firstBaseline)
  229. }
  230. @available(iOS 8.0, *)
  231. public var leftMargin: ConstraintDescriptionExtendable {
  232. return ConstraintDescriptionExtendable(self.backing.leftMargin)
  233. }
  234. @available(iOS 8.0, *)
  235. public var rightMargin: ConstraintDescriptionExtendable {
  236. return ConstraintDescriptionExtendable(self.backing.rightMargin)
  237. }
  238. @available(iOS 8.0, *)
  239. public var topMargin: ConstraintDescriptionExtendable {
  240. return ConstraintDescriptionExtendable(self.backing.topMargin)
  241. }
  242. @available(iOS 8.0, *)
  243. public var bottomMargin: ConstraintDescriptionExtendable {
  244. return ConstraintDescriptionExtendable(self.backing.bottomMargin)
  245. }
  246. @available(iOS 8.0, *)
  247. public var leadingMargin: ConstraintDescriptionExtendable {
  248. return ConstraintDescriptionExtendable(self.backing.leadingMargin)
  249. }
  250. @available(iOS 8.0, *)
  251. public var trailingMargin: ConstraintDescriptionExtendable {
  252. return ConstraintDescriptionExtendable(self.backing.trailingMargin)
  253. }
  254. @available(iOS 8.0, *)
  255. public var centerXWithinMargins: ConstraintDescriptionExtendable {
  256. return ConstraintDescriptionExtendable(self.backing.centerXWithinMargins)
  257. }
  258. @available(iOS 8.0, *)
  259. public var centerYWithinMargins: ConstraintDescriptionExtendable {
  260. return ConstraintDescriptionExtendable(self.backing.centerYWithinMargins)
  261. }
  262. }
  263. /**
  264. Used to internally manage building constraint
  265. */
  266. internal class ConstraintDescription {
  267. private var location: SourceLocation? = nil
  268. private var left: ConstraintDescription { return self.addConstraint(ConstraintAttributes.Left) }
  269. private var top: ConstraintDescription { return self.addConstraint(ConstraintAttributes.Top) }
  270. private var right: ConstraintDescription { return self.addConstraint(ConstraintAttributes.Right) }
  271. private var bottom: ConstraintDescription { return self.addConstraint(ConstraintAttributes.Bottom) }
  272. private var leading: ConstraintDescription { return self.addConstraint(ConstraintAttributes.Leading) }
  273. private var trailing: ConstraintDescription { return self.addConstraint(ConstraintAttributes.Trailing) }
  274. private var width: ConstraintDescription { return self.addConstraint(ConstraintAttributes.Width) }
  275. private var height: ConstraintDescription { return self.addConstraint(ConstraintAttributes.Height) }
  276. private var centerX: ConstraintDescription { return self.addConstraint(ConstraintAttributes.CenterX) }
  277. private var centerY: ConstraintDescription { return self.addConstraint(ConstraintAttributes.CenterY) }
  278. private var baseline: ConstraintDescription { return self.addConstraint(ConstraintAttributes.Baseline) }
  279. @available(iOS 8.0, *)
  280. private var firstBaseline: ConstraintDescription { return self.addConstraint(ConstraintAttributes.FirstBaseline) }
  281. @available(iOS 8.0, *)
  282. private var leftMargin: ConstraintDescription { return self.addConstraint(ConstraintAttributes.LeftMargin) }
  283. @available(iOS 8.0, *)
  284. private var rightMargin: ConstraintDescription { return self.addConstraint(ConstraintAttributes.RightMargin) }
  285. @available(iOS 8.0, *)
  286. private var topMargin: ConstraintDescription { return self.addConstraint(ConstraintAttributes.TopMargin) }
  287. @available(iOS 8.0, *)
  288. private var bottomMargin: ConstraintDescription { return self.addConstraint(ConstraintAttributes.BottomMargin) }
  289. @available(iOS 8.0, *)
  290. private var leadingMargin: ConstraintDescription { return self.addConstraint(ConstraintAttributes.LeadingMargin) }
  291. @available(iOS 8.0, *)
  292. private var trailingMargin: ConstraintDescription { return self.addConstraint(ConstraintAttributes.TrailingMargin) }
  293. @available(iOS 8.0, *)
  294. private var centerXWithinMargins: ConstraintDescription { return self.addConstraint(ConstraintAttributes.CenterXWithinMargins) }
  295. @available(iOS 8.0, *)
  296. private var centerYWithinMargins: ConstraintDescription { return self.addConstraint(ConstraintAttributes.CenterYWithinMargins) }
  297. // MARK: initializer
  298. init(fromItem: ConstraintItem) {
  299. self.fromItem = fromItem
  300. self.toItem = ConstraintItem(object: nil, attributes: ConstraintAttributes.None)
  301. }
  302. // MARK: multiplier
  303. private func multipliedBy(amount: FloatConvertible) -> ConstraintDescription {
  304. self.multiplier = amount.floatValue
  305. return self
  306. }
  307. private func dividedBy(amount: FloatConvertible) -> ConstraintDescription {
  308. self.multiplier = 1.0 / amount.floatValue;
  309. return self
  310. }
  311. // MARK: offset
  312. private func offset(amount: FloatConvertible) -> ConstraintDescription {
  313. self.constant = amount.floatValue
  314. return self
  315. }
  316. private func offset(amount: CGPoint) -> ConstraintDescription {
  317. self.constant = amount
  318. return self
  319. }
  320. private func offset(amount: CGSize) -> ConstraintDescription {
  321. self.constant = amount
  322. return self
  323. }
  324. private func offset(amount: EdgeInsets) -> ConstraintDescription {
  325. self.constant = amount
  326. return self
  327. }
  328. // MARK: inset
  329. private func inset(amount: FloatConvertible) -> ConstraintDescription {
  330. let value = CGFloat(amount.floatValue)
  331. self.constant = EdgeInsets(top: value, left: value, bottom: -value, right: -value)
  332. return self
  333. }
  334. private func inset(amount: EdgeInsets) -> ConstraintDescription {
  335. self.constant = EdgeInsets(top: amount.top, left: amount.left, bottom: -amount.bottom, right: -amount.right)
  336. return self
  337. }
  338. // MARK: priority
  339. private func priority(priority: FloatConvertible) -> ConstraintDescription {
  340. self.priority = priority.floatValue
  341. return self
  342. }
  343. private func priorityRequired() -> ConstraintDescription {
  344. return self.priority(1000.0)
  345. }
  346. private func priorityHigh() -> ConstraintDescription {
  347. return self.priority(750.0)
  348. }
  349. private func priorityMedium() -> ConstraintDescription {
  350. #if os(iOS) || os(tvOS)
  351. return self.priority(500.0)
  352. #else
  353. return self.priority(501.0)
  354. #endif
  355. }
  356. private func priorityLow() -> ConstraintDescription {
  357. return self.priority(250.0)
  358. }
  359. // MARK: Constraint
  360. internal var constraint: Constraint {
  361. if self.concreteConstraint == nil {
  362. if self.relation == nil {
  363. fatalError("Attempting to create a constraint from a ConstraintDescription before it has been fully chained.")
  364. }
  365. self.concreteConstraint = ConcreteConstraint(
  366. fromItem: self.fromItem,
  367. toItem: self.toItem,
  368. relation: self.relation!,
  369. constant: self.constant,
  370. multiplier: self.multiplier,
  371. priority: self.priority,
  372. location: self.location
  373. )
  374. }
  375. return self.concreteConstraint!
  376. }
  377. // MARK: Private
  378. private let fromItem: ConstraintItem
  379. private var toItem: ConstraintItem {
  380. willSet {
  381. if self.concreteConstraint != nil {
  382. fatalError("Attempting to modify a ConstraintDescription after its constraint has been created.")
  383. }
  384. }
  385. }
  386. private var relation: ConstraintRelation? {
  387. willSet {
  388. if self.concreteConstraint != nil {
  389. fatalError("Attempting to modify a ConstraintDescription after its constraint has been created.")
  390. }
  391. }
  392. }
  393. private var constant: Any = Float(0.0) {
  394. willSet {
  395. if self.concreteConstraint != nil {
  396. fatalError("Attempting to modify a ConstraintDescription after its constraint has been created.")
  397. }
  398. }
  399. }
  400. private var multiplier: Float = 1.0 {
  401. willSet {
  402. if self.concreteConstraint != nil {
  403. fatalError("Attempting to modify a ConstraintDescription after its constraint has been created.")
  404. }
  405. }
  406. }
  407. private var priority: Float = 1000.0 {
  408. willSet {
  409. if self.concreteConstraint != nil {
  410. fatalError("Attempting to modify a ConstraintDescription after its constraint has been created.")
  411. }
  412. }
  413. }
  414. private var concreteConstraint: ConcreteConstraint? = nil
  415. private func addConstraint(attributes: ConstraintAttributes) -> ConstraintDescription {
  416. if self.relation == nil {
  417. self.fromItem.attributes += attributes
  418. }
  419. return self
  420. }
  421. private func constrainTo(other: RelationTarget, relation: ConstraintRelation, location: SourceLocation) -> ConstraintDescription {
  422. self.location = location
  423. if let constant = other as? FloatConvertible {
  424. self.constant = constant.floatValue
  425. }
  426. let item = other.constraintItem
  427. if item.attributes != ConstraintAttributes.None {
  428. let toLayoutAttributes = item.attributes.layoutAttributes
  429. if toLayoutAttributes.count > 1 {
  430. let fromLayoutAttributes = self.fromItem.attributes.layoutAttributes
  431. if toLayoutAttributes != fromLayoutAttributes {
  432. NSException(name: "Invalid Constraint", reason: "Cannot constrain to multiple non identical attributes", userInfo: nil).raise()
  433. return self
  434. }
  435. item.attributes = ConstraintAttributes.None
  436. }
  437. }
  438. self.toItem = item
  439. self.relation = relation
  440. return self
  441. }
  442. @available(iOS 7.0, *)
  443. private func constrainTo(other: LayoutSupport, relation: ConstraintRelation, location: SourceLocation) -> ConstraintDescription {
  444. return constrainTo(ConstraintItem(object: other, attributes: ConstraintAttributes.None), relation: relation, location: location)
  445. }
  446. }