From 40a52d6966b746486c70fc7ddfa6fe66468fc8c0 Mon Sep 17 00:00:00 2001 From: relikd Date: Thu, 2 Jul 2020 17:36:08 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 5 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../TextualAutoLayout-Package.xcscheme | 91 ++++++++++ .../xcschemes/TextualAutoLayout.xcscheme | 115 +++++++++++++ LICENSE | 20 +++ Package.swift | 16 ++ README-anatomy.png | Bin 0 -> 14117 bytes README.md | 161 ++++++++++++++++++ .../TextualAutoLayout/TextualAutoLayout.swift | 124 ++++++++++++++ .../TextualAutoLayoutTests.swift | 76 +++++++++ 11 files changed, 623 insertions(+) create mode 100644 .gitignore create mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/TextualAutoLayout-Package.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/TextualAutoLayout.xcscheme create mode 100644 LICENSE create mode 100644 Package.swift create mode 100644 README-anatomy.png create mode 100644 README.md create mode 100644 Sources/TextualAutoLayout/TextualAutoLayout.swift create mode 100644 Tests/TextualAutoLayoutTests/TextualAutoLayoutTests.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95c4320 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..706eede --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/TextualAutoLayout-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/TextualAutoLayout-Package.xcscheme new file mode 100644 index 0000000..e40990b --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/TextualAutoLayout-Package.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/TextualAutoLayout.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/TextualAutoLayout.xcscheme new file mode 100644 index 0000000..140181d --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/TextualAutoLayout.xcscheme @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ad1c911 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright (c) 2020 Oleg Geier + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..76ab3c1 --- /dev/null +++ b/Package.swift @@ -0,0 +1,16 @@ +// swift-tools-version:5.0 +import PackageDescription + +let package = Package( + name: "TextualAutoLayout", + platforms: [ + .macOS(.v10_11), .iOS(.v9), .tvOS(.v9) // watchOS does not support NSLayout + ], + products: [ + .library(name: "TextualAutoLayout", targets: ["TextualAutoLayout"]), + ], + targets: [ + .target(name: "TextualAutoLayout", dependencies: []), + .testTarget(name: "TextualAutoLayoutTests", dependencies: ["TextualAutoLayout"]), + ] +) diff --git a/README-anatomy.png b/README-anatomy.png new file mode 100644 index 0000000000000000000000000000000000000000..fcb146485406f8eea2494a0aa9d99beba449a3d0 GIT binary patch literal 14117 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 x)dBSd3{Kpy68X)g ![anatomy of a constraint](README-anatomy.png) +> Copyright © 2018 Apple Inc ([source][2]) + +I love the simplicity of the depiction. However, when you start creating constraints programmatically, the same constraint looks more like this: + +```swift +let constraint = RedView.leadingAnchor.constraint(equalTo: BlueView.trailingAnchor, constant: 8.0) +constraint.isActive = true +``` + +Not only is this hard to read but it gets tedious when you have to create a bunch of constraints (and remember to activate all of them). +**Textual Auto Layout** (TAL) to the rescue; This is what the same constraint looks like in TAL: + +```swift +RedView.leadingAnchor =&= BlueView.trailingAnchor + 8.0 +``` + +This sets all constraint attributes and activates the constraint too. The only slight deviation to the illustration it uses `=&=` instead of `=`. + +There are two reasons for that. The obvious, `=` is already used for assignments. But also, you want to handle the other relations too. You have the `greaterThanOrEqualTo` and `lessThanOrEqualTo` relations at your disposal (`=>=` and `=<=` respectively). + + +### Going into a different dimension + +The example above uses a multiplier of `1.0`. Technically this is correct, though it does not make sense for location attributes. The documentation is clear: + +> You cannot use a nonidentity multiplier (a value other than 1.0) with location attributes. + +Thats why it is omitted altogether in the `=&=` relation above. But lets take a look where it does make sense, dimensional constraints: + +```swift +View.widthAnchor =>= 2 * View.heightAnchor +View.widthAnchor =<= 250 +``` + +These two constraints ensure that the view is at least twice as wide as tall, but at most 250 pt wide. Readability, 100%. +Again, what would you do without TAL?: + +```swift +View.widthAnchor.constraint(greaterThanOrEqualTo: View.heightAnchor, multiplier: 2).isActive = true +View.widthAnchor.constraint(lessThanOrEqualToConstant: 250).isActive = true +``` + + +### Prioritize your constraints + +Sometimes requiring a specific is just too strong. You can adjust the priority in the same line. Use the pipe symbol (`|`)1 and append the priority as needed: + +```swift +View.widthAnchor =&= 250 | .defaultHigh +``` + +-------------------- +1 To be consistent with Apple documentation, I'd like to use `@` for priority assignment (instead of `|`). But sadly you can't use `@` in an operator declaration. + + +### Doing more things at the same time + +Usually you need to set more than a single constraint. For example, you want to set a child view to the same size as the parent view, but inset by 5 px in all directions. You could create these four constraints individually, or use a little helper method: + +```swift +Child.anchor([.top, .bottom, .left, .right], to: Parent, padding: 5) +``` + +The `anchor(_,to:)` method will use the same relation attribute for both views. In this example the code above is equivalent to: + +```swift +Child.topAnchor =&= Parent.topAnchor + 5 +Child.leftAnchor =&= Parent.leftAnchor + 5 +Parent.bottomAnchor =&= Child.bottomAnchor + 5 +Parent.rightAnchor =&= Child.rightAnchor + 5 +Child.translatesAutoresizingMaskIntoConstraints = false +``` + +Notice how `bottomAnchor` and `rightAnchor` did change the relation direction instead of using `Child =&= Parent - 5` with a negative constant. This makes it easier when debugging constraints. You can simply look for your chosen `5 px` padding instead of thinking what edges are involved. + +Further you should note that `anchor(_,to:)` will set `translatesAutoresizingMaskIntoConstraints` to `false`. Most of the time you'll want that. In fact you'll probably want to set it for single constraints too. But give it some time and you'll see that you often use both constraint types at the same time. + +```swift +ChildA.anchor([.leadingMargin, .trailingMargin], to: Parent) +ChildB.anchor([.leadingMargin, .trailingMargin], to: Parent) +ChildB.topAnchor =&= ChildA.bottomAnchor + 8 +``` + +In that case both relevant views (`ChildA` and `ChildB`) have `translatesAutoresizingMaskIntoConstraints` set. Not `Parent` though, which is intended. E.g., `Parent ` may have `autoresizingMask = [.flexibleWidth, .flexibleHeight]`. + + +#### Limitations + +Since the `anchor(_,to:)` reuses the same relation attribute for both sides, you can't connect two different attributes (e.g., `.left =&= .right`). You can resolve this issue with individual constraints. + + +### Requiring greater control + +Even though it wasn't mentioned in the previous example, you can chain `anchor(_,to:)` with a priority, same as with individual constraints. Or more general, the `anchor` method returns a list of constraints. And you can apply functions on constraint lists. + +```swift +let x: [NSLayoutConstraint] +x = Child.anchor([.left, .right], to: Parent) +x | .defaultLow +// or short +Child.anchor([.left, .right], to: Parent) | .defaultLow +``` + +It doesn't stop there. I haven't mention it in the beginning, but every TAL expression has a return type. + +```swift +View.widthAnchor =<= 250 // returns NSLayoutConstraint +``` + +This allows you to chain, reuse or store constraints for later usage. + +```swift +let compactConstraints = [ + View.widthAnchor =&= 120, + View.heightAnchor =&= 35 +].setActive(false) +let regularConstraints = [ + View.widthAnchor =<= 250, + View.heightAnchor =<= 50 +] // implicitly isActive = true +regularConstraints | .fittingSizeLevel +``` + +And lastly, though not as helpful as the other features. You can constraint the intrinsic content size. + +```swift +Label.constrainHuggingCompression(.vertical, .required) +// which is equivalent to: +Label.setContentHuggingPriority(.vertical, for: .required) +Label.setContentCompressionResistancePriority(.vertical, for: .required) +``` + + +[1]: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html +[2]: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/Art/view_formula_2x.png diff --git a/Sources/TextualAutoLayout/TextualAutoLayout.swift b/Sources/TextualAutoLayout/TextualAutoLayout.swift new file mode 100644 index 0000000..787bd07 --- /dev/null +++ b/Sources/TextualAutoLayout/TextualAutoLayout.swift @@ -0,0 +1,124 @@ +#if os(macOS) +import AppKit +public typealias ConstraintPriority = NSLayoutConstraint.Priority +public typealias ConstraintAxis = NSLayoutConstraint.Orientation +public typealias ConstraintView = NSView +#else +import UIKit +public typealias ConstraintPriority = UILayoutPriority +public typealias ConstraintAxis = NSLayoutConstraint.Axis +public typealias ConstraintView = UIView +#endif + +/* + Readable Auto Layout Constraints + + Usage: + A.anchor =&= multiplier * B.anchor + constant | priority +*/ + +precedencegroup ReadableLayoutPrecedence { + higherThan: AdditionPrecedence + lowerThan: MultiplicationPrecedence +} + +infix operator =&= : ReadableLayoutPrecedence +infix operator =<= : ReadableLayoutPrecedence +infix operator =>= : ReadableLayoutPrecedence + +/// Create and activate an `equal` constraint between left and right anchor. Format: `A.anchor =&= multiplier * B.anchor + constant | priority` +@discardableResult public func =&= (l: NSLayoutAnchor, r: NSLayoutAnchor) -> NSLayoutConstraint { l.constraint(equalTo: r).on() } +/// Create and activate a `lessThan` constraint between left and right anchor. Format: `A.anchor =<= multiplier * B.anchor + constant | priority` +@discardableResult public func =<= (l: NSLayoutAnchor, r: NSLayoutAnchor) -> NSLayoutConstraint { l.constraint(lessThanOrEqualTo: r).on() } +/// Create and activate a `greaterThan` constraint between left and right anchor. Format: `A.anchor =>= multiplier * B.anchor + constant | priority` +@discardableResult public func =>= (l: NSLayoutAnchor, r: NSLayoutAnchor) -> NSLayoutConstraint { l.constraint(greaterThanOrEqualTo: r).on() } + +public extension NSLayoutDimension { // higher precedence, so multiply first + /// Create intermediate anchor multiplier result. + static func *(l: CGFloat, r: NSLayoutDimension) -> AnchorMultiplier { .init(anchor: r, m: l) } + /// Create and activate an `equal` constraint with constant value. Format: `A.anchor =&= constant | priority` + @discardableResult static func =&=(l: NSLayoutDimension, r: CGFloat) -> NSLayoutConstraint { l.constraint(equalToConstant: r).on() } + /// Create and activate a `lessThan` constraint with constant value. Format: `A.anchor =<= constant | priority` + @discardableResult static func =<=(l: NSLayoutDimension, r: CGFloat) -> NSLayoutConstraint { l.constraint(lessThanOrEqualToConstant: r).on() } + /// Create and activate a `greaterThan` constraint with constant value. Format: `A.anchor =>= constant | priority` + @discardableResult static func =>=(l: NSLayoutDimension, r: CGFloat) -> NSLayoutConstraint { l.constraint(greaterThanOrEqualToConstant: r).on() } +} + +/// Intermediate `NSLayoutConstraint` anchor with multiplier supplement +public struct AnchorMultiplier { + fileprivate let anchor: NSLayoutDimension, m: CGFloat +} + +public extension AnchorMultiplier { + /// Create and activate an `equal` constraint between left and right anchor. Format: `A.anchor =&= multiplier * B.anchor + constant | priority` + @discardableResult static func =&=(l: NSLayoutDimension, r: Self) -> NSLayoutConstraint { l.constraint(equalTo: r.anchor, multiplier: r.m).on() } + /// Create and activate a `lessThan` constraint between left and right anchor. Format: `A.anchor =<= multiplier * B.anchor + constant | priority` + @discardableResult static func =<=(l: NSLayoutDimension, r: Self) -> NSLayoutConstraint { l.constraint(lessThanOrEqualTo: r.anchor, multiplier: r.m).on() } + /// Create and activate a `greaterThan` constraint between left and right anchor. Format: `A.anchor =>= multiplier * B.anchor + constant | priority` + @discardableResult static func =>=(l: NSLayoutDimension, r: Self) -> NSLayoutConstraint { l.constraint(greaterThanOrEqualTo: r.anchor, multiplier: r.m).on() } +} + +public extension NSLayoutConstraint { + /// Change `isActive`to `true` and return `self` + func on() -> Self { isActive = true; return self } + /// Change `isActive`to `false` and return `self` + func off() -> Self { isActive = false; return self } + /// Change `constant`attribute and return `self` + @discardableResult static func +(l: NSLayoutConstraint, r: CGFloat) -> NSLayoutConstraint { l.constant = r; return l } + /// Change `constant` attribute and return `self` + @discardableResult static func -(l: NSLayoutConstraint, r: CGFloat) -> NSLayoutConstraint { l.constant = -r; return l } + /// Change `priority` attribute and return `self` + @discardableResult static func |(l: NSLayoutConstraint, r: ConstraintPriority) -> NSLayoutConstraint { l.priority = r; return l } +} + +/* + UIView extension to generate multiple constraints at once + + Usage: + child.anchor([.width, .height], to: parent) | .defaultLow +*/ + +public extension ConstraintView { + #if os(macOS) + /// Edges that need the relation to flip arguments. For these we need to inverse the constant value and relation. + private static let inverseItem: [NSLayoutConstraint.Attribute] = [.right, .bottom, .trailing, .lastBaseline] + #else + /// Edges that need the relation to flip arguments. For these we need to inverse the constant value and relation. + private static let inverseItem: [NSLayoutConstraint.Attribute] = [.right, .bottom, .trailing, .lastBaseline, .rightMargin, .bottomMargin, .trailingMargin] + #endif + + /// Create and active constraints for provided edges. Constraints will anchor the same edge on both `self` and `other`. + /// - Note: Will set `translatesAutoresizingMaskIntoConstraints = false` + /// - Parameters: + /// - edges: List of constraint attributes, e.g. `[.top, .bottom, .left, .right]` + /// - other: Instance to bind to, e.g. `UIView` or `UILayoutGuide` + /// - padding: Used as constant value. Multiplier will always be `1.0`. If you need to change the multiplier, use single constraints instead. (Default: `0`) + /// - rel: Constraint relation. (Default: `.equal`) + /// - Returns: List of created and active constraints + @discardableResult func anchor(_ edges: [NSLayoutConstraint.Attribute], to other: Any, padding: CGFloat = 0, if rel: NSLayoutConstraint.Relation = .equal) -> [NSLayoutConstraint] { + translatesAutoresizingMaskIntoConstraints = false + return edges.map { + let (A, B) = Self.inverseItem.contains($0) ? (other, self) : (self, other) + return NSLayoutConstraint(item: A, attribute: $0, relatedBy: rel, toItem: B, attribute: $0, multiplier: 1, constant: padding).on() + } + } + + /// Sets the priority with which a view resists being made smaller and larger than its intrinsic size. + func constrainHuggingCompression(_ axis: ConstraintAxis, _ priotity: ConstraintPriority) { + setContentHuggingPriority(priotity, for: axis) + setContentCompressionResistancePriority(priotity, for: axis) + } +} + +public extension Array where Element: NSLayoutConstraint { + /// set `priority` on all elements and return same list + @discardableResult static func |(l: Self, r: ConstraintPriority) -> Self { + for x in l { x.priority = r } + return l + } + /// set `isActive` on all elements and return `self` + @discardableResult func setActive(_ flag: Bool) -> Self { + flag ? NSLayoutConstraint.activate(self) : NSLayoutConstraint.deactivate(self) + return self + } +} diff --git a/Tests/TextualAutoLayoutTests/TextualAutoLayoutTests.swift b/Tests/TextualAutoLayoutTests/TextualAutoLayoutTests.swift new file mode 100644 index 0000000..409e9b6 --- /dev/null +++ b/Tests/TextualAutoLayoutTests/TextualAutoLayoutTests.swift @@ -0,0 +1,76 @@ +import XCTest +@testable import TextualAutoLayout + +final class TextualAutoLayoutTests: XCTestCase { + var parent, A, B: ConstraintView! + + override func setUp() { + parent = ConstraintView() + A = ConstraintView() + B = ConstraintView() + parent.addSubview(A) + parent.addSubview(B) + } + + func testBasic() { + let x = A.bottomAnchor =&= B.topAnchor + 20.3 | .defaultLow + XCTAssertEqual(x.relation, NSLayoutConstraint.Relation.equal) + XCTAssertEqual(x.priority, ConstraintPriority.defaultLow) + XCTAssertEqual(x.constant, 20.3) + XCTAssertEqual(x.isActive, true) + } + + func testDimensional() { + let x = A.heightAnchor =<= 2 * B.widthAnchor - 4 + XCTAssertEqual(x.relation, NSLayoutConstraint.Relation.lessThanOrEqual) + XCTAssertEqual(x.priority, ConstraintPriority.required) + XCTAssertEqual(x.multiplier, 2) + XCTAssertEqual(x.constant, -4) + XCTAssertEqual(x.isActive, true) + + XCTAssertEqual((A.widthAnchor =>= 2 * A.heightAnchor).constant, 0) + XCTAssertEqual((A.widthAnchor =<= 250).constant, 250) + } + + func testIntrinsicContent() { + A.constrainHuggingCompression(.vertical, .required) + // newly set values + XCTAssertEqual(A.contentCompressionResistancePriority(for: .vertical), ConstraintPriority.required) + XCTAssertEqual(A.contentHuggingPriority(for: .vertical), ConstraintPriority.required) + // default values + XCTAssertEqual(A.contentCompressionResistancePriority(for: .horizontal), ConstraintPriority.defaultHigh) + XCTAssertEqual(A.contentHuggingPriority(for: .horizontal), ConstraintPriority.defaultLow) + } + + func testReturnConstraint() { + XCTAssertEqual((A.rightAnchor =&= B.leftAnchor).firstItem as? ConstraintView, A) + } + + func testViewMultiConstraint() { + A.anchor([.left, .right, .top], to: parent!, padding: 76) | .defaultHigh + let x = parent.constraints + XCTAssertEqual(x.count, 3) + XCTAssertEqual(A.translatesAutoresizingMaskIntoConstraints, false) + XCTAssertEqual(parent.translatesAutoresizingMaskIntoConstraints, true) + + for u in x { + XCTAssertEqual(u.relation, NSLayoutConstraint.Relation.equal) + XCTAssertEqual(u.priority, ConstraintPriority.defaultHigh) + XCTAssertEqual(u.constant, 76) + XCTAssertEqual(u.firstAttribute, u.secondAttribute) + XCTAssertEqual(u.isActive, true) + + let first = u.firstItem as? ConstraintView + let second = u.secondItem as? ConstraintView + if u.firstAttribute == .right { + XCTAssertEqual(first, parent) + XCTAssertEqual(second, A) + } else { + XCTAssertEqual(first, A) + XCTAssertEqual(second, parent) + } + } + x.setActive(false) + for u in x { XCTAssertEqual(u.isActive, false) } + } +}