Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ TestProjects/cocoapods/macos/PinLayout-macOS.xcworkspace/
TestProjects/cocoapods/macos/Podfile.lock
TestProjects/cocoapods/tvos/PinLayout-tvOS.xcworkspace/
TestProjects/cocoapods/tvos/Podfile.lock

TestProjects/carthage/ios/Cartfile.resolved
TestProjects/carthage/ios/Carthage/

TestProjects/swift-package-manager

Example/PinLayoutExampleMacOS
Example/PinLayoutExampleMacOS
1 change: 1 addition & 0 deletions Example/.swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ disabled_rules: # rule identifiers to exclude from running

excluded: # paths to ignore during linting. overridden by `included`.
- Pods
- PinLayoutExampleMacOS
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "0940"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
62 changes: 49 additions & 13 deletions PinLayout.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "0940"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "0940"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "0940"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
3 changes: 0 additions & 3 deletions PinLayout.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PODS:
- Nimble (7.0.3)
- PinLayout (1.7.3)
- PinLayout (1.7.4)
- Quick (1.2.0)
- Reveal-SDK (10)
- SwiftLint (0.25.1)
Expand All @@ -25,7 +25,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
Nimble: 7f5a9c447a33002645a071bddafbfb24ea70e0ac
PinLayout: 04488e8fc8d19b59760bdf99a16deff7bfac422d
PinLayout: e20545b1fd5110133e0b03bed61f196161af8b36
Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08
Reveal-SDK: 7869ddf1f902cabbb07a1f0dd06bd25861a126f7
SwiftLint: ce933681be10c3266e82576dad676fa815a602e9
Expand Down
62 changes: 61 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Extremely Fast views layouting without auto layout. No magic, pure code, full co
* Swift 3.2+ / Swift 4.0 / Objective-C

### Recent features
* :star: Add `wrapContent()` methods that adjust view's width and height to wrap all its subviews. See [wrapContent](#wrapContent) for more information.

* :star: PinLayout now support macOS. See [macOS Support](#macos_support) for more information.

* :star: PinLayout expose the `safeAreaInsets` through [`UIView.pin.safeArea`](#safeAreaInsets), this property support not only iOS 11, but is also backward compatible for earlier iOS releases (7/8/9/10). See [safeAreaInsets support](#safeAreaInsets) for more information.
Expand All @@ -61,6 +63,7 @@ Extremely Fast views layouting without auto layout. No magic, pure code, full co
* [Aspect Ratio](#aspect_ratio)
* [Margins](#margins)
* [safeAreaInsets support](#safeAreaInsets)
* [WrapContent](#wrapCcontent)
* [justify, align](#justify_align)
* [UIView's transforms](#uiview_transform)
* [Warnings](#warnings)
Expand Down Expand Up @@ -1134,8 +1137,65 @@ This example runs perfectly on a iPhone X (iOS 11), but it also runs on any devi

<br/>

<a name="wrapContent"></a>
## WrapContent

<a name="aspect_ratio"></a>
The following methods are useful to adjust view's width and/or height to wrap all its subviews. These methods also adjust subviews position to create a tight wrap.

**Methods:**

* **`wrapContent()`**
**`wrapContent(padding: CGFloat)`**
**`wrapContent(padding: UIEdgeInsets)`**
Adjust the view's width and height to wrap all its subviews. The method also adjusts subviews's position to create a tight wrap. It is also possible to specify an optional padding around all subviews.
* **`wrapContent(:WrapType)`**
**`wrapContent(:WrapType, padding: CGFloat)`** **`wrapContent(:WrapType, padding: UIEdgeInsets)`**
Adjust the view's width AND/OR height to wrap all its subviews. Accept a WrapType parameter to define the wrapping type. It is also possible to specify an optional padding around all subviews.

**Types:**

* **`WrapType`** values:
* `.horizontally`: Adjust the view's width and update subviews's horizontal position.
* `.vertically`: Adjust only the view's height and update subviews's vertical position.
* `.all`: Adjust the view's width AND height and update subviews position. This is the default WrapType parameter value `wrapContent()` methods.

###### Usage examples:
```swift
view.pin.wrapContent().center() // wrap all subviews and centered the view inside its parent.
view.pin.wrapContent(padding: 20) // wrap all subviews with a padding of 20 pixels all around
view.pin.wrapContent(.horizontally)
```

###### Example:
This example show the result of different `wrapContent()` method calls.
Here is the initial state:

<img src="docs/images/wrapContent_before.png" width="200"/>

| Source code | Result | Description |
|---------------------|----------|-------------------|
| `view.pin.wrapContent()` | <img src="docs/images/wrapContent_all.png" width="200"/> | Adjust the view's height and width to tight fit its subviews. |
| `view.pin.wrapContent(padding: 10)` | <img src="docs/images/wrapContent_padding.png" width="200"/> | Adjust the view's height and width and add a padding of 10 pixels around its subviews. |
| `view.pin.wrapContent(.horizontally)` | <img src="docs/images/wrapContent_horizontally.png" width="200"/> | Adjust only the view's width. |
| `view.pin.wrapContent(.vertically)` | <img src="docs/images/wrapContent_vertically.png" width="200"/> | Adjust only the view's height. |


###### Example:
This example shows how a view (`containerView`) with two subviews (`imageView` and `label`), can be adjusted to the size of its subviews and then centered inside its parent.

<img src="docs/images/wrapContent_example_1.png" width="540"/>

```swift
label.pin.below(of: imageView, aligned: .center).marginTop(4)
containerView.pin.wrapContent().center()
```
* Line 1: Position the label below the imageView aligned on its center with a top margin of 4 pixels.
* Line 2: Adjust the `containerView`'s size and position its subviews to create a tight wrap around them, and then it center the `containerView` inside its parent (superview).

<br/>


<a name="justify_align"></a>
## justify() / align()

**Methods:**
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion Sources/Impl/PinLayoutImpl+Layouting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import AppKit
#endif

// MARK - UIView's frame computation methods
// MARK: UIView's frame computation methods
extension PinLayoutImpl {
public func layout() {
apply()
Expand Down
91 changes: 91 additions & 0 deletions Sources/Impl/PinLayoutImpl+WrapContent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) 2018 Luc Dion
// 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.

#if os(iOS) || os(tvOS)
import UIKit
#else
import AppKit
#endif

extension PinLayoutImpl {
func wrapContent() -> PinLayout {
return wrapContent(.all, padding: PEdgeInsets(top: 0, left: 0, bottom: 0, right: 0), { return "wrapContent()" })
}

func wrapContent(padding: CGFloat) -> PinLayout {
return wrapContent(.all, padding: PEdgeInsets(top: padding, left: padding, bottom: padding, right: padding), { return "wrapContent(padding: \(padding)" })
}

func wrapContent(padding: PEdgeInsets) -> PinLayout {
return wrapContent(.all, padding: padding, { return "wrapContent(padding: \(insetsDescription(padding))" })
}

func wrapContent(_ type: WrapType) -> PinLayout {
return wrapContent(type, padding: PEdgeInsets(top: 0, left: 0, bottom: 0, right: 0), { return "wrapContent(\(type.description)" })
}

func wrapContent(_ type: WrapType, padding: CGFloat) -> PinLayout {
return wrapContent(type, padding: PEdgeInsets(top: padding, left: padding, bottom: padding, right: padding), { return "wrapContent(\(type.description), padding: \(padding)" })
}

func wrapContent(_ type: WrapType, padding: PEdgeInsets) -> PinLayout {
return wrapContent(type, padding: padding, { return "wrapContent(\(type.description), padding: \(insetsDescription(padding))" })
}

private func wrapContent(_ type: WrapType, padding: PEdgeInsets, _ context: Context) -> PinLayout {
let subviews = view.subviews
guard !subviews.isEmpty else { return self }

let firstViewRect = Coordinates.getViewRect(subviews[0], keepTransform: keepTransform)
let boundingRect = subviews.reduce(firstViewRect, { (result, view) in
result.union(Coordinates.getViewRect(view, keepTransform: keepTransform))
})

var offsetDx: CGFloat = 0
var offsetDy: CGFloat = 0

if type == .all || type == .horizontally {
let contentWidth = boundingRect.width + padding.left + padding.right
if contentWidth >= 0 {
setWidth(contentWidth, context)
}

offsetDx = -boundingRect.minX + padding.left
}

if type == .all || type == .vertically {
let contentHeight = boundingRect.height + padding.top + padding.bottom
if contentHeight >= 0 {
setHeight(contentHeight, context)
}

offsetDy = -boundingRect.minY + padding.top
}

if offsetDx != 0 || offsetDy != 0 {
subviews.forEach { (view) in
let viewRect = Coordinates.getViewRect(view, keepTransform: keepTransform)
let newRect = viewRect.offsetBy(dx: offsetDx, dy: offsetDy)
Coordinates.setViewRect(view, toRect: newRect, keepTransform: keepTransform)
}
}

return self
}
}
16 changes: 5 additions & 11 deletions Sources/Impl/PinLayoutImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -728,14 +728,8 @@ class PinLayoutImpl: PinLayout {
func context() -> String { return "size(of \(viewDescription(view)))" }
return setSize(view.bounds.size, context)
}

// func wrapSubViews() -> PinLayout {
// let neededWidth = view.subviews.max(by: { subview1, subview2 in subview1.frame.maxX < subview2.frame.maxX })?.frame.maxX ?? 0
// let neededHeight = view.subviews.max(by: { subview1, subview2 in subview1.frame.maxY < subview2.frame.maxY })?.frame.maxY ?? 0
//
// return setSize(CGSize(width: neededWidth, height: neededHeight), { return "wrapSubViews()" })
// }


@discardableResult
func aspectRatio(_ ratio: CGFloat) -> PinLayout {
return setAspectRatio(ratio, context: { "aspectRatio(\(ratio))" })
}
Expand All @@ -761,7 +755,7 @@ class PinLayoutImpl: PinLayout {
#endif

func sizeToFit(_ fitType: FitType) -> PinLayout {
return setFitSize(fitType: fitType, { return "sizeToFit(\(fitType.name))" })
return setFitSize(fitType: fitType, { return "sizeToFit(\(fitType.description))" })
}

#if os(iOS) || os(tvOS)
Expand Down Expand Up @@ -992,7 +986,7 @@ extension PinLayoutImpl {
} else if fitType != nil && legacyFitSize {
warn("PinLayout Conflict: \(context()) won't be applied since it conflicts with fitSize().")
} else if let currentFitType = self.fitType, currentFitType != fitType {
warn("PinLayout Conflict: \(context()) won't be applied since it conflicts with sizeToFit(\(currentFitType.name)).")
warn("PinLayout Conflict: \(context()) won't be applied since it conflicts with sizeToFit(\(currentFitType.description)).")
} else {
if fitType == nil {
legacyFitSize = true
Expand All @@ -1006,7 +1000,7 @@ extension PinLayoutImpl {
@discardableResult
internal func setAspectRatio(_ ratio: CGFloat, context: Context) -> PinLayout {
if let fitType = fitType {
warn("PinLayout Conflict: \(context()) won't be applied since it conflicts with sizeToFit(\(fitType.name)).")
warn("PinLayout Conflict: \(context()) won't be applied since it conflicts with sizeToFit(\(fitType.description)).")
} else if legacyFitSize {
warn("PinLayout Conflict: \(context()) won't be applied since it conflicts with fitSize().")
} else if ratio <= 0 {
Expand Down
41 changes: 0 additions & 41 deletions Sources/Impl/TypesImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,6 @@ import Foundation
typealias Context = () -> String
typealias Size = (width: CGFloat?, height: CGFloat?)

extension HorizontalAlign {
var description: String {
switch self {
case .left: return "left"
case .center: return "center"
case .right: return "right"
case .start: return "start"
case .end: return "end"
}
}
}

extension VerticalAlign {
var description: String {
switch self {
case .top: return "top"
case .center: return "center"
case .bottom: return "bottom"
}
}
}

class EdgeListImpl: EdgeList {
internal let view: PView

Expand Down Expand Up @@ -185,26 +163,7 @@ class AnchorImpl: Anchor {
}
}

extension CGFloat {
public var description: String {
if self.truncatingRemainder(dividingBy: 1) == 0.0 {
return "\(Int(self))"
} else {
return "\(self)"
}
}
}

internal extension FitType {
var name: String {
switch self {
case .width: return ".width"
case .height: return ".height"
case .widthFlexible: return ".widthFlexible"
case .heightFlexible: return ".heightFlexible"
}
}

var isFlexible: Bool {
return self == .widthFlexible || self == .heightFlexible
}
Expand Down
18 changes: 0 additions & 18 deletions Sources/Pin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,6 @@

import Foundation

@objc public enum LayoutDirection: Int {
case auto
case ltr
case rtl
}

/// Control how PinLayout will calls `UIView.safeAreaInsetsDidChange` when the `UIView.pin.safeArea` change.
/// This support is usefull only on iOS 8/9/10. On iOS 11 `UIView.safeAreaInsetsDidChange` is supported
/// natively so this settings have no impact.
@objc public enum PinSafeAreaInsetsDidChangeMode: Int {
/// PinLayout won't call `UIView.safeAreaInsetsDidChange` on iOS 8/9/10.
case disable
/// PinLayout will call `UIView.safeAreaInsetsDidChange` only if the UIView implement the PinSafeAreaInsetsUpdate protocol.
case optIn
/// PinLayout will automatically calls `UIView.safeAreaInsetsDidChange` if the view has implemented this method.
case always
}

@objc public class Pin: NSObject {
@objc public static var layoutDirection = LayoutDirection.ltr

Expand Down
Loading