diff --git a/Material.xcodeproj/project.pbxproj b/Material.xcodeproj/project.pbxproj index de93b6798..08bfd4332 100644 --- a/Material.xcodeproj/project.pbxproj +++ b/Material.xcodeproj/project.pbxproj @@ -174,6 +174,9 @@ 9D054A6520D175AC00D0528D /* Material+UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D054A6320D175AC00D0528D /* Material+UIButton.swift */; }; 9D054A6620D175AC00D0528D /* Material+UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D054A6420D175AC00D0528D /* Material+UILabel.swift */; }; 9D39A81B20FE8ED100BA8FA1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D39A81A20FE8ED100BA8FA1 /* ViewController.swift */; }; + 9D494A38217F6B63003D66F1 /* LayoutAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D494A37217F6B63003D66F1 /* LayoutAttribute.swift */; }; + 9D494A3A217F6B70003D66F1 /* LayoutAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D494A39217F6B70003D66F1 /* LayoutAnchor.swift */; }; + 9D494A3C217F6B7D003D66F1 /* LayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D494A3B217F6B7D003D66F1 /* LayoutConstraint.swift */; }; 9D9089B92118914500605DC9 /* Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D9089B82118914500605DC9 /* Editor.swift */; }; 9DE25DE02170D7AF000C04DF /* Dialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE25DDF2170D7AF000C04DF /* Dialog.swift */; }; 9DE25DE22170D7C0000C04DF /* DialogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE25DE12170D7C0000C04DF /* DialogController.swift */; }; @@ -300,6 +303,9 @@ 9D054A6320D175AC00D0528D /* Material+UIButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UIButton.swift"; sourceTree = ""; }; 9D054A6420D175AC00D0528D /* Material+UILabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UILabel.swift"; sourceTree = ""; }; 9D39A81A20FE8ED100BA8FA1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 9D494A37217F6B63003D66F1 /* LayoutAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutAttribute.swift; sourceTree = ""; }; + 9D494A39217F6B70003D66F1 /* LayoutAnchor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutAnchor.swift; sourceTree = ""; }; + 9D494A3B217F6B7D003D66F1 /* LayoutConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutConstraint.swift; sourceTree = ""; }; 9D9089B82118914500605DC9 /* Editor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Editor.swift; sourceTree = ""; }; 9DE25DDF2170D7AF000C04DF /* Dialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dialog.swift; sourceTree = ""; }; 9DE25DE12170D7C0000C04DF /* DialogController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogController.swift; sourceTree = ""; }; @@ -652,6 +658,9 @@ isa = PBXGroup; children = ( 96BCB7811CB40DC500C806FE /* Layout.swift */, + 9D494A39217F6B70003D66F1 /* LayoutAnchor.swift */, + 9D494A37217F6B63003D66F1 /* LayoutAttribute.swift */, + 9D494A3B217F6B7D003D66F1 /* LayoutConstraint.swift */, ); name = Layout; sourceTree = ""; @@ -993,6 +1002,7 @@ 9DF352461FED210000B2A11B /* CheckButton.swift in Sources */, 965E810A1DD4D5C800D61E4B /* Font.swift in Sources */, 965E810B1DD4D5C800D61E4B /* RobotoFont.swift in Sources */, + 9D494A3C217F6B7D003D66F1 /* LayoutConstraint.swift in Sources */, 965E810C1DD4D5C800D61E4B /* DynamicFontType.swift in Sources */, 965E81101DD4D5C800D61E4B /* NavigationBar.swift in Sources */, 965E81111DD4D5C800D61E4B /* NavigationController.swift in Sources */, @@ -1015,6 +1025,7 @@ 965E81211DD4D5C800D61E4B /* TextStorage.swift in Sources */, 965E81221DD4D5C800D61E4B /* TextView.swift in Sources */, 965E80E71DD4C55200D61E4B /* Material+UIView.swift in Sources */, + 9D494A38217F6B63003D66F1 /* LayoutAttribute.swift in Sources */, 96B8D22D20CF82D5008BD149 /* FABMenu.swift in Sources */, 965E80E81DD4C55200D61E4B /* Material+CALayer.swift in Sources */, 965E80E91DD4C55200D61E4B /* Material+String.swift in Sources */, @@ -1054,6 +1065,7 @@ 965E80D21DD4C50600D61E4B /* Color.swift in Sources */, 96BFC1541E5E486F0075DE1F /* SpringAnimation.swift in Sources */, 9D054A6620D175AC00D0528D /* Material+UILabel.swift in Sources */, + 9D494A3A217F6B70003D66F1 /* LayoutAnchor.swift in Sources */, 965E80D31DD4C50600D61E4B /* Device.swift in Sources */, 965E80FD1DD4D59500D61E4B /* Toolbar.swift in Sources */, 965E80D41DD4C50600D61E4B /* Divider.swift in Sources */, diff --git a/Sources/iOS/ErrorTextField.swift b/Sources/iOS/ErrorTextField.swift index 8d9eb43e9..8351a5b10 100644 --- a/Sources/iOS/ErrorTextField.swift +++ b/Sources/iOS/ErrorTextField.swift @@ -69,9 +69,10 @@ open class ErrorTextField: TextField { get { return !errorLabel.isHidden } - set { - errorLabel.isHidden = !newValue - detailLabel.isHidden = newValue + set(value) { + errorLabel.isHidden = !value + detailLabel.isHidden = value + layoutSubviews() } } diff --git a/Sources/iOS/Layout.swift b/Sources/iOS/Layout.swift index 5cf1f3c06..27b33ef14 100644 --- a/Sources/iOS/Layout.swift +++ b/Sources/iOS/Layout.swift @@ -31,970 +31,797 @@ import UIKit import Motion -public class Layout { - /// Parent UIView context. - internal weak var parent: UIView? - - /// Child UIView context. - internal weak var child: UIView? - +/// A protocol that's conformed by UIView and UILayoutGuide. +public protocol Constraintable: class { } + +@available(iOS 9.0, *) +extension UILayoutGuide: Constraintable { } +extension UIView: Constraintable { } + +/// Layout extension for UIView. +public extension UIView { /** - An initializer that takes in a parent context. - - Parameter parent: An optional parent UIView. + Used to chain layout constraints on a child context. + - Parameter child: A child UIView to layout. + - Returns: A Layout instance. */ - public init(parent: UIView?) { - self.parent = parent + func layout(_ child: UIView) -> Layout { + addSubview(child) + child.translatesAutoresizingMaskIntoConstraints = false + return child.layout } - /** - An initializer that takes in a parent context and child context. - - Parameter parent: An optional parent UIView. - - Parameter child: An optional child UIView. - */ - public init(parent: UIView?, child: UIView?) { - self.parent = parent - self.child = child + /// Layout instance for the view. + var layout: Layout { + return Layout(constraintable: self) } - /** - Prints a debug message when the parent context is not available. - - Parameter function: A String representation of the function that - caused the issue. - - Returns: The current Layout instance. - */ - internal func debugParentNotAvailableMessage(function: String = #function) -> Layout { - debugPrint("[Material Layout Error: Parent view context is not available for \(function).") - return self + /// Anchor instance for the view. + var anchor: LayoutAnchor { + return LayoutAnchor(constraintable: self) } /** - Prints a debug message when the child context is not available. - - Parameter function: A String representation of the function that - caused the issue. - - Returns: The current Layout instance. + Anchor instance for safeAreaLayoutGuide. + Below iOS 11, it will be same as view.anchor. */ - internal func debugChildNotAvailableMessage(function: String = #function) -> Layout { - debugPrint("[Material Layout Error: Child view context is not available for \(function).") - return self + var safeAnchor: LayoutAnchor { + if #available(iOS 11.0, *) { + return LayoutAnchor(constraintable: safeAreaLayoutGuide) + } else { + return anchor + } } +} + +public struct Layout { + /// A weak reference to the constraintable. + weak var constraintable: Constraintable? - /** - Sets the width of a view. - - Parameter child: A child UIView to layout. - - Parameter width: A CGFloat value. - - Returns: The current Layout instance. - */ - @discardableResult - public func width(_ child: UIView, width: CGFloat) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() + /// Parent view of the view. + var parent: UIView? { + return (constraintable as? UIView)?.superview + } + + /// Returns the view that is being laied out. + private var view: UIView? { + var v = constraintable as? UIView + if #available(iOS 9.0, *), v == nil { + v = (constraintable as? UILayoutGuide)?.owningView } - self.child = child - Layout.width(parent: v, child: child, width: width) - return self + + return v } /** - Sets the width of a view assuming a child context view. - - Parameter width: A CGFloat value. - - Returns: The current Layout instance. + An initializer taking Constraintable. + - Parameter view: A Constraintable. */ - @discardableResult - public func width(_ width: CGFloat) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return self.width(v, width: width) + init(constraintable: Constraintable) { + self.constraintable = constraintable } - +} + +public extension Layout { /** - Sets the height of a view. - - Parameter child: A child UIView to layout. - - Parameter height: A CGFloat value. - - Returns: The current Layout instance. + Sets multiplier of the last created constraint. + Not meant for updating the multiplier as it will re-create the constraint. + - Parameter _ multiplier: A CGFloat multiplier. + - Returns: A Layout instance to allow chaining. */ - @discardableResult - public func height(_ child: UIView, height: CGFloat) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.height(parent: v, child: child, height: height) - return self + func multiply(_ multiplier: CGFloat) -> Layout { + return resetLastConstraint(multiplier: multiplier) } /** - Sets the height of a view assuming a child context view. - - Parameter height: A CGFloat value. - - Returns: The current Layout instance. + Sets priority of the last created constraint. + Not meant for updating the multiplier as it will re-create the constraint. + - Parameter _ value: A Float priority. + - Returns: A Layout instance to allow chaining. */ - @discardableResult - public func height(_ height: CGFloat) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return self.height(v, height: height) + func priority(_ value: Float) -> Layout { + return priority(.init(rawValue: value)) } /** - Sets the width and height of a view. - - Parameter child: A child UIView to layout. - - Parameter size: A CGSize value. - - Returns: The current Layout instance. + Sets priority of the last created constraint. + Not meant for updating the priority as it will re-create the constraint. + - Parameter _ priority: A UILayoutPriority. + - Returns: A Layout instance to allow chaining. */ - @discardableResult - public func size(_ child: UIView, size: CGSize) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.size(parent: v, child: child, size: size) - return self + func priority(_ priority: UILayoutPriority) -> Layout { + return resetLastConstraint(priority: priority) } /** - Sets the width and height of a view assuming a child context view. - - Parameter size: A CGSize value. - - Returns: The current Layout instance. + Removes the last created constraint and creates new one with the new multiplier and/or priority (if provided). + - Parameter multiplier: An optional CGFloat. + - Parameter priority: An optional UILayoutPriority. + - Returns: A Layout instance to allow chaining. */ - @discardableResult - public func size(_ size: CGSize = CGSize.zero) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() + private func resetLastConstraint(multiplier: CGFloat? = nil, priority: UILayoutPriority? = nil) -> Layout { + guard let v = view?.lastConstraint, v.isActive else { + return self } - return self.size(v, size: size) + v.isActive = false + let newV = NSLayoutConstraint(item: v.firstItem as Any, + attribute: v.firstAttribute, + relatedBy: v.relation, + toItem: v.secondItem, + attribute: v.secondAttribute, + multiplier: multiplier ?? v.multiplier, + constant: v.constant) + newV.priority = priority ?? v.priority + newV.isActive = true + view?.lastConstraint = newV + return self } - +} + +public extension Layout { /** - A collection of children views are horizontally stretched with optional left, - right padding and interim interimSpace. - - Parameter children: An Array UIView to layout. - - Parameter left: A CGFloat value for padding the left side. - - Parameter right: A CGFloat value for padding the right side. - - Parameter interimSpace: A CGFloat value for interim interimSpace. - - Returns: The current Layout instance. + Constraints top of the view to its parent's. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func horizontally(_ children: [UIView], left: CGFloat = 0, right: CGFloat = 0, interimSpace: InterimSpace = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - Layout.horizontally(parent: v, children: children, left: left, right: right, interimSpace: interimSpace) - return self + func top(_ offset: CGFloat = 0) -> Layout { + return constraint(.top, constant: offset) } /** - A collection of children views are vertically stretched with optional top, - bottom padding and interim interimSpace. - - Parameter children: An Array UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter interimSpace: A CGFloat value for interim interimSpace. - - Returns: The current Layout instance. + Constraints left of the view to its parent's. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func vertically(_ children: [UIView], top: CGFloat = 0, bottom: CGFloat = 0, interimSpace: InterimSpace = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - Layout.vertically(parent: v, children: children, top: top, bottom: bottom, interimSpace: interimSpace) - return self + func left(_ offset: CGFloat = 0) -> Layout { + return constraint(.left, constant: offset) } /** - A child view is horizontally stretched with optional left and right padding. - - Parameter child: A child UIView to layout. - - Parameter left: A CGFloat value for padding the left side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints right of the view to its parent. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func horizontally(_ child: UIView, left: CGFloat = 0, right: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.horizontally(parent: v, child: child, left: left, right: right) - return self + func right(_ offset: CGFloat = 0) -> Layout { + return constraint(.right, constant: -offset) } /** - A child view is horizontally stretched with optional left and right padding. - - Parameter left: A CGFloat value for padding the left side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints leading of the view to its parent's. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func horizontally(left: CGFloat = 0, right: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return horizontally(v, left: left, right: right) + func leading(_ offset: CGFloat = 0) -> Layout { + return constraint(.leading, constant: offset) } /** - A child view is vertically stretched with optional left and right padding. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Returns: The current Layout instance. + Constraints trailing of the view to its parent. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func vertically(_ child: UIView, top: CGFloat = 0, bottom: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.vertically(parent: v, child: child, top: top, bottom: bottom) - return self + func trailing(_ offset: CGFloat = 0) -> Layout { + return constraint(.trailing, constant: -offset) } /** - A child view is vertically stretched with optional left and right padding. - - Parameter top: A CGFloat value for padding the top side. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Returns: The current Layout instance. + Constraints bottom of the view to its parent's. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func vertically(top: CGFloat = 0, bottom: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return vertically(v, top: top, bottom: bottom) + func bottom(_ offset: CGFloat = 0) -> Layout { + return constraint(.bottom, constant: -offset) } /** - A child view is vertically and horizontally stretched with optional top, left, bottom and right padding. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter left: A CGFloat value for padding the left side. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints top-left of the view to its parent's. + - Parameter top: A CGFloat offset for top. + - Parameter left: A CGFloat offset for left. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func edges(_ child: UIView, top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.edges(parent: v, child: child, top: top, left: left, bottom: bottom, right: right) - return self + func topLeft(top: CGFloat = 0, left: CGFloat = 0) -> Layout { + return constraint(.topLeft, constants: top, left) } /** - A child view is vertically and horizontally stretched with optional top, left, bottom and right padding. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter left: A CGFloat value for padding the left side. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints top-right of the view to its parent's. + - Parameter top: A CGFloat offset for top. + - Parameter right: A CGFloat offset for right. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func edges(top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return edges(v, top: top, left: left, bottom: bottom, right: right) + func topRight(top: CGFloat = 0, right: CGFloat = 0) -> Layout { + return constraint(.topRight, constants: top, -right) } /** - A child view is aligned from the top with optional top padding. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Returns: The current Layout instance. + Constraints bottom-left of the view to its parent's. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter left: A CGFloat offset for left. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func top(_ child: UIView, top: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.top(parent: v, child: child, top: top) - return self + func bottomLeft(bottom: CGFloat = 0, left: CGFloat = 0) -> Layout { + return constraint(.bottomLeft, constants: -bottom, left) } /** - A child view is aligned from the top with optional top padding. - - Parameter top: A CGFloat value for padding the top side. - - Returns: The current Layout instance. + Constraints bottom-right of the view to its parent's. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter right: A CGFloat offset for right. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func top(_ top: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return self.top(v, top: top) + func bottomRight(bottom: CGFloat = 0, right: CGFloat = 0) -> Layout { + return constraint(.bottomRight, constants: -bottom, -right) } /** - A child view is aligned from the left with optional left padding. - - Parameter child: A child UIView to layout. - - Parameter left: A CGFloat value for padding the left side. - - Returns: The current Layout instance. + Constraints left and right of the view to its parent's. + - Parameter left: A CGFloat offset for left. + - Parameter right: A CGFloat offset for right. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func left(_ child: UIView, left: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.left(parent: v, child: child, left: left) - return self + func leftRight(left: CGFloat = 0, right: CGFloat = 0) -> Layout { + return constraint(.leftRight, constants: left, -right) } /** - A child view is aligned from the left with optional left padding. - - Parameter left: A CGFloat value for padding the left side. - - Returns: The current Layout instance. + Constraints top-leading of the view to its parent's. + - Parameter top: A CGFloat offset for top. + - Parameter leading: A CGFloat offset for leading. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func left(_ left: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return self.left(v, left: left) + func topLeading(top: CGFloat = 0, leading: CGFloat = 0) -> Layout { + return constraint(.topLeading, constants: top, leading) } /** - A child view is aligned from the bottom with optional bottom padding. - - Parameter child: A child UIView to layout. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Returns: The current Layout instance. + Constraints top-trailing of the view to its parent's. + - Parameter top: A CGFloat offset for top. + - Parameter trailing: A CGFloat offset for trailing. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func bottom(_ child: UIView, bottom: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.bottom(parent: v, child: child, bottom: bottom) - return self + func topTrailing(top: CGFloat = 0, trailing: CGFloat = 0) -> Layout { + return constraint(.topTrailing, constants: top, -trailing) } /** - A child view is aligned from the bottom with optional bottom padding. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Returns: The current Layout instance. + Constraints bottom-leading of the view to its parent's. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter leading: A CGFloat offset for leading. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func bottom(_ bottom: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return self.bottom(v, bottom: bottom) + func bottomLeading(bottom: CGFloat = 0, leading: CGFloat = 0) -> Layout { + return constraint(.bottomLeading, constants: -bottom, leading) } /** - A child view is aligned from the right with optional right padding. - - Parameter child: A child UIView to layout. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints bottom-trailing of the view to its parent's. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter trailing: A CGFloat offset for trailing. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func right(_ child: UIView, right: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.right(parent: v, child: child, right: right) - return self + func bottomTrailing(bottom: CGFloat = 0, trailing: CGFloat = 0) -> Layout { + return constraint(.bottomTrailing, constants: -bottom, -trailing) } /** - A child view is aligned from the right with optional right padding. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints leading and trailing of the view to its parent's. + - Parameter leading: A CGFloat offset for leading. + - Parameter trailing: A CGFloat offset for trailing. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func right(_ right: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return self.right(v, right: right) + func leadingTrailing(leading: CGFloat = 0, trailing: CGFloat = 0) -> Layout { + return constraint(.leadingTrailing, constants: leading, -trailing) } /** - A child view is aligned from the top left with optional top and left padding. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter left: A CGFloat value for padding the left side. - - Returns: The current Layout instance. + Constraints top and bottom of the view to its parent's. + - Parameter top: A CGFloat offset for top. + - Parameter bottom: A CGFloat offset for bottom. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func topLeft(_ child: UIView, top: CGFloat = 0, left: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.topLeft(parent: v, child: child, top: top, left: left) - return self + func topBottom(top: CGFloat = 0, bottom: CGFloat = 0) -> Layout { + return constraint(.topBottom, constants: top, -bottom) } /** - A child view is aligned from the top left with optional top and left padding. - - Parameter top: A CGFloat value for padding the top side. - - Parameter left: A CGFloat value for padding the left side. - - Returns: The current Layout instance. + Constraints center of the view to its parent's. + - Parameter offsetX: A CGFloat offset for horizontal center. + - Parameter offsetY: A CGFloat offset for vertical center. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func topLeft(top: CGFloat = 0, left: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return topLeft(v, top: top, left: left) + func center(offsetX: CGFloat = 0, offsetY: CGFloat = 0) -> Layout { + return constraint(.center, constants: offsetX, offsetY) } /** - A child view is aligned from the top right with optional top and right padding. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints horizontal center of the view to its parent's. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func topRight(_ child: UIView, top: CGFloat = 0, right: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.topRight(parent: v, child: child, top: top, right: right) - return self + func centerX(_ offset: CGFloat = 0) -> Layout { + return constraint(.centerX, constant: offset) } /** - A child view is aligned from the top right with optional top and right padding. - - Parameter top: A CGFloat value for padding the top side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints vertical center of the view to its parent's. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func topRight(top: CGFloat = 0, right: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return topRight(v, top: top, right: right) + func centerY(_ offset: CGFloat = 0) -> Layout { + return constraint(.centerY, constant: offset) } /** - A child view is aligned from the bottom left with optional bottom and left padding. - - Parameter child: A child UIView to layout. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter left: A CGFloat value for padding the left side. - - Returns: The current Layout instance. + Constraints width of the view to its parent's. + - Parameter offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func bottomLeft(_ child: UIView, bottom: CGFloat = 0, left: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.bottomLeft(parent: v, child: child, bottom: bottom, left: left) - return self + func width(offset: CGFloat = 0) -> Layout { + return constraint(.width, constant: offset) } /** - A child view is aligned from the bottom left with optional bottom and left padding. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter left: A CGFloat value for padding the left side. - - Returns: The current Layout instance. + Constraints height of the view to its parent's. + - Parameter offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func bottomLeft(bottom: CGFloat = 0, left: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return bottomLeft(v, bottom: bottom, left: left) + func height(offset: CGFloat = 0) -> Layout { + return constraint(.height, constant: offset) } /** - A child view is aligned from the bottom right with optional bottom and right padding. - - Parameter child: A child UIView to layout. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints edges of the view to its parent's. + - Parameter top: A CGFloat offset for top. + - Parameter left: A CGFloat offset for left. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter right: A CGFloat offset for right. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func bottomRight(_ child: UIView, bottom: CGFloat = 0, right: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.bottomRight(parent: v, child: child, bottom: bottom, right: right) - return self + func edges(top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) -> Layout { + return constraint(.edges, constants: top, left, -bottom, -right) } - +} + +public extension Layout { /** - A child view is aligned from the bottom right with optional bottom and right padding. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints width of the view to a constant value. + - Parameter _ width: A CGFloat value. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func bottomRight(bottom: CGFloat = 0, right: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return bottomRight(v, bottom: bottom, right: right) + func width(_ width: CGFloat) -> Layout { + return constraint(.constantWidth, constants: width) } /** - A child view is aligned at the center with an optional offsetX and offsetY value. - - Parameter child: A child UIView to layout. - - Parameter offsetX: A CGFloat value for the offset along the x axis. - - Parameter offsetX: A CGFloat value for the offset along the y axis. - - Returns: The current Layout instance. + Constraints height of the view to a constant value. + - Parameter _ height: A CGFloat value. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func center(_ child: UIView, offsetX: CGFloat = 0, offsetY: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.center(parent: v, child: child, offsetX: offsetX, offsetY: offsetY) - return self + func height(_ height: CGFloat) -> Layout { + return constraint(.constantHeight, constants: height) } - +} + +public extension Layout { /** - A child view is aligned at the center with an optional offsetX and offsetY value. - - Parameter offsetX: A CGFloat value for the offset along the x axis. - - Parameter offsetX: A CGFloat value for the offset along the y axis. - - Returns: The current Layout instance. + Constraints top of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func center(offsetX: CGFloat = 0, offsetY: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return center(v, offsetX: offsetX, offsetY: offsetY) + func top(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.top, to: anchor, constant: offset) } /** - A child view is aligned at the center horizontally with an optional offset value. - - Parameter child: A child UIView to layout. - - Parameter offset: A CGFloat value for the offset along the x axis. - - Returns: The current Layout instance. + Constraints left of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func centerHorizontally(_ child: UIView, offset: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.centerHorizontally(parent: v, child: child, offset: offset) - return self + func left(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.left, to: anchor, constant: offset) } /** - A child view is aligned at the center horizontally with an optional offset value. - - Parameter offset: A CGFloat value for the offset along the x axis. - - Returns: The current Layout instance. + Constraints right of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func centerHorizontally(offset: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return centerHorizontally(v, offset: offset) + func right(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.right, to: anchor, constant: -offset) } /** - A child view is aligned at the center vertically with an optional offset value. - - Parameter child: A child UIView to layout. - - Parameter offset: A CGFloat value for the offset along the y axis. - - Returns: The current Layout instance. + Constraints leading of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func centerVertically(_ child: UIView, offset: CGFloat = 0) -> Layout { - guard let v = parent else { - return debugParentNotAvailableMessage() - } - self.child = child - Layout.centerVertically(parent: v, child: child, offset: offset) - return self + func leading(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.leading, to: anchor, constant: offset) } /** - A child view is aligned at the center vertically with an optional offset value. - - Parameter offset: A CGFloat value for the offset along the y axis. - - Returns: The current Layout instance. + Constraints trailing of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ @discardableResult - public func centerVertically(offset: CGFloat = 0) -> Layout { - guard let v = child else { - return debugChildNotAvailableMessage() - } - return centerVertically(v, offset: offset) + func trailing(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.trailing, to: anchor, constant: -offset) } -} - -fileprivate extension Layout { + /** - Updates the consraints for a given view. - - Parameter for view: A UIView. + Constraints bottom of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ - class func updateConstraints(for view: UIView) { - view.setNeedsUpdateConstraints() - view.updateConstraintsIfNeeded() - view.setNeedsLayout() - view.layoutIfNeeded() + @discardableResult + func bottom(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.bottom, to: anchor, constant: -offset) } /** - Updates the constraints for a given Array of views. - - Parameter for [view]: An Array of UIViews. + Constraints top-leading of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter top: A CGFloat offset for top. + - Parameter leading: A CGFloat offset for leading. + - Returns: A Layout instance to allow chaining. */ - class func updateConstraints(for views: [UIView]) { - for v in views { - updateConstraints(for: v) - } + @discardableResult + func topLeading(_ anchor: LayoutAnchorable, top: CGFloat = 0, leading: CGFloat = 0) -> Layout { + return constraint(.topLeading, to: anchor, constants: top, leading) } -} - -/// Layout -extension Layout { + /** - Sets the width of a view. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter width: A CGFloat value. + Constraints top-trailing of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter top: A CGFloat offset for top. + - Parameter trailing: A CGFloat offset for trailing. + - Returns: A Layout instance to allow chaining. */ - public class func width(parent: UIView, child: UIView, width: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: width)) - updateConstraints(for: child) + @discardableResult + func topTrailing(_ anchor: LayoutAnchorable, top: CGFloat = 0, trailing: CGFloat = 0) -> Layout { + return constraint(.topTrailing, to: anchor, constants: top, -trailing) } /** - Sets the height of a view. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter height: A CGFloat value. + Constraints bottom-leading of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter leading: A CGFloat offset for leading. + - Returns: A Layout instance to allow chaining. */ - public class func height(parent: UIView, child: UIView, height: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: height)) - updateConstraints(for: child) + @discardableResult + func bottomLeading(_ anchor: LayoutAnchorable, bottom: CGFloat = 0, leading: CGFloat = 0) -> Layout { + return constraint(.bottomLeading, to: anchor, constants: -bottom, leading) } /** - Sets the width and height of a view. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter size: A CGSize value. + Constraints bottom-trailing of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter trailing: A CGFloat offset for trailing. + - Returns: A Layout instance to allow chaining. */ - public class func size(parent: UIView, child: UIView, size: CGSize = CGSize.zero) { - Layout.width(parent: parent, child: child, width: size.width) - Layout.height(parent: parent, child: child, height: size.height) + @discardableResult + func bottomTrailing(_ anchor: LayoutAnchorable, bottom: CGFloat = 0, trailing: CGFloat = 0) -> Layout { + return constraint(.bottomTrailing, to: anchor, constants: -bottom, -trailing) } /** - A collection of children views are horizontally stretched with optional left, - right padding and interim interimSpace. - - Parameter parent: A parent UIView context. - - Parameter children: An Array UIView to layout. - - Parameter left: A CGFloat value for padding the left side. - - Parameter right: A CGFloat value for padding the right side. - - Parameter interimSpace: A CGFloat value for interim interimSpace. + Constraints leading and trailing of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter leading: A CGFloat offset for leading. + - Parameter trailing: A CGFloat offset for trailing. + - Returns: A Layout instance to allow chaining. */ - public class func horizontally(parent: UIView, children: [UIView], left: CGFloat = 0, right: CGFloat = 0, interimSpace: InterimSpace = 0) { - prepareForConstraint(parent, children: children) - - if 0 < children.count { - parent.addConstraint(NSLayoutConstraint(item: children[0], attribute: .left, relatedBy: .equal, toItem: parent, attribute: .left, multiplier: 1, constant: left)) - for i in 1.. Layout { + return constraint(.leadingTrailing, to: anchor, constants: leading, -trailing) } /** - A collection of children views are vertically stretched with optional top, - bottom padding and interim interimSpace. - - Parameter parent: A parent UIView context. - - Parameter children: An Array UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter interimSpace: A CGFloat value for interim interimSpace. + Constraints top-left of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter top: A CGFloat offset for top. + - Parameter left: A CGFloat offset for left. + - Returns: A Layout instance to allow chaining. */ - public class func vertically(parent: UIView, children: [UIView], top: CGFloat = 0, bottom: CGFloat = 0, interimSpace: InterimSpace = 0) { - prepareForConstraint(parent, children: children) - - if 0 < children.count { - parent.addConstraint(NSLayoutConstraint(item: children[0], attribute: .top, relatedBy: .equal, toItem: parent, attribute: .top, multiplier: 1, constant: top)) - for i in 1.. Layout { + return constraint(.topLeft, to: anchor, constants: top, left) } /** - A child view is horizontally stretched with optional left and right padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter left: A CGFloat value for padding the left side. - - Parameter right: A CGFloat value for padding the right side. + Constraints top-right of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter top: A CGFloat offset for top. + - Parameter right: A CGFloat offset for right. + - Returns: A Layout instance to allow chaining. */ - public class func horizontally(parent: UIView, child: UIView, left: CGFloat = 0, right: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .left, relatedBy: .equal, toItem: parent, attribute: .left, multiplier: 1, constant: left)) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .right, relatedBy: .equal, toItem: parent, attribute: .right, multiplier: 1, constant: -right)) - updateConstraints(for: child) + @discardableResult + func topRight(_ anchor: LayoutAnchorable, top: CGFloat = 0, right: CGFloat = 0) -> Layout { + return constraint(.topRight, to: anchor, constants: top, -right) } /** - A child view is vertically stretched with optional left and right padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter bottom: A CGFloat value for padding the bottom side. + Constraints bottom-left of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter left: A CGFloat offset for left. + - Returns: A Layout instance to allow chaining. */ - public class func vertically(parent: UIView, child: UIView, top: CGFloat = 0, bottom: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .top, relatedBy: .equal, toItem: parent, attribute: .top, multiplier: 1, constant: top)) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .bottom, relatedBy: .equal, toItem: parent, attribute: .bottom, multiplier: 1, constant: -bottom)) - updateConstraints(for: child) + @discardableResult + func bottomLeft(_ anchor: LayoutAnchorable, bottom: CGFloat = 0, left: CGFloat = 0) -> Layout { + return constraint(.bottomLeft, to: anchor, constants: -bottom, left) } /** - A child view is vertically and horizontally stretched with optional top, left, bottom and right padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter left: A CGFloat value for padding the left side. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter right: A CGFloat value for padding the right side. + Constraints bottom-right of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter right: A CGFloat offset for right. + - Returns: A Layout instance to allow chaining. */ - public class func edges(parent: UIView, child: UIView, top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) { - horizontally(parent: parent, child: child, left: left, right: right) - vertically(parent: parent, child: child, top: top, bottom: bottom) + @discardableResult + func bottomRight(_ anchor: LayoutAnchorable, bottom: CGFloat = 0, right: CGFloat = 0) -> Layout { + return constraint(.bottomRight, to: anchor, constants: -bottom, -right) } /** - A child view is aligned from the top with optional top padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Returns: The current Layout instance. + Constraints left and right of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter left: A CGFloat offset for left. + - Parameter right: A CGFloat offset for right. + - Returns: A Layout instance to allow chaining. */ - public class func top(parent: UIView, child: UIView, top: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .top, relatedBy: .equal, toItem: parent, attribute: .top, multiplier: 1, constant: top)) - updateConstraints(for: child) + @discardableResult + func leftRight(_ anchor: LayoutAnchorable, left: CGFloat = 0, right: CGFloat = 0) -> Layout { + return constraint(.leftRight, to: anchor, constants: left, -right) } /** - A child view is aligned from the left with optional left padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter left: A CGFloat value for padding the left side. - - Returns: The current Layout instance. + Constraints top and bottom of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter top: A CGFloat offset for top. + - Parameter bottom: A CGFloat offset for bottom. + - Returns: A Layout instance to allow chaining. */ - public class func left(parent: UIView, child: UIView, left: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .left, relatedBy: .equal, toItem: parent, attribute: .left, multiplier: 1, constant: left)) - updateConstraints(for: child) + @discardableResult + func topBottom(_ anchor: LayoutAnchorable, top: CGFloat = 0, bottom: CGFloat = 0) -> Layout { + return constraint(.topBottom, to: anchor, constants: top, -bottom) } /** - A child view is aligned from the bottom with optional bottom padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Returns: The current Layout instance. + Constraints center of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter offsetX: A CGFloat offset for horizontal center. + - Parameter offsetY: A CGFloat offset for vertical center. + - Returns: A Layout instance to allow chaining. */ - public class func bottom(parent: UIView, child: UIView, bottom: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .bottom, relatedBy: .equal, toItem: parent, attribute: .bottom, multiplier: 1, constant: -bottom)) - updateConstraints(for: child) + @discardableResult + func center(_ anchor: LayoutAnchorable, offsetX: CGFloat = 0, offsetY: CGFloat = 0) -> Layout { + return constraint(.center, to: anchor, constants: offsetX, offsetY) } /** - A child view is aligned from the right with optional right padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints horizontal center of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ - public class func right(parent: UIView, child: UIView, right: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .right, relatedBy: .equal, toItem: parent, attribute: .right, multiplier: 1, constant: -right)) - updateConstraints(for: child) + @discardableResult + func centerX(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.centerX, to: anchor, constant: offset) } /** - A child view is aligned from the top left with optional top and left padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter left: A CGFloat value for padding the left side. - - Returns: The current Layout instance. + Constraints vertical center of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter _ offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ - public class func topLeft(parent: UIView, child: UIView, top t: CGFloat = 0, left l: CGFloat = 0) { - top(parent: parent, child: child, top: t) - left(parent: parent, child: child, left: l) + @discardableResult + func centerY(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.centerY, to: anchor, constant: offset) } /** - A child view is aligned from the top right with optional top and right padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter top: A CGFloat value for padding the top side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints height of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ - public class func topRight(parent: UIView, child: UIView, top t: CGFloat = 0, right r: CGFloat = 0) { - top(parent: parent, child: child, top: t) - right(parent: parent, child: child, right: r) + @discardableResult + func width(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.width, to: anchor, constant: offset) } /** - A child view is aligned from the bottom left with optional bottom and left padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter left: A CGFloat value for padding the left side. - - Returns: The current Layout instance. + Constraints height of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter offset: A CGFloat offset. + - Returns: A Layout instance to allow chaining. */ - public class func bottomLeft(parent: UIView, child: UIView, bottom b: CGFloat = 0, left l: CGFloat = 0) { - bottom(parent: parent, child: child, bottom: b) - left(parent: parent, child: child, left: l) + @discardableResult + func height(_ anchor: LayoutAnchorable, _ offset: CGFloat = 0) -> Layout { + return constraint(.height, to: anchor, constant: offset) } /** - A child view is aligned from the bottom right with optional bottom and right padding. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter bottom: A CGFloat value for padding the bottom side. - - Parameter right: A CGFloat value for padding the right side. - - Returns: The current Layout instance. + Constraints edges of the view to the given anchor. + - Parameter _ anchor: A LayoutAnchorable. + - Parameter top: A CGFloat offset for top. + - Parameter left: A CGFloat offset for left. + - Parameter bottom: A CGFloat offset for bottom. + - Parameter right: A CGFloat offset for right. + - Returns: A Layout instance to allow chaining. */ - public class func bottomRight(parent: UIView, child: UIView, bottom b: CGFloat = 0, right r: CGFloat = 0) { - bottom(parent: parent, child: child, bottom: b) - right(parent: parent, child: child, right: r) + @discardableResult + func edges(_ anchor: LayoutAnchorable, top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) -> Layout { + return constraint(.edges, to: anchor, constants: top, left, -bottom, -right) } - +} + +private extension Layout { /** - A child view is aligned at the center with an optional offsetX and offsetY value. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter offsetX: A CGFloat value for the offset along the x axis. - - Parameter offsetX: A CGFloat value for the offset along the y axis. - - Returns: The current Layout instance. + Constraints the view to its parent according to the provided attribute. + If the constraint already exists, will update its constant. + - Parameter _ attribute: A LayoutAttribute. + - Parameter constant: A CGFloat. + - Returns: A Layout instance to allow chaining. */ - public class func center(parent: UIView, child: UIView, offsetX: CGFloat = 0, offsetY: CGFloat = 0) { - centerHorizontally(parent: parent, child: child, offset: offsetX) - centerVertically(parent: parent, child: child, offset: offsetY) + func constraint(_ attribute: LayoutAttribute, constant: CGFloat) -> Layout { + return constraint([attribute], constants: constant) } /** - A child view is aligned at the center horizontally with an optional offset value. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter offset: A CGFloat value for the offset along the y axis. - - Returns: The current Layout instance. + Constraints the view to its parent according to the provided attributes. + If any of the constraints already exists, will update its constant. + - Parameter _ attributes: An array of LayoutAttribute. + - Parameter constants: A list of CGFloat. + - Returns: A Layout instance to allow chaining. */ - public class func centerHorizontally(parent: UIView, child: UIView, offset: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .centerX, relatedBy: .equal, toItem: parent, attribute: .centerX, multiplier: 1, constant: offset)) - updateConstraints(for: child) + func constraint(_ attributes: [LayoutAttribute], constants: CGFloat...) -> Layout { + var attributes = attributes + var anchor: LayoutAnchor! + + if attributes == .constantHeight || attributes == .constantWidth { + attributes.removeLast() + anchor = LayoutAnchor(constraintable: nil, attributes: [.notAnAttribute]) + } else { + + guard parent != nil else { + fatalError("[Material Error: Constraint requires view to have parent.") + } + + anchor = LayoutAnchor(constraintable: parent, attributes: attributes) + } + return constraint(attributes, to: anchor, constants: constants) } /** - A child view is aligned at the center vertically with an optional offset value. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. - - Parameter offset: A CGFloat value for the offset along the y axis. - - Returns: The current Layout instance. + Constraints the view to the given anchor according to the provided attribute. + If the constraint already exists, will update its constant. + - Parameter _ attribute: A LayoutAttribute. + - Parameter to anchor: A LayoutAnchorable. + - Parameter constant: A CGFloat. + - Returns: A Layout instance to allow chaining. */ - public class func centerVertically(parent: UIView, child: UIView, offset: CGFloat = 0) { - prepareForConstraint(parent, child: child) - parent.addConstraint(NSLayoutConstraint(item: child, attribute: .centerY, relatedBy: .equal, toItem: parent, attribute: .centerY, multiplier: 1, constant: offset)) - updateConstraints(for: child) + func constraint(_ attribute: LayoutAttribute, to anchor: LayoutAnchorable, constant: CGFloat) -> Layout { + return constraint([attribute], to: anchor, constants: constant) } /** - Creats an Array with a NSLayoutConstraint value. - - Parameter format: The VFL format string. - - Parameter options: Additional NSLayoutFormatOptions. - - Parameter metrics: An optional Dictionary of metric key / value pairs. - - Parameter views: A Dictionary of view key / value pairs. - - Returns: The Array instance. + Constraints the view to the given anchor according to the provided attributes. + If any of the constraints already exists, will update its constant. + - Parameter _ attributes: An array of LayoutAttribute. + - Parameter to anchor: A LayoutAnchorable. + - Parameter constants: A list of CGFloat. + - Returns: A Layout instance to allow chaining. */ - public class func constraint(format: String, options: NSLayoutConstraint.FormatOptions, metrics: [String: Any]?, views: [String: Any]) -> [NSLayoutConstraint] { - for (_, a) in views { - if let v = a as? UIView { - v.translatesAutoresizingMaskIntoConstraints = false - } - } - return NSLayoutConstraint.constraints( - withVisualFormat: format, - options: options, - metrics: metrics, - views: views - ) + func constraint(_ attributes: [LayoutAttribute], to anchor: LayoutAnchorable, constants: CGFloat...) -> Layout { + return constraint(attributes, to: anchor, constants: constants) } /** - Prepares the relationship between the parent view context and child view - to layout. If the child is not already added to the view hierarchy as the - parent's child, then it is added. - - Parameter parent: A parent UIView context. - - Parameter child: A child UIView to layout. + Constraints the view to the given anchor according to the provided attributes. + If any of the constraints already exists, will update its constant. + - Parameter _ attributes: An array of LayoutAttribute. + - Parameter to anchor: A LayoutAnchorable. + - Parameter constants: An array of CGFloat. + - Returns: A Layout instance to allow chaining. */ - private class func prepareForConstraint(_ parent: UIView, child: UIView) { - if parent != child.superview { - child.removeFromSuperview() - parent.addSubview(child) + func constraint(_ attributes: [LayoutAttribute], to anchor: LayoutAnchorable, constants: [CGFloat]) -> Layout { + let from = LayoutAnchor(constraintable: constraintable, attributes: attributes) + var to = anchor as? LayoutAnchor + if to?.attributes.isEmpty ?? true { + let v = (anchor as? UIView) ?? (anchor as? LayoutAnchor)?.constraintable + to = LayoutAnchor(constraintable: v, attributes: attributes) } - child.translatesAutoresizingMaskIntoConstraints = false + let constraint = LayoutConstraint(fromAnchor: from, toAnchor: to!, constants: constants) + + + let constraints = (view?.constraints ?? []) + (view?.superview?.constraints ?? []) + let newConstraints = constraint.constraints + for newConstraint in newConstraints { + guard let activeConstraint = constraints.first(where: { $0.equalTo(newConstraint) }) else { + newConstraint.isActive = true + view?.lastConstraint = newConstraint + continue + } + + activeConstraint.constant = newConstraint.constant + } + + return self } - +} + +private extension NSLayoutConstraint { /** - Prepares the relationship between the parent view context and an Array of - child UIViews. - - Parameter parent: A parent UIView context. - - Parameter children: An Array of UIViews. + Checks if the constraint is equal to given constraint. + - Parameter _ other: An NSLayoutConstraint. + - Returns: A Bool indicating whether constraints are equal. */ - private class func prepareForConstraint(_ parent: UIView, children: [UIView]) { - for v in children { - prepareForConstraint(parent, child: v) - } + func equalTo(_ other: NSLayoutConstraint) -> Bool { + return firstItem === other.firstItem + && secondItem === other.secondItem + && firstAttribute == other.firstAttribute + && secondAttribute == other.secondAttribute } } -/// A memory reference to the LayoutKey instance for UIView extensions. -fileprivate var LayoutKey: UInt8 = 0 +/// A memory reference to the lastConstraint of UIView. +private var LastConstraintKey: UInt8 = 0 -/// Layout extension for UIView. -extension UIView { - /// Layout reference. - public private(set) var layout: Layout { +private extension UIView { + /** + The last consntraint that's created by Layout system. + Used to set multiplier/priority on the last constraint. + */ + var lastConstraint: NSLayoutConstraint? { get { - return AssociatedObject.get(base: self, key: &LayoutKey) { - return Layout(parent: self) + return AssociatedObject.get(base: self, key: &LastConstraintKey) { + nil } } set(value) { - AssociatedObject.set(base: self, key: &LayoutKey, value: value) + AssociatedObject.set(base: self, key: &LastConstraintKey, value: value) } } - - /** - Used to chain layout constraints on a child context. - - Parameter child: A child UIView to layout. - - Returns: The current Layout instance. - */ - public func layout(_ child: UIView) -> Layout { - return Layout(parent: self, child: child) - } } diff --git a/Sources/iOS/LayoutAnchor.swift b/Sources/iOS/LayoutAnchor.swift new file mode 100644 index 000000000..b2902922b --- /dev/null +++ b/Sources/iOS/LayoutAnchor.swift @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2015 - 2018, Daniel Dahan and CosmicMind, Inc. . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of CosmicMind nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import UIKit + +/// A protocol that's conformed by UIView, LayoutAnchor, and Layout. +public protocol LayoutAnchorable { } + +extension UIView: LayoutAnchorable { } +extension Layout: LayoutAnchorable { } +extension LayoutAnchor: LayoutAnchorable { } + +public struct LayoutAnchor { + /// A weak reference to the constraintable. + weak var constraintable: Constraintable? + + /// An array of LayoutAttribute for the view. + let attributes: [LayoutAttribute] + + /** + An initializer taking constraintable and anchor attributes. + - Parameter view: A Constraintable. + - Parameter attributes: An array of LayoutAtrribute. + */ + init(constraintable: Constraintable?, attributes: [LayoutAttribute] = []) { + self.constraintable = constraintable + self.attributes = attributes + } +} + +public extension LayoutAnchor { + /// A layout anchor representing top of the view. + var top: LayoutAnchor { + return anchor(.top) + } + + /// A layout anchor representing bottom of the view. + var bottom: LayoutAnchor { + return anchor(.bottom) + } + + /// A layout anchor representing left of the view. + var left: LayoutAnchor { + return anchor(.left) + } + + /// A layout anchor representing right of the view. + var right: LayoutAnchor { + return anchor(.right) + } + + /// A layout anchor representing leading of the view. + var leading: LayoutAnchor { + return anchor(.leading) + } + + /// A layout anchor representing trailing of the view. + var trailing: LayoutAnchor { + return anchor(.trailing) + } + + /// A layout anchor representing top-left of the view. + var topLeft: LayoutAnchor { + return acnhor(.topLeft) + } + + /// A layout anchor representing top-right of the view. + var topRight: LayoutAnchor { + return acnhor(.topRight) + } + + /// A layout anchor representing bottom-left of the view. + var bottomLeft: LayoutAnchor { + return acnhor(.bottomLeft) + } + + /// A layout anchor representing bottom-right of the view. + var bottomRight: LayoutAnchor { + return acnhor(.bottomRight) + } + + /// A layout anchor representing top-leading of the view. + var topLeading: LayoutAnchor { + return acnhor(.topLeading) + } + + /// A layout anchor representing top-trailing of the view. + var topTrailing: LayoutAnchor { + return acnhor(.topTrailing) + } + + /// A layout anchor representing bottom-leading of the view. + var bottomLeading: LayoutAnchor { + return acnhor(.bottomLeading) + } + + /// A layout anchor representing bottom-trailing of the view. + var bottomTrailing: LayoutAnchor { + return acnhor(.bottomTrailing) + } + + /// A layout anchor representing top and bottom of the view. + var topBottom: LayoutAnchor { + return acnhor(.topBottom) + } + + /// A layout anchor representing left and right of the view. + var leftRight: LayoutAnchor { + return acnhor(.leftRight) + } + + /// A layout anchor representing leading and trailing of the view. + var leadingTrailing: LayoutAnchor { + return acnhor(.leadingTrailing) + } + + /// A layout anchor representing center of the view. + var center: LayoutAnchor { + return acnhor(.center) + } + + /// A layout anchor representing horizontal center of the view. + var centerX: LayoutAnchor { + return anchor(.centerX) + } + + /// A layout anchor representing vertical center of the view. + var centerY: LayoutAnchor { + return anchor(.centerY) + } + + /// A layout anchor representing top, left, bottom and right of the view. + var edges: LayoutAnchor { + return acnhor(.edges) + } + + /// A layout anchor representing width of the view. + var width: LayoutAnchor { + return anchor(.width) + } + /// A layout anchor representing height of the view. + var height: LayoutAnchor { + return anchor(.height) + } +} + +private extension LayoutAnchor { + /** + Creates LayoutAnchor with the given attribute. + - Parameter attribute: A LayoutAttribute. + - Returns: A LayoutAnchor. + */ + func anchor(_ attribute: LayoutAttribute) -> LayoutAnchor { + return LayoutAnchor(constraintable: constraintable, attributes: [attribute]) + } + + /** + Creates LayoutAnchor with the given attributes. + - Parameter attributes: An array of LayoutAttribute. + - Returns: A LayoutAnchor. + */ + func acnhor(_ attributes: [LayoutAttribute]) -> LayoutAnchor { + return LayoutAnchor(constraintable: constraintable, attributes: attributes) + } +} diff --git a/Sources/iOS/LayoutAttribute.swift b/Sources/iOS/LayoutAttribute.swift new file mode 100644 index 000000000..1a0f53468 --- /dev/null +++ b/Sources/iOS/LayoutAttribute.swift @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 - 2018, Daniel Dahan and CosmicMind, Inc. . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of CosmicMind nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import UIKit + +/// A typealias for NSLayoutConstraint.Attribute +internal typealias LayoutAttribute = NSLayoutConstraint.Attribute + +internal extension Array where Element == LayoutAttribute { + /// A LayoutAttribute array containing top and left. + static var topLeft: [LayoutAttribute] { + return [.top, .left] + } + + /// A LayoutAttribute array containing top and right. + static var topRight: [LayoutAttribute] { + return [.top, .right] + } + + /// A LayoutAttribute array containing bottom and left. + static var bottomLeft: [LayoutAttribute] { + return [.bottom, .left] + } + + /// A LayoutAttribute array containing bottom and right. + static var bottomRight: [LayoutAttribute] { + return [.bottom, .right] + } + + /// A LayoutAttribute array containing left and right. + static var leftRight: [LayoutAttribute] { + return [.left, .right] + } + + /// A LayoutAttribute array containing top and leading. + static var topLeading: [LayoutAttribute] { + return [.top, .leading] + } + + /// A LayoutAttribute array containing top and trailing. + static var topTrailing: [LayoutAttribute] { + return [.top, .trailing] + } + + /// A LayoutAttribute array containing bottom and leading. + static var bottomLeading: [LayoutAttribute] { + return [.bottom, .leading] + } + + /// A LayoutAttribute array containing bottom and trailing. + static var bottomTrailing: [LayoutAttribute] { + return [.bottom, .trailing] + } + + /// A LayoutAttribute array containing left and trailing. + static var leadingTrailing: [LayoutAttribute] { + return [.leading, .trailing] + } + + /// A LayoutAttribute array containing top and bottom. + static var topBottom: [LayoutAttribute] { + return [.top, .bottom] + } + + /// A LayoutAttribute array containing centerX and centerY. + static var center: [LayoutAttribute] { + return [.centerX, .centerY] + } + + /// A LayoutAttribute array containing top, left, bottom and right. + static var edges: [LayoutAttribute] { + return [.top, .left, .bottom, .right] + } + + /// A LayoutAttribute array for constant height. + static var constantHeight: [LayoutAttribute] { + return [.height, .notAnAttribute] + } + + /// A LayoutAttribute array for constant width. + static var constantWidth: [LayoutAttribute] { + return [.width, .notAnAttribute] + } +} diff --git a/Sources/iOS/LayoutConstraint.swift b/Sources/iOS/LayoutConstraint.swift new file mode 100644 index 000000000..59bf6e2d2 --- /dev/null +++ b/Sources/iOS/LayoutConstraint.swift @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015 - 2018, Daniel Dahan and CosmicMind, Inc. . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of CosmicMind nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import UIKit + +internal struct LayoutConstraint { + /// `From` anchor for the constraint. + private let fromAnchor: LayoutAnchor + + /// `To` anchor for the constraint. + private let toAnchor: LayoutAnchor + + /// An array of constants for the constraint. + private var constants: [CGFloat] + + /** + An initializer taking `from` and `to` anchors and constants for the constraint. + - Parameter fromAnchor: A LayoutAnchor. + - Parameter toAnchor: A LayoutAnchor. + - Parameter constants: An array of CGFloat. + */ + init(fromAnchor: LayoutAnchor, toAnchor: LayoutAnchor, constants: [CGFloat]) { + self.fromAnchor = fromAnchor + self.toAnchor = toAnchor + self.constants = constants + } +} + +internal extension LayoutConstraint { + /// Creates an array of NSLayoutConstraint from a LayoutConstraint. + var constraints: [NSLayoutConstraint] { + guard fromAnchor.attributes.count == toAnchor.attributes.count else { + fatalError("[Material Error: The number of attributes of anchors does not match.]") + } + + guard fromAnchor.attributes.count == constants.count else { + fatalError("[Material Error: The number of constants does not match the number of constraints.]") + } + + var v: [NSLayoutConstraint] = [] + + zip(zip(fromAnchor.attributes, toAnchor.attributes), constants).forEach { + v.append(NSLayoutConstraint(item: fromAnchor.constraintable as Any, + attribute: $0.0, + relatedBy: .equal, + toItem: toAnchor.constraintable, + attribute: $0.1, + multiplier: 1, + constant: $1)) + } + + + return v + } +}