UIView+Sweeter.swift 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. //
  2. // UIView+Sweeter.swift
  3. //
  4. // Created by Yonat Sharon on 2019-02-08.
  5. //
  6. import UIKit
  7. public extension UIView {
  8. /// Sweeter: Set constant attribute. Example: `constrain(.width, to: 17)`
  9. @discardableResult func constrain(
  10. _ at: NSLayoutConstraint.Attribute,
  11. to: CGFloat = 0,
  12. ratio: CGFloat = 1,
  13. relation: NSLayoutConstraint.Relation = .equal,
  14. priority: UILayoutPriority = .required,
  15. identifier: String? = nil
  16. ) -> NSLayoutConstraint {
  17. let constraint = NSLayoutConstraint(
  18. item: self, attribute: at, relatedBy: relation,
  19. toItem: nil, attribute: .notAnAttribute, multiplier: ratio, constant: to
  20. )
  21. constraint.priority = priority
  22. constraint.identifier = identifier
  23. addConstraintWithoutConflict(constraint)
  24. return constraint
  25. }
  26. /// Sweeter: Pin subview at a specific place. Example: `constrain(label, at: .top)`
  27. @discardableResult func constrain(
  28. _ subview: UIView,
  29. at: NSLayoutConstraint.Attribute,
  30. diff: CGFloat = 0,
  31. ratio: CGFloat = 1,
  32. relation: NSLayoutConstraint.Relation = .equal,
  33. priority: UILayoutPriority = .required,
  34. identifier: String? = nil
  35. ) -> NSLayoutConstraint {
  36. let constraint = NSLayoutConstraint(
  37. item: subview, attribute: at, relatedBy: relation,
  38. toItem: self, attribute: at, multiplier: ratio, constant: diff
  39. )
  40. constraint.priority = priority
  41. constraint.identifier = identifier
  42. addConstraintWithoutConflict(constraint)
  43. return constraint
  44. }
  45. /// Sweeter: Pin two subviews to each other. Example:
  46. ///
  47. /// `constrain(label, at: .leading, to: textField)`
  48. ///
  49. /// `constrain(textField, at: .top, to: label, at: .bottom, diff: 8)`
  50. @discardableResult func constrain(
  51. _ subview: UIView,
  52. at: NSLayoutConstraint.Attribute,
  53. to subview2: UIView,
  54. at at2: NSLayoutConstraint.Attribute = .notAnAttribute,
  55. diff: CGFloat = 0,
  56. ratio: CGFloat = 1,
  57. relation: NSLayoutConstraint.Relation = .equal,
  58. priority: UILayoutPriority = .required,
  59. identifier: String? = nil
  60. ) -> NSLayoutConstraint {
  61. let at2real = at2 == .notAnAttribute ? at : at2
  62. let constraint = NSLayoutConstraint(
  63. item: subview, attribute: at, relatedBy: relation,
  64. toItem: subview2, attribute: at2real, multiplier: ratio, constant: diff
  65. )
  66. constraint.priority = priority
  67. constraint.identifier = identifier
  68. addConstraintWithoutConflict(constraint)
  69. return constraint
  70. }
  71. /// Sweeter: Add subview pinned to specific places. Example: `addConstrainedSubview(button, constrain: .centerX, .centerY)`
  72. @discardableResult func addConstrainedSubview(_ subview: UIView, constrain: NSLayoutConstraint.Attribute...) -> [NSLayoutConstraint] {
  73. return addConstrainedSubview(subview, constrainedAttributes: constrain)
  74. }
  75. @discardableResult internal func addConstrainedSubview(_ subview: UIView, constrainedAttributes: [NSLayoutConstraint.Attribute]) -> [NSLayoutConstraint] {
  76. subview.translatesAutoresizingMaskIntoConstraints = false
  77. addSubview(subview)
  78. return constrainedAttributes.map { self.constrain(subview, at: $0) }
  79. }
  80. internal func addConstraintWithoutConflict(_ constraint: NSLayoutConstraint) {
  81. removeConstraints(constraints.filter {
  82. constraint.firstItem === $0.firstItem
  83. && constraint.secondItem === $0.secondItem
  84. && constraint.firstAttribute == $0.firstAttribute
  85. && constraint.secondAttribute == $0.secondAttribute
  86. })
  87. addConstraint(constraint)
  88. }
  89. /// Sweeter: Search the view hierarchy recursively for a subview that conforms to `predicate`
  90. func viewInHierarchy(frontFirst: Bool = true, where predicate: (UIView) -> Bool) -> UIView? {
  91. if predicate(self) { return self }
  92. let views = frontFirst ? subviews.reversed() : subviews
  93. for subview in views {
  94. if let found = subview.viewInHierarchy(frontFirst: frontFirst, where: predicate) {
  95. return found
  96. }
  97. }
  98. return nil
  99. }
  100. /// Sweeter: Search the view hierarchy recursively for a subview with `aClass`
  101. func viewWithClass<T>(_ aClass: T.Type, frontFirst: Bool = true) -> T? {
  102. return viewInHierarchy(frontFirst: frontFirst, where: { $0 is T }) as? T
  103. }
  104. /// Sweeter: The color used to tint the view, as inherited from its superviews.
  105. var actualTintColor: UIColor {
  106. var tintedView: UIView? = self
  107. while let currentView = tintedView, nil == currentView.tintColor {
  108. tintedView = currentView.superview
  109. }
  110. return tintedView?.tintColor ?? UIColor(red: 0, green: 0.5, blue: 1, alpha: 1) // swiftlint:disable:this no_magic_numbers
  111. }
  112. }