Skip to content

Commit 703848a

Browse files
authored
Merge pull request #1 from MillerTechnologyPeru/feature/Observable
Support @observable view models
2 parents c6e92df + 83f76dd commit 703848a

69 files changed

Lines changed: 323 additions & 146 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
build-ios:
11+
name: Build for iOS
12+
runs-on: macos-15
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Select Xcode
19+
uses: maxim-lobanov/setup-xcode@v1
20+
with:
21+
xcode-version: latest-stable
22+
23+
- name: Build CarPlayDemo for iOS Simulator
24+
run: |
25+
xcodebuild build \
26+
-project Xcode/CarPlayDemo.xcodeproj \
27+
-scheme CarPlayDemo \
28+
-destination 'generic/platform=iOS Simulator'

Package.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// swift-tools-version: 6.0
1+
// swift-tools-version: 6.2
22
import PackageDescription
33

44
let package = Package(
55
name: "CarPlayUI",
6-
platforms: [.iOS(.v13)],
6+
platforms: [.iOS(.v17)],
77
products: [
88
.library(
99
name: "CarPlayUI",
@@ -14,14 +14,18 @@ let package = Package(
1414
.target(
1515
name: "CarPlayUI",
1616
swiftSettings: [
17-
.swiftLanguageMode(.v6)
17+
.swiftLanguageMode(.v6),
18+
.enableUpcomingFeature("InferIsolatedConformances"),
19+
.enableUpcomingFeature("NonisolatedNonsendingByDefault")
1820
]
1921
),
2022
.testTarget(
2123
name: "CarPlayUITests",
2224
dependencies: ["CarPlayUI"],
2325
swiftSettings: [
24-
.swiftLanguageMode(.v6)
26+
.swiftLanguageMode(.v6),
27+
.enableUpcomingFeature("InferIsolatedConformances"),
28+
.enableUpcomingFeature("NonisolatedNonsendingByDefault")
2529
]
2630
)
2731
]

Sources/CarPlayUI/Extensions/CPGridButton.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import CarPlay
1010

1111
internal extension CPGridButton {
1212

13+
@MainActor
1314
convenience init(label: ParentView, action: @escaping (CPGridButton) -> ()) {
1415
// extract labels
1516
var labels = label.children.compactMap {

Sources/CarPlayUI/Extensions/CPPointOfInterest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import CarPlay
1111
@available(iOS 14.0, *)
1212
internal extension CPPointOfInterest {
1313

14+
@MainActor
1415
convenience init(_ view: ViewObject) {
1516
if #available(iOS 16.0, *) {
1617
self.init(

Sources/CarPlayUI/Extensions/CPTextButton.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import CarPlay
1111
@available(iOS 14.0, *)
1212
internal extension CPTextButton {
1313

14+
@MainActor
1415
convenience init(button: Button<Text>) {
1516
let title = _TextProxy(button.label).rawText
1617
let textStyle = CPTextButtonStyle(role: button.role)

Sources/CarPlayUI/Extensions/UIImage.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import CarPlay
1111

1212
internal extension UIImage {
1313

14+
@MainActor
1415
convenience init?(
1516
_ image: _ImageProxy,
1617
environment: EnvironmentValues = .defaultEnvironment
@@ -38,6 +39,7 @@ internal extension UIImage {
3839
}
3940
}
4041

42+
@MainActor
4143
static func unsafe(
4244
_ image: Image,
4345
environment: EnvironmentValues = .defaultEnvironment,
@@ -46,7 +48,8 @@ internal extension UIImage {
4648
) -> UIImage {
4749
unsafe(_ImageProxy(image), environment: environment, file: file, line: line)
4850
}
49-
51+
52+
@MainActor
5053
static func unsafe(
5154
_ image: _ImageProxy,
5255
environment: EnvironmentValues = .defaultEnvironment,

Sources/CarPlayUI/TokamakCore/Animation/Animatable.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import Foundation
1919

2020
public protocol Animatable {
21-
associatedtype AnimatableData: VectorArithmetic
22-
var animatableData: Self.AnimatableData { get set }
21+
associatedtype _AnimatableData: VectorArithmetic
22+
var animatableData: Self._AnimatableData { get set }
2323
}
2424

2525
public protocol _PrimitiveAnimatable {}
@@ -32,7 +32,7 @@ public extension Animatable where Self: VectorArithmetic {
3232
}
3333
}
3434

35-
public extension Animatable where Self.AnimatableData == EmptyAnimatableData {
35+
public extension Animatable where Self._AnimatableData == EmptyAnimatableData {
3636
var animatableData: EmptyAnimatableData {
3737
@inlinable get { EmptyAnimatableData() }
3838
// swiftlint:disable:next unused_setter_value
@@ -140,26 +140,34 @@ public struct AnimatablePair<First, Second>: VectorArithmetic
140140
}
141141

142142
extension CGPoint: Animatable {
143-
public var animatableData: AnimatablePair<CGFloat, CGFloat> {
143+
public typealias _AnimatableData = AnimatablePair<CGFloat, CGFloat>
144+
public var animatableData: _AnimatableData {
144145
@inlinable get { .init(x, y) }
145146
@inlinable set { (x, y) = newValue[] }
146147
}
147148
}
148149

149150
extension CGSize: Animatable {
150-
public var animatableData: AnimatablePair<CGFloat, CGFloat> {
151+
public typealias _AnimatableData = AnimatablePair<CGFloat, CGFloat>
152+
public var animatableData: _AnimatableData {
151153
@inlinable get { .init(width, height) }
152154
@inlinable set { (width, height) = newValue[] }
153155
}
154156
}
155157

156158
extension CGRect: Animatable {
157-
public var animatableData: AnimatablePair<CGPoint.AnimatableData, CGSize.AnimatableData> {
159+
public typealias _AnimatableData = AnimatablePair<AnimatablePair<CGFloat, CGFloat>, AnimatablePair<CGFloat, CGFloat>>
160+
public var animatableData: _AnimatableData {
158161
@inlinable get {
159-
.init(origin.animatableData, size.animatableData)
162+
.init(
163+
AnimatablePair(origin.x, origin.y),
164+
AnimatablePair(size.width, size.height)
165+
)
160166
}
161167
@inlinable set {
162-
(origin.animatableData, size.animatableData) = newValue[]
168+
let value = newValue[]
169+
origin = .init(x: value.0[].0, y: value.0[].1)
170+
size = .init(width: value.1[].0, height: value.1[].1)
163171
}
164172
}
165173
}

Sources/CarPlayUI/TokamakCore/Animation/Animation.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ public struct _AnimationProxy {
143143
public struct _AnimationModifier<Value>: ViewModifier, Equatable
144144
where Value: Equatable
145145
{
146-
public var animation: Animation?
147-
public var value: Value
146+
public nonisolated(unsafe) var animation: Animation?
147+
public nonisolated(unsafe) var value: Value
148148

149149
@inlinable
150150
public init(animation: Animation?, value: Value) {
@@ -155,10 +155,9 @@ public struct _AnimationModifier<Value>: ViewModifier, Equatable
155155
private struct ContentWrapper: View, Equatable {
156156
let content: Content
157157
let animation: Animation?
158-
let value: Value
158+
nonisolated(unsafe) let value: Value
159159

160-
@State
161-
private var lastValue: Value?
160+
@State private nonisolated(unsafe) var lastValue: Value?
162161

163162
var body: some View {
164163
content.transaction {
@@ -168,7 +167,7 @@ public struct _AnimationModifier<Value>: ViewModifier, Equatable
168167
}
169168
}
170169

171-
static func == (lhs: Self, rhs: Self) -> Bool {
170+
nonisolated static func == (lhs: Self, rhs: Self) -> Bool {
172171
lhs.value == rhs.value
173172
}
174173
}
@@ -177,7 +176,7 @@ public struct _AnimationModifier<Value>: ViewModifier, Equatable
177176
ContentWrapper(content: content, animation: animation, value: value)
178177
}
179178

180-
public static func == (lhs: Self, rhs: Self) -> Bool {
179+
public nonisolated static func == (lhs: Self, rhs: Self) -> Bool {
181180
lhs.value == rhs.value
182181
&& lhs.animation == rhs.animation
183182
}

Sources/CarPlayUI/TokamakCore/Fiber/Fiber+Content.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import Foundation
2020
public extension FiberReconciler.Fiber {
2121
enum Content {
2222
/// The underlying `App` instance and a function to visit it generically.
23-
case app(Any, visit: (AppVisitor) -> ())
23+
case app(Any, visit: @MainActor (AppVisitor) -> ())
2424
/// The underlying `Scene` instance and a function to visit it generically.
25-
case scene(Any, visit: (SceneVisitor) -> ())
25+
case scene(Any, visit: @MainActor (SceneVisitor) -> ())
2626
/// The underlying `View` instance and a function to visit it generically.
27-
case view(Any, visit: (ViewVisitor) -> ())
27+
case view(Any, visit: @MainActor (ViewVisitor) -> ())
2828
}
2929

3030
/// Create a `Content` value for a given `App`.

Sources/CarPlayUI/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@
1616
//
1717

1818
extension FiberReconciler.Fiber: CustomDebugStringConvertible {
19-
public var debugDescription: String {
20-
let memoryAddress = String(format: "%010p", unsafeBitCast(self, to: Int.self))
21-
if case let .view(view, _) = content,
22-
let text = view as? Text
23-
{
24-
return "Text(\"\(text.storage.rawText)\") (\(memoryAddress))"
19+
public nonisolated var debugDescription: String {
20+
MainActor.assumeIsolated {
21+
let memoryAddress = String(format: "%010p", unsafeBitCast(self, to: Int.self))
22+
if case let .view(view, _) = content,
23+
let text = view as? Text
24+
{
25+
return "Text(\"\(text.storage.rawText)\") (\(memoryAddress))"
26+
}
27+
return "\(typeInfo?.name ?? "Unknown") (\(memoryAddress))"
2528
}
26-
return "\(typeInfo?.name ?? "Unknown") (\(memoryAddress))"
2729
}
2830

2931
private func flush(level: Int = 0) -> String {

0 commit comments

Comments
 (0)