Skip to content

Commit ae0d031

Browse files
constantine-frymeta-codesync[bot]
authored andcommitted
Expand the docs around @quicklayout usage and the manual integration
Summary: People have reported to me that Devmate often uses .applyFrame when using QuickLayout. However, it's against the recommendation. I updated the docs so that it's even more clear. Differential Revision: D87983350 fbshipit-source-id: f7dd2c18a5e4de9c8b671447c9b845cadeaba4d1
1 parent 64ea1d6 commit ae0d031

10 files changed

+323
-63
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
id: manual-layout-integration-uiviewcontrollers
3+
---
4+
5+
import useBaseUrl from '@docusaurus/useBaseUrl';
6+
7+
# UIViewControllers
8+
9+
Since the macro is not available for view controllers, you need to manually manage the view hierarchy and override `viewDidLayoutSubviews`.
10+
In the example below, note how `viewDidLayoutSubviews` insets the `view.bounds` by the `safeAreaInsets` and then applies the frame to the body.
11+
```swift
12+
final class MyViewController: UIViewController {
13+
14+
var body: Layout {
15+
...
16+
}
17+
18+
override func viewDidLoad() {
19+
super.viewDidLoad()
20+
view.addSubviews {
21+
view1
22+
view2
23+
}
24+
}
25+
26+
override func viewDidLayoutSubviews() {
27+
super.viewDidLayoutSubviews()
28+
let layoutRect = view.bounds.inset(by: view.safeAreaInsets)
29+
body.applyFrame(layoutRect)
30+
}
31+
}
32+
```

Sources/QuickLayout/docs/docs/how-to-use/quick-layout-without-macro.mdx renamed to Sources/QuickLayout/docs/docs/how-to-use/manual-layout-integration.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ id: manual-layout-integration
44

55
import useBaseUrl from '@docusaurus/useBaseUrl';
66

7-
# Manual Layout Integration
7+
# Manual Integration
8+
9+
When needed, you can still use QuickLayout without the macro by manually managing the view hierarchy and overriding the `layoutSubviews` and `sizeThatFits` methods.
810

9-
The @QuickLayout macro is available for all view instances, but not view controllers. When needed, you can still use QuickLayout without this macro. In that case, you will need to manually manage the view hierarchy and override the `layoutSubviews` and `sizeThatFits` methods. The snippet below demonstrates a view that does not utilize the @QuickLayout macro, but still utilizes a declarative syntax for layout.
1011
```swift
1112
import QuickLayout
1213

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
id: macro-layout-integration-bodyContainerView
3+
---
4+
5+
import useBaseUrl from '@docusaurus/useBaseUrl';
6+
7+
# Custom Content View
8+
9+
By default, QuickLayout adds subviews directly within the view itself.
10+
However, some views may require a different container view for their subviews.
11+
You can override the `bodyContainerView` property to specify an alternative container view.
12+
For example, collection view and table view cells use this to ensure views are added to the `contentView`.
13+
14+
```swift
15+
@QuickLayout
16+
final class ExperimentedView: UIView {
17+
18+
let contentView = UIView()
19+
20+
init() {
21+
super.init(frame: .zero)
22+
addsSubview(contentView)
23+
}
24+
25+
override public var bodyContainerView: UIView {
26+
contentView /// This will be the container view for the body.
27+
}
28+
29+
var body: Layout {
30+
...
31+
}
32+
}
33+
```
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
id: macro-layout-integration-donts
3+
---
4+
5+
import useBaseUrl from '@docusaurus/useBaseUrl';
6+
7+
# Traps
8+
9+
:::danger Don't use @QuickLayout macro on UIViewControllers
10+
[See this page for integration with UIViewControllers](manual-layout-integration-uiviewcontrollers.mdx).
11+
```swift
12+
@QuickLayout /// <-- 🛑 Don't use the macro on UIViewControllers
13+
final class MyViewController: UIViewController {
14+
15+
var body: Layout {
16+
...
17+
}
18+
}
19+
```
20+
:::
21+
22+
:::danger DON'T
23+
When using @QuickLayout, don't call `body.applyFrame()`.
24+
This will result in double measurements.
25+
26+
27+
```swift
28+
@QuickLayout
29+
final class VerySlowView: UIView {
30+
31+
var body: Layout {
32+
...
33+
}
34+
35+
/// @QuickLayout macro will insert additional
36+
/// _QuickLayoutViewImplementation call into this method.
37+
override func layoutSubviews() {
38+
super.layoutSubviews()
39+
body.applyFrame(self.bounds) /// <-- 🛑 Don't call applyFrame when using @QuickLayout macro
40+
}
41+
}
42+
```
43+
:::
44+
45+
:::danger Don't create views in the body
46+
```swift
47+
@QuickLayout
48+
final class MyView: UIView {
49+
50+
var body: Layout {
51+
HStack {
52+
UIView() /// <-- 🛑 Don't create views in the body
53+
}
54+
}
55+
}
56+
```
57+
:::
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
id: macro-layout-integration-dos
3+
---
4+
5+
import useBaseUrl from '@docusaurus/useBaseUrl';
6+
7+
# Tips
8+
9+
:::tip It's OK to override layoutSubviews
10+
11+
This is a great way to add custom layout logic to your view.
12+
```swift
13+
@QuickLayout
14+
final class ViewWithAdditionalLayoutBehaviour: UIView {
15+
16+
let label = UILabel()
17+
18+
var body: Layout {
19+
...
20+
}
21+
22+
/// Feel free to override layoutSubviews to customize your layout.
23+
override func layoutSubviews() {
24+
super.layoutSubviews()
25+
label.frame = ....
26+
}
27+
}
28+
```
29+
:::
30+
31+
:::tip It's OK to override sizeThatFits
32+
33+
```swift
34+
@QuickLayout
35+
final class ViewWithFixedSize: UIView {
36+
37+
var body: Layout {
38+
...
39+
}
40+
41+
/// 👍 It's OK to override sizeThatFits
42+
override func sizeThatFits(_ size: CGSize) -> CGSize {
43+
/// Note you can achieve the same result
44+
/// by using the `.frame(height: 100)` modifier in the body.
45+
CGSize(width: size.widith, height: 100)
46+
}
47+
}
48+
```
49+
50+
:::
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
id: macro-layout-integration-isBodyEnabled
3+
---
4+
5+
import useBaseUrl from '@docusaurus/useBaseUrl';
6+
7+
8+
# Experimentation
9+
10+
To help you experiment with QuickLayout, the macro provides a way to disable QuickLayout on a specific view.
11+
This is achieved by overriding the `isBodyEnabled` property.
12+
13+
```swift
14+
@QuickLayout
15+
final class ExperimentedView: UIView {
16+
17+
override var isBodyEnabled: Bool {
18+
featureFlags.isQuickLayoutEnabled
19+
}
20+
21+
init() {
22+
super.init(frame: .zero)
23+
if !isBodyEnabled {
24+
self.addSubviews {
25+
label
26+
image
27+
}
28+
}
29+
}
30+
31+
var body: Layout {
32+
...
33+
}
34+
35+
override public func layoutSubviews() {
36+
super.layoutSubviews()
37+
/// Note: you don't need to call
38+
/// _QuickLayoutViewImplementation.layoutSubviews(self)
39+
/// because it's automatically injected by the macro.
40+
if !isBodyEnabled {
41+
imperativeManualLayoutSubviews()
42+
}
43+
}
44+
45+
override public func sizeThatFits(_ size: CGSize) -> CGSize {
46+
if !isBodyEnabled {
47+
imperativeManualSizeThatFits(size)
48+
}
49+
/// OK to call for migration.
50+
_QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? .zero
51+
}
52+
}
53+
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
id: macro-layout-integration-state-updates
3+
---
4+
5+
import useBaseUrl from '@docusaurus/useBaseUrl';
6+
7+
# State Updates
8+
9+
When the state of a view changes, call `setNeedsLayout` to trigger a layout update. This applies whether you're using the `@QuickLayout` macro or manual integration.
10+
11+
With the `@QuickLayout` macro, calling `setNeedsLayout` will:
12+
- Update the view hierarchy based on the current state
13+
- Animate any views that are added or removed
14+
- Re-layout all subviews
15+
16+
The example below demonstrates a stateful view that lazily creates a `label` when the user presses the `increaseButton` for the first time, and updates the layout accordingly.
17+
18+
```swift
19+
@QuickLayout
20+
final class CounterView: UIView {
21+
22+
private var count = 0
23+
24+
private lazy var label {
25+
// This view will only be created when the count is set to 1 for the first time.
26+
UILabel()
27+
}
28+
29+
private lazy var increaseButton = {
30+
let button = UIButton(type: .system)
31+
button.setTitle("Increase", for: .normal)
32+
button.addTarget(self, action: #selector(addCount), for: .touchUpInside)
33+
return button
34+
}()
35+
36+
private lazy var resetButton = {
37+
let button = UIButton(type: .system)
38+
button.setTitle("Reset", for: .normal)
39+
button.addTarget(self, action: #selector(resetCount), for: .touchUpInside)
40+
return button
41+
}()
42+
43+
var body: Layout {
44+
VStack(spacing: 8) {
45+
if count > 0 {
46+
label
47+
}
48+
HStack(spacing: 8) {
49+
increaseButton
50+
resetButton
51+
}
52+
}
53+
}
54+
55+
@objc private func addCount() {
56+
count += 1
57+
label.text = "Count: \(count)"
58+
setNeedsLayout() // <-- This is the key line.
59+
}
60+
61+
@objc private func resetCount() {
62+
count = 0
63+
setNeedsLayout() // <-- This is the key line.
64+
}
65+
}
66+
```

0 commit comments

Comments
 (0)