简体   繁体   中英

SwiftUI - onChange not getting called

Adding the code first to help better understand the problem.

struct MainSetupView: View {
    @StateObject var viewModel: MainViewModel
    
    var body: some View {
        VStack {
            switch viewModel.state {
            case .idle:
                Color.clear.onAppear(perform: { viewModel.prepare() })
            case .loading:
                ProgressView()
            case .showMessage(let messageType, let messageText):
                ErrorHandlingView(messageType: messageType, messageText: messageText)
            }
        }.onAppear() {
            viewModel.state = .idle
        }
    }
}

struct ErrorHandlingView: View {
    @State var messageType: String
    var messageText: String

    @State private var messageViewImageDetails: (imageName: String, isSystemImage: Bool) = (imageName: "", isSystemImage: false)

    var body: some View {
        VStack {
            MessageViewWithActions(imageName: messageViewImageDetails.imageName, isSystemImage: messageViewImageDetails.isSystemImage, mainMessageText: messageText)
        }.onChange(of: messageType) {_ in
            self.messageViewImageDetails = getImageForError()
            // call more functions
        }
    }
    
    private func getImageForError() -> (imageName: String, isSystemImage: Bool) {
        var imageName = ""
        var isSystemImage = false
        
        switch messageType {
        case "noInternetError":
            imageName = "xmark.icloud"
            isSystemImage = true
        case "userSignedOut":
            imageName = "person.crop.circle.fill.badge.xmark"
            isSystemImage = true
        case "userSignedIn":
            imageName = "person.crop.circle.badge.checkmark"
            isSystemImage = true
        // more cases.
        default:
            imageName = "info.circle"
            isSystemImage = true
        }
        
        return (imageName: imageName, isSystemImage: isSystemImage)
    }
}

If "MainSetupView" is open and I receive a message ie .showMessage, "ErrorHandlingView" is displayed and I set "messageType" and "messageText" while calling ErrorHandlingView. While first message is being displayed, let's say I receive another message and "ErrorHandlingView" gets called again, now with a different message type. However "onChange(of: messageType)" in "ErrorHandlingView" never gets called.

"onChange(of: messageType)" calls a function "getImageForError" which tells the app which image to display based on the value of messageType. Since code in onChange is not getting executed, "getImageForError" never gets called and hence "messageViewImageDetails" is never populated which results in view without image.

Any idea why is.onChange() not being executed? What am I doing wrong?

Note: I have lots of code and I am sharing pieces of code which I thought would be helpful. Let me know if more code is needed.

Edit : Adding code for MessageViewWithActions.

struct MessageViewWithActions: View {
    // Image properties
    var imageName: String?
    var isSystemImage: Bool = false
    
    // Text properties
    var mainMessageText: String?

    var body: some View {
        ScrollView {
            VStack {
                Text("")
                VStack {
                    if let imageName = imageName {
                        if isSystemImage {
                            Image(systemName: imageName)
                        } else {
                            Image(imageName)
                        }
                    }
                }.frame(maxWidth: .infinity).padding(.bottom, 8)
                
                if let mainMessageText = mainMessageText {
                    Text(mainMessageText).font(.subheadline).fixedSize(horizontal: false, vertical: true)
                }
            }
        }
    }
}

Changing messageType to Binding var instead of State var worked. Sharing updated code for future:

struct MainSetupView: View {
    @StateObject var viewModel: MainViewModel
    @State var messageType: String
    
    var body: some View {
        VStack {
            switch viewModel.state {
            case .idle:
                Color.clear.onAppear(perform: { viewModel.prepare() })
            case .loading:
                ProgressView()
            case .showMessage(_, let messageText):
                ErrorHandlingView(messageType: messageType, messageText: messageText)
            }
        }.onAppear() {
            viewModel.state = .idle
        }.onReceive(viewModel.$state, perform: { (value) in
            switch value {
            case .showMessage(let messageType, _):
                self.messageType = messageType
            case .idle, .loading:
                print("")
            }
        })
    }
}

struct ErrorHandlingView: View {
    @Binding var messageType: String
    var messageText: String

    @State private var messageViewImageDetails: (imageName: String, isSystemImage: Bool) = (imageName: "", isSystemImage: false)

    var body: some View {
        VStack {
            MessageViewWithActions(imageName: messageViewImageDetails.imageName, isSystemImage: messageViewImageDetails.isSystemImage, mainMessageText: messageText)
        }.onChange(of: messageType) {_ in
            self.messageViewImageDetails = getImageForError()
            // call more functions
        }
    }
    
    private func getImageForError() -> (imageName: String, isSystemImage: Bool) {
        var imageName = ""
        var isSystemImage = false
        
        switch messageType {
        case "noInternetError":
            imageName = "xmark.icloud"
            isSystemImage = true
        case "userSignedOut":
            imageName = "person.crop.circle.fill.badge.xmark"
            isSystemImage = true
        case "userSignedIn":
            imageName = "person.crop.circle.badge.checkmark"
            isSystemImage = true
        // more cases.
        default:
            imageName = "info.circle"
            isSystemImage = true
        }
        
        return (imageName: imageName, isSystemImage: isSystemImage)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM