Skip to content

Commit 183aecd

Browse files
committed
Initial work supporting controllers
1 parent 20a6e6f commit 183aecd

File tree

7 files changed

+331
-168
lines changed

7 files changed

+331
-168
lines changed

Sources/UIKitBackend/BaseWidget.swift

Lines changed: 0 additions & 117 deletions
This file was deleted.

Sources/UIKitBackend/UIKitBackend+Container.swift

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
11
import SwiftCrossUI
22
import UIKit
33

4-
final class ScrollWidget: WrapperWidget<UIScrollView> {
4+
final class ScrollWidget: ContainerWidget, UIScrollViewDelegate {
5+
private var scrollView = UIScrollView()
56
private var childWidthConstraint: NSLayoutConstraint?
67
private var childHeightConstraint: NSLayoutConstraint?
7-
8-
private let innerChild: BaseWidget
9-
10-
init(child innerChild: BaseWidget) {
11-
self.innerChild = innerChild
12-
super.init(child: UIScrollView())
13-
14-
child.addSubview(innerChild)
15-
16-
NSLayoutConstraint.activate([
17-
innerChild.topAnchor.constraint(equalTo: child.contentLayoutGuide.topAnchor),
18-
innerChild.bottomAnchor.constraint(equalTo: child.contentLayoutGuide.bottomAnchor),
19-
innerChild.leftAnchor.constraint(equalTo: child.contentLayoutGuide.leftAnchor),
20-
innerChild.rightAnchor.constraint(equalTo: child.contentLayoutGuide.rightAnchor),
21-
])
8+
9+
private lazy var contentLayoutGuideConstraints: [NSLayoutConstraint] = [
10+
scrollView.contentLayoutGuide.leadingAnchor.constraint(equalTo: child.view.leadingAnchor),
11+
scrollView.contentLayoutGuide.trailingAnchor.constraint(equalTo: child.view.trailingAnchor),
12+
scrollView.contentLayoutGuide.topAnchor.constraint(equalTo: child.view.topAnchor),
13+
scrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: child.view.bottomAnchor)
14+
]
15+
16+
override func loadView() {
17+
view = scrollView
18+
scrollView.translatesAutoresizingMaskIntoConstraints = false
19+
scrollView.delegate = self
2220
}
23-
21+
22+
override func viewWillLayoutSubviews() {
23+
NSLayoutConstraint.activate(contentLayoutGuideConstraints)
24+
super.viewWillLayoutSubviews()
25+
}
26+
2427
func setScrollBars(
2528
hasVerticalScrollBar: Bool,
2629
hasHorizontalScrollBar: Bool
@@ -29,8 +32,8 @@ final class ScrollWidget: WrapperWidget<UIScrollView> {
2932
case (true, true):
3033
childHeightConstraint!.isActive = false
3134
case (false, nil):
32-
childHeightConstraint = innerChild.heightAnchor.constraint(
33-
equalTo: child.heightAnchor)
35+
childHeightConstraint = child.view.heightAnchor.constraint(
36+
equalTo: scrollView.heightAnchor)
3437
fallthrough
3538
case (false, false):
3639
childHeightConstraint!.isActive = true
@@ -42,68 +45,67 @@ final class ScrollWidget: WrapperWidget<UIScrollView> {
4245
case (true, true):
4346
childWidthConstraint!.isActive = false
4447
case (false, nil):
45-
childWidthConstraint = innerChild.widthAnchor.constraint(equalTo: child.widthAnchor)
48+
childWidthConstraint = child.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
4649
fallthrough
4750
case (false, false):
4851
childWidthConstraint!.isActive = true
4952
default:
5053
break
5154
}
5255

53-
child.showsVerticalScrollIndicator = hasVerticalScrollBar
54-
child.showsHorizontalScrollIndicator = hasHorizontalScrollBar
56+
scrollView.showsVerticalScrollIndicator = hasVerticalScrollBar
57+
scrollView.showsHorizontalScrollIndicator = hasHorizontalScrollBar
5558
}
5659
}
5760

5861
extension UIKitBackend {
5962
public func createContainer() -> Widget {
60-
BaseWidget()
63+
BaseViewWidget()
6164
}
6265

6366
public func removeAllChildren(of container: Widget) {
64-
container.subviews.forEach { $0.removeFromSuperview() }
67+
container.childWidgets.forEach { $0.removeFromParentWidget() }
6568
}
6669

6770
public func addChild(_ child: Widget, to container: Widget) {
68-
container.addSubview(child)
71+
child.add(toWidget: container)
6972
}
7073

7174
public func setPosition(
7275
ofChildAt index: Int,
7376
in container: Widget,
7477
to position: SIMD2<Int>
7578
) {
76-
guard index < container.subviews.count else {
79+
guard index < container.childWidgets.count else {
7780
assertionFailure("Attempting to set position of nonexistent subview")
7881
return
7982
}
8083

81-
let child = container.subviews[index] as! BaseWidget
84+
let child = container.childWidgets[index]
8285
child.x = position.x
8386
child.y = position.y
8487
}
8588

8689
public func removeChild(_ child: Widget, from container: Widget) {
87-
assert(child.isDescendant(of: container))
88-
child.removeFromSuperview()
90+
assert(child.view.isDescendant(of: container.view))
91+
child.removeFromParentWidget()
8992
}
9093

9194
public func createColorableRectangle() -> Widget {
92-
BaseWidget()
95+
BaseViewWidget()
9396
}
9497

9598
public func setColor(ofColorableRectangle widget: Widget, to color: Color) {
96-
widget.backgroundColor = color.uiColor
99+
widget.view.backgroundColor = color.uiColor
97100
}
98101

99102
public func setCornerRadius(of widget: Widget, to radius: Int) {
100-
widget.layer.cornerRadius = CGFloat(radius)
101-
widget.layer.masksToBounds = true
102-
widget.setNeedsLayout()
103+
widget.view.layer.cornerRadius = CGFloat(radius)
104+
widget.view.layer.masksToBounds = true
103105
}
104106

105107
public func naturalSize(of widget: Widget) -> SIMD2<Int> {
106-
let size = widget.intrinsicContentSize
108+
let size = widget.view.intrinsicContentSize
107109
return SIMD2(
108110
Int(size.width.rounded(.awayFromZero)),
109111
Int(size.height.rounded(.awayFromZero))

Sources/UIKitBackend/UIKitBackend+Control.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,16 @@ final class TextFieldWidget: WrapperWidget<UITextField>, UITextFieldDelegate {
103103
}
104104
#endif
105105

106-
final class ClickableWidget: WrapperWidget<BaseWidget> {
106+
final class ClickableWidget: ContainerWidget {
107107
private var gestureRecognizer: UITapGestureRecognizer!
108108
var onClick: (() -> Void)?
109109

110-
override init(child: BaseWidget) {
110+
override init(child: some WidgetProtocol) {
111111
super.init(child: child)
112112

113113
gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewTouched))
114114
gestureRecognizer.cancelsTouchesInView = true
115-
child.addGestureRecognizer(gestureRecognizer)
115+
child.view.addGestureRecognizer(gestureRecognizer)
116116
}
117117

118118
@objc

Sources/UIKitBackend/UIKitBackend+Picker.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import SwiftCrossUI
22
import UIKit
33

4-
protocol Picker: BaseWidget {
4+
protocol Picker: WidgetProtocol {
55
func setOptions(to options: [String])
66
func setChangeHandler(to onChange: @escaping (Int?) -> Void)
77
func setSelectedOption(to index: Int?)

Sources/UIKitBackend/UIKitBackend+Window.swift

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import UIKit
33
final class RootViewController: UIViewController {
44
unowned var backend: UIKitBackend
55
var resizeHandler: ((CGSize) -> Void)?
6+
private var childWidget: (any WidgetProtocol)?
67

78
init(backend: UIKitBackend) {
89
self.backend = backend
@@ -33,13 +34,20 @@ final class RootViewController: UIViewController {
3334
backend.onTraitCollectionChange?()
3435
}
3536

36-
func setChild(to child: UIView) {
37-
view.subviews.forEach { $0.removeFromSuperview() }
38-
view.addSubview(child)
39-
37+
func setChild(to child: some WidgetProtocol) {
38+
childWidget?.removeFromParentWidget()
39+
child.removeFromParentWidget()
40+
41+
let childController = child.controller
42+
view.addSubview(child.view)
43+
if let childController {
44+
addChild(childController)
45+
childController.didMove(toParent: self)
46+
}
47+
4048
NSLayoutConstraint.activate([
41-
child.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor),
42-
child.heightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.heightAnchor),
49+
child.view.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor),
50+
child.view.heightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.heightAnchor)
4351
])
4452
}
4553
}
@@ -76,9 +84,8 @@ extension UIKitBackend {
7684
// of the screen could be obscured (e.g. covered by the notch). In the future we
7785
// might want to let users decide what to do, but for now, lie and say that the safe
7886
// area insets aren't even part of the window.
79-
// If/when this is updated, ``RootViewController/setChild(to:)``,
80-
// ``BaseWidget/updateLeftConstraint()``, and ``BaseWidget/updateTopConstraint()``
81-
// will also need to be updated.
87+
// If/when this is updated, ``RootViewController`` and ``WidgetProtocolHelpers`` will
88+
// also need to be updated.
8289
let size = window.safeAreaLayoutGuide.layoutFrame.size
8390
return SIMD2(Int(size.width), Int(size.height))
8491
}

Sources/UIKitBackend/UIViewRepresentable.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ where Self: UIViewRepresentable {
150150
)
151151

152152
if !dryRun {
153-
representingWidget.width = size.size.x
154-
representingWidget.height = size.size.y
153+
representingWidget.frame.size.width = CGFloat(size.size.x)
154+
representingWidget.frame.size.height = CGFloat(size.size.y)
155155
}
156156

157157
return ViewUpdateResult.leafView(size: size)
@@ -165,7 +165,7 @@ where Coordinator == Void {
165165
}
166166
}
167167

168-
final class RepresentingWidget<Representable: UIViewRepresentable>: BaseWidget {
168+
final class RepresentingWidget<Representable: UIViewRepresentable>: BaseViewWidget {
169169
var representable: Representable
170170
var context: UIViewRepresentableContext<Representable.Coordinator>?
171171

0 commit comments

Comments
 (0)