重新渲染父级时在 SwiftUI 中关闭视图

问题描述

使用 iOS14.4、Swift5.3.2、XCode12.2,

我尝试关闭 SwiftUI 的 GridView(见下面的代码)。

解除功能\.presentationMode as explained here@Environment 属性完成。

一切正常,直到我引入了一个 @Binding 属性,该属性关闭的那一刻改变了父视图。 (请参阅下面代码摘录中的 dataStr = titles[idx])。

我读到 \.presentationMode 的解雇仅适用于在显示子视图期间父视图未更新的情况。

但是当用户点击此处播放的 GridView 元素时,我绝对需要在父视图上引起突变。

如何重写以便更新父视图并关闭子视图仍然有效?

struct GridView: View {
    
    @Environment(\.presentationMode) private var presentationMode
    
    @Binding var dataStr: String
    
    @State private var titles = [String]()
    
    let layout = [
        GridItem(.flexible()),GridItem(.flexible())
    ]
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: layout,spacing: 10) {
                ForEach(titles.indices,id: \.self) { idx in
                    vstack {
                        Text(titles[idx])
                        Image(titles[idx])
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(width: (UIScreen.main.bounds.width / 2) - 40)
                    }
                        .onTapGesture {

                            // WITHOUT THIS LINE OF CODE - EVERYTHING WORKS. WHY???????????????
                            dataStr = titles[idx]

                            self.presentationMode.wrappedValue.dismiss()
                        }
                }
            }
            .padding()
        }
    }
}

@jnpdx 所问,在这里您可以看到父视图。请在 GridView(dataStr: self.$dataStr)....

.sheet() 中找到 ToolBarItem()
import SwiftUI

struct MainView: View {
    
    @EnvironmentObject var mediaviewmodel: Mediaviewmodel
    @EnvironmentObject var commService: CommunicationService
    
    @State private var dataStr = ""
    @State private var connectionsLabel = ""
    @State private var commumincationRole: THRole = .noMode
    @State private var showingInfo = false
    @State private var showingGrid = false
    
    init() {
        UINavigationBar.appearance().tintColor = UIColor(named: "title")        
    }
    
    var body: some View {
        NavigationView {
            if mediaviewmodel.mediaList.isEmpty {
                LoadingAnimationView()                    
                    .navigationBarHidden(true)
                    .ignoresSafeArea()
            } else {
                if dataStr.isEmpty {
                    
                    MainButtonView(dataStr: $dataStr,commumincationRole: $commumincationRole,connectionsLabel: $connectionsLabel
                    )
                        .navigationBarHidden(false)
                        .navigationTitle("Trihow Pocket")
                        .navigationBarColor(backgroundColor: UIColor(named: "btnInactive"),titleColor: UIColor(named: "title"))
                        .toolbar {
                            ToolbarItem(placement: .navigationBarLeading) {
                                Button(action: {
                                    showingInfo.toggle()
                                }) {
                                    Image(systemName: "ellipsis")
                                }
                                .sheet(isPresented: $showingInfo) {
                                    InfoView()
                                }
                            }
                            ToolbarItem(placement: .navigationBarTrailing) {
                                Button(action: {
                                    showingGrid.toggle()
                                }) {
                                    Image(systemName: "square.grid.3x3")
                                }
                                .sheet(isPresented: $showingGrid) {

                                    // GRIDVIEW CALLING THE CHILD-VIEW IS HERE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                                    GridView(dataStr: self.$dataStr)
                                }
                            }
                    }
                } else {
                    let str = self.dataStr
                    #if os(iOS)
                    PageViewiOS(dataStr: self.$dataStr,commumincationRole: $commumincationRole)
                        .navigationBarHidden(true)
                        .onAppear() {
                            if commumincationRole == .moderatorMode {
                                commService.send(thCmd: THCmd(key: .tagID,sender: "",content: str))
                            }
                        }
                        .ignoresSafeArea()
                    #elseif os(macOS)
                    PageViewMacOS()
                        .ignoresSafeArea()
                    #endif
                }
            }
        }
        .onTHComm_PeerAction(service: commService) { (peers) in
            
            let idsOrNames = peers.map { (peer) -> String in
                if let id = peer.id {
                    return "\(id)"
                } else if let name = peer.name {
                    return "\(name)"
                } else {
                    return ""
                }
            }
            connectionsLabel = "Connected devices: \n\(idsOrNames.lineFeedString)"
        }
        .onTHComm_ReceiveCmd(service: commService) { (thCmd) in
            if (commumincationRole == .moderatorMode) || (commumincationRole == .discoveryMode) {
                switch thCmd.key {
                case .tagID:
                    dataStr = thCmd.content
                case .closeID:
                    dataStr = ""
                default:
                    break
                }
            }
        }
        .onTHComm_LastMessageLog(service: commService) { (log) in
            print(log)
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
            .environmentObject(Mediaviewmodel())
            .environmentObject(MultipeerConnectivityService())
    }
}

解决方法

@jnpdx 的帮助下,我找到了解决方法。

将绑定属性(即我的示例中的 dataStr)包装到一个延迟块中,该块在大约 50 毫秒后执行:

.onTapGesture {
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) {
        dataStr = thumNames[idx]
    }
    self.presentationMode.wrappedValue.dismiss()
}

当然,这个解决方法只适用于我的情况,因为我不再需要保持子视图打开。可能还有其他情况需要在关闭子视图之前更新父视图(即,此处 dataStr 的更新可以在子视图关闭时刻完成)。

对于子视图在关闭之前更新父视图的任何情况,我仍然想知道如何处理解雇问题。这些是 SwiftUI 的关闭功能从那时起不再起作用的情况。父视图的任何变化都会导致子视图以某种方式 separate 并且 dismisss 不再起作用。

知道在这种情况下该怎么做吗?