我有一个自定义 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)
}
}
}
好的,这里有一些建议:
现在解释:
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
以避免错误。所以你必须更新你的主体,这样它实际上只包含块调用和显示逻辑,这将更具可读性/可更新性。
太多状态,您最好创建为您的大视图实现
ObservableObject
的 viewModel 并将所有状态保留为 @Published
并在视图中使用此视图模型作为 @StateObject 并联系 viewModel.someVar
中的值或将状态绑定到渲染为 $viewModel.someVar
,视图应该尽可能轻,因此从中删除所有变量。
不需要zIndex,ZStack中的视图顺序就足够了,如果视图顺序为1,2,3,那么3将是最上面的,2在它下面,1在3下面,2
避免索引,因为在某些情况下您可能会遇到意外崩溃,并了解 SwiftUI 如何更新 foreach 中的视图,了解什么是
Identifiable
以及如何控制它,为集合模型提供自定义 id,您可以使其超级平滑如果你做对了。
您的警报的解决方案是将其放在
ZStack
的最底部,+以使其平滑添加带有您想要的过渡动画的.transition
修改器。