SwiftUI: представления с разным расположением элементов панели инструментов вызывают сбой приложения

#swiftui

#swiftui

Вопрос:

Я получаю сбой приложения при использовании представлений с разными размещениями элементов панели инструментов. Ниже приведен фрагмент кода, который позволяет воспроизвести сбой.

Поведение

Я использую 3 представления, каждое со своей собственной определенной панелью инструментов. homeView и profileView имеют ту же структуру панели инструментов. Каждый из них использует элемент панели инструментов с .bottomBar размещением. С addView другой стороны, элемент панели инструментов на панели инструментов помещен в действие `.cancellationAction’.

При нажатии viewState элемента панели инструментов значение в ViewModel изменяется. Это приводит к изменению текущего отображаемого представления.

Сообщение об ошибке:

[error] precondition failure: invalid attribute id: 70981 AttributeGraph precondition failure: invalid attribute id: 70981.

Цель

iPhone X 14.0 (18A373)

Фрагмент кода:

 import SwiftUI

struct SomeView: View {
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        NavigationView() {
            content
        }
    }
    
    private var content: AnyView {
        switch(viewModel.viewState) {
        case .home: return AnyView(homeView());
        case .add: return AnyView(addView());
        case .profile: return AnyView(profileView());
        }
    }
    
    func homeView() -> some View {
        Text("Home View")
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    HStack {
                        Button("Home", action: { viewModel.setViewState(state: .home) })
                        Button("Add", action: { viewModel.setViewState(state: .add) })
                        Button("Profile", action: { viewModel.setViewState(state: .profile) })
                    }
                }
            }
    }
    
    func addView() -> some View {
        Text("Add View")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel", action: { viewModel.setViewState(state: .home)})
                }
            }
    }
    
    func profileView() -> some View {
        Text("Profile View")
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    HStack {
                        Button("Home", action: { viewModel.setViewState(state: .home) })
                        Button("Add", action: { viewModel.setViewState(state: .add) })
                        Button("Profile", action: { viewModel.setViewState(state: .profile) })
                    }
                }
            }
    }
}

// MARK: View Model

extension SomeView {
    class ViewModel: ObservableObject {
        @Published var viewState: ViewState
        
        enum ViewState {
            case home, add, profile
        }
        
        init() {
            self.viewState = .home
        }
        
        func setViewState(state: ViewState) {
            self.viewState = state;
        }
    }
}

// MARK: Preview

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}
  

Ответ №1:

Да, для меня это похоже на ошибку.

Возможный обходной путь — поместить каждое представление в отдельный NavigationView :

 struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        content
    }

    @ViewBuilder
    var content: some View {
        switch viewModel.viewState {
        case .home: NavigationView { homeView() }
        case .add: NavigationView { addView() }
        case .profile: NavigationView { profileView() }
        }
    }

    ...
}
  

Кроме того, вы можете добавить нижнюю панель в addView :

 func addView() -> some View {
    Text("Add View")
        .toolbar {
            ToolbarItem(placement: .cancellationAction) {
                Button("Cancel", action: { viewModel.setViewState(state: .profile) })
            }
            ToolbarItem(placement: .bottomBar) {
                Text("")
            }
        }
}
  

или просто отобразите addView как sheet .