Skip to content

Commit 2074a63

Browse files
committed
Merge remote-tracking branch 'upstream/main' into better-concurrency
2 parents 38f57fc + 84015e4 commit 2074a63

File tree

9 files changed

+86
-12
lines changed

9 files changed

+86
-12
lines changed

.github/workflows/build-test-and-docs.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,44 @@ jobs:
126126
xcodebuild: true
127127
xcodebuild-device-type: ${{ matrix.device-type }}
128128

129+
uikit-catalyst:
130+
runs-on: macos-14
131+
steps:
132+
- name: Force Xcode 15.4
133+
run: sudo xcode-select -switch /Applications/Xcode_15.4.app
134+
135+
- name: Swift version
136+
run: swift --version
137+
138+
- name: Install xcbeautify
139+
run: brew install xcbeautify
140+
141+
- uses: actions/checkout@v3
142+
143+
- name: Build
144+
run: |
145+
set -uxo pipefail
146+
buildtarget () {
147+
# Use the same derived data path as DocC compilation so that we don't duplicate work.
148+
xcodebuild -derivedDataPath /tmp/data -skipMacroValidation -scheme "$1" -destination "variant=Mac Catalyst,arch=arm64,platform=macOS" build | xcbeautify --renderer github-actions
149+
}
150+
151+
buildtarget SwiftCrossUI
152+
buildtarget UIKitBackend
153+
154+
cd Examples
155+
156+
buildtarget CounterExample
157+
buildtarget GreetingGeneratorExample
158+
buildtarget NavigationExample
159+
buildtarget StressTestExample
160+
buildtarget NotesExample
161+
buildtarget PathsExample
162+
buildtarget ControlsExample
163+
buildtarget RandomNumberGeneratorExample
164+
# TODO test whether this works on Catalyst
165+
# buildtarget SplitExample
166+
129167
windows:
130168
runs-on: windows-latest
131169
defaults:

Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SwiftCrossUI/State/Binding.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
/// as a writable reference to the value.
33
@dynamicMemberLookup
44
@propertyWrapper
5-
public class Binding<Value> {
5+
public struct Binding<Value> {
66
public var wrappedValue: Value {
77
get {
88
getValue()
99
}
10-
set {
10+
nonmutating set {
1111
setValue(newValue)
1212
}
1313
}
@@ -32,6 +32,21 @@ public class Binding<Value> {
3232
self.setValue = set
3333
}
3434

35+
public init?(_ other: Binding<Value?>) {
36+
if let initialValue = other.wrappedValue {
37+
self.init(
38+
get: {
39+
other.wrappedValue ?? initialValue
40+
},
41+
set: { newValue in
42+
other.wrappedValue = newValue
43+
}
44+
)
45+
} else {
46+
return nil
47+
}
48+
}
49+
3550
/// Projects a property of a binding.
3651
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> Binding<T> {
3752
get {

Sources/SwiftCrossUI/State/State.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ public struct State<Value>: DynamicProperty, StateProperty {
2828
/// setting a new value. This isn't in a didSet property accessor
2929
/// because we want more granular control over when it does and
3030
/// doesn't trigger.
31+
///
32+
/// Additionally updates the downstream observation if the
33+
/// wrapped value is an Optional<some ObservableObject> and the
34+
/// current case has toggled.
3135
func postSet() {
3236
// If the wrapped value is an Optional<some ObservableObject>
3337
// then we need to observe/unobserve whenever the optional

Sources/SwiftCrossUI/SwiftCrossUI.docc/Hot reloading.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
## Overview
44

55
SwiftCrossUI supports hot reloading when used together with Swift Bundler.
6-
Currently this is only support on macOS, but the macros work everywhere so
6+
Currently this is only supported on macOS, but the macros work everywhere so
77
that you can safely leave them in even when unused.
88

99
Use ``HotReloadable`` to enable hot reloading within your app. Use
1010
``hotReloadable`` to define hot reloading boundaries; anything within a hot
11-
reloading boundary gets reloaded during hot loading (with state persisted),
11+
reloading boundary gets reloaded during hot reloading (with state persisted),
1212
and everything outside remains unchanged within any given hot reloading session.
1313

1414
## Topics

Sources/SwiftCrossUI/SwiftCrossUI.docc/Quick start.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Running your new app with Swift Bundler is as simple as it gets!
3333
swift bundler run
3434
```
3535

36-
On macOS you can provide the `--device <device_name_or_id>` option to run the app on a connacted iOS or tvOS device, or the `--simulator <simulator_name_or_id>` option to run the app in a simulator.
36+
On macOS you can provide the `--device <device_name_or_id>` option to run the app on a connected iOS or tvOS device, or the `--simulator <simulator_name_or_id>` option to run the app in a simulator.
3737

3838
```sh
3939
# You can use any substring of the target device name. For example,

Sources/SwiftCrossUI/Views/Modifiers/AlertModifier.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ extension View {
1111
actions: actions()
1212
)
1313
}
14+
15+
public func alert(
16+
_ title: Binding<String?>,
17+
@AlertActionsBuilder actions: () -> [AlertAction]
18+
) -> some View {
19+
AlertModifierView(
20+
child: self,
21+
title: title.wrappedValue ?? "",
22+
isPresented: Binding {
23+
title.wrappedValue != nil
24+
} set: { newValue in
25+
if !newValue {
26+
title.wrappedValue = nil
27+
}
28+
},
29+
actions: actions()
30+
)
31+
}
1432
}
1533

1634
struct AlertModifierView<Child: View>: TypeSafeView {

Sources/SwiftCrossUI/Views/Slider.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,10 @@ public struct Slider: ElementaryView, View {
104104
maximum: maximum,
105105
decimalPlaces: decimalPlaces,
106106
environment: environment
107-
) { [weak value] newValue in
108-
guard let value = value else {
109-
return
107+
) { newValue in
108+
if let value {
109+
value.wrappedValue = newValue
110110
}
111-
value.wrappedValue = newValue
112111
}
113112

114113
if let value = value?.wrappedValue {

Sources/UIKitBackend/UIKitBackend+Picker.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ final class UITableViewPicker: WrapperWidget<UITableView>, Picker, UITableViewDe
142142
extension UIKitBackend {
143143
public func createPicker() -> Widget {
144144
#if targetEnvironment(macCatalyst)
145-
if UIDevice.current.userInterfaceIdiom == .mac {
145+
if #available(macCatalyst 14, *), UIDevice.current.userInterfaceIdiom == .mac {
146146
return UITableViewPicker()
147147
} else {
148148
return UIPickerViewPicker()

0 commit comments

Comments
 (0)