diff --git a/Sources/Layout/UIKit/UIView+AutoLayout.swift b/Sources/Layout/UIKit/UIView+AutoLayout.swift index 18eb62bd..4c5bd7dc 100644 --- a/Sources/Layout/UIKit/UIView+AutoLayout.swift +++ b/Sources/Layout/UIKit/UIView+AutoLayout.swift @@ -434,4 +434,57 @@ extension UIView { right.constraint(to: superview.margins.right, constant: -inset) ] } + + // MARK: - Safe Area + + /// Creates constraints aligning the edges of the receiver to the safe area of the superview with an inset. + /// + /// - Parameter inset: The inset value. + /// + /// - Returns: The created constraints. + public func safeAreaConstraints( + inset: CGFloat = 0 + ) -> [NSLayoutConstraint] { + safeAreaConstraints(insets: UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)) + } + + /// Creates constraints aligning the edges of the receiver to the safe area of the superview with directional insets + /// ([`NSDirectionalEdgeInsets`](https://developer.apple.com/documentation/uikit/nsdirectionaledgeinsets)). + /// + /// - Parameter insets: The directional insets. + /// + /// - Returns: The created constraints. + public func safeAreaConstraints( + insets: DirectionalInsets + ) -> [NSLayoutConstraint] { + assert(superview != nil, "safeAreaConstraints(insets:) requires superview") + guard let superview: UIView + else { return [] } + return [ + leading.constraint(to: superview.safeArea.leading, constant: insets.leading), + trailing.constraint(to: superview.safeArea.trailing, constant: -insets.trailing), + top.constraint(to: superview.safeArea.top, constant: insets.top), + bottom.constraint(to: superview.safeArea.bottom, constant: -insets.bottom) + ] + } + + /// Creates constraints aligning the edges of the receiver to the safe area of the superview with canonical insets + /// ([`UIEdgeInsets`](https://developer.apple.com/documentation/uikit/uiedgeinsets)). + /// + /// - Parameter insets: The canonical insets. + /// + /// - Returns: The created constraints. + public func safeAreaConstraints( + insets: CanonicalInsets + ) -> [NSLayoutConstraint] { + assert(superview != nil, "safeAreaConstraints(insets:) requires superview") + guard let superview: UIView + else { return [] } + return [ + left.constraint(to: superview.safeArea.left, constant: insets.left), + right.constraint(to: superview.safeArea.right, constant: -insets.right), + top.constraint(to: superview.safeArea.top, constant: insets.top), + bottom.constraint(to: superview.safeArea.bottom, constant: -insets.bottom) + ] + } } diff --git a/Tests/LayoutTests/UIKit/UIView+AutoLayoutTests.swift b/Tests/LayoutTests/UIKit/UIView+AutoLayoutTests.swift index 47eea0de..6856844a 100644 --- a/Tests/LayoutTests/UIKit/UIView+AutoLayoutTests.swift +++ b/Tests/LayoutTests/UIKit/UIView+AutoLayoutTests.swift @@ -987,4 +987,101 @@ final class UIViewAutoLayoutTests: XCTestCase { expect(constraints[0]).to(match(view.left.constraint(to: superview.margins.left, constant: inset))) expect(constraints[1]).to(match(view.right.constraint(to: superview.margins.right, constant: -inset))) } + + // MARK: - Safe Area + + func testSageAreaConstraintsInset() { + + // GIVEN + + let superview: UIView = .init() + let view: UIView = .init() + superview.addSubview(view) + + // WHEN + + let constraints: [NSLayoutConstraint] = view.safeAreaConstraints() + + // THEN + + expect(constraints.count) == 4 + expect(constraints[0]).to(match(view.left.constraint(to: superview.safeArea.left))) + expect(constraints[1]).to(match(view.right.constraint(to: superview.safeArea.right))) + expect(constraints[2]).to(match(view.top.constraint(to: superview.safeArea.top))) + expect(constraints[3]).to(match(view.bottom.constraint(to: superview.safeArea.bottom))) + } + + func testSageAreaConstraintsInset_givenInset() { + + // GIVEN + + let superview: UIView = .init() + let view: UIView = .init() + superview.addSubview(view) + let inset: CGFloat = 10 + + // WHEN + + let constraints: [NSLayoutConstraint] = view.safeAreaConstraints(inset: inset) + + // THEN + + expect(constraints.count) == 4 + expect(constraints[0]).to(match(view.left.constraint(to: superview.safeArea.left, constant: inset))) + expect(constraints[1]).to(match(view.right.constraint(to: superview.safeArea.right, constant: -inset))) + expect(constraints[2]).to(match(view.top.constraint(to: superview.safeArea.top, constant: inset))) + expect(constraints[3]).to(match(view.bottom.constraint(to: superview.safeArea.bottom, constant: -inset))) + } + + func testSageAreaConstraintsInsetsDirectional() { + + // GIVEN + + let superview: UIView = .init() + let view: UIView = .init() + superview.addSubview(view) + let insets: NSDirectionalEdgeInsets = .init(top: 1, leading: 2, bottom: 3, trailing: 4) + + // WHEN + + let constraints: [NSLayoutConstraint] = view.safeAreaConstraints(insets: insets) + + // THEN + + expect(constraints.count) == 4 + expect(constraints[0]) + .to(match(view.leading.constraint(to: superview.safeArea.leading, constant: insets.leading))) + expect(constraints[1]) + .to(match(view.trailing.constraint(to: superview.safeArea.trailing, constant: -insets.trailing))) + expect(constraints[2]) + .to(match(view.top.constraint(to: superview.safeArea.top, constant: insets.top))) + expect(constraints[3]) + .to(match(view.bottom.constraint(to: superview.safeArea.bottom, constant: -insets.bottom))) + } + + func testSageAreaConstraintsInsetsCanonical() { + + // GIVEN + + let superview: UIView = .init() + let view: UIView = .init() + superview.addSubview(view) + let insets: UIEdgeInsets = .init(top: 1, left: 2, bottom: 3, right: 4) + + // WHEN + + let constraints: [NSLayoutConstraint] = view.safeAreaConstraints(insets: insets) + + // THEN + + expect(constraints.count) == 4 + expect(constraints[0]) + .to(match(view.left.constraint(to: superview.safeArea.left, constant: insets.left))) + expect(constraints[1]) + .to(match(view.right.constraint(to: superview.safeArea.right, constant: -insets.right))) + expect(constraints[2]) + .to(match(view.top.constraint(to: superview.safeArea.top, constant: insets.top))) + expect(constraints[3]) + .to(match(view.bottom.constraint(to: superview.safeArea.bottom, constant: -insets.bottom))) + } } diff --git a/cheatsheet.html b/cheatsheet.html index 92b6f762..ed78b180 100644 --- a/cheatsheet.html +++ b/cheatsheet.html @@ -343,6 +343,12 @@
marginConstraints(insets: canonical)
sideMarginConstraints()
sideMarginConstraints(inset: 100)+
Constrains to superview safe area.
+
safeAreaConstraints()+
safeAreaConstraints(inset: 100)+
safeAreaConstraints(insets: directional)+
safeAreaConstraints(insets: canonical)
activate()