From 0f5a031f0505b11d31e0384e4b45bee37a68108d Mon Sep 17 00:00:00 2001 From: S4cha Date: Mon, 30 Mar 2020 13:06:03 +0200 Subject: [PATCH] Adds support for %-based margins in layout blocks + tests --- Sources/Stevia/Stevia+Operators.swift | 5 +++ Sources/Stevia/Stevia+Stacks.swift | 28 +++++++++++++++++ Tests/SteviaTests/FullLayoutTests.swift | 41 +++++++++++++++++++++++-- Tests/SteviaTests/LayoutTests.swift | 9 ++++++ Tests/SteviaTests/SizeTests.swift | 20 ++++++++++++ 5 files changed, 100 insertions(+), 3 deletions(-) diff --git a/Sources/Stevia/Stevia+Operators.swift b/Sources/Stevia/Stevia+Operators.swift index 48413482..904f6fa8 100644 --- a/Sources/Stevia/Stevia+Operators.swift +++ b/Sources/Stevia/Stevia+Operators.swift @@ -32,6 +32,11 @@ public func ~ (left: UIView, right: CGFloat) -> UIView { return left.height(right) } +@discardableResult +public func ~ (left: UIView, right: SteviaPercentage) -> UIView { + return left.height(right) +} + @discardableResult public func ~ (left: UIView, right: SteviaFlexibleMargin) -> UIView { return left.height(right) diff --git a/Sources/Stevia/Stevia+Stacks.swift b/Sources/Stevia/Stevia+Stacks.swift index c3d56f59..b85598aa 100644 --- a/Sources/Stevia/Stevia+Stacks.swift +++ b/Sources/Stevia/Stevia+Stacks.swift @@ -41,6 +41,7 @@ public extension UIView { func layout(_ objects: [Any]) -> [UIView] { var previousMargin: CGFloat? var previousFlexibleMargin: SteviaFlexibleMargin? + var previousPercentMargin: SteviaPercentage? for (i, o) in objects.enumerated() { @@ -77,6 +78,23 @@ public extension UIView { } } previousFlexibleMargin = nil + } else if let ppm = previousPercentMargin { + if i == 1 { + v.top(ppm) // only if first view + } else { + if let vx = objects[i-2] as? UIView { + // Add layout guide to suport %-based spaces. + let percent = ppm.value / 100 + let lg = UILayoutGuide() + addLayoutGuide(lg) + NSLayoutConstraint.activate([ + lg.topAnchor.constraint(equalTo: vx.bottomAnchor), + lg.heightAnchor.constraint(equalTo: heightAnchor, multiplier: percent), + v.topAnchor.constraint(equalTo: lg.bottomAnchor) + ]) + } + } + previousPercentMargin = nil } else { tryStackViewVerticallyWithPreviousView(v, index: i, objects: objects) } @@ -106,6 +124,16 @@ public extension UIView { va.first!.bottom(fm) } } + case let pm as SteviaPercentage: + previousPercentMargin = pm // Store margin for next pass + if i != 0 && i == (objects.count - 1) { + //Last Margin, Bottom + if let previousView = objects[i-1] as? UIView { + previousView.bottom(pm) + } else if let va = objects[i-1] as? [UIView] { + va.first!.bottom(pm) + } + } case _ as String:() //Do nothin' ! case let a as [UIView]: align(horizontally: a) diff --git a/Tests/SteviaTests/FullLayoutTests.swift b/Tests/SteviaTests/FullLayoutTests.swift index 78eb31d6..9c3ff0c6 100644 --- a/Tests/SteviaTests/FullLayoutTests.swift +++ b/Tests/SteviaTests/FullLayoutTests.swift @@ -15,6 +15,9 @@ class TestView: UIView { let password = UITextField() let login = UIButton() + let view1 = UIView() + let view2 = UIView() + convenience init() { self.init(frame: CGRect.zero) @@ -26,13 +29,26 @@ class TestView: UIView { layout( 100, - |-email-22-| ~ 80, + |-email-22-| ~ 10%, 20, |password.width(54) ~ 47, "", login.centerHorizontally() ~ 99, 7 ) + + sv( + view1, + view2 + ) + + layout( + 10%, + |view1| ~ 20, + 33%, + |view2|, + 20% + ) } } @@ -71,7 +87,7 @@ class FullLayoutTests: XCTestCase { XCTAssertEqual(v.email.frame.origin.x, 8, accuracy: CGFloat(Float.ulpOfOne)) XCTAssertEqual(v.email.frame.width, win.frame.width - 8 - 22, accuracy: CGFloat(Float.ulpOfOne)) - XCTAssertEqual(v.email.frame.height, 80, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.email.frame.height, win.frame.height*0.1, accuracy: 0.5) // Password XCTAssertEqual(v.password.frame.origin.y, @@ -107,7 +123,7 @@ class FullLayoutTests: XCTestCase { XCTAssertEqual(v.email.frame.origin.x, 22, accuracy: CGFloat(Float.ulpOfOne)) XCTAssertEqual(v.email.frame.width, win.frame.width - 8 - 22, accuracy: CGFloat(Float.ulpOfOne)) - XCTAssertEqual(v.email.frame.height, 80, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.email.frame.height, win.frame.height*0.1, accuracy: 0.5) // Password XCTAssertEqual(v.password.frame.origin.y, @@ -126,4 +142,23 @@ class FullLayoutTests: XCTestCase { accuracy: CGFloat(magicalIphoneXShift)) XCTAssertEqual(v.login.frame.height, 99, accuracy: CGFloat(Float.ulpOfOne)) } + + func testPercentLayout() { + XCTAssertEqual(vc.view.frame.origin.x, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(vc.view.frame.origin.y, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(vc.view.frame.width, win.frame.width, + accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(vc.view.frame.height, win.frame.height, + accuracy: CGFloat(Float.ulpOfOne)) + + v.layoutIfNeeded() + + XCTAssertEqual(v.view1.frame.origin.y, v.frame.height*0.1, accuracy: 0.5) + XCTAssertEqual(v.view1.frame.origin.x, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.view1.frame.width, v.frame.width, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.view2.frame.origin.y, (v.frame.height*0.1) + 20 + (v.frame.height*0.33), accuracy: 0.5) + XCTAssertEqual(v.view2.frame.origin.x, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.view2.frame.origin.y + v.view2.frame.height, (v.frame.height*0.8), accuracy: 0.5) + XCTAssertEqual(v.view2.frame.width, v.frame.width, accuracy: CGFloat(Float.ulpOfOne)) + } } diff --git a/Tests/SteviaTests/LayoutTests.swift b/Tests/SteviaTests/LayoutTests.swift index ed6dd304..47ab9f4b 100644 --- a/Tests/SteviaTests/LayoutTests.swift +++ b/Tests/SteviaTests/LayoutTests.swift @@ -158,6 +158,15 @@ class LayoutTests: XCTestCase { XCTAssertEqual(v.frame.height, 180, accuracy: CGFloat(Float.ulpOfOne)) } + func testHeightPercentage() { + v ~ 25% + ctrler.view.layoutIfNeeded() // This is needed to force auto-layout to kick-in + XCTAssertEqual(v.frame.origin.y, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.frame.origin.x, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.frame.width, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.frame.height, ctrler.view.frame.height*0.25, accuracy: 0.5) + } + func testMultipleHeightsAtOnce() { let v1 = UIView() let v2 = UIView() diff --git a/Tests/SteviaTests/SizeTests.swift b/Tests/SteviaTests/SizeTests.swift index 36271e41..219c9e07 100644 --- a/Tests/SteviaTests/SizeTests.swift +++ b/Tests/SteviaTests/SizeTests.swift @@ -47,6 +47,26 @@ class SizeTests: XCTestCase { XCTAssertEqual(v.frame.height, 23, accuracy: CGFloat(Float.ulpOfOne)) } + func testHeightPercentage() { + v.width(100) + v.height(40%) + ctrler.view.layoutIfNeeded() + XCTAssertEqual(v.frame.origin.y, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.frame.origin.x, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.frame.width, 100, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.frame.height, ctrler.view.frame.height*0.4, accuracy:0.5) + } + + func testWidthPercentage() { + v.height(100) + v.width(87%) + ctrler.view.layoutIfNeeded() + XCTAssertEqual(v.frame.origin.y, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.frame.origin.x, 0, accuracy: CGFloat(Float.ulpOfOne)) + XCTAssertEqual(v.frame.width, ctrler.view.frame.width*0.87, accuracy: 0.5) + XCTAssertEqual(v.frame.height, 100, accuracy: CGFloat(Float.ulpOfOne)) + } + func testEqualSizes() { let width: CGFloat = 24 let height: CGFloat = 267