diff --git a/Sources/Impl/PinLayoutImpl+Layouting.swift b/Sources/Impl/PinLayoutImpl+Layouting.swift index 8a8995e3..4e386b34 100644 --- a/Sources/Impl/PinLayoutImpl+Layouting.swift +++ b/Sources/Impl/PinLayoutImpl+Layouting.swift @@ -25,6 +25,16 @@ // MARK: UIView's frame computation methods extension PinLayout { + /** + The method will execute PinLayout commands immediately. This method is **required only if your + source codes should also work in Xcode Playgrounds**. Outside of playgrounds, PinLayout executes + this method implicitly, it is not necessary to call it. + + Examples: + ```swift + view.pin.top(20).width(100).layout() + ``` + */ public func layout() { apply() } diff --git a/Sources/Impl/PinLayoutImpl+Size.swift b/Sources/Impl/PinLayoutImpl+Size.swift index c5bc4486..5da4f740 100644 --- a/Sources/Impl/PinLayoutImpl+Size.swift +++ b/Sources/Impl/PinLayoutImpl+Size.swift @@ -94,16 +94,43 @@ extension PinLayout { return setSize(view.getRect(keepTransform: keepTransform).size, context) } + /** + Set the view aspect ratio. + + AspectRatio is applied only if a single dimension (either width or height) can be determined, + in that case the aspect ratio will be used to compute the other dimension. + + * AspectRatio is defined as the ratio between the width and the height (width / height). + * An aspect ratio of 2 means the width is twice the size of the height. + * AspectRatio respects the min (minWidth/minHeight) and the max (maxWidth/maxHeight) + dimensions of an item. + */ @discardableResult public func aspectRatio(_ ratio: CGFloat) -> PinLayout { return setAdjustSizeType(.aspectRatio(ratio), { "aspectRatio(\(ratio))" }) } + /** + Set the view aspect ratio using another UIView's aspect ratio. + + AspectRatio is applied only if a single dimension (either width or height) can be determined, + in that case the aspect ratio will be used to compute the other dimension. + + * AspectRatio is defined as the ratio between the width and the height (width / height). + * AspectRatio respects the min (minWidth/minHeight) and the max (maxWidth/maxHeight) + dimensions of an item. + */ public func aspectRatio(of view: View) -> PinLayout { let rect = view.getRect(keepTransform: keepTransform) return setAdjustSizeType(.aspectRatio(rect.width / rect.height), { "aspectRatio(of: \(viewDescription(view)))" }) } + /** + If the layouted view is an UIImageView, this method will set the aspectRatio using + the UIImageView's image dimension. + + For other types of views, this method as no impact. + */ #if os(iOS) || os(tvOS) public func aspectRatio() -> PinLayout { func context() -> String { return "aspectRatio()" } @@ -121,12 +148,71 @@ extension PinLayout { } #endif + /** + The method adjust the view's size based on the view's `sizeThatFits()` method result. + PinLayout will adjust either the view's width or height based on the `fitType` parameter value. + + Notes: + * If margin rules apply, margins will be applied when determining the reference dimension (width/height). + * The resulting size will always respect `minWidth` / `maxWidth` / `minHeight` / `maxHeight`. + + - Parameter fitType: Identify the reference dimension (width / height) that will be used + to adjust the view's size: + + .width: The method adjust the view's size based on the **reference width**. + * If properties related to the width have been pinned (e.g: width, left & right, margins, ...), + the **reference width will be determined by these properties**, if not the **current view's width** + will be used. + * The resulting width will always **match the reference width**. + + .height: The method adjust the view's size based on the **reference height**. + * If properties related to the height have been pinned (e.g: height, top & bottom, margins, ...), + the **reference height will be determined by these properties**, if not the **current view's height** + will be used. + * The resulting height will always **match the reference height**. + + .widthFlexible: Similar to `.width`, except that PinLayout won't constrain the resulting width to + match the reference width. The resulting width may be smaller or bigger depending on the view's + sizeThatFits(..) method result. For example a single line UILabel may returns a smaller width if its + string is smaller than the reference width. + + .heightFlexible: Similar to `.height`, except that PinLayout won't constrain the resulting height to + match the reference height. The resulting height may be smaller or bigger depending on the view's + sizeThatFits(..) method result. + + Examples: + + ``` + // Adjust the view's size based on a width of 100 pixels. + // The resulting width will always match the pinned property `width(100)`. + view.pin.width(100).sizeToFit(.width) + + // Adjust the view's size based on view's current width. + // The resulting width will always match the view's original width. + // The resulting height will never be bigger than the specified `maxHeight`. + view.pin.sizeToFit(.width).maxHeight(100) + + // Adjust the view's size based on 100% of the superview's height. + // The resulting height will always match the pinned property `height(100%)`. + view.pin.height(100%).sizeToFit(.height) + + // Adjust the view's size based on view's current height. + // The resulting width will always match the view's original height. + view.pin.sizeToFit(.height) + + // Since `.widthFlexible` has been specified, its possible that the resulting + // width will be smaller or bigger than 100 pixels, based of the label's sizeThatFits() + // method result. + label.pin.width(100).sizeToFit(.widthFlexible) + ``` + */ @available(OSX 10.10, *) public func sizeToFit(_ fitType: FitType) -> PinLayout { return setAdjustSizeType(fitType.toAdjustSizeType(), { return "sizeToFit(\(fitType.description))" }) } #if os(iOS) || os(tvOS) + @available(*, deprecated, message: "fitSize() is deprecated, please use sizeToFit(fitType: FitType)") public func fitSize() -> PinLayout { return setAdjustSizeType(.fitSizeLegacy, { return "fitSize()" }) } diff --git a/Sources/Impl/PinLayoutImpl+WrapContent.swift b/Sources/Impl/PinLayoutImpl+WrapContent.swift index 3e782d26..75668099 100644 --- a/Sources/Impl/PinLayoutImpl+WrapContent.swift +++ b/Sources/Impl/PinLayoutImpl+WrapContent.swift @@ -24,26 +24,61 @@ import AppKit #endif extension PinLayout { + /** + Adjust the view's width & height to wrap all its subviews. The method also adjust subviews position to create a tight wrap. + */ public func wrapContent() -> PinLayout { return wrapContent(.all, padding: PEdgeInsets(top: 0, left: 0, bottom: 0, right: 0), { return "wrapContent()" }) } + /** + Adjust the view's width & height to wrap all its subviews. The method also adds a padding around all subviews. + + - Parameters: + - padding: Specify a padding value. + */ public func wrapContent(padding: CGFloat) -> PinLayout { return wrapContent(.all, padding: PEdgeInsets(top: padding, left: padding, bottom: padding, right: padding), { return "wrapContent(padding: \(padding)" }) } + /** + Adjust the view's width & height to wrap all its subviews. The method also adds a padding around all subviews. + + - Parameters: + - padding: Specify a padding using an UIEdgeInsets. + */ public func wrapContent(padding: PEdgeInsets) -> PinLayout { return wrapContent(.all, padding: padding, { return "wrapContent(padding: \(insetsDescription(padding))" }) } + /** + Adjust the view's width AND/OR height to wrap all its subviews. + + - Parameters: + - type: Specify the wrap type (.all, .horizontally, .vertically) + */ public func wrapContent(_ type: WrapType) -> PinLayout { return wrapContent(type, padding: PEdgeInsets(top: 0, left: 0, bottom: 0, right: 0), { return "wrapContent(\(type.description)" }) } + /** + Adjust the view's width AND/OR height to wrap all its subviews. The method also adds a padding around all subviews. + + - Parameters: + - type: Specify the wrap type (.all, .horizontally, .vertically) + - padding: Specify a padding value. + */ public func wrapContent(_ type: WrapType, padding: CGFloat) -> PinLayout { return wrapContent(type, padding: PEdgeInsets(top: padding, left: padding, bottom: padding, right: padding), { return "wrapContent(\(type.description), padding: \(padding)" }) } + /** + Adjust the view's width AND/OR height to wrap all its subviews. The method also adds a padding around all subviews. + + - Parameters: + - type: Specify the wrap type (.all, .horizontally, .vertically) + - padding: Specify a padding using an UIEdgeInsets. + */ public func wrapContent(_ type: WrapType, padding: PEdgeInsets) -> PinLayout { return wrapContent(type, padding: padding, { return "wrapContent(\(type.description), padding: \(insetsDescription(padding))" }) } diff --git a/Sources/Impl/PinLayoutImpl.swift b/Sources/Impl/PinLayoutImpl.swift index b0c13bac..5d3a2b45 100644 --- a/Sources/Impl/PinLayoutImpl.swift +++ b/Sources/Impl/PinLayoutImpl.swift @@ -98,9 +98,12 @@ public class PinLayout { } #endif + // + // MARK: Layout using distances from superview’s edges // // top, left, bottom, right // + public func top() -> PinLayout { top({ return "top()" }) return self @@ -251,6 +254,13 @@ public class PinLayout { return self } + // Pin multiple edges at once. + + /** + Pin all edges on its superview's corresponding edges (top, bottom, left, right). + + Similar to calling `view.top().bottom().left().right()` + */ public func all() -> PinLayout { top({ "all() top coordinate" }) bottom({ "all() bottom coordinate" }) @@ -259,6 +269,11 @@ public class PinLayout { return self } + /** + Pin all edges on its superview's corresponding edges (top, bottom, left, right). + + Similar to calling `view.top().bottom().left().right()` + */ public func all(_ value: CGFloat) -> PinLayout { top(value, { "all(\(value)) top coordinate" }) bottom(value, { "all(\(value)) bottom coordinate" }) @@ -267,6 +282,11 @@ public class PinLayout { return self } + /** + Pin all edges on its superview's corresponding edges (top, bottom, left, right). + + Similar to calling `view.top().bottom().left().right()` + */ public func all(_ insets: PEdgeInsets) -> PinLayout { top(insets.top, { "all(\(insets)) top coordinate" }) bottom(insets.bottom, { "all(\(insets)) bottom coordinate" }) @@ -275,57 +295,100 @@ public class PinLayout { return self } + /** + Pin the left and right edges on its superview's corresponding edges. + + Similar to calling `view.left().right()`. + */ public func horizontally() -> PinLayout { right({ "horizontally() right coordinate" }) left({ "horizontally() left coordinate" }) return self } + /** + Pin the left and right edges on its superview's corresponding edges. + + Similar to calling `view.left().right()`. + */ public func horizontally(_ value: CGFloat) -> PinLayout { left(value, { return "horizontally(\(value)) left coordinate" }) right(value, { return "horizontally(\(value)) right coordinate" }) return self } + /** + Pin the left and right edges on its superview's corresponding edges. + + Similar to calling `view.left().right()`. + */ public func horizontally(_ percent: Percent) -> PinLayout { left(percent, { return "horizontally(\(percent.description)) left coordinate" }) right(percent, { return "horizontally(\(percent.description)) right coordinate" }) return self } + /** + Pin the left and right edges on its superview's corresponding edges. + + Similar to calling `view.left().right()`. + */ public func horizontally(_ insets: PEdgeInsets) -> PinLayout { left(insets.left, { return "horizontally(\(insets)) left coordinate" }) right(insets.right, { return "horizontally(\(insets)) right coordinate" }) return self } + /** + Pin the **top and bottom edges** on its superview's corresponding edges. + + Similar to calling `view.top().bottom()`. + */ public func vertically() -> PinLayout { top({ "vertically() top coordinate" }) bottom({ "vertically() bottom coordinate" }) return self } + /** + Pin the **top and bottom edges** on its superview's corresponding edges. + + Similar to calling `view.top().bottom()`. + */ public func vertically(_ value: CGFloat) -> PinLayout { top(value, { return "vertically(\(value)) top coordinate" }) bottom(value, { return "vertically(\(value)) bottom coordinate" }) return self } + /** + Pin the **top and bottom edges** on its superview's corresponding edges. + + Similar to calling `view.top().bottom()`. + */ public func vertically(_ percent: Percent) -> PinLayout { top(percent, { return "vertically(\(percent.description)) top coordinate" }) bottom(percent, { return "vertically(\(percent.description)) bottom coordinate" }) return self } + /** + Pin the **top and bottom edges** on its superview's corresponding edges. + The UIEdgeInsets.top is used to pin the top edge and the UIEdgeInsets.bottom + for the bottom edge. + */ public func vertically(_ insets: PEdgeInsets) -> PinLayout { top(insets.top, { return "vertically(\(insets)) top coordinate" }) bottom(insets.bottom, { return "vertically(\(insets)) bottom coordinate" }) return self } + // + // MARK: Layout using edges // // top, left, bottom, right // + public func top(to edge: VerticalEdge) -> PinLayout { func context() -> String { return relativeEdgeContext(method: "top", edge: edge) } if let coordinate = computeCoordinate(forEdge: edge, context) { @@ -389,12 +452,15 @@ public class PinLayout { } return self } - + + // + // MARK: Layout using anchors // // topLeft, topCenter, topRight, // centerLeft, center, centerRight, // bottomLeft, bottomCenter, bottomRight, // + public func topLeft(to anchor: Anchor) -> PinLayout { func context() -> String { return relativeAnchorContext(method: "topLeft", anchor: anchor) } if let coordinatesList = computeCoordinates(forAnchors: [anchor], context) { @@ -632,8 +698,9 @@ public class PinLayout { } // - // width, height + // MARK: Width, height // + public func width(_ width: CGFloat) -> PinLayout { return setWidth(width, { return "width(\(width))" }) } @@ -707,10 +774,11 @@ public class PinLayout { guard let layoutSuperviewRect = layoutSuperviewRect(context) else { return self } return setMaxHeight(percent.of(layoutSuperviewRect.height), context) } - + // - // justify, align + // MARK: justify / align // + public func justify(_ value: HorizontalAlign) -> PinLayout { justify = value return self @@ -722,13 +790,20 @@ public class PinLayout { } // - // Margins + // MARK: Margins // + + /** + Set the top margin. + */ public func marginTop(_ value: CGFloat) -> PinLayout { marginTop = value return self } + /** + Set the top margin. + */ public func marginTop(_ percent: Percent) -> PinLayout { func context() -> String { return "marginTop(\(percent.description))" } return marginTop(percent, context) @@ -740,11 +815,17 @@ public class PinLayout { return self } + /** + Set the left margin. + */ public func marginLeft(_ value: CGFloat) -> PinLayout { marginLeft = value return self } + /** + Set the left margin. + */ public func marginLeft(_ percent: Percent) -> PinLayout { func context() -> String { return "marginLeft(\(percent.description))" } return marginLeft(percent, context) @@ -756,11 +837,17 @@ public class PinLayout { return self } + /** + Set the bottom margin. + */ public func marginBottom(_ value: CGFloat) -> PinLayout { marginBottom = value return self } + /** + Set the bottom margin. + */ public func marginBottom(_ percent: Percent) -> PinLayout { func context() -> String { return "marginBottom(\(percent.description))" } return marginBottom(percent, context) @@ -772,11 +859,17 @@ public class PinLayout { return self } + /** + Set the right margin. + */ public func marginRight(_ value: CGFloat) -> PinLayout { marginRight = value return self } + /** + Set the right margin. + */ public func marginRight(_ percent: Percent) -> PinLayout { func context() -> String { return "marginRight(\(percent.description))" } return marginRight(percent, context) @@ -788,32 +881,68 @@ public class PinLayout { return self } + // RTL support + + /** + Set the start margin. + + Depends on the value of `Pin.layoutDirection(...)`: + * In LTR direction, start margin specify the **left** margin. + * In RTL direction, start margin specify the **right** margin. + */ @discardableResult public func marginStart(_ value: CGFloat) -> PinLayout { return isLTR() ? marginLeft(value) : marginRight(value) } + /** + Set the start margin. + + Depends on the value of `Pin.layoutDirection(...)`: + * In LTR direction, start margin specify the **left** margin. + * In RTL direction, start margin specify the **right** margin. + */ public func marginStart(_ percent: Percent) -> PinLayout { func context() -> String { return "marginStart(\(percent.description))" } return isLTR() ? marginLeft(percent, context) : marginRight(percent, context) } - + + /** + Set the end margin. + + Depends on the value of `Pin.layoutDirection(...)`: + * In LTR direction, end margin specify the **right** margin. + * In RTL direction, end margin specify the **left** margin. + */ @discardableResult public func marginEnd(_ value: CGFloat) -> PinLayout { return isLTR() ? marginRight(value) : marginLeft(value) } + /** + Set the end margin. + + Depends on the value of `Pin.layoutDirection(...)`: + * In LTR direction, end margin specify the **right** margin. + * In RTL direction, end margin specify the **left** margin. + */ public func marginEnd(_ percent: Percent) -> PinLayout { func context() -> String { return "marginEnd(\(percent.description))" } return isLTR() ? marginRight(percent, context) : marginLeft(percent, context) } + /** + Set the left, right, start and end margins to the specified value. + */ public func marginHorizontal(_ value: CGFloat) -> PinLayout { marginLeft = value marginRight = value return self } + /** + Set the left, right, start and end margins to the specified value. + */ public func marginHorizontal(_ percent: Percent) -> PinLayout { func context() -> String { return "marginHorizontal(\(percent.description))" } return marginHorizontal(percent, context) @@ -823,12 +952,18 @@ public class PinLayout { return marginLeft(percent, context).marginRight(percent, context) } + /** + Set the top and bottom margins to the specified value. + */ public func marginVertical(_ value: CGFloat) -> PinLayout { marginTop = value marginBottom = value return self } + /** + Set the top and bottom margins to the specified value. + */ public func marginVertical(_ percent: Percent) -> PinLayout { func context() -> String { return "marginVertical(\(percent.description))" } return marginVertical(percent, context) @@ -837,7 +972,11 @@ public class PinLayout { private func marginVertical(_ percent: Percent, _ context: Context) -> Self { return marginTop(percent, context).marginBottom(percent, context) } - + + /** + Set all margins using UIEdgeInsets. + This method is particularly useful to set all margins using iOS 11 `UIView.safeAreaInsets`. + */ public func margin(_ insets: PEdgeInsets) -> PinLayout { marginTop = insets.top marginBottom = insets.bottom @@ -846,6 +985,12 @@ public class PinLayout { return self } + /** + Set margins using NSDirectionalEdgeInsets. + This method is particularly to set all margins using iOS 11 `UIView.directionalLayoutMargins`. + + Available only on iOS 11 and higher. + */ #if os(iOS) || os(tvOS) @available(tvOS 11.0, iOS 11.0, *) public func margin(_ directionalInsets: NSDirectionalEdgeInsets) -> PinLayout { @@ -857,6 +1002,9 @@ public class PinLayout { } #endif + /** + Set all margins to the specified value. + */ public func margin(_ value: CGFloat) -> PinLayout { marginTop = value marginLeft = value @@ -865,6 +1013,9 @@ public class PinLayout { return self } + /** + Set all margins to the specified value. + */ public func margin(_ percent: Percent) -> PinLayout { func context() -> String { return "margin(\(percent.description))" } return marginTop(percent, context) @@ -873,6 +1024,9 @@ public class PinLayout { .marginRight(percent, context) } + /** + Set individually top, horizontal margins and bottom margin. + */ public func margin(_ top: CGFloat, _ left: CGFloat, _ bottom: CGFloat, _ right: CGFloat) -> PinLayout { marginTop = top marginLeft = left @@ -881,6 +1035,9 @@ public class PinLayout { return self } + /** + Set individually top, horizontal margins and bottom margin. + */ public func margin(_ top: Percent, _ left: Percent, _ bottom: Percent, _ right: Percent) -> PinLayout { func context() -> String { return "margin(top: \(top.description), left: \(left.description), bottom: \(bottom.description), right: \(right.description)" @@ -891,6 +1048,9 @@ public class PinLayout { .marginRight(right, context) } + /** + Set individually vertical margins (top, bottom) and horizontal margins (left, right, start, end). + */ public func margin(_ vertical: CGFloat, _ horizontal: CGFloat) -> PinLayout { marginTop = vertical marginLeft = horizontal @@ -899,11 +1059,17 @@ public class PinLayout { return self } + /** + Set individually vertical margins (top, bottom) and horizontal margins (left, right, start, end). + */ public func margin(_ vertical: Percent, _ horizontal: Percent) -> PinLayout { func context() -> String { return "margin(vertical: \(vertical.description), horizontal: \(horizontal.description)"} return marginVertical(vertical, context).marginHorizontal(horizontal, context) } + /** + Set individually top, horizontal margins and bottom margin. + */ public func margin(_ top: CGFloat, _ horizontal: CGFloat, _ bottom: CGFloat) -> PinLayout { marginTop = top marginLeft = horizontal @@ -912,11 +1078,20 @@ public class PinLayout { return self } + /** + Set individually top, horizontal margins and bottom margin. + */ public func margin(_ top: Percent, _ horizontal: Percent, _ bottom: Percent) -> PinLayout { func context() -> String { return "margin(top: \(top.description), horizontal: \(horizontal.description), bottom: \(bottom.description)"} return marginTop(top, context).marginHorizontal(horizontal, context).marginBottom(bottom, context) } + /// Normally if only either left or right has been specified, PinLayout will MOVE the view to apply left or right margins. + /// This is also true even if the width has been set. + /// Calling pinEdges() will force PinLayout to pin the four edges and then apply left and/or right margins, and this without + /// moving the view. + /// + /// - Returns: PinLayout public func pinEdges() -> PinLayout { shouldPinEdges = true return self