Skip to content

Commit 3e4d3a7

Browse files
committed
iPad-only split views
1 parent 8243bc4 commit 3e4d3a7

File tree

3 files changed

+147
-19
lines changed

3 files changed

+147
-19
lines changed

Sources/UIKitBackend/UIKitBackend+Container.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ extension UIKitBackend {
6868
}
6969

7070
public func addChild(_ child: Widget, to container: Widget) {
71-
child.add(toWidget: container)
71+
container.add(childWidget: child)
7272
}
7373

7474
public func setPosition(
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import UIKit
2+
3+
#if os(iOS)
4+
final class SplitWidget: WrapperControllerWidget<UISplitViewController>,
5+
UISplitViewControllerDelegate
6+
{
7+
var resizeHandler: (() -> Void)?
8+
private let sidebarContainer: ContainerWidget
9+
private let mainContainer: ContainerWidget
10+
11+
init(sidebarWidget: any WidgetProtocol, mainWidget: any WidgetProtocol) {
12+
// UISplitViewController requires its children to be controllers, not views
13+
sidebarContainer = ContainerWidget(child: sidebarWidget)
14+
mainContainer = ContainerWidget(child: mainWidget)
15+
16+
super.init(child: UISplitViewController())
17+
18+
child.delegate = self
19+
20+
child.preferredDisplayMode = .oneBesideSecondary
21+
child.preferredPrimaryColumnWidthFraction = 0.3
22+
23+
child.viewControllers = [sidebarContainer, mainContainer]
24+
}
25+
26+
override func viewDidLoad() {
27+
NSLayoutConstraint.activate([
28+
sidebarContainer.view.leadingAnchor.constraint(
29+
equalTo: sidebarContainer.child.view.leadingAnchor),
30+
sidebarContainer.view.trailingAnchor.constraint(
31+
equalTo: sidebarContainer.child.view.trailingAnchor),
32+
sidebarContainer.view.topAnchor.constraint(
33+
equalTo: sidebarContainer.child.view.topAnchor),
34+
sidebarContainer.view.bottomAnchor.constraint(
35+
equalTo: sidebarContainer.child.view.bottomAnchor),
36+
mainContainer.view.leadingAnchor.constraint(
37+
equalTo: mainContainer.child.view.leadingAnchor),
38+
mainContainer.view.trailingAnchor.constraint(
39+
equalTo: mainContainer.child.view.trailingAnchor),
40+
mainContainer.view.topAnchor.constraint(
41+
equalTo: mainContainer.child.view.topAnchor),
42+
mainContainer.view.bottomAnchor.constraint(
43+
equalTo: mainContainer.child.view.bottomAnchor),
44+
])
45+
46+
super.viewDidLoad()
47+
}
48+
49+
override func viewDidLayoutSubviews() {
50+
super.viewDidLayoutSubviews()
51+
resizeHandler?()
52+
}
53+
}
54+
55+
extension UIKitBackend {
56+
public func createSplitView(
57+
leadingChild: any WidgetProtocol,
58+
trailingChild: any WidgetProtocol
59+
) -> any WidgetProtocol {
60+
precondition(
61+
UIDevice.current.userInterfaceIdiom != .phone,
62+
"NavigationSplitView is currently unsupported on iPhone and iPod touch.")
63+
64+
return SplitWidget(sidebarWidget: leadingChild, mainWidget: trailingChild)
65+
}
66+
67+
public func setResizeHandler(
68+
ofSplitView splitView: Widget,
69+
to action: @escaping () -> Void
70+
) {
71+
let splitWidget = splitView as! SplitWidget
72+
splitWidget.resizeHandler = action
73+
}
74+
75+
public func sidebarWidth(ofSplitView splitView: Widget) -> Int {
76+
let splitWidget = splitView as! SplitWidget
77+
return Int(splitWidget.child.primaryColumnWidth.rounded(.toNearestOrEven))
78+
}
79+
80+
public func setSidebarWidthBounds(
81+
ofSplitView splitView: Widget,
82+
minimum minimumWidth: Int,
83+
maximum maximumWidth: Int
84+
) {
85+
let splitWidget = splitView as! SplitWidget
86+
splitWidget.child.minimumPrimaryColumnWidth = CGFloat(minimumWidth)
87+
splitWidget.child.maximumPrimaryColumnWidth = CGFloat(maximumWidth)
88+
}
89+
}
90+
#endif

Sources/UIKitBackend/Widget.swift

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ public protocol WidgetProtocol: UIResponder {
1010
var controller: UIViewController? { get }
1111

1212
var childWidgets: [any WidgetProtocol] { get set }
13-
var parentWidget: (any WidgetProtocol)? { get }
13+
var parentWidget: (any WidgetProtocol)? { get set }
1414

15-
func add(toWidget other: some WidgetProtocol)
15+
func add(childWidget: some WidgetProtocol)
1616
func removeFromParentWidget()
1717
}
1818

@@ -136,13 +136,23 @@ class BaseViewWidget: UIView, WidgetProtocolHelpers {
136136
updateTopConstraint()
137137
}
138138

139-
func add(toWidget other: some WidgetProtocol) {
140-
if parentWidget === other { return }
141-
removeFromParentWidget()
139+
func add(childWidget: some WidgetProtocol) {
140+
if childWidget.parentWidget === self { return }
141+
childWidget.removeFromParentWidget()
142142

143-
other.view.addSubview(self)
144-
parentWidget = other
145-
other.childWidgets.append(self)
143+
let childController = childWidget.controller
144+
145+
addSubview(childWidget.view)
146+
147+
if let controller,
148+
let childController
149+
{
150+
controller.addChild(childController)
151+
childController.didMove(toParent: controller)
152+
}
153+
154+
childWidgets.append(childWidget)
155+
childWidget.parentWidget = self
146156
}
147157

148158
func removeFromParentWidget() {
@@ -208,19 +218,21 @@ class BaseControllerWidget: UIViewController, WidgetProtocolHelpers {
208218
fatalError("init(coder:) is not used for this view")
209219
}
210220

211-
func add(toWidget other: some WidgetProtocol) {
212-
if parentWidget === other { return }
213-
removeFromParentWidget()
221+
func add(childWidget: some WidgetProtocol) {
222+
if childWidget.parentWidget === self { return }
223+
childWidget.removeFromParentWidget()
224+
225+
let childController = childWidget.controller
214226

215-
other.view.addSubview(view)
227+
view.addSubview(childWidget.view)
216228

217-
if let otherController = other.controller {
218-
otherController.addChild(self)
219-
didMove(toParent: otherController)
229+
if let childController {
230+
addChild(childController)
231+
childController.didMove(toParent: self)
220232
}
221233

222-
parentWidget = other
223-
other.childWidgets.append(self)
234+
childWidgets.append(childWidget)
235+
childWidget.parentWidget = self
224236
}
225237

226238
func removeFromParentWidget() {
@@ -270,6 +282,32 @@ class ContainerWidget: BaseControllerWidget {
270282
init(child: some WidgetProtocol) {
271283
self.child = child
272284
super.init()
273-
child.add(toWidget: self)
285+
add(childWidget: child)
286+
}
287+
}
288+
289+
class WrapperControllerWidget<Controller: UIViewController>: BaseControllerWidget {
290+
let child: Controller
291+
292+
init(child: Controller) {
293+
self.child = child
294+
super.init()
295+
}
296+
297+
override func loadView() {
298+
super.loadView()
299+
300+
view.addSubview(child.view)
301+
addChild(child)
302+
303+
child.view.translatesAutoresizingMaskIntoConstraints = false
304+
NSLayoutConstraint.activate([
305+
child.view.topAnchor.constraint(equalTo: view.topAnchor),
306+
child.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
307+
child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
308+
child.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
309+
])
310+
311+
child.didMove(toParent: self)
274312
}
275313
}

0 commit comments

Comments
 (0)