Home > Blockchain >  Access @StateObject from function outside of view
Access @StateObject from function outside of view

Time:11-07

I have a class (WatchlistClass) that conforms to the "ObservableObject" protocol and that holds a @Published var (watchlist). The var, in turn, holds a whole array of stocks and some information ([StockInformation]), which is supposed to update my view (WatchlistView) with the current stock prices and stuff. That part works perfectly fine, but the class should access the @StateObject in the view to change the data. Accessing it directly in the class doesn't work because it doesn't read the data from the @StateObject. I tried to access the @StateObject in the view directly but that also creates a new instance of the class that has an empty array. Using "static" on a @Published var throws an error. I can't, for the life of me, figure out how to access a @StateObject directly inside the view to read and modify the data that it holds. Any help would be appreciated.

class WatchlistClass: ObservableObject {
    static let shared = WatchlistClass()
    
    @Published var watchlist: [StockInformation] = []
    
    struct StockInformation: Identifiable {
        ...
    }
    

    func WatchlistTasksGetFundamentalsDelegate(string: String) {
        ...            
            DispatchQueue.main.async {
                self.watchlist.append(stockInformation) // This works and updates the view as expected
            }
        ...
    }


    private let timer = Timer.scheduledTimer(withTimeInterval: 4.0, repeats: true) { _ in
        if self.watchlist.isEmpty == false { // This does not work
            ...
    }
}
struct WatchlistView: View {
    @StateObject var watchlistClass = WatchlistClass()
    ...
}

CodePudding user response:

Your timer is an instance variable but its closure is not a instance of the class and has no self.

You're going to have to do something to put "self" into the scope of the timer's block. One way to do that would be to create the timer in a member function of the instance:

private func makeTimer() -> Timer {
    return Timer.scheduledTimer(withTimeInterval: 4.0, repeats: true) { [weak self] _ in
        if let empty = self?.watchlist.isEmpty,
            empty == false {
        }
    }
}

When you call makeTimer() your code will be executing in the context of an instance. It will have access to a self.

Note that I have changed your block so that it capture's self "weakly" because the timer could exist beyond the life of the object so you have to be proactive against that possibility.

You could call makeTimer from your initializer.

CodePudding user response:

When using a singleton pattern, you usually want to declare init on the object as private so you're sure you can only create one instance of it.

Then, to access that singleton version, you'll use WatchlistClass.shared:

class WatchlistClass: ObservableObject {
    static let shared = WatchlistClass()
    
    @Published var watchlist: [StockInformation] = []
    
    struct StockInformation: Identifiable {
        //...
    }
    
    private init() { }
}

struct WatchlistView: View {
    @StateObject var watchlistClass = WatchlistClass.shared
    
    var body: some View {
        //...
    }
}

In terms of your Timer, it would be helpful to have more information about what purpose it is serving here. For example, if it's just checking watchlist to react to it, my suggestion would be to use Combine to watch it instead of a Timer.

If the purpose of the singleton version was just to try to just access to the Timer and have it be able to access the array, maybe a pattern like this would be better:

import Combine

class WatchlistClass: ObservableObject {
    
    @Published var watchlist: [StockInformation] = []
    
    private var cancellable : AnyCancellable?
    
    struct StockInformation: Identifiable {
        var id = UUID()
        //...
    }
    
    public init() {
        cancellable = Timer.publish(every: 1, on: .main, in: .default)
            .autoconnect()
            .receive(on: RunLoop.main)
            .sink { _ in
                if !self.watchlist.isEmpty {
                    
                }
            }
    }
}

struct WatchlistView: View {
    @StateObject var watchlistClass = WatchlistClass()
    
    var body: some View {
        Text("Hello, world!")
    }
}

This would create the Timer on init and give it access to the instance's watchlist array.

  • Related