SwiftUI Отключить определенный выбор tabItem в TabView?

У меня есть TabView, который представляет лист после нажатия на [+] (2nd) tabItem. В то же время ContentView также переключает выбор вкладки TabView, поэтому, когда я закрываю представленный лист, выбранная вкладка остается пустой без какого-либо содержимого. Не идеальный пользовательский интерфейс.

Мой вопрос:

Мне интересно, как я могу просто отключить этот конкретный tabItem, чтобы он не «вел себя как вкладка», а просто отображал лист, сохраняя предыдущий выбор вкладки до нажатия элемента [+]. Возможно ли это с помощью SwiftUI, или я должен использовать другой способ достижения этого эффекта?

Изображение моей панели вкладок:

введите здесь описание изображения

Вот код моего ContentView, а мой TabView:

struct SheetPresenter<Content>: View where Content: View {
    @EnvironmentObject var appState: AppState
    @Binding var isPresenting: Bool

    var content: Content
    var body: some View {
        Text("")
            .sheet(isPresented: self.$isPresenting, onDismiss: {
                // change back to previous tab selection
                print("New listing sheet was dismissed")

            }, content: { self.content})
            .onAppear {
                DispatchQueue.main.async {
                    self.isPresenting = true
                    print("New listing sheet appeared with previous tab as tab \(self.appState.selectedTab).")
                }
            }
    }
}

struct ContentView: View {
    @EnvironmentObject var appState: AppState
    @State private var selection = 0
    @State var newListingPresented = false

    var body: some View {

        $appState.selectedTab back to just '$selection'
        TabView(selection: $appState.selectedTab){

            // Browse
            BrowseView()
                .tabItem {
                    Image(systemName: (selection == 0 ? "square.grid.2x2.fill" : "square.grid.2x2")).font(.system(size: 22))
                }
                .tag(0)


            // New Listing
            SheetPresenter(isPresenting: $newListingPresented, content: NewListingView(isPresented: self.$newListingPresented))
                .tabItem {
                    Image(systemName: "plus.square").font(.system(size: 22))
                }
                .tag(1)

            // Bag
            BagView()
                .tabItem {
                    Image(systemName: (selection == 2 ? "bag.fill" : "bag")).font(.system(size: 22))
                }
                .tag(2)

            // Profile
            ProfileView()
                .tabItem {
                    Image(systemName: (selection == 3 ? "person.crop.square.fill" : "person.crop.square")).font(.system(size: 22))
                }
                .tag(3)
        }.edgesIgnoringSafeArea(.top)
    }
}

А вот AppState:

final class AppState: ObservableObject {

    @Published var selectedTab: Int = 0
}

person Steven Schafer    schedule 31.12.2019    source источник


Ответы (3)


Вы довольно близки к тому, чего хотите достичь. Вам просто нужно будет сохранить предыдущий выбранный индекс вкладки и сбросить текущий выбранный индекс вкладки с этим сохраненным значением во время закрытия листа. Это означает:

.sheet(isPresented: self.$isPresenting, onDismiss: {
    // change back to previous tab selection
    self.appState.selectedTab = self.appState.previousSelectedTab
}, content: { self.content })

Так как же отслеживать индекс последней выбранной вкладки, который синхронизируется со свойством selectedTab AppState? Возможно, есть и другие способы сделать это с помощью API из самого Combine фреймворка, но самое простое решение, которое приходит мне на ум:

final class AppState: ObservableObject {
    // private setter because no other object should be able to modify this
    private (set) var previousSelectedTab = -1 
    @Published var selectedTab: Int = 0 {
        didSet {
            previousSelectedTab = oldValue
        }
    }
}

Предостережения:

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

person nayem    schedule 01.01.2020
comment
Это дает тот же эффект, что и я. Простое решение с использованием обозревателя свойств! Спасибо - person Steven Schafer; 02.01.2020

Вы можете добавить что-нибудь в закрытие листа, чтобы переключить tabView на другие вкладки. Может быть, вы можете вставить какую-нибудь анимацию в процессе.

struct SheetPresenter<Content>: View where Content: View {

    @EnvironmentObject var appState: AppState
    @Binding var isPresenting: Bool
    @Binding var showOtherTab: Int
    var content: Content

    var body: some View {
        Text("")
        .sheet(isPresented: self.$isPresenting,
               onDismiss: {
                   // change back to previous tab selection
                   self.showOtherTab = 0
               } ,
               content: { self.content})
        .onAppear {
            DispatchQueue.main.async {
                self.isPresenting = true
                print("New listing sheet appeared with previous tab as tab \(self.appState.selectedTab).")
            }
        }
    }
}

struct ContentView: View {

    @EnvironmentObject var appState: AppState
    @State private var selection = 0
    @State var newListingPresented = false

    var body: some View {
        //  $appState.selectedTab back to just '$selection'
        TabView(selection: $appState.selectedTab){
            // Browse
            Text("BrowseView") //BrowseView()
                .tabItem {
                    Image(systemName: (selection == 0 ? "square.grid.2x2.fill" : "square.grid.2x2"))
                        .font(.system(size: 22))
            }   .tag(0)
            // New Listing
            SheetPresenter(isPresenting: $newListingPresented,
                           showOtherTab: $appState.selectedTab,
                           content: Text("1232"))//NewListingView(isPresented: self.$newListingPresented))
                .tabItem {
                    Image(systemName: "plus.square")
                        .font(.system(size: 22))
                }   .tag(1)
            // Bag
            //  BagView()
            Text("BAGVIEW")
                .tabItem {
                    Image(systemName: (selection == 2 ? "bag.fill" : "bag"))
                        .font(.system(size: 22))
            }   .tag(2)
            // Profile
            Text("ProfileView") //   ProfileView()
                .tabItem {
                     Image(systemName: (selection == 3 ? "person.crop.square.fill" : "person.crop.square"))
                         .font(.system(size: 22))
            }   .tag(3)
        }   .edgesIgnoringSafeArea(.top)
    }
}
person E.Coms    schedule 31.12.2019
comment
Это работает в том смысле, что вкладка [+] не выбрана, однако ранее выбранная вкладка не сохраняется, поэтому это не совсем то, что я ищу. Как мне сохранить выбранную вкладку до нажатия [+]? - person Steven Schafer; 01.01.2020
comment
self.showOtherTab = 0, вы можете использовать tabItem для замены 0 - person E.Coms; 02.01.2020

Я смог воспроизвести следующее поведение вкладок Instagram с помощью SwiftUI и MVVM:

  1. когда выбрана средняя вкладка, откроется модальное представление
  2. при закрытии средней вкладки снова выбирается ранее выбранная вкладка, а не средняя вкладка

A. ViewModels (одна для всей вкладки, другая для конкретной вкладки)

import Foundation

class TabContainerViewModel: ObservableObject {
    
    //tab with sheet that will not be selected
    let customActionTab: TabItemViewModel.TabItemType = .addPost
    
    //selected tab: this is the most important code; here, when the selected tab is the custom action tab, set the flag that is was selected, then whatever is the old selected tab, make it the selected tab
    @Published var selectedTab: TabItemViewModel.TabItemType = .feed {
        didSet{
            if selectedTab == customActionTab {
                customActionTabSelected = true
                selectedTab = oldValue
            }
        }
    }
    //flags whether the middle tab is selected or not
    var customActionTabSelected: Bool = false
    
    //create the individual tabItemViewModels that will get displayed
    let tabItemViewModels:[TabItemViewModel] = [
        TabItemViewModel(imageName:"house.fill", title:"Feed", type: .feed),
        TabItemViewModel(imageName:"magnifyingglass.circle.fill", title:"Search", type: .search),
        TabItemViewModel(imageName:"plus.circle.fill", title:"Add Post", type: .addPost),
        TabItemViewModel(imageName:"heart.fill", title:"Notifications", type: .notifications),
        TabItemViewModel(imageName:"person.fill", title:"Profile", type: .profile),
    ]
}

//this is the individual tabitem ViewModel
import SwiftUI

struct TabItemViewModel: Hashable {
    let imageName:String
    let title:String
    let type: TabItemType
    
    enum TabItemType {
        case feed
        case search
        case addPost
        case notifications
        case profile
    }
}

Б. Просмотр (использует ViewModels)

import SwiftUI

struct TabContainerView: View {
    
    @StateObject private var tabContainerViewModel = TabContainerViewModel()
    
    @ViewBuilder
    func tabView(for tabItemType: TabItemViewModel.TabItemType) -> some View {
        switch tabItemType {
        case .feed:
            FeedView()
        case .search:
            SearchView()
        case .addPost:
            AddPostView(tabContainerViewModel: self.tabContainerViewModel)
        case .notifications:
            NotificationsView()
        case .profile:
            ProfileView()
        }
    }
    
    var body: some View {
        TabView(selection: $tabContainerViewModel.selectedTab){
            ForEach(tabContainerViewModel.tabItemViewModels, id: \.self){ viewModel in
                tabView(for: viewModel.type)
                    .tabItem {
                        Image(systemName: viewModel.imageName)
                        Text(viewModel.title)
                    }
                    .tag(viewModel.type)
            }
        }
        .accentColor(.primary)
        .sheet(isPresented: $tabContainerViewModel.customActionTabSelected) {
            PicsPicker()
        }
    }
}

struct TabContainerView_Previews: PreviewProvider {
    static var previews: some View {
        TabContainerView()
    }
}

Примечание. В ходе расследования я попытался добавить код в onAppear на средней вкладке. Однако я обнаружил, что в SwiftUI есть текущая ошибка, которая запускает onAppear, даже если была нажата другая вкладка. Таким образом, описанный выше способ кажется лучшим.

Удачного кодирования!

Использованная литература:

  1. https://www.youtube.com/watch?v=SZj3CjMfT-8
person Meo Flute    schedule 11.11.2020