Отклонить представление в SwiftUI при повторном рендеринге родителя

Используя iOS14.4, Swift5.3.2, XCode12.2,

Я пытаюсь отклонить SwiftUI GridView (см. Код ниже).

Функция увольнения выполняется свойством @Environment как объяснено здесь .

Все работает до того момента, пока я не представил @Bindingсвойство, которое мутирует parent-View в самый момент увольнения. (видеть dataStr = titles[idx] в отрывке кода ниже).

Я прочитал это увольнение \.presentationMode работает только в том случае, если родительский View не обновляется во время отображения дочернего View.

Но мне абсолютно необходимо вызвать мутацию в родительском представлении, когда пользователь нажимает на элемент GridView, который здесь играет.

Как я могу переписать так, чтобы parent-View обновлялся, а отключение Child-View все еще работало?

      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()
        }
    }
}

Как спросил jn_pdx , здесь вы можете увидеть jn_pdx parent-View. Пожалуйста, найдите 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())
    }
}

1 ответ

С помощью jn_pdx я нашел обходной путь.

Оберните свойство привязки (например, в моем примере) в отложенный блок, который выполняется примерно через 50 мс:

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

Конечно, этот обходной путь работает только в моем случае, потому что мне больше не нужно держать дочернее представление открытым. Могут быть и другие ситуации, когда необходимо обновить Parent-View перед закрытием Child-View (т.е. здесь обновление dataStr можно сделать прямо в момент закрытия Child-View).

Мне все еще интересно, как справиться с проблемами увольнения в любом случае, когда Child-View выполняет обновление Parent-View перед закрытием. Это ситуации, когда функция закрытия SwiftUI с этого момента больше не работает. Любая мутация Parent-View приводит к тому, что Child-View separate как-то и dismisss больше не работает.

Есть идеи, что делать в таком случае?

Другие вопросы по тегам