I'm in the process of migrating to the Observation framework but it seems like it is not compatible with didSet. I cannot find information about if this is just not supported or a new approach needs to be implemented?
import Observation
@Observable class MySettings {
var windowSize: CGSize = .zero
var isInFullscreen = false
var scalingMode: ScalingMode = .scaled {
didSet {
...
}
}
...
}
This code triggers this error:
Instance member 'scalingMode' cannot be used on type 'MySettings'; did you mean to use a value of this type instead?
Anyone knows what needs to be done? Thanks!
Observation
RSS for tagMake responsive apps that update the presentation when underlying data changes.
Posts under Observation tag
32 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
When I update a variable inside my model that is marked @Transient, my view does not update with this change. Is this normal? If I update a non-transient variable inside the model at the same time that I update the transient one, then both changes are propagated to my view.
Here is an example of the model:
@Model public class WaterData {
public var target: Double = 3000
@Transient public var samples: [HKQuantitySample] = []
}
Updating samples only does not propagate to my view.
Hello,
I’m building a health-related app for both watchOS and iOS, which needs to monitor certain health data (e.g., heart rate, active energy).
Before updating to watchOS 26, the queries worked reliably without any issues. However, after adapting to watchOS 26, some users have reported that health data updates stop being delivered.
What I’ve observed:
HKObserverQuery with enableBackgroundDelivery is set up normally.
On WatchOS 26, the query sometimes stops delivering updates entirely after a certain point, and once an update is missed, it may stop delivering further updates completely.
Restarting the Apple Watch temporarily restores delivery, but the problem reoccurs after some time.
This makes background health data monitoring unreliable for my app.
Here’s a simplified version of the code we are using:
guard let heartType = HKObjectType.quantityType(forIdentifier: .heartRate) else { return }
let query = HKObserverQuery(sampleType: heartType, predicate: nil) { query, completionHandler, error in
if let error = error {
logEvent("Observer error: \(error.localizedDescription)")
return
}
logEvent("Heart rate changed")
MyNotificationManager.shared.sendNotification() // Send a local notification
completionHandler()
}
healthStore.execute(query)
healthStore.enableBackgroundDelivery(for: heartType, frequency: .hourly) { success, error in
if success {
logEvent("Background heart rate delivery enabled")
} else {
logEvent("Failed to enable background heart rate delivery: \(error?.localizedDescription ?? "Unknown error")")
}
}
Could you please clarify:
Is this a known issue with HKObserverQuery and enableBackgroundDelivery on watchOS 26?
Are there any recommended workarounds or best practices to ensure continuous background delivery of health data?
Thank you in advance for your help.
Topic:
App & System Services
SubTopic:
Health & Fitness
Tags:
Health and Fitness
watchOS
HealthKit
Observation
According to docs, .focusedObject() usage should be moved to .focusedValue() when migrating to @Observable, but there is no .focusedSceneValue() overload that accepts Observable like with .focusedValue(). So how are we supposed migrate .focusedSceneObject() to @Observable?
With the new @Observable macro, it looks like every time the struct of a view is reinitialized, any observable class marked as @State in the struct also gets reinitialized. Moreover, the result of the reinitialization immediately gets discarded.
This is in contrast to @StateObject and ObservableObject, where the class would only be initialized at the first creation of the view. The initialization method of the class would never be called again between view updates.
Is this a bug or an expected behavior? This redundant reinitialization causes performance issues when the init method of the observable class does anything slightly heavyweight.
Feedback ID: FB13697724
Recently I noticed how my ViewModels aren't deallocating and they end up as a memory leaks. I found something similar in this thread but this is also happening without using @Observation. Check the source code below:
class CellViewModel: Identifiable {
let id = UUID()
var color: Color = Color.red
init() { print("init") }
deinit { print("deinit") }
}
struct CellView: View {
let viewModel: CellViewModel
var body: some View {
ZStack {
Color(viewModel.color)
Text(viewModel.id.uuidString)
}
}
}
@main
struct LeakApp: App {
@State var list = [CellViewModel]()
var body: some Scene {
WindowGroup {
Button("Add") {
list.append(CellViewModel())
}
Button("Remove") {
list = list.dropLast()
}
ScrollView {
LazyVStack {
ForEach(list) { model in
CellView(viewModel: model)
}
}
}
}
}
}
When I tap the Add button twice in the console I will see "init" message twice. So far so good. But then I click the Remove button twice and I don't see any "deinit" messages.
I used the Debug Memory Graph in Xcode and it showed me that two CellViewModel objects are in the memory and they are owned by the CellView and some other objects that I don't know where are they coming from (I assume from SwiftUI internally).
I tried using VStack instead of LazyVStack and that did worked a bit better but still not 100% "deinits" were in the Console.
I tried using weak var
struct CellView: View {
weak var viewModel: CellViewModel?
....
}
but this also helped only partially.
The only way to fully fix this is to have a separate class that holds the list of items and to use weak var viewModel: CellViewModel?. Something like this:
class CellViewModel: Identifiable {
let id = UUID()
var color: Color = Color.red
init() { print("init") }
deinit { print("deinit") }
}
struct CellView: View {
var viewModel: CellViewModel?
var body: some View {
ZStack {
if let viewModel = viewModel {
Color(viewModel.color)
Text(viewModel.id.uuidString)
}
}
}
}
@Observable
class ListViewModel {
var list = [CellViewModel]()
func insert() {
list.append(CellViewModel())
}
func drop() {
list = list.dropLast()
}
}
@main
struct LeakApp: App {
@State var viewModel = ListViewModel()
var body: some Scene {
WindowGroup {
Button("Add") {
viewModel.insert()
}
Button("Remove") {
viewModel.drop()
}
ScrollView {
LazyVStack {
ForEach(viewModel.list) { model in
CellView(viewModel: model)
}
}
}
}
}
}
But this won't work if I want to use @Bindable such as
@Bindable var viewModel: CellViewModel?
I don't understand why SwiftUI doesn't want to release the objects?
When I run my app with XCode on my iPhone, and then moved into the background, I'm getting a EXC_BREAKPOINT exception after a few minutes, seemingly when iOS attempts to call my app with a BGAppRefreshTask:
Thread 23 Queue: com.apple.BGTaskScheduler (com.mycompany.MyApp.RefreshTask) (serial)
0 _dispatch_assert_queue_fail
12 _pthread_wqthread
Enqueued from com.apple.duet.activityscheduler.client.xpcqueue (Thread 23)
0 dispatch_async
20 start_wqthread
I can't quite understand the reason from this crash. In the background task, I'm attempting to update live activities. In the process, it might encounter code that calls MainActor and manipulate @Observable objects. Might that be the reason?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Swift
Background Tasks
Observation
The "What's new in UIKit" session introduces new observation tracking features and mentions that they are "on by default" in 26. Is it possible to disable this feature?
We have our own system built on ObservableObject that keeps our UIKit models/views in sync and triggers updates. We want to make sure there isn't contention between the new feature and our own.
So any time I create a class that's both @Observable and Codable, e.g.
@Observable class GameLocationManager : Codable {
I get a warning in the macro expansion code:
@ObservationIgnored private let _$observationRegistrar = Observation.ObservationRegistrar()
Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten.
I've been ignoring them for now, but there are at least a half a dozen of them now in my (relatively small) codebase, and I'd like to find a solution (ideally one that doesn't require me to write init(decoder:) for every @Observable class in my project...), especially since I'm not sure what the actual consequences of ignoring this might be.
In WWDC 2023 there was a good summary of how to handle the iOS 17 Observation capability.
But despite the clear graphics, it was still ambiguous (for me.)
I want to inject a class (view-model) so that it can be used in the complete view heirarchy, and used in bindings to allow bi-directional communication.
As far as I can tell there are 2 ways of declaring the VM (alternatives 1 and 2 in my code), and 2 ways of consuming the VM in a view (alternatives 3 and 4 in my code). Using the flow-diagram I can't determine which is best.
Here's the crux of my #Observable problem.
import SwiftUI
// MARK: - Model
struct MyMod {
var title = "Hello, World!"
}
// MARK: - MVV
@Observable
class MyMVV {
var model: MyMod
init() {
self.model = MyMod()
}
}
// MARK: - App
@main
struct MyApp: App {
@Bindable var myGlobalMVV = MyMVV() // Alternative 1
// @State var myGlobalMVV = MyMVV() // Alternative 2
var body: some Scene {
WindowGroup {
ContentView()
.environment(myGlobalMVV) // inject
}
}
}
struct ContentView: View {
var body: some View {
ContentDeepHierarchyView()
}
}
struct ContentDeepHierarchyView: View {
@Environment(MyMVV.self) var myGlobalMVV // digest
var body: some View {
@Bindable var myLocalMVV = myGlobalMVV // Alternative 3
TextField("The new title", text: $myLocalMVV.model.title) // Alternative 3
TextField("The new title", text: Bindable(myGlobalMVV).model.title) // Alternative 4
}
Opinions?
Im building an recipe app for the social media of my mother. i already have the functionality for the users, when a user gets created an empty array gets initiated at the database named favoriteRecipes, which stores the id of his favorite recipes to show in a view.
This is my AuthViewModel which is relevant for the user stuff:
import Firebase
import FirebaseAuth
import FirebaseFirestore
protocol AuthenticationFormProtocol {
var formIsValid: Bool { get }
}
@MainActor
class AuthViewModel : ObservableObject {
@Published var userSession: FirebaseAuth.User?
@Published var currentUser: User?
@Published var currentUserId: String?
init() {
self.userSession = Auth.auth().currentUser
Task {
await fetchUser()
}
}
func signIn(withEmail email: String, password: String) async throws {
do {
let result = try await Auth.auth().signIn(withEmail: email, password: password)
self.userSession = result.user
await fetchUser() // fetch user sonst profileview blank
} catch {
print("DEBUG: Failed to log in with error \(error.localizedDescription)")
}
}
func createUser(withEmail email: String, password: String, fullName: String) async throws {
do {
let result = try await Auth.auth().createUser(withEmail: email, password: password)
self.userSession = result.user
let user = User(id: result.user.uid, fullName: fullName, email: email)
let encodedUser = try Firestore.Encoder().encode(user)
try await Firestore.firestore().collection("users").document(result.user.uid).setData(encodedUser)
await fetchUser()
} catch {
print("Debug: Failed to create user with error \(error.localizedDescription)")
}
}
func signOut() {
do {
try Auth.auth().signOut() // sign out user on backend
self.userSession = nil // wipe out user session and take back to login screen
self.currentUser = nil // wipe out current user data model
} catch {
print("DEBUG: Failed to sign out with error \(error.localizedDescription)")
}
}
func deleteAcocount() {
let user = Auth.auth().currentUser
user?.delete { error in
if let error = error {
print("DEBUG: Error deleting user: \(error.localizedDescription)")
} else {
self.userSession = nil
self.currentUser = nil
}
}
}
func fetchUser() async {
guard let uid = Auth.auth().currentUser?.uid else { return }
currentUserId = uid
let userRef = Firestore.firestore().collection("users").document(uid)
do {
let snapshot = try await userRef.getDocument()
if snapshot.exists {
self.currentUser = try? snapshot.data(as: User.self)
print("DEBUG: current user is \(String(describing: self.currentUser))")
} else {
// Benutzer existiert nicht mehr in Firebase, daher setzen wir die userSession auf nil
self.userSession = nil
self.currentUser = nil
}
} catch {
print("DEBUG: Fehler beim Laden des Benutzers: \(error.localizedDescription)")
}
}
}
This is the code to fetch the favorite recipes, i use the id of the user to access the collection and get the favoriteRecipes out of the array:
import SwiftUI
@MainActor
class FavoriteRecipeViewModel: ObservableObject {
@Published var favoriteRecipes: [Recipe] = []
@EnvironmentObject var viewModel: AuthViewModel
private var db = Firestore.firestore()
init() {
Task {
await fetchFavoriteRecipes()
}
}
func fetchFavoriteRecipes() async{
let userRef = db.collection("users").document(viewModel.userSession?.uid ?? "")
do {
let snapshot = try await userRef.collection("favoriteRecipes").getDocuments()
let favoriteIDs = snapshot.documents.map { $0.documentID }
let favoriteRecipes = try await fetchRecipes(recipeIDs: favoriteIDs)
} catch {
print("DEBUG: Failed to load favorite recipes for user: \(error.localizedDescription)")
}
}
func fetchRecipes(recipeIDs: [String]) async throws -> [Recipe] {
var recipes: [Recipe] = []
for id in recipeIDs {
let snapshot = try await db.collection("recipes").document(id).getDocument()
if let recipe = try? snapshot.data(as: Recipe.self) {
recipes.append(recipe)
}
}
return recipes
}
}
Now the Problem occurs at the build of the project, i get the error
SwiftUICore/EnvironmentObject.swift:92: Fatal error: No ObservableObject of type AuthViewModel found. A View.environmentObject(_:) for AuthViewModel may be missing as an ancestor of this view.
I already passed the ViewModel instances as EnvironmentObject in the App Struct.
import SwiftUI
import FirebaseCore
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
@main
struct NimetAndSonApp: App {
@StateObject var viewModel = AuthViewModel()
@StateObject var recipeViewModel = RecipeViewModel()
@StateObject var favoriteRecipeViewModel = FavoriteRecipeViewModel()
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(viewModel)
.environmentObject(recipeViewModel)
.environmentObject(favoriteRecipeViewModel)
}
}
}
Hello, following is the issue: I have a @Observable view model which has an array of @Observable Items.
Tapping an item leads to a detail like view to submit a value to the item. This work thru bindings. However I have the need to replace the contents of the array entirely with a fresh version loaded from the network. It will contain the "same" objects with the same id but some values might have changed. So replacing the entire array seems to not update the UI because the IDs are the same as before. Also this seems to break the bindings because when replacing the array, editing no longer updates the UI.
How to test the behavior:
Launch the app in simulator.
Add some values to the items by tapping on an item and then on add.
Notice how changes are updated.
Tap the blue button to sync fresh data to the array. (Not replacing the actual array)
Confirm everything is still working
Replace the array with the red button.
Editing and UI updates are broken from now on.
What is the proper way to handle this scenario?
Project: https://github.com/ChristianSchuster/DTS_DataReplaceExample.git
I'm having the following issue:
Type 'AVPlayer.Type' cannot conform to 'ObservableObject'
struct MusicEditorView: View {
@ObservedObject var audioPlayer = AVPlayer
and this is the class:
class MusicPlayer: ObservableObject {
private var audioPlayer: AVPlayer?
private var timer: Timer?
func playSound(named sFileName: String){
if let url = Bundle.main.url(forResource: sFileName, withExtension: "mp3"){
audioPlayer = try? AVPlayer(url: url)
audioPlayer?.play()
}
}
func pause(){
audioPlayer?.pause()
}
func getcurrentProgress() -> Double{
guard let currentTime = audioPlayer?.currentItem?.currentTime().seconds else { return 0 }
guard let duration = audioPlayer?.currentItem?.duration.seconds else { return 0 }
return duration > 0 ? (currentTime / duration) * 100 : 0
}
func startProgressTimer(updateProgress: @escaping (Double, Double) -> Void){
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
guard let currentTime = self.audioPlayer?.currentItem?.currentTime().seconds else { return }
guard let duration = self.audioPlayer?.currentItem?.duration.seconds else { return }
updateProgress(currentTime, duration)
}
}
func stopProgressTimer(){
timer?.invalidate()
}
struct Sound: Identifiable, Codable {
var id = UUID()
var name: String
var fileName: String
}
}
}
Hi,
Previously, we would conform model objects to the ObservableObject protocol and use the @StateObject property wrapper when storing them to an owned binding in a View.
Now, if I understand correctly, it is recommended that we use the new @Observable macro/protocol in place of ObservableObject and use the @State property wrapper rather than @StateObject. This is my understanding from documentation articles such as Migrating from the Observable Object protocol to the Observable macro.
However, the StateObject property wrapper has an initialiser which takes an autoclosure parameter:
extension StateObject {
public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
}
This is an extremely important initialiser for state objects that are expensive to allocate. As far as I can tell, the @State property wrapper lacks an equivalent initialiser.
What is the recommended migration strategy for objects which made use of this on StateObject?
Thanks
I’m trying to create a property wrapper that that can manage shared state across any context, which can get notified if changes happen from somewhere else.
I'm using mutex, and getting and setting values works great. However, I can't find a way to create an observer pattern that the property wrappers can use.
The problem is that I can’t trigger a notification from a different thread/context, and have that notification get called on the correct thread of the parent object that the property wrapper is used within.
I would like the property wrapper to work from anywhere: a SwiftUI view, an actor, or from a class that is created in the background. The notification preferably would get called synchronously if triggered from the same thread or actor, or otherwise asynchronously. I don’t have to worry about race conditions from the notification because the state only needs to reach eventuall consistency.
Here's the simplified pseudo code of what I'm trying to accomplish:
// A single source of truth storage container.
final class MemoryShared<Value>: Sendable {
let state = Mutex<Value>(0)
func withLock(_ action: (inout Value) -> Void) {
state.withLock(action)
notifyObservers()
}
func get() -> Value
func notifyObservers()
func addObserver()
}
// Some shared state used across the app
static let globalCount = MemoryShared<Int>(0)
// A property wrapper to access the shared state and receive changes
@propertyWrapper
struct SharedState<Value> {
public var wrappedValue: T {
get { state.get() }
nonmutating set { // Can't set directly }
}
var publisher: Publisher {}
init(state: MemoryShared) {
// ...
}
}
// I'd like to use it in multiple places:
@Observable
class MyObservable {
@SharedState(globalCount)
var count: Int
}
actor MyBackgroundActor {
@SharedState(globalCount)
var count: Int
}
@MainActor
struct MyView: View {
@SharedState(globalCount)
var count: Int
}
What I’ve Tried
All of the examples below are using the property wrapper within a @MainActor class. However the same issue happens no matter what context I use the wrapper in: The notification callback is never called on the context the property wrapper was created with.
I’ve tried using @isolated(any) to capture the context of the wrapper and save it to be called within the state in with unchecked sendable, which doesn’t work:
final class MemoryShared<Value: Sendable>: Sendable {
// Stores the callback for later.
public func subscribe(callback: @escaping @isolated(any) (Value) -> Void) -> Subscription
}
@propertyWrapper
struct SharedState<Value> {
init(state: MemoryShared<Value>) {
MainActor.assertIsolated() // Works!
state.subscribe {
MainActor.assertIsolated() // Fails
self.publisher.send()
}
}
}
I’ve tried capturing the isolation within a task with AsyncStream. This actually compiles with no sendable issues, but still fails:
@propertyWrapper
struct SharedState<Value> {
init(isolation: isolated (any Actor)? = #isolation, state: MemoryShared<Value>) {
let (taskStream, continuation) = AsyncStream<Value>.makeStream()
// The shared state sends new values to the continuation.
subscription = state.subscribe(continuation: continuation)
MainActor.assertIsolated() // Works!
let task = Task {
_ = isolation
for await value in taskStream {
_ = isolation
MainActor.assertIsolated() // Fails
}
}
}
}
I’ve tried using multiple combine subjects and publishers:
final class MemoryShared<Value: Sendable>: Sendable {
let subject: PassthroughSubject<T, Never> // ...
var publisher: Publisher {} // ...
}
@propertyWrapper
final class SharedState<Value> {
var localSubject: Subject
init(state: MemoryShared<Value>) {
MainActor.assertIsolated() // Works!
handle = localSubject.sink {
MainActor.assertIsolated() // Fails
}
stateHandle = state.publisher.subscribe(localSubject)
}
}
I’ve also tried:
Using NotificationCenter
Making the property wrapper a class
Using NSKeyValueObserving
Using a box class that is stored within the wrapper.
Using @_inheritActorContext.
All of these don’t work, because the event is never called from the thread the property wrapper resides in.
Is it possible at all to create an observation system that notifies the observer from the same context as where the observer was created?
Any help would be greatly appreciated!
No real intruduction for this, so I'll get to the point:
All this code is on GitHub: https://github.com/the-trumpeter/Timetaber-for-iWatch
But first, sorry;
/*
I got roasted,
last time I posted;
for not defining my stuff.
This'll be different,
but's gonna be rough;
'cuz there's lots and lots
to get through:
*/
//this is 'Timetaber Watch App/Define (No expressions)/Courses_vDef.swift' on the GitHub:
struct Course {
let name: String
let icon: String
let room: String
let colour: String
let listName: String
let listIcon: String
let joke: String
init(name: String, icon: String, room: String? = nil, colour: String,
listName: String? = nil, listIcon: String? = nil, joke: String? = nil)
{
self.name = name
self.icon = icon
self.room = room ?? "None"
self.colour = colour
self.listName = listName ?? name
self.listIcon = listIcon ?? (icon+".circle.fill")
self.joke = joke ?? ""
}
}
//this is 'Timetaber Watch App/TimeManager_fDef.swift' on the GitHub:
func getCurrentClass(date: Date) -> Array<Course> {
//returns the course in session depending on the input date
//it is VERY long but
//all you really need to know is what it returns:
//basically: return [rightNow, nextUp]
}
/*
I thought that poetry
would be okay,
But poorly thought things through:
For I'll probably find
that people online
will treat my rhymes like spew.
*/
So into the question:
I have a bunch of views, all (intendedly) watching two variables inside of a class:
//Github: 'Timetaber Watch App/TimetaberApp.swift'
class GlobalData: ObservableObject {
@Published var currentCourse: Course = getCurrentClass(date: .now)[0] // the current timetabled class in session.
@Published var nextCourse: Course = getCurrentClass(date: .now)[1] // the next timetabled class in session
}
...and a bunch of views using them in different ways as follows:
(Sorry, don't have the characters to define functions called in these)
import SwiftUI
//Github: 'Timetaber Watch App/Views/HomeView.swift'
struct HomeView: View {
@StateObject var data = GlobalData()
var body: some View {
//HERE:
let icon = data.currentCourse.icon
let name = data.currentCourse.name
let colour = data.currentCourse.colour
let room = roomOrBlank(course: data.currentCourse)
let next = data.nextCourse
VStack {
//CURRENT CLASS
Image(systemName: icon)
.foregroundColor(Color(colour))//add an SF symbol element
.imageScale(.large)
.font(.system(size: 25).weight(.semibold))
Text(name)
.font(.system(size:23).weight(.bold))
.foregroundColor(Color(colour))
.padding(.bottom, 0.1)
//ROOM
Text(room+"\n")
.multilineTextAlignment(.center)
.foregroundStyle(.gray)
.font(.system(size: 15))
if next.name != noSchool.name {
Spacer()
//NEXT CLASS
Text(nextPrefix(course: next))
.font(.system(size: 15))
Text(getNextString(course: next))
.font(.system(size: 15))
.multilineTextAlignment(.center)
}
}.padding()
}
}
// Github: 'Timetaber Watch App/Views/ListView.swift'
struct listTemplate: View {
@StateObject var data = GlobalData()
var listedCourse: Course = failCourse(feedback: "lT.12")
var courseTime: String = ""
init(course: Course, courseTime: String) {
self.courseTime = courseTime
self.listedCourse = course
}
var body: some View {
let localroom = if listedCourse.room == "None" {
"" } else { listedCourse.room }
let image = if listedCourse.listIcon == "custom1" {
Image(.paintbrushPointedCircleFill)
} else { Image(systemName: listedCourse.listIcon) }
HStack{
image
.foregroundColor(Color(listedCourse.colour))
.padding(.leading, 5)
Text(listedCourse.name)
.bold()
Spacer()
Text(courseTime)
Text(localroom).bold().padding(.trailing, 5)
}
.padding(.bottom, 1)
.background(data.currentCourse.name==listedCourse.name ? Color(listedCourse.colour).colorInvert(): nil) //HERE
}
}
struct listedDay: View {
let day: Dictionary<Int, Course>
var body: some View {
let dayKeys = Array(day.keys).sorted(by: <)
List {
ForEach((0...dayKeys.count-2), id: \.self) {
let num = $0
listTemplate(course: day[dayKeys[num]] ?? failCourse(feedback: "lD.53"), courseTime: time24toNormal(time24: dayKeys[num]))
}
}
}
}
struct ListView: View {
var body: some View {
if storage.shared.termRunningGB && weekdayFunc(inDate: .now) != 1
&& weekdayFunc(inDate: .now) != 7 {
ScrollView {
listedDay(
day: getTimetableDay(
isWeekA:
getIfWeekIsA_FromDateAndGhost(
originDate: .now,
ghostWeek: storage.shared.ghostWeekGB
),
weekDay: weekdayFunc(inDate: .now)
)
)
}
} else if !storage.shared.termRunningGB {
Text("There's no term running.\nThe day's classes will be displayed here.")
.multilineTextAlignment(.center)
.foregroundStyle(.gray)
.font(.system(size: 13))
} else {
Text("No school today.\nThe day's classes will be displayed here.")
.multilineTextAlignment(.center)
.foregroundStyle(.gray)
.font(.system(size: 13))
}
}
}
//There's one more view but I can't fit it for characters.
//On GitHub: 'Timetaber Watch App/Views/SettingsView.swift'
So...
THE FUNCTION:
This function is called when changes are made that will affect the correct output of getCurrentClass. It is intended to reload the views and the current/next variables to reflect those changes.\
//GHub: 'Timetaber Watch App/StorageManager.swift'
func reload() -> Void {
@ObservedObject var globalData: GlobalData //this line is erroring, I don't know how to fix it. Is this even the best/proper way to do this?
let courseData = getCurrentClass(date: .now)
globalData.currentCourse = courseData[0]
globalData.nextCourse = courseData[1]
//Variable '_globalData' used by function definition before being initialized
//that is the error appearing on those above two redefinitions.
print("Setup done\n")
}
Thanks!
-Gill
Although I can't see anything in Apple's documentation to this effect, I'm coming to believe that ReferenceFileDocument is incompatible with @Observable.
But hopefully I've just missed something!
I have an app in which the data model is @Observable, and views see it through
@Environment(dataModel.self) private var dataModel
Since there are a large number of views, only some of which may need to be redrawn at a given time, Apple's documentation leads me to believe that @Observable may be smarter about only redrawing views that actually need redrawing than @Published and @ObservedObject.
I originally wrote the app without document persistence, and injected the data model into the environment like this:
@main
struct MyApp: App {
@State private var dataModel = DataModel()
var body: some Scene {
WindowGroup {
myDocumentView()
.environment(dataModel)
}
}
}
I’ve been trying to make the app document based. Although I started using SwiftData, it has trouble with Codable (you need to explicitly code each element), and a long thread in the Developer forum suggests that SwiftData does not support the Undo manager - and in any event, simple JSON serialization is all that this app requires - not a whole embedded SQLLite database.
At first, it seems to be easy to switch to a DocumentGroup:
@main
struct MyApp: App {
var body: some Scene {
DocumentGroup(newDocument: {DataModel() } ) { file in
myDocumentView()
.environment(file.document) }
}
}
Since I've written everything using @Observable, I thought that I'd make my data model conform to ReferenceFileDocument like this:
import SwiftUI
import SwiftData
import UniformTypeIdentifiers
@Observable class DataModel: Identifiable, Codable, @unchecked Sendable, ReferenceFileDocument {
// Mark: ReferenceFileDocument protocol
static var readableContentTypes: [UTType] {
[.myuttype]
}
required init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
let decodedModel = try MyModel(json: data)
if decodedModel != nil {
self = decodedModel!
} else {
print("Unable to decode the document.")
}
} else {
throw CocoaError(.fileReadCorruptFile)
}
}
func snapshot(contentType: UTType) throws -> Data {
try self.json()
}
func fileWrapper(snapshot: Data,
configuration: WriteConfiguration) throws -> FileWrapper {
FileWrapper(regularFileWithContents: snapshot)
}
var nodes = [Node]() // this is the actual data model
init() {
newDocument()
}
... etc.
I've also tried a similar approach in which the ReferenceFileDocument is a separate module that serializes an instance of the data model.
The problem I'm currently experiencing is that I can't figure out how to:
a) inject the newly created, or newly deserialized data model into the environment so that views can take advantage of it's @Observable properties, or
b) how to cause changes in the @Observable data model to trigger serialization (actually I can observe them triggering serialization, but what's being serialized is an empty instance of the data model).
I make data model changes through a call to the Undo manager:
// MARK: - Undo
func undoablyPerform(_ actionName: String, with undoManager: UndoManager? = nil, doit: () -> Void) {
let oldNodes = self.nodes
doit()
undoManager?.registerUndo(withTarget: self) { myself in
self.undoablyPerform(actionName, with: undoManager) {
self.nodes = oldNodes
}
}
undoManager?.setActionName(actionName)
}
The views looks like this:
import SwiftUI
import CoreGraphics
struct myDocumentView: View {
@Environment(DataModel.self) private var dataModel
@Environment(\.undoManager) var undoManager
... etc.
Some things work - if I prepopulate the model, it serializes correctly, and gets written to a file.
Unfortunately, in the view hierarchy, myModel is always empty.
Have I done something wrong? Do I need to abandon @Observable?
I've tried conforming the model to ObservedObject, adding @Published, and injecting it as an @ObservedObject - and viewing as @EnvironmentObject var dataModel: DataModel
But it's still not injected correctly into the View hierarchy.
Edit - I may have identified the problem - will update this question when confirmed.
Hi,
In the Apple Scrumdinger sample, the SpeechRecognizer class conforms to the Observable protocol:
public actor SpeechRecognizer: Observable {
public enum RecognizerError: Error {
case nilRecognizer
.
.
.
The developer help text suggests that the protocol conformance does not add observation functionality.
This class does not use the @Observable macro.
So, how does this work under the hood?
Greetings,
With MacOS 15 Sequoia, Apple updated key-value-observations in such a way, that an unremoved observation by a now deallocated object will no longer cause an exception, when the observed object triggers said observation.
While this is fundamentally a positive change, the issue comes with the fact, that this change affects systems based on the version of their operating system and not the version of the operating system of the build system.
Many of our customers use old operating system versions and old hardware - meaning they can't easily update. Since we need to use up to date Xcode versions to develop for newer systems, our developers tend to have rather new OS versions.
This means, that we are increasingly facing bugs which only happen in customer systems but not in most developer (or QA) systems.
Currently, we still can use earlier OS versions with Xcode to fix these bugs, but once the used Xcode versions no longer support MacOS 14 or below this will become a major hurdle.
Are there known solutions to this issue?
We are currently attempting to swizzle observer adding and removal in order to fix the problem for old systems as well, but this has proven to be quite difficult and unstable. Since any weakly held property in the middle of an observation keypath can cause crashes, one would have to split up observations into multiple subobservations, which is less simple than it sounds, due to custom implementations of addObserver (such as there seems to be in array controller proxies) and collection operators.
Thanks for any suggestions!
Hello, I can't seem to set any breakpoint in didSet for all properties inside Observable.
Is this a bug?
XcodeVersion 15.2
(15C500b)
Thanks!