Skip to content

Commit f9377f5

Browse files
committed
Address PR comments
1 parent 08422e5 commit f9377f5

File tree

2 files changed

+52
-47
lines changed

2 files changed

+52
-47
lines changed

Sources/UIKitBackend/UIKitBackend.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ public final class UIKitBackend: AppBackend {
1919
public let defaultTableRowContentHeight = -1
2020
public let defaultTableCellVerticalPadding = -1
2121

22-
public init() {}
23-
2422
var onTraitCollectionChange: (() -> Void)?
2523

2624
private let appDelegateClass: ApplicationDelegate.Type
2725

28-
public init(appDelegateClass: ApplicationDelegate.Type = ApplicationDelegate.self) {
26+
public init() {
27+
self.appDelegateClass = ApplicationDelegate.self
28+
}
29+
30+
public init(appDelegateClass: ApplicationDelegate.Type) {
2931
self.appDelegateClass = appDelegateClass
3032
}
3133

Sources/UIKitBackend/UIViewRepresentable.swift

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,32 @@ where Content == Never {
2828
/// the view hierarchy (i.e. through bindings), and is often the view's delegate.
2929
func makeCoordinator() -> Coordinator
3030

31-
/// Compute the view's preferred size.
31+
/// Compute the view's size.
3232
/// - Parameters:
3333
/// - proposal: The proposed frame for the view to render in.
3434
/// - uiVIew: The view being queried for its preferred size.
3535
/// - context: The context, including the coordinator and environment values.
36-
/// - Returns: The preferred size for the view, ideally one that fits within `proposal`,
37-
/// or `nil` to use a default size for this view.
38-
func sizeThatFits(
39-
_ proposal: CGSize, uiView: UIViewType, context: UIViewRepresentableContext<Coordinator>
40-
) -> CGSize?
36+
/// - Returns: Information about the view's size. The ``SwiftCrossUI/ViewSize/size``
37+
/// property is what frame the view will actually be rendered with if the current layout
38+
/// pass is not a dry run, while the other properties are used to inform the layout engine
39+
/// how big or small the view can be. Pass `nil` for the maximum width/height if the view
40+
/// has no maximum size (and therefore may occupy the entire screen).
41+
///
42+
/// The default implementation uses `uiView.intrinsicContentSize` and `uiView.sizeThatFits(_:)`
43+
/// to determine the return value.
44+
func determineViewSize(
45+
for proposal: SIMD2<Int>, uiView: UIViewType,
46+
context: UIViewRepresentableContext<Coordinator>
47+
) -> ViewSize
4148

4249
/// Called to clean up the view when it's removed.
4350
/// - Parameters:
44-
/// - uiVIew: The view being queried for its preferred size.
51+
/// - uiVIew: The view being dismantled.
4552
/// - coordinator: The coordinator.
4653
///
54+
/// This method is called after all UIKit lifecycle methods, such as
55+
/// `uiView.didMoveToSuperview()`.
56+
///
4757
/// The default implementation does nothing.
4858
static func dismantleUIView(_ uiView: UIViewType, coordinator: Coordinator)
4959
}
@@ -53,17 +63,30 @@ extension UIViewRepresentable {
5363
// no-op
5464
}
5565

56-
public func sizeThatFits(
57-
_ proposal: CGSize, uiView: UIViewType, context _: UIViewRepresentableContext<Coordinator>
58-
) -> CGSize? {
59-
// For many growable views, such as WKWebView, sizeThatFits just returns the current
60-
// size -- which is 0 x 0 on first render. So, check if the view can grow to fill
61-
// the available space first.
62-
let intrinsicContentSize = uiView.intrinsicContentSize
63-
if intrinsicContentSize.width < 0.0 || intrinsicContentSize.height < 0.0 {
64-
return nil
65-
}
66-
return uiView.sizeThatFits(proposal)
66+
public func determineViewSize(
67+
for proposal: SIMD2<Int>, uiView: UIViewType,
68+
context _: UIViewRepresentableContext<Coordinator>
69+
) -> ViewSize {
70+
let intrinsicSize = uiView.intrinsicContentSize
71+
let sizeThatFits = uiView.sizeThatFits(
72+
CGSize(width: CGFloat(proposal.x), height: CGFloat(proposal.y)))
73+
let roundedSize = SIMD2(
74+
Int(sizeThatFits.width.rounded(.up)),
75+
Int(sizeThatFits.height.rounded(.up)))
76+
return ViewSize(
77+
size: SIMD2(
78+
intrinsicSize.width < 0.0 ? proposal.x : min(proposal.x, roundedSize.x),
79+
intrinsicSize.height < 0.0 ? proposal.y : min(proposal.y, roundedSize.y)
80+
),
81+
idealSize: SIMD2(
82+
intrinsicSize.width < 0.0 ? proposal.x : roundedSize.x,
83+
intrinsicSize.height < 0.0 ? proposal.y : roundedSize.y
84+
),
85+
minimumWidth: max(0, Int(intrinsicSize.width.rounded(.awayFromZero))),
86+
minimumHeight: max(0, Int(intrinsicSize.height.rounded(.awayFromZero))),
87+
maximumWidth: nil,
88+
maximumHeight: nil
89+
)
6790
}
6891
}
6992

@@ -108,34 +131,14 @@ where Self: UIViewRepresentable {
108131
dryRun: Bool
109132
) -> ViewUpdateResult {
110133
let representingWidget = widget as! RepresentingWidget<Self>
111-
representingWidget.updateContext(environment: environment)
134+
representingWidget.update(with: environment)
112135

113-
let preferredSize =
114-
representingWidget.representable.sizeThatFits(
115-
CGSize(width: proposedSize.x, height: proposedSize.y),
136+
let size =
137+
representingWidget.representable.determineViewSize(
138+
for: proposedSize,
116139
uiView: representingWidget.subview,
117140
context: representingWidget.context!
118-
) ?? representingWidget.subview.intrinsicContentSize
119-
120-
let roundedSize = SIMD2(
121-
Int(preferredSize.width.rounded(.awayFromZero)),
122-
Int(preferredSize.height.rounded(.awayFromZero)))
123-
124-
// Not only does -1 x -1 mean "grow to fill", UIKit allows you to return -1 for only one axis!
125-
let size = ViewSize(
126-
size: SIMD2(
127-
roundedSize.x < 0 ? proposedSize.x : min(proposedSize.x, roundedSize.x),
128-
roundedSize.y < 0 ? proposedSize.y : min(proposedSize.y, roundedSize.y)
129-
),
130-
idealSize: SIMD2(
131-
roundedSize.x < 0 ? proposedSize.x : roundedSize.x,
132-
roundedSize.y < 0 ? proposedSize.y : roundedSize.y
133-
),
134-
minimumWidth: roundedSize.x > proposedSize.x ? roundedSize.x : 0,
135-
minimumHeight: roundedSize.y > proposedSize.y ? roundedSize.y : 0,
136-
maximumWidth: nil,
137-
maximumHeight: nil
138-
)
141+
)
139142

140143
if !dryRun {
141144
representingWidget.width = size.size.x
@@ -173,7 +176,7 @@ final class RepresentingWidget<Representable: UIViewRepresentable>: BaseWidget {
173176
return view
174177
}()
175178

176-
func updateContext(environment: EnvironmentValues) {
179+
func update(with environment: EnvironmentValues) {
177180
if context == nil {
178181
context = .init(coordinator: representable.makeCoordinator(), environment: environment)
179182
} else {

0 commit comments

Comments
 (0)