如何在 SwiftUI 中的 TabBar 上显示提醒?

问题描述 投票:0回答:1

我有一个自定义 TabBar 视图,并且有一个选项卡 (MyGamesView),上面有自定义单元格。该单元格中有一个按钮。通过单击该按钮,我必须显示警报并调用 API。但我的问题是,如果我从该选项卡(MyGamesView)显示警报,那么它的外观如下:

如果我在 MainTabBarView 上显示我的警报,那么它看起来不错。但我必须在 API 调用中传递所选单元格的 ID。我无法将所选单元格的 id 传递到 MainTabBarView 上。但从 MyGamesView 呈现警报看起来不太好。

如何从 MyGamesView 中呈现提醒?我已经尝试过 .zIndex(1) 处于警报状态,但它不起作用。如果有人可以帮助我,我将不胜感激!

这是我的 MainTabBarView:

struct MainTabBarView: View {
    // MARK: - HIDING NATIVE TAB BAR
    init(){
        UITabBar.appearance().isHidden = true
    }
    
    // MARK: - Variables
   @Environment(\.rootPresentationMode) private var rootPresentationMode: Binding<RootPresentationMode>
   @EnvironmentObject private var alertManager: AlertManager
   @State private var tabBarItems = [
       TabBarItems(imageIcon: "home_icon", selectedImageIcon: "homeSelected_icon", title: "Home"),
       TabBarItems(imageIcon: "calendar_icon", selectedImageIcon: "calendarSelected_icon", title: "My Games"),
       TabBarItems(imageIcon: "notification_icon", selectedImageIcon: "notificationSelected_icon", title: "Notification"),
       TabBarItems(imageIcon: "setting_icon", selectedImageIcon: "settingSelected_icon", title: "Setting"),
   ]
   @State private var selectedTab = 0
   @State private var goToCreateMatch: Bool = false
   @State private var isLoading = false
   @State private var suggestHomeCourt: String = ""
   @StateObject var bannerVM = BannerViewModel()
       
    var body: some View {
        NavigationView {
            ZStack(alignment: .bottom) {
                TabView(selection: $selectedTab) {
                    HomeView()
                        .tag(0)
                        .environmentObject(alertManager)
                    MyGamesView()
                        .tag(1)
                        .environmentObject(alertManager)
                    NotificationView()
                        .tag(2)
                    SettingView()
                        .tag(3)
                        .environmentObject(alertManager)
                }
                .zIndex(0)
                
                // MARK: - Custom Alert for Delete Account
                if self.alertManager.presentAlertForDeleteAccount {
                    VStack {
                        CustomAlertView(alertTitle: "Delete Account", showMsg: true, alertMessage: "Are you sure you want to delete the account? You will lose all records and you cannot restore it again later.", nonFilledButtonTitle: "Cancel", filledButtonTitle: "Delete", nonFilledButtonAction: {
                             // MARK: - Button Cancel Action (Delete Account)
                            withAnimation {
                                self.alertManager.presentAlertForDeleteAccount.toggle()
                            }
                        }, filledButtonAction: {
                            // MARK: - Button Delete Action (Delete Account)
                            withAnimation {
                                self.deleteAccountApiCall()
                            }
                        }, presentAlert: $alertManager.presentAlertForDeleteAccount)
                    }
                    .zIndex(1)
                }
                
                // MARK: - Custom Alert for Logout
                if self.alertManager.presentAlertForLogOut {
                    VStack {
                        CustomAlertView(alertTitle: "Are you sure you want to Log Out?", nonFilledButtonTitle: "Cancel", filledButtonTitle: "Log Out", nonFilledButtonAction: {
                            // MARK: - Button Cancel Action (Logout)
                            withAnimation {
                                self.alertManager.presentAlertForLogOut.toggle()
                            }
                        }, filledButtonAction: {
                            // MARK: - Button Logout Action (Logout)
                            withAnimation {
                                self.logOutApiCall()
                            }
                        }, presentAlert: $alertManager.presentAlertForLogOut)
                    }
                    .zIndex(1)
                }
                
                GeometryReader { proxy in
                    VStack {
                        Spacer()
                        HStack(alignment: .bottom, spacing: 20) {
                            Spacer()
                            ForEach(0..<2) { index in
                                Button{
                                    selectedTab = index
                                } label: {
                                    CustomTabItem(imageName: tabBarItems[index].imageIcon, selectedImageName: tabBarItems[index].selectedImageIcon, title: tabBarItems[index].title, isActive: (selectedTab == index))
                                }
                            }
                           Spacer()
                           Spacer()
                            ForEach(2..<self.tabBarItems.count) { index in
                                Button{
                                    selectedTab = index
                                } label: {
                                    CustomTabItem(imageName: tabBarItems[index].imageIcon, selectedImageName: tabBarItems[index].selectedImageIcon, title: tabBarItems[index].title, isActive: (selectedTab == index))
                                }
                            }
                            Spacer()
                        }
                        .font(.footnote)
                        .padding(.top, 42)
                        .overlay(alignment: .top) {
                            VStack(spacing: -3) {
                                 // MARK: - Button Create
                                Button {
                                    self.goToCreateMatch = true
                                } label: {
                                    Image("plus_icon")
                                        .resizable()
                                        .scaledToFit()
                                        .padding()
                                        .frame(width: 60, height: 60)
                                        .foregroundStyle(.white)
                                        .background {
                                            Circle()
                                                .fill(Color.custom64B054Color)
                                                .shadow(radius: 3)
                                        }
                                }
                                .padding(9)
                                Text("Create")
                                    .latoRegularFont(size: 12)
                                    .foregroundColor(Color.black.opacity(0.6))
                            }
                        }
                        .padding(.bottom, max(32, proxy.safeAreaInsets.bottom))
                        .background {
                            CustomTabBarShape()
                                .fill(.white)
                                .shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: -1)
                        }
                    }
                    .ignoresSafeArea(edges: .bottom)
                }
                
                // MARK: - Navigate to Create Match
                NavigationLink("", destination: CreateMatchView().navigationBarHidden(true).navigationBarBackButtonHidden(true), isActive: $goToCreateMatch)
            }
            .ignoresSafeArea()
            .navigationBarHidden(true)
            .navigationBarBackButtonHidden(true)
        }
    }
    
}

这是我的 MyGamesView:

struct MyGamesView: View {
    // MARK: - Variables
    @State private var playerName: String = ""
    @State private var pastShown: Bool = false
    @State private var presentAlert: Bool = false
    @State private var goToGroupChat: Bool = false
    private var items = ["Upcoming", "Past"]
    @GestureState private var dragState = CGSize.zero
    @EnvironmentObject private var alertManager: AlertManager
    @State private var userData = UtilityMethods.getUserData()
    @StateObject private var matchListViewModel = MatchListViewModel()
    @State private var selectedMatch: MatchListModel?
    @State private var isLoading = false
    @StateObject var bannerVM = BannerViewModel()
    @State private var type: String = "2" //type(1=>all, 2=>my games upcoming, 3=>my games past)
    @State private var page: Int = 1
    
    var body: some View {
            ZStack {
                VStack {
                    HStack {
                        VStack(alignment: .leading) {
                            Text("Hello")
                                .robotoRegularFont(size: 14)
                                .foregroundColor(Color.custom64B054Color)
                            Text("\(self.userData?.firstName ?? "") \(self.userData?.lastName ?? "")")
                                .robotoRegularFont(size: 14)
                                .foregroundColor(Color.custom333333Color)
                        }
                        Spacer()
                        Image("appIcon_icon")
                            .resizable()
                            .frame(width: 32, height: 32)
                    }
                    .padding(.top, 24)
                    
                    // MARK: - Upcoming and Past Views
                    VStack(spacing: 8) {
                        HStack(alignment: .center, spacing: 0) {
                            Spacer()
                            
                            // MARK: - Button Upcoming
                            ZStack {
                                Button {
                                    self.pastShown = false
                                    self.type = "2"
                                    DispatchQueue.main.async {
                                        self.matchListApiCall(type: self.type)
                                    }
                                } label: {
                                    Text("Upcoming")
                                        .font(.custom(self.pastShown ? "Roboto-Light" : "Roboto-Medium", size: 15))
                                        .foregroundColor(self.pastShown ? .black.opacity(0.7) : .black)
                                }
                            }
                            .frame(width: UIScreen.main.bounds.width / 2 - 18)
                            Spacer()
                            Spacer()
                            
                            // MARK: - Button Past
                            ZStack {
                                Button {
                                    self.pastShown = true
                                    self.type = "3"
                                    DispatchQueue.main.async {
                                        self.matchListApiCall(type: self.type)
                                    }
                                } label: {
                                    Text("Past")
                                        .font(.custom(self.pastShown ? "Roboto-Medium" : "Roboto-Light", size: 15))
                                        .foregroundColor(self.pastShown ? .black : .black.opacity(0.7))
                                }
                            }
                            .frame(width: UIScreen.main.bounds.width / 2 - 18)
                            Spacer()
                        }
                        
                        let upcoming = Capsule()
                            .fill(Color.black.opacity(self.pastShown ? 0.2 : 1))
                            .frame(maxWidth: .infinity, maxHeight: 1.5)
                        let past = Capsule()
                            .fill(Color.black.opacity(self.pastShown ? 1 : 0.2))
                            .frame(maxWidth: .infinity, maxHeight: 1.5)
                        
                        HStack(alignment: .center, spacing: 0) {
                            upcoming
                            past
                        }
                    }
                    .padding(.top, 8)
                    .padding(.horizontal, -18)
                    
                    ScrollView(.vertical, showsIndicators: false) {
                        // MARK: - Past View
                        if self.pastShown {
                            LazyVStack(spacing: 20) {
                                ForEach(self.matchListViewModel.matchListData.indices, id: \.self) { index in
                                    MyGamesMatchInfoCell(matchListModel: self.matchListViewModel.matchListData[index], isPastMatch: true, messageButtonAction: {
                                        self.goToGroupChat.toggle()
                                    })
                                }
                            }
                            .padding(.bottom, 100)
                        } else {
                            // MARK: - Upcoming View
                            LazyVStack(spacing: 20) {
                                ForEach(self.matchListViewModel.matchListData.indices, id: \.self) { index in
                                    MyGamesMatchInfoCell(matchListModel: self.matchListViewModel.matchListData[index], messageButtonAction: {
                                        self.goToGroupChat.toggle()
                                    }, addGuestButtonAction: {
                                        withAnimation {
                                            self.selectedMatch = self.matchListViewModel.matchListData[index]
                                            self.alertManager.presentAlertForAddGuestOnUpcomingView.toggle()
                                        }
                                    }, leaveMatchButtonAction: {
                                        withAnimation {
                                            self.selectedMatch = self.matchListViewModel.matchListData[index]
                                            self.alertManager.presentAlertForLeaveMatch.toggle()
                                        }
                                    })
                                }
                            }
                            .padding(.bottom, 100)
                        }
                    }
                    .padding(.top)
                    .ignoresSafeArea()
                }
                .padding(.horizontal, 18)
                
                 // MARK: - Swipe Gesture to switch between Upcoming and Past views
                .gesture(DragGesture()
                    .onEnded { value in
                        print("value ",value.translation.width)
                        let direction = Utility.shared.detectDirection(value: value)
                        if direction == .left {
                            print("Upcoming action")
                            self.pastShown = false
                            self.type = "2"
                            DispatchQueue.main.async {
                                self.matchListApiCall(type: self.type)
                            }
                        }
                        if direction == .right {
                            print("Past action")
                            self.pastShown = true
                            self.type = "3"
                            DispatchQueue.main.async {
                                self.matchListApiCall(type: self.type)
                            }
                        }
                    }
                )
                
                if self.isLoading {
                    ActivityIndicator()
                }
                
                // MARK: - Custom Alert for Add Guest on UpcomingView
                if self.alertManager.presentAlertForAddGuestOnUpcomingView {
                    VStack {
                        CustomAlertView(alertTitle: "Are you sure you want to add a Guest in this match?", nonFilledButtonTitle: "No", filledButtonTitle: "Yes", nonFilledButtonAction: {
                            // MARK: - Button No Action
                            withAnimation {
                                self.alertManager.presentAlertForAddGuestOnUpcomingView.toggle()
                            }
                        }, filledButtonAction: {
                            // MARK: - Button Yes Action
                            withAnimation {
                                self.joinMatchApiCall(matchId: "\(self.selectedMatch?.id ?? 0)", type: "1", status: "2")
                            }
                        }, presentAlert: $alertManager.presentAlertForAddGuestOnUpcomingView)
                    }
                    .zIndex(1)
                }
                
                // MARK: - Navigate to Group Chat
                NavigationLink("", destination: GroupChatView().navigationBarHidden(true).navigationBarBackButtonHidden(true), isActive: $goToGroupChat)
            }
            .banner(data: $bannerVM.bannerData, show: $bannerVM.showBanner)
        
        // MARK: - onAppear Method
            .onAppear {
                self.userData = UtilityMethods.getUserData()
                self.initialDetails()
            }
        
            .navigationBarHidden(true)
            .navigationBarBackButtonHidden(true)
    }
}

这是我的自定义 AlertView:

struct CustomAlertView: View {
    // MARK: - Variables
    var alertTitle: String
    var showMsg: Bool? = false
    var isOneButton: Bool? = false
    var alertMessage: String? = ""
    var nonFilledButtonTitle: String
    var filledButtonTitle: String
    var nonFilledButtonAction: (()->Void)?
    var filledButtonAction: (()->Void)?
    @Binding var presentAlert: Bool
    
    var body: some View {
        if self.presentAlert {
            ZStack {
                Color.black.opacity(0.58)
                    .edgesIgnoringSafeArea(.all)
                    .onTapGesture {
                        self.presentAlert = false
                    }
                VStack(alignment: .center) {
                     // MARK: - Alert Title
                    Text(self.alertTitle)
                        .robotoBoldFont(size: 16)
                        .foregroundColor(Color.black.opacity(0.94))
                        .multilineTextAlignment(.center)
                    
                    if self.showMsg ?? false {
                        Text(self.alertMessage ?? "")
                            .robotoBoldFont(size: 12)
                            .foregroundColor(Color.customA5A5A5Color)
                            .multilineTextAlignment(.center)
                            .padding(.top, 1)
                            .padding(.bottom, 8)
                    }
                    
                    VStack {
                        Rectangle()
                            .fill(Color.black.opacity(0.1))
                            .frame(maxWidth: .infinity, maxHeight: 1)
                    }
    //                Divider()
                    .padding(.bottom, 20)
                    HStack(spacing: 24) {
                        Spacer()
                        if self.isOneButton ?? false {
                            // MARK: - Filled Button
                            Button {
                                self.filledButtonAction?()
                            } label: {
                                Text("Okay")
                                    .robotoBoldFont(size: 16)
                                    .foregroundColor(Color.white)
                                    .padding()
                                    .background(
                                        Rectangle()
                                            .foregroundColor(.clear)
                                            .frame(width: 115, height: 44)
                                            .background(Color.custom64B054Color)
                                            .cornerRadius(12)
                                            .overlay(
                                                RoundedRectangle(cornerRadius: 12)
                                                    .stroke(Color.custom64B054Color.opacity(0.5), lineWidth: 1)
                                            )
                                    )
                            }
                            Spacer()
                        } else {
                            // MARK: - Non-Filled Button
                           Button {
                               self.nonFilledButtonAction?()
                           } label: {
                               Text(self.nonFilledButtonTitle)
                                   .robotoBoldFont(size: 16)
                                   .foregroundColor(Color.black.opacity(0.94))
                                   .padding()
                                   .background(
                                       Rectangle()
                                           .foregroundColor(.clear)
                                           .frame(width: 115, height: 44)
                                           .background(.white)
                                           .cornerRadius(12)
                                           .overlay(
                                               RoundedRectangle(cornerRadius: 12)
                                                   .stroke(Color.custom64B054Color.opacity(0.5), lineWidth: 1)
                                           )
                                   )
                           }
                           Spacer()
                           
                           // MARK: - Filled Button
                           Button {
                               self.filledButtonAction?()
                           } label: {
                               Text(self.filledButtonTitle)
                                   .robotoBoldFont(size: 16)
                                   .foregroundColor(Color.white)
                                   .padding()
                                   .background(
                                       Rectangle()
                                           .foregroundColor(.clear)
                                           .frame(width: 115, height: 44)
                                           .background(Color.custom64B054Color)
                                           .cornerRadius(12)
                                           .overlay(
                                               RoundedRectangle(cornerRadius: 12)
                                                   .stroke(Color.custom64B054Color.opacity(0.5), lineWidth: 1)
                                           )
                                   )
                           }
                           Spacer()
                        }
                    }
                    .padding(.bottom, 10)
                    .frame(maxWidth: .infinity)
                }
                .padding()
                .frame(width: UIScreen.main.bounds.width - 36)
                .background(
                    Color.white
                )
                .cornerRadius(20)
            }
            .zIndex(2)
        }
    }
}
swift swiftui alert z-index tabbar
1个回答
0
投票

好的,这里有一些建议:

  1. 你的观点太大了!很难分析和看到一些东西
  2. 视图中的 @State 值太多。
  3. 停止使用 zIndexes,根本不需要。
  4. 尽量避免在 ForEach 中使用索引

现在解释:

  1. 你应该使用子变量(推荐)或子函数(如果你需要传递一些东西来构建块)来分割你的视图。 例如在你的身体下面添加:
private var pastShownView: some View {
    LazyVStack(spacing: 20) {                            
        ForEach(self.matchListViewModel.matchListData.indices, 
                id: \.self) { index in
            MyGamesMatchInfoCell(
                matchListModel: self.matchListViewModel.matchListData[index], 
                isPastMatch: true, 
                messageButtonAction: {
                    self.goToGroupChat.toggle()
                }
            )
        }
    }
    .padding(.bottom, 100)
}

然后在代码中你将得到:

if self.pastShown {
    pastShownView
}

如果您在块中返回不同的视图,则在私有之前使用

@ViewBuilder
以避免错误。所以你必须更新你的主体,这样它实际上只包含块调用和显示逻辑,这将更具可读性/可更新性。

  1. 太多状态,您最好创建为您的大视图实现

    ObservableObject
    的 viewModel 并将所有状态保留为
    @Published
    并在视图中使用此视图模型作为 @StateObject 并联系
    viewModel.someVar
    中的值或将状态绑定到渲染为
    $viewModel.someVar
    ,视图应该尽可能轻,因此从中删除所有变量。

  2. 不需要zIndex,ZStack中的视图顺序就足够了,如果视图顺序为1,2,3,那么3将是最上面的,2在它下面,1在3下面,2

  3. 避免索引,因为在某些情况下您可能会遇到意外崩溃,并了解 SwiftUI 如何更新 foreach 中的视图,了解什么是

    Identifiable
    以及如何控制它,为集合模型提供自定义 id,您可以使其超级平滑如果你做对了。

您的警报的解决方案是将其放在

ZStack
的最底部,+以使其平滑添加带有您想要的过渡动画的
.transition
修改器。

© www.soinside.com 2019 - 2024. All rights reserved.