SwiftUI - onChange 没有被调用

tec*_*man 7 ios swift watchos swiftui

先添加代码有助于更好地理解问题。

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

如果“MainSetupView”打开并且我收到一条消息,即.showMessage,则会显示“ErrorHandlingView”,并且我在调用ErrorHandlingView时设置“messageType”和“messageText”。当显示第一条消息时,假设我收到另一条消息,并且再次调用“ErrorHandlingView”,现在使用不同的消息类型。然而,“ErrorHandlingView”中的“onChange(of: messageType)”永远不会被调用。

“onChange(of: messageType)”调用函数“getImageForError”,该函数根据 messageType 的值告诉应用程序要显示哪个图像。由于 onChange 中的代码没有被执行,因此“getImageForError”永远不会被调用,因此“messageViewImageDetails”永远不会被填充,这会导致视图没有图像。

知道为什么 .onChange() 没有被执行吗?我究竟做错了什么?

注意:我有很多代码,我正在分享一些我认为会有帮助的代码。如果需要更多代码,请告诉我。

编辑:添加 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)
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

tec*_*man 4

将 messageType 更改为 Binding var 而不是 State var 有效。分享未来更新的代码:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)