Skip to content

Commit 57ede1e

Browse files
committed
Fix UIKitBackend onOpenURL, fix WindowGroupNode windowSizeIsFinal logic
UIKitBackend's onOpenURL stuff didn't handle URLs from launch because the delegate method gets fired before any view graphs get a chance to register their respective onOpenURL methods. It also didn't handle post-launch URLs (universal links received when already open in the background) cause the required delegate methods weren't implemented.
1 parent 1507ea4 commit 57ede1e

16 files changed

+169
-97
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public final class AppKitBackend: AppBackend {
7373
)
7474
}
7575

76-
public func isFixedSizeWindow(_ window: Window) -> Bool {
76+
public func isWindowProgrammaticallyResizable(_ window: Window) -> Bool {
7777
!window.styleMask.contains(.fullScreen)
7878
}
7979

@@ -183,24 +183,35 @@ public final class AppKitBackend: AppBackend {
183183
let appMenu = NSMenu(title: appName)
184184
appMenu.addItem(
185185
withTitle: "About \(appName)",
186-
action: #selector(NSApp.orderFrontStandardAboutPanel(_:)), keyEquivalent: "")
186+
action: #selector(NSApp.orderFrontStandardAboutPanel(_:)),
187+
keyEquivalent: ""
188+
)
187189
appMenu.addItem(NSMenuItem.separator())
188190

189191
let hideMenu = appMenu.addItem(
190-
withTitle: "Hide \(appName)", action: #selector(NSApp.hide(_:)), keyEquivalent: "h")
192+
withTitle: "Hide \(appName)",
193+
action: #selector(NSApp.hide(_:)),
194+
keyEquivalent: "h"
195+
)
191196
hideMenu.keyEquivalentModifierMask = .command
192197

193198
let hideOthers = appMenu.addItem(
194-
withTitle: "Hide Others", action: #selector(NSApp.hideOtherApplications(_:)),
195-
keyEquivalent: "h")
199+
withTitle: "Hide Others",
200+
action: #selector(NSApp.hideOtherApplications(_:)),
201+
keyEquivalent: "h"
202+
)
196203
hideOthers.keyEquivalentModifierMask = [.option, .command]
197204

198205
appMenu.addItem(
199-
withTitle: "Show All", action: #selector(NSApp.unhideAllApplications(_:)),
200-
keyEquivalent: "")
206+
withTitle: "Show All",
207+
action: #selector(NSApp.unhideAllApplications(_:)),
208+
keyEquivalent: ""
209+
)
201210

202211
let quitMenu = appMenu.addItem(
203-
withTitle: "Quit \(appName)", action: #selector(NSApp.terminate(_:)), keyEquivalent: "q"
212+
withTitle: "Quit \(appName)",
213+
action: #selector(NSApp.terminate(_:)),
214+
keyEquivalent: "q"
204215
)
205216
quitMenu.keyEquivalentModifierMask = .command
206217

Sources/GtkBackend/GtkBackend.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ public final class GtkBackend: AppBackend {
119119
return SIMD2(size.width, size.height)
120120
}
121121

122-
public func isFixedSizeWindow(_ window: Window) -> Bool {
122+
public func isWindowProgrammaticallyResizable(_ window: Window) -> Bool {
123123
// TODO: Detect whether window is fullscreen
124-
return false
124+
return true
125125
}
126126

127127
public func setSize(ofWindow window: Window, to newSize: SIMD2<Int>) {

Sources/SwiftCrossUI/Backend/AppBackend.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import Foundation
1111
/// ``AppBackend/setTitle(ofWindow:to:)``, ``AppBackend/setResizability(ofWindow:to:)``,
1212
/// ``AppBackend/setChild(ofWindow:to:)``, ``AppBackend/show(window:)``,
1313
/// ``AppBackend/runMainLoop()``, ``AppBackend/runInMainThread(action:)``,
14-
/// ``AppBackend/isFixedSizeWindow(_:)``, ``AppBackend/show(widget:)``.
14+
/// ``AppBackend/isWindowProgrammaticallyResizable(_:)``,
15+
/// ``AppBackend/show(widget:)``.
1516
/// Many of these can simply be given dummy implementations until you're ready
1617
/// to implement them properly.
1718
///
@@ -119,7 +120,7 @@ public protocol AppBackend {
119120
func size(ofWindow window: Window) -> SIMD2<Int>
120121
/// Check whether a window is programmatically resizable. This value does not necessarily
121122
/// reflect whether the window is resizable by the user.
122-
func isFixedSizeWindow(_ window: Window) -> Bool
123+
func isWindowProgrammaticallyResizable(_ window: Window) -> Bool
123124
/// Sets the size of the given window in pixels.
124125
func setSize(ofWindow window: Window, to newSize: SIMD2<Int>)
125126
/// Sets the minimum width and height of the window. Prevents the user from making the

Sources/SwiftCrossUI/Scenes/WindowGroupNode.swift

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ public final class WindowGroupNode<Content: View>: SceneGraphNode {
5252
self.scene,
5353
proposedWindowSize: newSize,
5454
backend: backend,
55-
environment: parentEnvironment
55+
environment: parentEnvironment,
56+
windowSizeIsFinal:
57+
!backend.isWindowProgrammaticallyResizable(window)
5658
)
5759
}
5860
}
@@ -66,16 +68,17 @@ public final class WindowGroupNode<Content: View>: SceneGraphNode {
6668
fatalError("Scene updated with a backend incompatible with the window it was given")
6769
}
6870

69-
let isFixedSize = backend.isFixedSizeWindow(window)
71+
let isProgramaticallyResizable =
72+
backend.isWindowProgrammaticallyResizable(window)
7073

7174
_ = update(
7275
newScene,
73-
proposedWindowSize: isFirstUpdate && !isFixedSize
76+
proposedWindowSize: isFirstUpdate && isProgramaticallyResizable
7477
? (newScene ?? scene).defaultSize
7578
: backend.size(ofWindow: window),
7679
backend: backend,
7780
environment: environment,
78-
windowSizeIsFinal: isFixedSize
81+
windowSizeIsFinal: !isProgramaticallyResizable
7982
)
8083
}
8184

@@ -119,16 +122,18 @@ public final class WindowGroupNode<Content: View>: SceneGraphNode {
119122
}
120123
.with(\.window, window)
121124

122-
// Perform a dry-run update of the root view to check if the window needs to
123-
// change size.
124-
let contentResult = viewGraph.update(
125-
with: newScene?.body,
126-
proposedSize: proposedWindowSize,
127-
environment: environment,
128-
dryRun: !windowSizeIsFinal
129-
)
130-
125+
let dryRunResult: ViewUpdateResult?
131126
if !windowSizeIsFinal {
127+
// Perform a dry-run update of the root view to check if the window
128+
// needs to change size.
129+
let contentResult = viewGraph.update(
130+
with: newScene?.body,
131+
proposedSize: proposedWindowSize,
132+
environment: environment,
133+
dryRun: true
134+
)
135+
dryRunResult = contentResult
136+
132137
let newWindowSize = computeNewWindowSize(
133138
currentProposedSize: proposedWindowSize,
134139
backend: backend,
@@ -148,6 +153,8 @@ public final class WindowGroupNode<Content: View>: SceneGraphNode {
148153
windowSizeIsFinal: false
149154
)
150155
}
156+
} else {
157+
dryRunResult = nil
151158
}
152159

153160
let finalContentResult = viewGraph.update(
@@ -178,14 +185,14 @@ public final class WindowGroupNode<Content: View>: SceneGraphNode {
178185
// Anyway, Gtk3Backend isn't really intended to be a recommended
179186
// backend so I think this is a fine solution for now (people should
180187
// only use Gtk3Backend if they can't use GtkBackend).
181-
if finalContentResult.size != contentResult.size {
188+
if let dryRunResult, finalContentResult.size != dryRunResult.size {
182189
print(
183190
"""
184191
warning: Final window content size didn't match dry-run size. This is a sign that
185192
either view size caching is broken or that backend.naturalSize(of:) is
186193
broken (or both).
187-
-> contentSize: \(contentResult.size)
188-
-> finalContentSize: \(finalContentResult.size)
194+
-> dryRunResult.size: \(dryRunResult.size)
195+
-> finalContentResult.size: \(finalContentResult.size)
189196
"""
190197
)
191198

@@ -203,17 +210,20 @@ public final class WindowGroupNode<Content: View>: SceneGraphNode {
203210
scene,
204211
proposedWindowSize: newWindowSize,
205212
backend: backend,
206-
environment: environment
213+
environment: environment,
214+
windowSizeIsFinal: true
207215
)
208216
}
209217
}
210218

219+
// Set this even if the window isn't programmatically resizable
220+
// because the window may still be user resizable.
211221
if scene.resizability.isResizable {
212222
backend.setMinimumSize(
213223
ofWindow: window,
214224
to: SIMD2(
215-
contentResult.size.minimumWidth,
216-
contentResult.size.minimumHeight
225+
finalContentResult.size.minimumWidth,
226+
finalContentResult.size.minimumHeight
217227
)
218228
)
219229
}
@@ -222,8 +232,8 @@ public final class WindowGroupNode<Content: View>: SceneGraphNode {
222232
ofChildAt: 0,
223233
in: containerWidget.into(),
224234
to: SIMD2(
225-
(proposedWindowSize.x - contentResult.size.size.x) / 2,
226-
(proposedWindowSize.y - contentResult.size.size.y) / 2
235+
(proposedWindowSize.x - finalContentResult.size.size.x) / 2,
236+
(proposedWindowSize.y - finalContentResult.size.size.y) / 2
227237
)
228238
)
229239

Sources/SwiftCrossUI/ViewGraph/ViewGraph.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,22 @@ public class ViewGraph<Root: View> {
2626
/// The environment most recently provided by this node's parent scene.
2727
private var parentEnvironment: EnvironmentValues
2828

29+
private var isFirstUpdate = true
30+
private var setIncomingURLHandler: (@escaping (URL) -> Void) -> Void
31+
2932
/// Creates a view graph for a root view with a specific backend.
3033
public init<Backend: AppBackend>(
31-
for view: Root, backend: Backend, environment: EnvironmentValues
34+
for view: Root,
35+
backend: Backend,
36+
environment: EnvironmentValues
3237
) {
3338
rootNode = AnyViewGraphNode(for: view, backend: backend, environment: environment)
3439

3540
self.view = view
3641
windowSize = .zero
3742
parentEnvironment = environment
3843
currentRootViewResult = ViewUpdateResult.leafView(size: .empty)
39-
40-
backend.setIncomingURLHandler { url in
41-
self.currentRootViewResult.preferences.onOpenURL?(url)
42-
}
44+
setIncomingURLHandler = backend.setIncomingURLHandler(to:)
4345
}
4446

4547
/// Recomputes the entire UI (e.g. due to the root view's state updating).
@@ -60,6 +62,12 @@ public class ViewGraph<Root: View> {
6062
dryRun: dryRun
6163
)
6264
self.currentRootViewResult = result
65+
if isFirstUpdate, !dryRun {
66+
setIncomingURLHandler { url in
67+
self.currentRootViewResult.preferences.onOpenURL?(url)
68+
}
69+
isFirstUpdate = false
70+
}
6371
return result
6472
}
6573

Sources/UIKitBackend/BaseWidget.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,39 @@ public class BaseWidget: UIView {
66
private var widthConstraint: NSLayoutConstraint?
77
private var heightConstraint: NSLayoutConstraint?
88

9-
internal var x = 0 {
9+
var x = 0 {
1010
didSet {
1111
if x != oldValue {
1212
updateLeftConstraint()
1313
}
1414
}
1515
}
1616

17-
internal var y = 0 {
17+
var y = 0 {
1818
didSet {
1919
if y != oldValue {
2020
updateTopConstraint()
2121
}
2222
}
2323
}
2424

25-
internal var width = 0 {
25+
var width = 0 {
2626
didSet {
2727
if width != oldValue {
2828
updateWidthConstraint()
2929
}
3030
}
3131
}
3232

33-
internal var height = 0 {
33+
var height = 0 {
3434
didSet {
3535
if height != oldValue {
3636
updateHeightConstraint()
3737
}
3838
}
3939
}
4040

41-
internal init() {
41+
init() {
4242
super.init(frame: .zero)
4343

4444
self.translatesAutoresizingMaskIntoConstraints = false
@@ -89,7 +89,7 @@ extension UIKitBackend {
8989
public typealias Widget = BaseWidget
9090
}
9191

92-
internal class WrapperWidget<View: UIView>: BaseWidget {
92+
class WrapperWidget<View: UIView>: BaseWidget {
9393
init(child: View) {
9494
super.init()
9595

Sources/UIKitBackend/Font+UIFont.swift

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

44
extension Font {
5-
internal var uiFont: UIFont {
5+
var uiFont: UIFont {
66
switch self {
77
case .system(let size, let weight, let design):
88
let weight: UIFont.Weight =

Sources/UIKitBackend/UIColor+Color.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftCrossUI
22
import UIKit
33

44
extension UIColor {
5-
internal convenience init(color: Color) {
5+
convenience init(color: Color) {
66
self.init(
77
red: CGFloat(color.red),
88
green: CGFloat(color.green),
@@ -13,7 +13,7 @@ extension UIColor {
1313
}
1414

1515
extension Color {
16-
internal init(_ uiColor: UIColor) {
16+
init(_ uiColor: UIColor) {
1717
let ciColor = CIColor(color: uiColor)
1818

1919
self.init(
@@ -24,7 +24,7 @@ extension Color {
2424
)
2525
}
2626

27-
internal var uiColor: UIColor {
27+
var uiColor: UIColor {
2828
UIColor(color: self)
2929
}
3030
}

Sources/UIKitBackend/UIKitBackend+Alert.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import UIKit
33

44
extension UIKitBackend {
55
public final class Alert {
6-
internal let controller: UIAlertController
7-
internal var handler: ((Int) -> Void)?
6+
let controller: UIAlertController
7+
var handler: ((Int) -> Void)?
88

9-
internal init() {
9+
init() {
1010
self.controller = UIAlertController(title: nil, message: nil, preferredStyle: .alert)
1111
}
1212
}
@@ -25,9 +25,9 @@ extension UIKitBackend {
2525

2626
for (i, actionLabel) in actionLabels.enumerated() {
2727
let action = UIAlertAction(title: actionLabel, style: .default) {
28-
[unowned self, weak alert] _ in
28+
[weak alert] _ in
2929
guard let alert else { return }
30-
alert.handler!(i)
30+
alert.handler?(i)
3131
}
3232
alert.controller.addAction(action)
3333
}
@@ -38,7 +38,7 @@ extension UIKitBackend {
3838
window: Window?,
3939
responseHandler handleResponse: @escaping (Int) -> Void
4040
) {
41-
guard let window = window ?? mainWindow else {
41+
guard let window = window ?? Self.mainWindow else {
4242
assertionFailure("Could not find window in which to display alert")
4343
return
4444
}
@@ -49,6 +49,5 @@ extension UIKitBackend {
4949

5050
public func dismissAlert(_ alert: Alert, window: Window?) {
5151
alert.controller.dismiss(animated: false)
52-
5352
}
5453
}

Sources/UIKitBackend/UIKitBackend+Container.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-
internal final class ScrollWidget: WrapperWidget<UIScrollView> {
4+
final class ScrollWidget: WrapperWidget<UIScrollView> {
55
private var childWidthConstraint: NSLayoutConstraint?
66
private var childHeightConstraint: NSLayoutConstraint?
77

0 commit comments

Comments
 (0)