diff --git a/Neatly/LayoutFormat.swift b/Neatly/LayoutFormat.swift index c3a9677..d5f4cc8 100644 --- a/Neatly/LayoutFormat.swift +++ b/Neatly/LayoutFormat.swift @@ -21,17 +21,19 @@ public protocol LayoutFormatDescribing { public extension Layout { enum Format { - + case stackLowPriorityPadding(axis: NSLayoutConstraint.Axis, spacing: CGFloat, insets: UIEdgeInsets) case stack(axis: NSLayoutConstraint.Axis, spacing: CGFloat, insets: UIEdgeInsets) case fill(axis: NSLayoutConstraint.Axis, spacing: CGFloat, insets: UIEdgeInsets) case table(columns: Int, horizontalSpacing: CGFloat, verticalSpacing: CGFloat, insets: UIEdgeInsets) var formatDescribing: LayoutFormatDescribing { switch self { + case let .stackLowPriorityPadding(axis, spacing, insets): + return StackLayout(axis: axis, lowPrioritySpacing: false, spacing: spacing, lowPriorityPadding: true, insets: insets, fill: false) case let .stack(axis, spacing, insets): - return StackLayout(axis: axis, spacing: spacing, insets: insets, fill: false) + return StackLayout(axis: axis, lowPrioritySpacing: false, spacing: spacing, lowPriorityPadding: false, insets: insets, fill: false) case let .fill(axis, spacing, insets): - return StackLayout(axis: axis, spacing: spacing, insets: insets, fill: true) + return StackLayout(axis: axis, lowPrioritySpacing: false, spacing: spacing, lowPriorityPadding: false, insets: insets, fill: true) case let .table(columns, horizontalSpacing, verticalSpacing, insets): return TableLayout(columns: columns, spacing: (horizontalSpacing, verticalSpacing), insets: insets) } diff --git a/Neatly/StackLayout.swift b/Neatly/StackLayout.swift index 3f7b887..345ef9d 100644 --- a/Neatly/StackLayout.swift +++ b/Neatly/StackLayout.swift @@ -10,7 +10,9 @@ import UIKit struct StackLayout { let axis: NSLayoutConstraint.Axis + let lowPrioritySpacing: Bool let spacing: CGFloat + let lowPriorityPadding: Bool let insets: UIEdgeInsets let fill: Bool } @@ -20,20 +22,38 @@ extension StackLayout: LayoutFormatDescribing { public func prepare(sizedViews: [SizedView], in superview: UIView) -> [NSLayoutConstraint] { switch self.axis { case .vertical: - return StackLayout.prepareVertical(for: sizedViews, in: superview, spacing: spacing, insets: insets, fill: fill) + return StackLayout.prepareVertical(for: sizedViews, in: superview, + lowPrioritySpacing: lowPrioritySpacing, + spacing: spacing, + lowPriorityPadding: lowPriorityPadding, + insets: insets, + fill: fill) case .horizontal: - return StackLayout.prepareHorizontal(for: sizedViews, in: superview, spacing: spacing, insets: insets, fill: fill) + return StackLayout.prepareHorizontal(for: sizedViews, in: superview, + lowPrioritySpacing: lowPrioritySpacing, + spacing: spacing, + lowPriorityPadding: lowPriorityPadding, + insets: insets, + fill: fill) @unknown default: fatalError("unexpected axis: \(self.axis)") } } - static func prepareVertical(for sizedViews: [SizedView], in superview: UIView, spacing: CGFloat, insets: UIEdgeInsets, fill: Bool) -> [NSLayoutConstraint] { + static func prepareVertical(for sizedViews: [SizedView], + in superview: UIView, + lowPrioritySpacing: Bool, + spacing: CGFloat, + lowPriorityPadding: Bool, + insets: UIEdgeInsets, + fill: Bool) -> [NSLayoutConstraint] { return prepare( for: sizedViews, in: superview, + lowPrioritySpacing: lowPrioritySpacing, spacing: spacing, fill: fill, + lowPriorityPadding: lowPriorityPadding, minorPadding: (insets.left, insets.right), majorPadding: (insets.top, insets.bottom), minorEdgeMinimum: { $0.leftAnchor }, @@ -47,12 +67,20 @@ extension StackLayout: LayoutFormatDescribing { minorCenter: { $0.centerXAnchor }) } - static func prepareHorizontal(for sizedViews: [SizedView], in superview: UIView, spacing: CGFloat, insets: UIEdgeInsets, fill: Bool) -> [NSLayoutConstraint] { + static func prepareHorizontal(for sizedViews: [SizedView], + in superview: UIView, + lowPrioritySpacing: Bool, + spacing: CGFloat, + lowPriorityPadding: Bool, + insets: UIEdgeInsets, + fill: Bool) -> [NSLayoutConstraint] { return prepare( for: sizedViews, in: superview, + lowPrioritySpacing: lowPrioritySpacing, spacing: spacing, fill: fill, + lowPriorityPadding: lowPriorityPadding, minorPadding: (insets.top, insets.bottom), majorPadding: (insets.left, insets.right), minorEdgeMinimum: { $0.topAnchor }, @@ -74,10 +102,12 @@ extension StackLayout: LayoutFormatDescribing { static func prepare( for sizedViews: [SizedView], in superview: UIView, + lowPrioritySpacing: Bool, spacing: CGFloat, fill: Bool, - minorPadding: (CGFloat, CGFloat), - majorPadding: (CGFloat, CGFloat), + lowPriorityPadding: Bool, + minorPadding: (top: CGFloat, bottom: CGFloat), + majorPadding: (left: CGFloat, right: CGFloat), minorEdgeMinimum: AxisAnchorProducer, minorEdgeMaximum: AxisAnchorProducer, majorEdgeMinimum: AxisAnchorProducer, @@ -95,33 +125,49 @@ extension StackLayout: LayoutFormatDescribing { let minEdge = majorEdgeMinimum(firstView.view).constraint(equalTo: majorEdgeMinimum(superview), constant: majorPadding.0) let viewConstraints = sizedViews.sliding().flatMap { view, nextView -> [NSLayoutConstraint] in - let majorConstraints = self.prepareMajor(forView: view, in: superview, majorPadding: majorPadding, + let majorConstraints = self.prepareMajor(forView: view, in: superview, lowPriorityPadding: lowPriorityPadding, majorPadding: majorPadding, majorEdgeMinimum: majorEdgeMinimum, majorEdgeMaximum: majorEdgeMaximum, majorDimension: majorDimension, majorDimensionValue: majorDimensionValue) - let minorConstraints = self.prepareMinor(forView: view, in: superview, minorPadding: minorPadding, + let minorConstraints = self.prepareMinor(forView: view, in: superview, lowPriorityPadding: lowPriorityPadding, minorPadding: minorPadding, minorEdgeMinimum: minorEdgeMinimum, minorEdgeMaximum: minorEdgeMaximum, minorDimension: minorDimension, minorDimensionValue: minorDimensionValue, minorCenter: minorCenter) - - let spacing = [majorEdgeMinimum(nextView.view).constraint(equalTo: majorEdgeMaximum(view.view), constant: spacing)] + let spacingConstraint = majorEdgeMinimum(nextView.view).constraint(equalTo: majorEdgeMaximum(view.view), constant: spacing) + let spacing = lowPriorityPadding ? [spacingConstraint.with(priority: .defaultLow)] : [spacingConstraint] let width = fill ? [majorDimension(nextView.view).constraint(equalTo: majorDimension(view.view), multiplier: 1).with(priority: 500)] : [] return majorConstraints + minorConstraints + spacing + width } let lastViewMajor = self.prepareMajor( - forView: lastView, in: superview, majorPadding: majorPadding, - majorEdgeMinimum: majorEdgeMinimum, majorEdgeMaximum: majorEdgeMaximum, - majorDimension: majorDimension, majorDimensionValue: majorDimensionValue) + forView: lastView, + in: superview, + lowPriorityPadding: lowPriorityPadding, + majorPadding: majorPadding, + majorEdgeMinimum: majorEdgeMinimum, + majorEdgeMaximum: majorEdgeMaximum, + majorDimension: majorDimension, + majorDimensionValue: majorDimensionValue) let lastViewMinor = self.prepareMinor( - forView: lastView, in: superview, minorPadding: minorPadding, - minorEdgeMinimum: minorEdgeMinimum, minorEdgeMaximum: minorEdgeMaximum, - minorDimension: minorDimension, minorDimensionValue: minorDimensionValue, + forView: lastView, + in: superview, + lowPriorityPadding: lowPriorityPadding, + minorPadding: minorPadding, + minorEdgeMinimum: minorEdgeMinimum, + minorEdgeMaximum: minorEdgeMaximum, + minorDimension: minorDimension, + minorDimensionValue: minorDimensionValue, minorCenter: minorCenter) - let lastViewRight = fill ? [majorEdgeMaximum(lastView.view).constraint(equalTo: majorEdgeMaximum(superview), constant: -majorPadding.1)] : [] + let lastViewRight: [NSLayoutConstraint] + if fill { + let lastViewRightConstraint = majorEdgeMaximum(lastView.view).constraint(equalTo: majorEdgeMaximum(superview), constant: -majorPadding.right) + lastViewRight = lowPriorityPadding ? [lastViewRightConstraint.with(priority: .defaultLow)] : [lastViewRightConstraint] + } else { + lastViewRight = [] + } let lastViewConstraints = lastViewMajor + lastViewMinor + lastViewRight @@ -131,13 +177,14 @@ extension StackLayout: LayoutFormatDescribing { private static func prepareMajor( forView sizedView: SizedView, in superview: UIView, - majorPadding: (CGFloat, CGFloat), + lowPriorityPadding: Bool, + majorPadding: (left: CGFloat, right: CGFloat), majorEdgeMinimum: AxisAnchorProducer, majorEdgeMaximum: AxisAnchorProducer, majorDimension: DimensionAnchorProducer, majorDimensionValue: ValueProducer) -> [NSLayoutConstraint] { - let maxEdge = majorEdgeMaximum(sizedView.view).constraint(lessThanOrEqualTo: majorEdgeMaximum(superview), constant: -majorPadding.1) + let maxEdge = majorEdgeMaximum(sizedView.view).constraint(lessThanOrEqualTo: majorEdgeMaximum(superview), constant: -majorPadding.right) let majorDimensionConstraint = majorDimensionValue(sizedView).map { major in majorDimension(sizedView.view).constraint(equalToConstant: major) @@ -149,7 +196,8 @@ extension StackLayout: LayoutFormatDescribing { private static func prepareMinor( forView sizedView: SizedView, in superview: UIView, - minorPadding: (CGFloat, CGFloat), + lowPriorityPadding: Bool, + minorPadding: (top: CGFloat, bottom: CGFloat), minorEdgeMinimum: AxisAnchorProducer, minorEdgeMaximum: AxisAnchorProducer, minorDimension: DimensionAnchorProducer, @@ -157,16 +205,19 @@ extension StackLayout: LayoutFormatDescribing { minorCenter: AxisAnchorProducer) -> [NSLayoutConstraint] { if let fixedMinorDimension = minorDimensionValue(sizedView) { + let minorConstraintMix = minorEdgeMinimum(sizedView.view).constraint(greaterThanOrEqualTo: minorEdgeMinimum(superview), constant: minorPadding.top) + let minorConstraintMax = minorEdgeMaximum(sizedView.view).constraint(lessThanOrEqualTo: minorEdgeMaximum(superview), constant: -minorPadding.bottom) + return [ - minorEdgeMinimum(sizedView.view).constraint(greaterThanOrEqualTo: minorEdgeMinimum(superview), constant: minorPadding.0), - minorEdgeMaximum(sizedView.view).constraint(lessThanOrEqualTo: minorEdgeMaximum(superview), constant: -minorPadding.1), + lowPriorityPadding ? minorConstraintMix.with(priority: .defaultLow) : minorConstraintMix, + lowPriorityPadding ? minorConstraintMax.with(priority: .defaultLow) : minorConstraintMax, minorDimension(sizedView.view).constraint(equalToConstant: fixedMinorDimension), minorCenter(sizedView.view).constraint(equalTo: minorCenter(superview)) ] } else { return [ - minorEdgeMinimum(sizedView.view).constraint(equalTo: minorEdgeMinimum(superview), constant: minorPadding.0).with(priority: .for(.stackItemMinorEdgeAnchors)), - minorEdgeMaximum(sizedView.view).constraint(equalTo: minorEdgeMaximum(superview), constant: -minorPadding.1).with(priority: .for(.stackItemMinorEdgeAnchors)) + minorEdgeMinimum(sizedView.view).constraint(equalTo: minorEdgeMinimum(superview), constant: minorPadding.top).with(priority: .for(.stackItemMinorEdgeAnchors)), + minorEdgeMaximum(sizedView.view).constraint(equalTo: minorEdgeMaximum(superview), constant: -minorPadding.bottom).with(priority: .for(.stackItemMinorEdgeAnchors)) ] } }