Post not yet marked as solved
I am exploring on managing state in SwiftUI app with purpose built Views due to the advantages for managing dependency with Environment.
This is the minimal example I came up with:
@MainActor
struct AsyncStateModifier<T: Equatable>: View {
let input: T
let action: (T) async -> Void
@Environment var queue: AsyncActionQueue
var body: some View {
return EmptyView()
.onChange(of: input, initial: true) { old, new in
queue.process(action: action, with: input)
}
}
}
The drawback of this approach is initial: true allows the onChange callback to fire when view appears and since EmptyView doesn't appear the action is never executed initially.
When replacing EmptyView with Rectangle().hidden() this can be achieved, but I wanted to avoid having any impact on view hierarchy and EmptyView is suitable for that. Is there any alternative approach to make something like this possible?
Post not yet marked as solved
I am little confused about when to use State / StateObject / ObservedObject.
What I have researched and what I understand:
@State --> for value types
@StateObject --> for reference types
@ObservedObject --> child objects who needs reference to above two (the parent object should have @State/@StateObject and the object should conform to Observable)
I am clear about Environment object.
Post not yet marked as solved
i'm having trouble modifying an optional environment object.
i'm using the .environment modifier to pass along an optional object to other views. to access it in other views, i have to get it through an @Environment property wrapper. but i can't modify it even if i redeclare it in the body as @Bindable. here's an example code:
@main
struct MyApp: App {
@State private var mySession: MySession?
var body: some Scene {
HomeScreen()
.environment(mySession)
}
}
now for the HomeScreen:
struct HomeScreen: View {
@Environment(MySession.self) private var mySession: MySession?
var body: some View {
@Bindable var mySession = mySession
Button {
mySession = MySession()
} label: {
Text("Create Session")
}
}
}
an error shows up in the @Bindable declaration saying init(wrappedValue:)' is unavailable: The wrapped value must be an object that conforms to Observable. but MySession is declared as @Observable. in fact it works just fine if i don't make the environment optional, but i have to setup MySession in the root of the app, which goes against the app flow.
Post not yet marked as solved
If I annotate a class with @Observable I get this error in @Query:
Expansion of macro 'Query()' produced an unexpected 'init' accessor
If I remove @Observable the error goes away.
Elsewhere I have .environment referencing the class. With @Observable this complains that the class needs to be @Observable.
I am mystified. Does anyone have a suggestion?
Post not yet marked as solved
Originally asked on Swift Forums: https://forums.swift.org/t/using-bindable-with-a-observable-type/70993
I'm using SwiftUI environments in my app to hold a preferences object which is an @Observable object
But I want to be able to inject different instances of the preferences object for previews vs the production code so I've abstracted my production object in to a Preferences protocol and updated my Environment key's type to:
protocol Preferences { }
@Observable
final class MyPreferencesObject: Preferences { }
@Observable
final class MyPreviewsObject: Preferences { }
// Environment key
private struct PreferencesKey: EnvironmentKey {
static let defaultValue : Preferences & Observable = MyPreferencesObject()
}
extension EnvironmentValues {
var preferences: Preferences & Observable {
get { self[PreferencesKey.self] }
set { self[PreferencesKey.self] = newValue }
}
}
The compiler is happy with this until I go to use @Bindable in my code where the compiler explodes with a generic error,
eg:
@Environment(\.preferences) private var preferences
// ... code
@Bindable var preferences = preferences
If I change the environment object back to a conforming type eg:
@Observable
final class MyPreferencesObject() { }
private struct PreferencesKey: EnvironmentKey {
static let defaultValue : MyPreferencesObject = MyPreferencesObject()
}
extension EnvironmentValues {
var preferences: MyPreferencesObject {
get { self[PreferencesKey.self] }
set { self[PreferencesKey.self] = newValue }
}
}
Then @Bindable is happy again and things compile.
Is this a known issue/limitation? Or am I missing something here?
Post not yet marked as solved
Hello everyone, I hope you are well. I have a question about .environment. I have an observable viewModel which has some functions and publishing value. I'm observing this viewModel in only 2 views but I'm using viewModel functions in every view. Should I use it (.environment). if I should use it, should I use this environment macro (@Environment(ViewModel.Self) var vm) for only functions in view? Thank you so much.
Post not yet marked as solved
I am targeting iOS17 and using @Observable.
I have an array of items, and I want to present a sheet when the array is greater than zero.
My @Observable looks like this:
@Observable class BStoreOO {
var items: [Int] = []
var showBS: Bool { items.isEmpty }
func updateItems(newItems: [Int]) {
self.items = newItems
}
}
When this array gets added to, from another view, I would like to present a sheet/popover.
Inside my ContentView I have a @State `... that uses that @Observable
struct ContentView: View {
@State var bStore = BStoreOO()
Further down in my View, how should I toggle this view on the basis of the observed array not being empty? I have tried a number of ways. Some error, some don't, but none present the sheet!
For example:
.popover(isPresented: bStore.showBS) {
PopoverContent()
}
Gives the error "Cannot convert value of type 'Bool' to expected argument type 'Binding'"
If I add a State like this: @State private var isShowingBS = false
and then add this:
.onChange(of: bStore.items) {
self.isShowingBS = self.bStore.items.count > 0
}
I don't get errors but nothing is presented.
What is the correct way to bind the presentation of the sheet to whether the observed items array is empty or not?
Using SwiftUI, the timecode (seconds notation) has been referenced using ObservableObject as follows. In this case, the timecode values were reflected in Text in real time without any problem.
struct ContentView: View {
.
.
.
var body: some View {
NavigationStack {
// Time Code Text
Text(String(format:"%02d:%02d:%02d", sMinute, sSecond, sMsec))
.font(Font(UIFont.monospacedDigitSystemFont(ofSize: 30.0, weight: .regular)))
class ViewModel: ObservableObject {
// Time Code "%02d:%02d:%02d"
@Published var sMinute = 0
@Published var sSecond = 0
@Published var sMsec = 0
When I changed it to @Observable class as follows, the timecode operation is stopped and the values are updated only when the operation is finished.
@Observable class ViewModel {
// Time Code "%02d:%02d:%02d"
var sMinute = 0
var sSecond = 0
var sMsec = 0
Is it possible to do something with the @Observable class that would allow it to be constantly monitored and updated in real time?
Or should we change it back?
If we have a history of changing to @Observable in relation to other functions, and we have no choice but to revert back, is this where we need to make a change that would keep it functional separately?
Post not yet marked as solved
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)
Post not yet marked as solved
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!
Post not yet marked as solved
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've just started tinkering with @Observable and have run into a question... What is the best practice for observing an Observable object outside of SwiftUI? For example:
@Observable
class CountingService {
public var count: Int = 0
}
@Observable
class ObservableViewModel {
public var service: CountingService
init(service: CountingService) {
self.service = service
// how to bind to value changes on service?
}
// suggestion I've seen that doesn't smell right
func checkCount() {
_ = withObservationTracking {
service.count
} onChange: {
DispatchQueue.main.async {
print("count: \(service.count)")
checkCount()
}
}
}
}
Historically using ObservableObject I'd have used Combine to monitor changes to service. That doesn't seem possible with @Observable and I don't know that I've come across an accepted / elegant solution? Perhaps there isn't one? There's no particular reason that CountingService has to be @Observable -- it's just nice and clean.
Any suggestions would be appreciated!
Post not yet marked as solved
in the SampleTrips, for the pure SwiftData version, when I add a living accommodation from the trip detail view, the trip detail view won’t update until I go back to the trip list view and enter again.
why didn’t the trip detail view update?
I have tested the pure CoreData version in sample code, it worked well.
is this a bug or a feature of SwiftData?
Post not yet marked as solved
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!
Post not yet marked as solved
I get this error while migrating from ObservableObject to @Observable.
Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
My original code:
struct SomeView: View {
@StateObject private var viewModel = ViewModel()
}
After migration:
@MainActor @Observable class BaseViewModel {
}
@MainActor class ViewModel: BaseViewModel {
}
struct SomeView: View {
@State private var viewModel = ViewModel()
}
As discussed here. It seems like @StateObject is adding @MainActor compliance to my View under the hood because it's wrappedValue and projectedValue properties are marked as @MainActor, while on @State they are not.
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
@frozen @propertyWrapper public struct StateObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject {
...
@MainActor public var wrappedValue: ObjectType { get }
....
@MainActor public var projectedValue: ObservedObject<ObjectType>.Wrapper { get }
}
One solution for this is to mark my View explicitly as @MainActor struct ViewModel: View but it have it side effects, for example code like:
Button(action: resendButtonAction) {
Text(resendButtonAttributedTitle())
}
Will result a warning
Converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor 'MainActor'
While could be easily solved by using instead
Button(action: { resendButtonAction() } ) {
Text(resendButtonAttributedTitle())
}
I still feel like marking the whole View explicitly as @MainActor is not a good practice.
Adding fake @StateObject property to my view also do the trick, but it's a hack (the same for @FetchRequest).
Can anyone think of a more robust solution for this?
Post not yet marked as solved
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
Post not yet marked as solved
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
Post not yet marked as solved
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()
}
Post not yet marked as solved
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!")
}
}
}
}
}
}
Post not yet marked as solved
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?