Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,11 @@ let package = Package(
.testTarget(
name: "SwiftTUITests",
dependencies: ["SwiftTUI"]),
.executableTarget(
name: "SwiftTUIExample",
dependencies: [
"SwiftTUI"
]
)
]
)
4 changes: 4 additions & 0 deletions Sources/SwiftTUI/PropertyWrappers/View+ObservableObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import Combine

extension View {
func setupObservedObjectProperties(node: Node) {
log("Inside setupObservedObjectProperties")

for (label, value) in Mirror(reflecting: self).children {
if let label, let observedObject = value as? AnyObservedObject {
log("subscribing to label: \(label)")
node.subscriptions[label] = observedObject.subscribe {
log("ObservableObject observed a change. invalidating node...")
node.root.application?.invalidateNode(node)
}
}
Expand Down
28 changes: 28 additions & 0 deletions Sources/SwiftTUI/PropertyWrappers/View+Observation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Observation
import Foundation

@available(macOS 14.0, *)
extension View {
func setupObservableClassProperties(node: Node) {
for (_, value) in Mirror(reflecting: self).children {
if let observable = value as? Observation.Observable {
startObservation(node: node)
}
}
}

func startObservation(node: Node) {
log("Starting observation")
withObservationTracking {
self.body
} onChange: {
defer {
Task { @MainActor in
startObservation(node: node)
}
}
log("Observation observed a change. invalidating node...")
node.root.application?.invalidateNode(node)
}
}
}
4 changes: 4 additions & 0 deletions Sources/SwiftTUI/ViewGraph/ComposedView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ struct ComposedView<I: View>: GenericView {
#if os(macOS)
view.setupObservedObjectProperties(node: node)
#endif
if #available(macOS 14.0, *) {
view.setupObservableClassProperties(node: node)
}
node.addNode(at: 0, Node(view: view.body.view))
}

func updateNode(_ node: Node) {
log("calling updateNode")
view.setupStateProperties(node: node)
view.setupEnvironmentProperties(node: node)
node.view = self
Expand Down
115 changes: 115 additions & 0 deletions Sources/SwiftTUIExample/ExampleApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@

import SwiftTUI

@main struct ExampleApp {
static func main() {
if #available(macOS 14.0, *) {
Application(
rootView: ExampleView(),
runLoopType: .cocoa
)
.start()
} else {
// Fallback on earlier versions
log("This ExampleApp requires macOS 14.0 or newer")
}
}
}

@available(macOS 14.0, *)
struct ExampleView: View {
var body: some View {
HStack {
ObservationView().border()
CombineView().border()
}
}
}

// MARK: Observation example
import Observation

@available(macOS 14.0, *)
struct ObservationView: View {
let vm: CounterViewModel
init(vm: CounterViewModel = .init()) {
self.vm = vm
}

var body: some View {
VStack {
Text("ObservationView").underline()
if vm.showCounter {
Text("int: \(vm.int)")
}
VStack {
Button("Show/Hide Counter") {
vm.showCounter.toggle()
}
Button("↑") {
log("Incrementing...")
vm.int += 1
log("Counter is now: \(vm.int)")
}
Button("↓") {
log("Decrementing...")
vm.int -= 1
log("Counter is now: \(vm.int)")
}
}
}
}
}

@available(macOS 14.0, *)
@Observable class CounterViewModel {
var int: Int
var showCounter: Bool

init(int: Int = 0, showCounter: Bool = true) {
self.int = int
self.showCounter = showCounter
}
}



// MARK: Combine Example
struct CombineView: View {
@ObservedObject var vm: CombineViewModel = CombineViewModel()

var body: some View {
VStack {
Text("CombineView").underline()
if vm.showCounter {
Text("int: \(vm.int)")
}
VStack {
Button("Show/Hide Counter") {
vm.showCounter.toggle()
}
Button("↑") {
log("Incrementing...")
vm.int += 1
log("Counter is now: \(vm.int)")
}
Button("↓") {
log("Decrementing...")
vm.int -= 1
log("Counter is now: \(vm.int)")
}
}
}
}
}

import Combine
class CombineViewModel: ObservableObject {
@Published var int = 0
@Published var showCounter = true

init(int: Int = 0, showCounter: Bool = true) {
self.int = int
self.showCounter = showCounter
}
}