DevB2F
DevB2F

Reputation: 5085

onReceive called infinitely in swiftUI

I am getting an infinite loop, onReceive is called infinitely when using a protocol with @Published variable. To use @Published variable in a protocol I am using this stackoverflow answer. This line in MyImageView:

if self.viewModel.answered {

seems to cause a call in

onReceive { self.viewModel.objectWillChange

inside ContentView.

Any idea how to make this work?

This is the entire code:

EnvObject:

import UIKit
import SwiftUI
import Combine
import Foundation

protocol MyProtocol: AnyObject {
    var answered: Bool { get set }
    var answeredPublished: Published<Bool> { get }
    var answeredPublisher: Published<Bool>.Publisher { get }
}

class GameViewModel: ObservableObject & MyProtocol {

    @Published var answered = false
    var answeredPublished: Published<Bool> { _answered }
    var answeredPublisher: Published<Bool>.Publisher { $answered }

}

Views:

import SwiftUI

struct MyImageView: View {
    @EnvironmentObject var viewModel: GameViewModel

    var body: some View {
        if self.viewModel.answered {
            print("not nil")
            return AnyView(Image("clearImage")
                .resizable()
                .scaledToFit()
            )
        }
        else {
            print("nil")
            return AnyView(
                Text(self.viewModel.answered ? "true" : "false")
            )
        }
    }
}

struct ViewBelow<ViewModel: ObservableObject & MyProtocol>: View {
    @EnvironmentObject var viewModel: ViewModel

    var body: some View {
        Text("below")
        .onReceive(self.viewModel.objectWillChange,

           perform: { _ in
               print("onReceive target")
           }
        )
    }
}


struct ContentView: View {
    @EnvironmentObject var viewModel: GameViewModel
    var body: some View {
        GeometryReader {
            geo in
            VStack {
                MyImageView()

                ViewBelow<GameViewModel>()
            }
        }
    }
}

SceneDelegate import UIKit import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        let contentView = ContentView().environmentObject(GameViewModel())

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

Upvotes: 2

Views: 1404

Answers (1)

DevB2F
DevB2F

Reputation: 5085

This worked for me:

class MyPublishedProperties {
    @Published var answered = false
}

protocol MyProtocol: MyPublishedProperties {
    func changePublishedPropertyValue(newValue: Bool)
}


class GameViewModel: ObservableObject & MyPublishedProperties, MyProtocol {

    func changePublishedPropertyValue(newValue: Bool) {
        answered = newValue
    }
}

Upvotes: 1

Related Questions