Using SwiftData I have one relatively short list of @Model class objects that are referenced in several views. I thought it would be handy to query once and share via the environment but my Table in other views does not update when data changes as it does when I repeat the Query in each view. Is this a bad idea or perhaps I implemented it improperly?
@Observable
class AllAccounts: ObservableObject {
var accounts: [Account] = []
}
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
// an instance of the AllAccounts class, will save Query results to all.accounts
@StateObject var all = AllAccounts()
// one query for all accounts once in app, store in the environment`
@Query private var allAccounts: [Account]
.onAppear {
all.accounts = allAccounts
}
.environment(all)
Observation
RSS for tagMake responsive apps that update the presentation when underlying data changes.
Posts under Observation tag
55 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
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!
Hey there i try to adept the Observable macro for my SwiftUI Project.
I wanna get data from HealthKit and transform it to FHIR.
Here i need one class for managing this process which will be shared through my project in order to access these data.
The following Codesnippet raises an error:
import Observation
import HealthKit
import FHIR
@Observable
class HealthKitManager{
let healthStore = HKHealthStore()
init(){
}
func requestAuthorization(){
let authTypes: Set = [HKQuantityType(.stepCount),
HKQuantityType(.heartRate)
]
guard HKHealthStore.isHealthDataAvailable() else {
print("health data not available!")
return
}
healthStore.requestAuthorization(toShare: nil, read: authTypes) {
success, error in
if success {
print("OK Fetching")
} else {
print("\(String(describing: error))")
}
}
}
}
So the FHIR module contains a Models module which define different models.
In the FHIR world there is also a concept called observation... now the problems begin.
open class Observation : Models.DomainResource {
...
}
When i try import Observation for the Observable macro and also import FHIR in order to create Observations, XCode rises an error: Observable' is not a member type of class 'Models.Observation'
It's also said that Observation is declared in the FHIR.Models.Observation class which is true, but it seems that this raises the Problem with the @Observable macro.
It's also said that Type 'Observation' has no member 'ObservationRegistrar'
Which comes from the Observable Macro i think.
When i don't import FHIR then everything works fine, but then my use case is broken because i need these standard in order to get my use case running.
All in all i need big help and be thankfull for possible solutions!
I am using the Observable macro and when I use @Environment property wrapper to instance my model the preview stop working. Sample code below
my model Library
import SwiftUI
import Observation
@Observable class Library {
// ...
}
Now in my main app I created an instance of Library and add that instance to the environment
@main
struct BookReaderApp: App {
@State private var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environment(library)
}
}
}
Now if I want to retrieve the Library instance from any view using the @Environment property wrapper the preview stop working completely
struct LibraryView: View {
@Environment(Library.self) private var library
var body: some View {
List(library.books) { book in
BookView(book: book)
}
}
}
#Preview {
LibraryView()
}
Check the 2 screenshots below
Any idea why this is happening? Is there any workaround? I am working with Xcode Version 15.2. Thanks in advance for any kind of help!
I am new to programing apps, and I am trying to figure out how to use one item out of an array of items in a Text line. I am not getting any complaints from Xcode, but then the preview crashes giving me a huge error report that it keeps sending to Apple. I have cut out all the extra stuff from the app to get just the basics. What I want it to print on the screed is "Hello Bill How are you?" with Bill being from the observable array. The first picture below is about 2 seconds after I removed the // from in front of the line that reads Text("(friend2.friend2[1].name)"). The other two pictures are the main app page and the page where I setup the array. At the very bottom is a text file of the huge report it kept sending to Apple, until I turned of the option of sending to Apple. Would someone please explain what I am doing wrong. Well a side from probably everything.
Error code.txt
I am surprised at what I am seeing with the new @Observable framework in iOS 17.
It seems that if the view containing the @State var, ie viewModel, is modified, then the init of that viewModel will also be called. This is not the behavior of the prior StateObject method of using a viewModel.
As I understand the @State should not be reinitialized on redrawing on the View, is this the expected behavior?
Example:
struct ContentView: View {
@State var offset = false
var body: some View {
VStack {
InnerView()
.offset(x: offset ? 200 : 0)
Button("Offset") {
offset.toggle()
}
}
}
}
// iOS 17 Observable method:
struct InnerView: View {
@State var viewModel: ViewModel
init() {
self._viewModel = State(wrappedValue: ViewModel())
}
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
@Observable
class ViewModel {
init() {
print("ViewModel Init")
}
}
// StateObject method:
struct InnerView: View {
@StateObject var viewModel: ViewModel
init() {
self._viewModel = StateObject(wrappedValue: ViewModel())
}
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
class ViewModel: ObservableObject {
init() {
print("ViewModel Init")
}
}
I am working on a app that uses the new Observation framework and MVVM design pattern.
I have a view composed of a list and a header that shows statistics about the displayed content by using an Observable ViewModel.
When I add new contents, it is added to the list but the header seems to not take it into account even if its init method and body property are called.
@Observable
final class ViewModel {
let contents: [String]
init(contents: [String]) {
self.contents = contents
print("ViewModel init")
}
deinit {
print("ViewModel deinit")
}
}
struct ContentHeaderView: View {
@State private var viewModel: ViewModel
init(contents: [String]) {
self._viewModel = State(
initialValue: ViewModel(contents: contents)
)
}
var body: some View {
Text("\(self.viewModel.contents.count)")
}
}
struct ContentListView: View {
@State private var contents = ["Content 1"]
var body: some View {
NavigationStack {
VStack {
ContentHeaderView(contents: self.contents)
List(self.contents, id: \.self) { content in
Text(content)
}
}
.toolbar {
ToolbarItem {
Button("Add Content") {
let newContent = "New Content \(self.contents.count + 1)"
self.contents.append(newContent)
}
}
}
}
}
}
In this example, when I tap the "Add Content" toolbar button, the header view that's supposed to show the number of content still display "1" even if the array now contains 2 elements.
I added print statements to show that a new ViewModel is created every time the view is updated and the currently displayed view still uses the first created ViewModel which in fact contains a array with only one element.
ContentHeaderView Init // on appear
ContentHeaderView Init // First button tap
// Every other tap
ContentHeaderView Init
ContentHeaderView Deinit
I've read the threads 736239 and 736110 that discusses about similar issues when presenting sheets. I have similar code without issues since it's been fixed in iOS 17.2 beta 1 but it seems to still happen on view updates.
Can you please confirm that this is an issue with the Observable framework and State properties or is there something I'm doing wrong ?
Thanks
after i import swiftui, there's no @observable?? it shows unknown attribute...
and i cant import observation or swiftdata
import Foundation
import SwiftUI
@Observable
class BusinessModel{
var businesses = [Business]()
var selectedBusiness: Business?
var input: String = ""
var service = dataservice()
}
I am learning SwiftUI.
Error:
SwiftUI/Environment+Objects.swift:32: Fatal error: No Observable object of type ViewModel found. A View.environmentObject(_:) for ViewModel may be missing as an ancestor of this view.
I do not get the point why this happens.
The error is shown in the line : "renderer.render {...
import SwiftData
import SwiftUI
@Observable
class ViewModel {
var groupNumber: String = "Gruppe"
var sumOfHours: Int = 0
var personsToPrint: [Person]? = nil
@MainActor func pdfReport(person: [Person]) {
if personsToPrint != nil {
for person in personsToPrint! {
let renderer = ImageRenderer(content: PersonDetailView(person: person))
let url = URL.documentsDirectory.appending(path: "\(person.lastname) \(person.firstname).pdf")
print(person.lastname, person.firstname)
renderer.render { size, context in
var box = CGRect(x: 0, y: 0, width: size.width, height: size.height)
guard let pdf = CGContext(url as CFURL, mediaBox: &box, nil) else {
return
}
pdf.beginPDFPage(nil)
context(pdf)
pdf.endPDFPage()
pdf.closePDF()
let data = try! Data(contentsOf: url) //pdf muss erst in Daten umgewandelt werden.
do {
try data.write(to: url)
print("Daten wurden hier gespeichert: \(url)")
} catch {
print("PDF could not be saved!")
}
}
}
}
}
}
Hi guys,
I am trying to use the new @Observable macro. According to the documentation, this new macro will automatically generate observable properties, but I am having issues using my properties when Bindings are necessary.
Previously I had a setup like this:
class Example: ObservableObject {
@Published public var myArray = [CustomType]()
}
I initialised one instance of the Example-class in @main
@StateObject private var exampleClass = Example()
And added as an .environmentObject onto the root view
ContentView()
.environmentObject(exampleClass)
In child views I was able to access the @Published property as a binding via
@EnvironmentObject private var example: Example
$example.myArray
What I am trying now
This is the setup that I am trying with now:
@Observable class Example {
public var myArray = [CustomType]()
}
State instead of StateObject in @main
@State private var exampleClass = Example()
.environment instead of .environmentObject
ContentView()
.environmentObject(exampleClass)
@Environment instead of @EnvironmentObject
@Environment(Example.self) private var example
This new setup is not letting me access $example.myArray. Is this intended? Could someone explain why?
I am following this Apple Article on how to setup an AVPlayer. The only difference is I am using @Observable instead of an ObservableObject with @Published vars.
Using @Observable results in the following error: Cannot find '$isPlaying' in scope
If I remove the "$" symbol I get a bit more insight:
Cannot convert value of type 'Bool' to expected argument type 'Published<Bool>.Publisher'
If I change by class to an OO, it works fine, although, is there anyway to get this to work with @Observable?
Following this Apple Article, I copied their code over for observePlayingState().
The only difference I am using @Observable instead of ObservableObject and @Published for var isPlaying.
We get a bit more insight after removing the $ symbol, leading to a more telling error of: Cannot convert value of type 'Bool' to expected argument type 'Published.Publisher'
Is there anyway to get this working with @Observable?
Not sure if this code is supposed to leak memory or am I missing something?
import SwiftUI
import Observation
@Observable class SheetViewModel {
let color: Color = Color.red
init() { print("init") }
deinit { print("deinit") }
}
struct SheetView: View {
@State var viewModel = SheetViewModel()
var body: some View {
Color(viewModel.color)
}
}
@main
struct ObservableMemoryLeakApp: App {
@State var presented: Bool = false
var body: some Scene {
WindowGroup {
Button("Show") {
presented = true
}
.sheet(isPresented: $presented) {
SheetView()
}
}
}
}
Every time the sheet is presented/dismissed, a new SheetViewModel is created (init is printed) but it never released (deinit not printed). Also, all previously created SheetViewModel instances are visible in Memory Graph and have "Leaked allocation" badge.
Reverting to ObservableObject/@StateObject fixes the issue, deinit is called every time the sheet is dismissed:
-import Observation
-@Observable class SheetViewModel {
+class SheetViewModel: ObservableObject {
- @State var viewModel = SheetViewModel()
+ @StateObject var viewModel = SheetViewModel()
Does this mean there's a bug in Observation framework?
Reported as FB13015569.
In my test app I have two squares (red and green) that change their size when I click a toggle button. I use two different approaches to change the scale. The red square is scaled using the .visualEffect modifier and the green square is scaled using the .scaleEffect modifier. When I click the button the first time, both squares change size. But when I click again and again, only the green square changes its size. The red square stays the same after the first change. Self._printChanges() show that both views get changes each time. What am I doing wrong? Or is this a bug?
import SwiftUI
import Observation
struct ContentView: View {
@State private var model: Model = Model()
var body: some View {
VStack {
HStack {
RedSquareView()
GreenSquareView()
}
.environment(model)
Button {
model.scaled.toggle()
} label: {
Text("Toggle scale")
}
}
.padding()
}
}
struct RedSquareView: View {
@Environment(Model.self) var model
var body: some View {
let _ = Self._printChanges()
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
.visualEffect { content, geometryProxy in
content.scaleEffect(model.scaled ? 0.5 : 1.0, anchor: .center)
}
.animation(.bouncy, value: model.scaled)
}
}
struct GreenSquareView: View {
@Environment(Model.self) var model
var body: some View {
let _ = Self._printChanges()
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100)
.scaleEffect(model.scaled ? 0.5 : 1.0, anchor: .center)
.animation(.bouncy, value: model.scaled)
}
}
@Observable
final class Model: Sendable {
var scaled: Bool = false
}
#Preview {
ContentView()
}
I've encountered a problem which I don't know if is related to swiftui, the observation framework, or both
If I run the following code I have two tabs, with the second tab that use a "lazy model" which is deallocated each time disappears (this is necessary for my use case)
If I switch to the second tab, all works right. If I return to the first tab, the onDisappear on the foo view should force the "Bar" variable to nil , because the FooView may be still allocated (it is a tab bar) but that resource should be released
If that bar variable is set to nil, the MyBar should be replaced by the ProgressView in the "background"
I expect regarding that the Bar that:
the instance is nil on Foo
no other view should be shown with that instance (MyBar is now disappeared)
Because no ref, the Bar observable object should be now deallocated
In reality the Bar object is still in my memory graph
Any suggestions? is it a bug?
@Observable
class Bar {
var hello: String = ""
}
struct Foo: View {
@State
var bar: Bar?
@ViewBuilder
private var content: some View {
if let bar {
MyBar(bar: bar)
} else {
ProgressView()
}
}
var body: some View {
content
.onAppear {
bar = Bar()
}
.onDisappear {
self.bar = nil
}
}
}
struct MyBar: View {
@Bindable
var bar: Bar
var body: some View {
Text("MyBar")
}
}
struct ContentView: View {
@State
var tag: Int = 0
var body: some View {
TabView(selection: $tag) {
Text("First")
.tag(0)
.tabItem {
Text("First")
}
Foo()
.tag(1)
.tabItem {
Text("Foo")
}
}
}
}
I'm working on a SwiftUI app using SwiftData for state management. I've encountered an issue with view updates when mutating a model. Here's a minimal example to illustrate the problem:
import SwiftUI
import SwiftData
// MARK: - Models
@Model
class A {
@Relationship var bs: [B] = [B]()
init(bs: [B]) {
self.bs = bs
}
}
@Model
class B {
@Relationship var cs: [C] = [C]()
init(cs: [C]) {
self.cs = cs
}
}
@Model
class C {
var done: Bool
init(done: Bool) {
self.done = done
}
}
// MARK: - Views
struct CView: View {
var c: C
var body: some View {
@Bindable var c = c
HStack {
Toggle(isOn: $c.done, label: {
Text("Done")
})
}
}
}
struct BView: View {
var b: B
var body: some View {
List(b.cs) { c in
CView(c: c)
}
}
}
struct AView: View {
var a: A
var body: some View {
List(a.bs) { b in
NavigationLink {
BView(b: b)
} label: {
Text("B \(b.cs.allSatisfy({ $0.done }).description)")
}
}
}
}
struct ContentView: View {
@Query private var aList: [A]
var body: some View {
NavigationStack {
List(aList) { a in
NavigationLink {
AView(a: a)
} label: {
Text("A \(a.bs.allSatisfy({ $0.cs.allSatisfy({ $0.done }) }).description)")
}
}
}
}
}
@main
struct Minimal: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
#Preview {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try! ModelContainer(for: A.self, configurations: config)
var c = C(done: false)
container.mainContext.insert(c)
var b = B(cs: [c])
container.mainContext.insert(b)
var a = A(bs: [b])
container.mainContext.insert(a)
return ContentView()
.modelContainer(container)
}
In this setup, I have a CView where I toggle the state of a model C. After toggling C and navigating back, the grandparent view AView does not reflect the updated state (it still shows false instead of true). However, if I navigate back to the root ContentView and then go to AView, the status is updated correctly.
Why doesn't AView update immediately after mutating C in CView, but updates correctly when navigating back to the root ContentView? I expected the grandparent view to reflect the changes immediately as per SwiftData's observation mechanism.
Hello,
my goal is it to implement an Edit Sheet of a Model with Discard changes in SwiftData.
Xcode Version 15.0 beta 6 (15A5219j)
Approaches
In the following modelContext refers to the context fetched from the environment by the View. Also code for showing the sheet (eg. toggling isPresented) is omitted.
// App
ContentView()
.modelContainer(for: Model.self, isAutosaveEnabled: true, isUndoEnabled: true)
// In Content View
@Query var models: [Model]
//...
ForEach(models) { model in
ModelView(model: model)
//eg. longpresgesture that calls openEditSheet
.sheet(..., content: {
EditModelView(model: model)
}
}
Disabling autosave + rollback
// In ContentView
func openEditSheet() {
modelContext.autosaveEnabled = false
}
// In EditModelView
func discardEditSheet() {
modelContext.rollback()
modelContext.autosaveEnabled = true
}
func saveEditSheet() {
try? modelContext.save() // probably not needed if autosave gets enabled anyway
modelContext.autosaveEnabled = true
}
However this approach does not work, as SwiftData continues to save anyway and therefore there is nothing to rollback.
modelContext in Memory
// In ContentView
let context = ModelContext(modelContext.container)
context.autosaveEnabled = false
context.container.configuration.removeAll()
context.container.configuration.insert(ModelConfiguration(..., inMemory: true)
//...
EditModelView()
.modelContext(context)
// Also tried: .enviroment(\.modelContext, context)
// In EditModelView
func discardEditSheet() {
modelContext.rollback() // probably not needed as the container is never saved
}
func saveEditSheet() {
try? modelContext.save() // move to persistent storage from memory
}
The idea was to use something like parent in CoreData. However as this is (currently) not supported.
Also tried this by modifying the modelContext directly (instead of creating a new context) and its container directly or before creating the context.
The child can not be a ModelContainer as you can not pass a context to it or set its mainContext (get-only).
However this approach does not work, as the container does not seem to stay in memory. Maybe related to previous.
BackingData
EditModelView(model: Model(backingData: model.persistentBackingData)
not sure about this one, played around a little with it but not sure what it means / should mean and how I would expect it to work. I have never been great at cooking :).
UndoManager (preferred)
// In ContentView
func openEditSheet() {
modelContext.undoManager?.beginUndoGrouping()
}
// In EditModelView
func discardEditSheet() {
modelContext.undoManager?.endUndoGrouping()
modelContext.undoManager?.undoNestedGroup()
// Also tried adding: try? modelContext.save()
}
func saveEditSheet() {
modelContext.undoManager?.endUndoGrouping()
}
This approach does works kinda weird:
The ModelView of the updated model in ContentView's ForEach is not updated regarding the undo action.
However when I re-open the EditTaskView of the updated model it has the expected state, even though the model to edit is passed by the ContentView (which does not have the correct state?).
After relaunching the app or when modifying the model again (this time not undoing) the previous sate changes are recognised
Therefore this looks like it does what I want, with the ContentView having the correct state, but not displaying it. Only reason I can think of is the Model: Observable not getting triggered and therefore no View update.
Conclusion
After playing around with the above approaches and combining them in every possible way, i think that:
disabling autosave at runtime is currently not working and this is a bug (otherwise autosaveEnabled should be get-only).
UndoManager does not trigger a View update of a Model/Observable and this is a bug
Would be happy to hear other opinions on this. Am I missing something here? Am I ******?
Question
However as my conclusion does not fix my problem I am wondering:
Are my approaches (theoretically) correct?
How do I fix / workaround the UndoManager issue? If I identified it correctly, how can I manually notify the view that a Observable model has changed?
class PostManager: ObservableObject {
static let shared = PostManager()
private init() {}
@Published var containers: [PostContainer] = []
// Other code
}
class PostContainer: ObservableObject {
var id: UUID = UUID()
var timestamp: Date
var subreddit: String
var posts: [Post]
var type: ContainerType
var active: Bool
// Init
}
@Model
final class Post: Decodable, ObservableObject {
// Other code
}
In my main view, a network request is made and a PostContainer is created if it doesn't exist. This code works fine, and the view is updated correctly.
let container = PostContainer(timestamp: Date(), subreddit: subreddit, posts: posts, type: .search, active: true)
self.containers.append(container)
If the user wants to see more, they press a button and another request is made. This time, the new data will be added to the PostContainer instead of creating a new one.
if let container = self.containers.first(where: {$0.subreddit == subreddit}) {
// Update previous container
container.timestamp = Date()
print(container.posts.count)
// Map IDs from container and then remove duplicates
let existingIDs = Set(container.posts.map { $0.id })
let filtered = posts.filter { !existingIDs.contains($0.id) }
// Append new post
container.posts.append(contentsOf: filtered)
container.active = true
}
This code is working fine as well, except for the view is not updating. In the view, PostManager is an @EnvironmentObject. I also have a computed variable to get the post and sort them. I added a print statement to that variable and saw that it wasn't being printed even though more data was being added to the PostContainer. At one point, I created an ID for the List that displays the data and had the code inside PostManager update that ID when it was finished. This of course worked, but it's not ideal.
How can I get the view to update when post are appended inside of PostContainer?
Our app has an architecture based on ViewModels.
Currently, we are working on migrating from the ObservableObject protocol to the Observable macro (iOS 17+).
The official docs about this are available here: https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro
Our ViewModels that were previously annotated with @StateObject now use just @State, as recommended in the official docs.
Some of our screens (a screen is a SwiftUI view with a corresponding ViewModel) are presented modally. We expect that after dismissing a SwiftUI view that was presented modally, its corresponding ViewModel, which is owned by this view (via the @State modifier), will be deinitialized. However, it seems there is a memory leak, as the ViewModel is not deinitialized after a modal view is dismissed.
Here's a simple code where ModalView is presented modally (through the .sheet modifier), and ModalViewModel, which is a @State of ModalView, is never deinitialized.
import SwiftUI
import Observation
@Observable
final class ModalViewModel {
init() {
print("Simple ViewModel Inited")
}
deinit {
print("Simple ViewModel Deinited") // never called
}
}
struct ModalView: View {
@State var viewModel: ModalViewModel = ModalViewModel()
let closeButtonClosure: () -> Void
var body: some View {
ZStack {
Color.yellow
.ignoresSafeArea()
Button("Close") {
closeButtonClosure()
}
}
}
}
struct ContentView: View {
@State var presentSheet: Bool = false
var body: some View {
Button("Present sheet modally") {
self.presentSheet = true
}
.sheet(isPresented: $presentSheet) {
ModalView {
self.presentSheet = false
}
}
}
}
#Preview {
ContentView()
}
Is this a bug in the iOS 17 beta version or intended behavior? Is it possible to build a relationship between the View and ViewModel in a way where the ViewModel will be deinitialized after the View is dismissed?
Thank you in advance for the help.
Since iOS/iPadOS beta 6, I get a crash just by simply selecting an item in the sidebar of a navigation view. The selected item is part of an app mode, with conforms to the Observation protocol:
import SwiftUI
import Observation
@main
struct MREAApp: App {
@State private var appModel = AppModel.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(appModel)
}
}
}
@Observable class AppModel {
static var shared = AppModel()
var selectedFolder: Folder? = nil
}
struct Folder: Hashable, Identifiable {
let id = UUID()
let name: String
}
struct ContentView: View {
@Environment(AppModel.self) private var appModel
var folders = [Folder(name: "Recents"), Folder(name: "Deleted"), Folder(name: "Custom")]
var body: some View {
NavigationSplitView {
SidebarView(appModel: appModel, folders: folders)
} detail: {
if let folder = appModel.selectedFolder {
Text(folder.name)
}
else {
Text("No selection")
}
}
}
}
struct SidebarView: View {
@Bindable var appModel: AppModel
var folders: [Folder]
var body: some View {
List(selection: $appModel.selectedFolder) {
ForEach(folders, id: \.self) { folder in
NavigationLink(value: folder) {
Text(folder.name)
}
}
}
}
}
To reproduce the bug, just tap on one of the item.
Oddly enough, this works fine in the Simulator.
macOS 14 beta 5 is not affected either.
Apple folks: FB12981860