Skip to content

Commit

Permalink
Auto Layout helpers: add insets to edge pinning and safe area with in…
Browse files Browse the repository at this point in the history
…sets (#39)

* Add insets to `pinSubviewToAllEdges` with zero insets by default + basic tests.

* Add Auto Layout helpers to pin a subview to safe area, with basic tests.

* Bump version to beta.
  • Loading branch information
jaclync authored Aug 15, 2019
1 parent 0407a2b commit f5b4847
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 5 deletions.
2 changes: 1 addition & 1 deletion WordPressUI.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "WordPressUI"
s.version = "1.3.4"
s.version = "1.3.5-beta.1"
s.summary = "Home of reusable WordPress UI components."

s.description = <<-DESC
Expand Down
4 changes: 4 additions & 0 deletions WordPressUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
02CCC587230121440051D40B /* UIView+AutoLayoutHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CCC586230121440051D40B /* UIView+AutoLayoutHelperTests.swift */; };
17576E6320AC7A28008612EF /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17576E6220AC7A28008612EF /* GradientView.swift */; };
1A40951C2271B3C4009AA86D /* NSBundle+ResourceBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A40951B2271B3C4009AA86D /* NSBundle+ResourceBundle.swift */; };
43067E2B203C8CC4001DD610 /* UIControl+BlockEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43067E2A203C8CC4001DD610 /* UIControl+BlockEvents.swift */; };
Expand Down Expand Up @@ -86,6 +87,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
02CCC586230121440051D40B /* UIView+AutoLayoutHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+AutoLayoutHelperTests.swift"; sourceTree = "<group>"; };
17576E6220AC7A28008612EF /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = "<group>"; };
1A40951B2271B3C4009AA86D /* NSBundle+ResourceBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSBundle+ResourceBundle.swift"; sourceTree = "<group>"; };
43067E2A203C8CC4001DD610 /* UIControl+BlockEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIControl+BlockEvents.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -211,6 +213,7 @@
B5226C67207CCDB2003C606E /* GravatarTest.swift */,
B529F288202C855B00895D88 /* UIColorHelpersTests.swift */,
577FC78622985DD0005BA78F /* UIView+ChangeLayoutMarginsTests.swift */,
02CCC586230121440051D40B /* UIView+AutoLayoutHelperTests.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -566,6 +569,7 @@
files = (
577FC78722985DD0005BA78F /* UIView+ChangeLayoutMarginsTests.swift in Sources */,
9A6EC88D21DA4832007815FF /* UIViewControllerHelperTest.swift in Sources */,
02CCC587230121440051D40B /* UIView+AutoLayoutHelperTests.swift in Sources */,
B529F289202C855B00895D88 /* UIColorHelpersTests.swift in Sources */,
57BC0C6D228DF1E000C1F070 /* UIView+GhostTests.swift in Sources */,
57BC0C6F228DF35400C1F070 /* UITableView+GhostTests.swift in Sources */,
Expand Down
44 changes: 40 additions & 4 deletions WordPressUI/Extensions/UIView+Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,24 @@ extension UIView {
addConstraints(newConstraints)
}

/// Adds constraints that pin a subview to self with zero insets.
///
/// - Parameter subview: a subview to be pinned to self.
@objc public func pinSubviewToAllEdges(_ subview: UIView) {
pinSubviewToAllEdges(subview, insets: .zero)
}

/// Adds constraints that pin a subview to self with padding insets.
///
/// - Parameters:
/// - subview: a subview to be pinned to self.
/// - insets: spacing between each subview edge to self. A positive value for an edge indicates that the subview is inside self on that edge.
@objc public func pinSubviewToAllEdges(_ subview: UIView, insets: UIEdgeInsets) {
NSLayoutConstraint.activate([
leadingAnchor.constraint(equalTo: subview.leadingAnchor),
trailingAnchor.constraint(equalTo: subview.trailingAnchor),
topAnchor.constraint(equalTo: subview.topAnchor),
bottomAnchor.constraint(equalTo: subview.bottomAnchor),
leadingAnchor.constraint(equalTo: subview.leadingAnchor, constant: -insets.left),
trailingAnchor.constraint(equalTo: subview.trailingAnchor, constant: insets.right),
topAnchor.constraint(equalTo: subview.topAnchor, constant: -insets.top),
bottomAnchor.constraint(equalTo: subview.bottomAnchor, constant: insets.bottom),
])
}

Expand All @@ -32,6 +44,30 @@ extension UIView {
])
}

/// Adds constraints that pin a subview to self's safe area with padding insets.
///
/// - Parameters:
/// - subview: a subview to be pinned to self's safe area.
@objc public func pinSubviewToSafeArea(_ subview: UIView) {
pinSubviewToSafeArea(subview, insets: .zero)
}

/// Adds constraints that pin a subview to self's safe area with padding insets.
///
/// - Parameters:
/// - subview: a subview to be pinned to self's safe area.
/// - insets: spacing between each subview edge to self's safe area. A positive value for an edge indicates that the subview is inside safe area on that edge.
@objc public func pinSubviewToSafeArea(_ subview: UIView, insets: UIEdgeInsets) {
if #available(iOS 11.0, *) {
NSLayoutConstraint.activate([
safeAreaLayoutGuide.leadingAnchor.constraint(equalTo: subview.leadingAnchor, constant: -insets.left),
safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: subview.trailingAnchor, constant: insets.right),
safeAreaLayoutGuide.topAnchor.constraint(equalTo: subview.topAnchor, constant: -insets.top),
safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: subview.bottomAnchor, constant: insets.bottom),
])
}
}

@objc public func findFirstResponder() -> UIView? {
if isFirstResponder {
return self
Expand Down
122 changes: 122 additions & 0 deletions WordPressUITests/Extensions/UIView+AutoLayoutHelperTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import XCTest

@testable import WordPressUI

class UIViewAutoLayoutHelperTests: XCTestCase {
private var view: UIView!
private var subview: UIView!

override func setUp() {
super.setUp()
view = UIView(frame: .zero)
subview = UIView(frame: .zero)
}

// MARK: tests for `pinSubviewToAllEdges`

func testPinSubviewToAllEdgesWithZeroInsets() {
view.addSubview(subview)
view.pinSubviewToAllEdges(subview)

let topConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.topAnchor && $0.secondAnchor == subview.topAnchor })
XCTAssertEqual(topConstraint.constant, 0)

let leadingConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.leadingAnchor && $0.secondAnchor == subview.leadingAnchor })
XCTAssertEqual(leadingConstraint.constant, 0)

let trailingConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.trailingAnchor && $0.secondAnchor == subview.trailingAnchor })
XCTAssertEqual(trailingConstraint.constant, 0)

let bottomConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.bottomAnchor && $0.secondAnchor == subview.bottomAnchor })
XCTAssertEqual(bottomConstraint.constant, 0)
XCTAssertEqual(bottomConstraint.secondAnchor, subview.bottomAnchor)
}

func testPinSubviewToAllEdgesWithNonZeroInsets() {
view.addSubview(subview)
let insets = UIEdgeInsets(top: 10, left: 12, bottom: 17, right: 25)
view.pinSubviewToAllEdges(subview, insets: insets)

// Self.top = subview.top - insets.top
let topConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.topAnchor && $0.secondAnchor == subview.topAnchor })
XCTAssertEqual(topConstraint.constant, -insets.top)

// Self.leading = subview.leading - insets.left
let leadingConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.leadingAnchor && $0.secondAnchor == subview.leadingAnchor })
XCTAssertEqual(leadingConstraint.constant, -insets.left)

// Self.trailing = subview.trailing + insets.right
let trailingConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.trailingAnchor && $0.secondAnchor == subview.trailingAnchor })
XCTAssertEqual(trailingConstraint.constant, insets.right)

// Self.bottom = subview.bottom + insets.bottom
let bottomConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.bottomAnchor && $0.secondAnchor == subview.bottomAnchor })
XCTAssertEqual(bottomConstraint.constant, insets.bottom)
}

// MARK: tests for `pinSubviewToSafeArea`

func testPinSubviewToSafeAreaWithZeroInsets() {
view.addSubview(subview)
view.pinSubviewToSafeArea(subview)

let topConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.safeAreaLayoutGuide.topAnchor && $0.secondAnchor == subview.topAnchor })
XCTAssertEqual(topConstraint.constant, 0)

let leadingConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.safeAreaLayoutGuide.leadingAnchor && $0.secondAnchor == subview.leadingAnchor })
XCTAssertEqual(leadingConstraint.constant, 0)

let trailingConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.safeAreaLayoutGuide.trailingAnchor && $0.secondAnchor == subview.trailingAnchor })
XCTAssertEqual(trailingConstraint.constant, 0)

let bottomConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.safeAreaLayoutGuide.bottomAnchor && $0.secondAnchor == subview.bottomAnchor })
XCTAssertEqual(bottomConstraint.constant, 0)
}

func testPinSubviewToSafeAreaWithNonZeroInsets() {
view.addSubview(subview)
let insets = UIEdgeInsets(top: 10, left: 12, bottom: 17, right: 25)
view.pinSubviewToSafeArea(subview, insets: insets)

// Self safe area.top = subview.top - insets.top
let topConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.safeAreaLayoutGuide.topAnchor && $0.secondAnchor == subview.topAnchor })
XCTAssertEqual(topConstraint.constant, -insets.top)

// Self safe area.leading = subview.leading - insets.left
let leadingConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.safeAreaLayoutGuide.leadingAnchor && $0.secondAnchor == subview.leadingAnchor })
XCTAssertEqual(leadingConstraint.constant, -insets.left)

// Self safe area.trailing = subview.trailing + insets.right
let trailingConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.safeAreaLayoutGuide.trailingAnchor && $0.secondAnchor == subview.trailingAnchor })
XCTAssertEqual(trailingConstraint.constant, insets.right)

// Self safe area.bottom = subview.bottom + insets.bottom
let bottomConstraint = getConstraint(from: view,
filter: { $0.firstAnchor == view.safeAreaLayoutGuide.bottomAnchor && $0.secondAnchor == subview.bottomAnchor })
XCTAssertEqual(bottomConstraint.constant, insets.bottom)
}

private func getConstraint(from view: UIView, filter: (NSLayoutConstraint) -> Bool) -> NSLayoutConstraint {
let constraints = view.constraints.filter(filter)
guard let constraint = constraints.first, constraints.count == 1 else {
XCTFail("Exactly one constraint corresponding to the given filter should have been created")
fatalError()
}
return constraint
}
}

0 comments on commit f5b4847

Please sign in to comment.