// // UIView+Sweeter.swift // // Created by Yonat Sharon on 2019-02-08. // import UIKit public extension UIView { /// Sweeter: Set constant attribute. Example: `constrain(.width, to: 17)` @discardableResult func constrain( _ at: NSLayoutConstraint.Attribute, to: CGFloat = 0, ratio: CGFloat = 1, relation: NSLayoutConstraint.Relation = .equal, priority: UILayoutPriority = .required, identifier: String? = nil ) -> NSLayoutConstraint { let constraint = NSLayoutConstraint( item: self, attribute: at, relatedBy: relation, toItem: nil, attribute: .notAnAttribute, multiplier: ratio, constant: to ) constraint.priority = priority constraint.identifier = identifier addConstraintWithoutConflict(constraint) return constraint } /// Sweeter: Pin subview at a specific place. Example: `constrain(label, at: .top)` @discardableResult func constrain( _ subview: UIView, at: NSLayoutConstraint.Attribute, diff: CGFloat = 0, ratio: CGFloat = 1, relation: NSLayoutConstraint.Relation = .equal, priority: UILayoutPriority = .required, identifier: String? = nil ) -> NSLayoutConstraint { let constraint = NSLayoutConstraint( item: subview, attribute: at, relatedBy: relation, toItem: self, attribute: at, multiplier: ratio, constant: diff ) constraint.priority = priority constraint.identifier = identifier addConstraintWithoutConflict(constraint) return constraint } /// Sweeter: Pin two subviews to each other. Example: /// /// `constrain(label, at: .leading, to: textField)` /// /// `constrain(textField, at: .top, to: label, at: .bottom, diff: 8)` @discardableResult func constrain( _ subview: UIView, at: NSLayoutConstraint.Attribute, to subview2: UIView, at at2: NSLayoutConstraint.Attribute = .notAnAttribute, diff: CGFloat = 0, ratio: CGFloat = 1, relation: NSLayoutConstraint.Relation = .equal, priority: UILayoutPriority = .required, identifier: String? = nil ) -> NSLayoutConstraint { let at2real = at2 == .notAnAttribute ? at : at2 let constraint = NSLayoutConstraint( item: subview, attribute: at, relatedBy: relation, toItem: subview2, attribute: at2real, multiplier: ratio, constant: diff ) constraint.priority = priority constraint.identifier = identifier addConstraintWithoutConflict(constraint) return constraint } /// Sweeter: Add subview pinned to specific places. Example: `addConstrainedSubview(button, constrain: .centerX, .centerY)` @discardableResult func addConstrainedSubview(_ subview: UIView, constrain: NSLayoutConstraint.Attribute...) -> [NSLayoutConstraint] { return addConstrainedSubview(subview, constrainedAttributes: constrain) } @discardableResult internal func addConstrainedSubview(_ subview: UIView, constrainedAttributes: [NSLayoutConstraint.Attribute]) -> [NSLayoutConstraint] { subview.translatesAutoresizingMaskIntoConstraints = false addSubview(subview) return constrainedAttributes.map { self.constrain(subview, at: $0) } } internal func addConstraintWithoutConflict(_ constraint: NSLayoutConstraint) { removeConstraints(constraints.filter { constraint.firstItem === $0.firstItem && constraint.secondItem === $0.secondItem && constraint.firstAttribute == $0.firstAttribute && constraint.secondAttribute == $0.secondAttribute }) addConstraint(constraint) } /// Sweeter: Search the view hierarchy recursively for a subview that conforms to `predicate` func viewInHierarchy(frontFirst: Bool = true, where predicate: (UIView) -> Bool) -> UIView? { if predicate(self) { return self } let views = frontFirst ? subviews.reversed() : subviews for subview in views { if let found = subview.viewInHierarchy(frontFirst: frontFirst, where: predicate) { return found } } return nil } /// Sweeter: Search the view hierarchy recursively for a subview with `aClass` func viewWithClass(_ aClass: T.Type, frontFirst: Bool = true) -> T? { return viewInHierarchy(frontFirst: frontFirst, where: { $0 is T }) as? T } /// Sweeter: The color used to tint the view, as inherited from its superviews. var actualTintColor: UIColor { var tintedView: UIView? = self while let currentView = tintedView, nil == currentView.tintColor { tintedView = currentView.superview } return tintedView?.tintColor ?? UIColor(red: 0, green: 0.5, blue: 1, alpha: 1) // swiftlint:disable:this no_magic_numbers } }