Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LayoutManager #66

Merged
merged 4 commits into from
Nov 25, 2021
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
81 changes: 81 additions & 0 deletions Demo_MondrianLayout/Book.Manager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import MondrianLayout
import StorybookKit
import UIKit

var _book_layoutManager: BookView {
BookNavigationLink(title: "LayoutManager") {

let manager = LayoutManager()
var counter = 0

BookPreview {
ExampleView(width: 200, height: 200) { view in

let box1 = UIView.mock(backgroundColor: .neon(.cyan))
let box2 = UIView.mock(backgroundColor: .neon(.cyan))
let box3 = UIView.mock(backgroundColor: .neon(.cyan))

manager.setup(on: view) {

if counter % 2 == 0 {

VStackBlock {
box1
.viewBlock
.size(.smallSquare)

box2
.viewBlock
.size(.smallSquare)

box3
.viewBlock
.size(.smallSquare)

StackingSpacer(minLength: 0)
}
} else {

HStackBlock {
box1
.viewBlock
.size(.largeSquare)

box2
.viewBlock
.size(.largeSquare)

box3
.viewBlock
.size(.largeSquare)

StackingSpacer(minLength: 0)
}
}
Comment on lines +14 to +54
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to use


}

}
}
.addButton(
"Update",
handler: { view in
counter += 1
manager.reloadLayout()
}
)
.addButton(
"Update Animated",
handler: { view in
counter += 1
UIViewPropertyAnimator(duration: 1.2, dampingRatio: 0.9) {
manager.reloadLayout()
view.layoutIfNeeded()
}
.startAnimation()
}
)
.title("LayoutManager")

}
}
2 changes: 2 additions & 0 deletions Demo_MondrianLayout/Book.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ let book = Book(title: "MondrianLayout") {

_book_classic

_book_layoutManager

BookNavigationLink(title: "Instagram Post") {
BookPreview {
InstagramPostView()
Expand Down
16 changes: 16 additions & 0 deletions MondrianLayout.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
4B384D8A2675CDA9001B0267 /* LayoutBuilderContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B384D892675CDA9001B0267 /* LayoutBuilderContext.swift */; };
4B384D8E2675CF47001B0267 /* MondrianLayout.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B4057622675C501003B28A1 /* MondrianLayout.framework */; };
4B384D8F2675CF47001B0267 /* MondrianLayout.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B4057622675C501003B28A1 /* MondrianLayout.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4B3B0F72274E8F98001C9E6B /* LayoutManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3B0F71274E8F98001C9E6B /* LayoutManager.swift */; };
4B3B0F74274E9A3B001C9E6B /* Book.Manager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3B0F73274E9A3B001C9E6B /* Book.Manager.swift */; };
4B4057732675C51E003B28A1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4057722675C51E003B28A1 /* AppDelegate.swift */; };
4B40577C2675C51F003B28A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B40577B2675C51F003B28A1 /* Assets.xcassets */; };
4B40577F2675C51F003B28A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B40577D2675C51F003B28A1 /* LaunchScreen.storyboard */; };
Expand Down Expand Up @@ -131,6 +133,8 @@
4B309029269824D7001AB89B /* Book.RelativeBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Book.RelativeBlock.swift; sourceTree = "<group>"; };
4B3156442707E4B700BEC0E3 /* AnyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyView.swift; sourceTree = "<group>"; };
4B384D892675CDA9001B0267 /* LayoutBuilderContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutBuilderContext.swift; sourceTree = "<group>"; };
4B3B0F71274E8F98001C9E6B /* LayoutManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutManager.swift; sourceTree = "<group>"; };
4B3B0F73274E9A3B001C9E6B /* Book.Manager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Book.Manager.swift; sourceTree = "<group>"; };
4B4057622675C501003B28A1 /* MondrianLayout.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MondrianLayout.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4B4057702675C51E003B28A1 /* Demo_MondrianLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo_MondrianLayout.app; sourceTree = BUILT_PRODUCTS_DIR; };
4B4057722675C51E003B28A1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -225,6 +229,14 @@
name = Frameworks;
sourceTree = "<group>";
};
4B3B0F70274E8F8D001C9E6B /* Manager */ = {
isa = PBXGroup;
children = (
4B3B0F71274E8F98001C9E6B /* LayoutManager.swift */,
);
path = Manager;
sourceTree = "<group>";
};
4B4057582675C501003B28A1 = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -252,6 +264,7 @@
4B4057642675C501003B28A1 /* MondrianLayout */ = {
isa = PBXGroup;
children = (
4B3B0F70274E8F8D001C9E6B /* Manager */,
4B9B07EE267E1C2900818A40 /* Classic */,
4BFF75CF2677CC16003FA7F0 /* Descriptors */,
4B9A1B84267A286D0029907C /* LayoutBlocks */,
Expand Down Expand Up @@ -279,6 +292,7 @@
4B47F63C2678DCAC00BDE2DB /* Book.ZStackBlock.swift */,
4B309029269824D7001AB89B /* Book.RelativeBlock.swift */,
4B7382072679EBED00D77986 /* Book.SafeArea.swift */,
4B3B0F73274E9A3B001C9E6B /* Book.Manager.swift */,
4B9B07F3267E586400818A40 /* Book.Classic.swift */,
4B1256BD267CA74700FE9612 /* Book.ViewController.swift */,
4B40577B2675C51F003B28A1 /* Assets.xcassets */,
Expand Down Expand Up @@ -550,6 +564,7 @@
4B9E1A1C2676333C00D1E1C9 /* _LayoutBlockType.swift in Sources */,
4B3156452707E4B700BEC0E3 /* AnyView.swift in Sources */,
4B64B1C926762C1400460282 /* UIView+Mondrian.swift in Sources */,
4B3B0F72274E8F98001C9E6B /* LayoutManager.swift in Sources */,
4B64B1C726762A8200460282 /* HStackBlock.swift in Sources */,
4B4CA90E26A3251300863217 /* Optimization.swift in Sources */,
4B64B1BB2675F98500460282 /* VHStackContentBuilder.swift in Sources */,
Expand All @@ -575,6 +590,7 @@
4B47F63F2678E35400BDE2DB /* Book.Overlay.swift in Sources */,
4BBFF31726EC7DFF00FCC451 /* Book.Sizing.swift in Sources */,
4B1F05D2267A6D5B00A66CC7 /* InstagramPostView.swift in Sources */,
4B3B0F74274E9A3B001C9E6B /* Book.Manager.swift in Sources */,
4B47F6432679040600BDE2DB /* SwiftUIComparison.swift in Sources */,
4B4057732675C51E003B28A1 /* AppDelegate.swift in Sources */,
4BE4E9462675CABE00D096DC /* Book.swift in Sources */,
Expand Down
15 changes: 15 additions & 0 deletions MondrianLayout/Descriptors/LayoutBuilderContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public final class LayoutBuilderContext {

public weak var targetView: UIView?
public let name: String?
public private(set) var isActive = false

public init(
name: String? = nil,
Expand Down Expand Up @@ -73,10 +74,18 @@ public final class LayoutBuilderContext {
*/
public func activate() {

assert(Thread.isMainThread)

guard let targetView = targetView else {
return
}

guard isActive == false else {
return
}

isActive = true

viewAppliers.forEach { $0() }

managedLayoutGuides.forEach {
Expand All @@ -100,6 +109,12 @@ public final class LayoutBuilderContext {
return
}

guard isActive == true else {
return
}

isActive = false

managedLayoutGuides.forEach {
targetView.removeLayoutGuide($0)
}
Expand Down
8 changes: 8 additions & 0 deletions MondrianLayout/Extensions/UIView+Mondrian.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ public enum EntrypointBuilder {
return components.map { .container($0) }
}

public static func buildEither(first component: [EntrypointBuilder.Either]) -> [EntrypointBuilder.Either] {
return component
}

public static func buildEither(second component: [EntrypointBuilder.Either]) -> [EntrypointBuilder.Either] {
return component
}

}

extension MondrianNamespace where Base : UIView {
Expand Down
71 changes: 71 additions & 0 deletions MondrianLayout/Manager/LayoutManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

import UIKit

/**
An object that manages layout.
It supports updating the layout.
In the case of defining distinct layouts under some conditions, this helps.

```swift
/// Defines a instance
/// It needs to be retained somewhere such as inside view controller or view.
let manager = LayoutManager()

/// Sets up the layout in the escaping closure
manager.setup(on: view) {
VStackBlock {
...
}
}

/// Calls when you need to get a new layout.
/// It calls the closure set in `setup` to build subviews again.
manager.reloadLayout()
```

*/
public final class LayoutManager {

private var _layoutBuilder: (() -> [EntrypointBuilder.Either])?
private var currentContext: LayoutBuilderContext?
private weak var targetView: UIView?

public init() {
}

/**
Setting up the layout of subviews on the view.
the layout closure is called each calling `reloadLayout` and after `setup`.

Please avoid creating view and layout-guide instances in the closure. Performance decreases by creating a new instance and destroying the previous one.
*/
public func setup(
on view: UIView,
@EntrypointBuilder layout: @escaping () -> [EntrypointBuilder.Either]
) {
targetView = view
_layoutBuilder = layout
reloadLayout()
}

/**
Re lays out the subviews with deactivating the current layout.
*/
public func reloadLayout() {
guard let _layoutBuilder = _layoutBuilder else {
return
}

guard let targetView = targetView else {
return
}

let previousContext = currentContext

previousContext?.deactivate()
let newContext = targetView.mondrian.buildSubviews(_layoutBuilder)
currentContext = newContext

}

}
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,15 @@ ZStackBlock {
}
```





## Updating layout dynamically

`LayoutManager` does support it.
If we need to change the layout each some conditions such as depending traits, this object helps that.

## Animations

// TODO:
Expand Down