I am working on developing a quiz app. I have the following code, in two SwiftUI Views. Right now, the entire screen background color changes if you get an answer correct(to green)/incorrect(to red) but I want only the button background color to change, and the background of the screen to remain white. How do I implement this in the code?
Content View Swift:
import SwiftUI
struct ContentView: View {
let question = Question(questionText: "What was the first computer bug?", possibleAnswers: ["Ant", "Beetle", "Moth", "Fly"], correctAnswerIndex: 2)
#State var mainColor = Color(red: 255/255, green: 255/255, blue: 255/255)
#State var textBackgroundColor = Color.white
var body: some View {
ZStack {
mainColor.ignoresSafeArea()
VStack{
Text("Question 1 of 10")
.font(.callout)
.foregroundColor(.gray)
.padding(.bottom, 1)
.padding(.trailing, 170)
.multilineTextAlignment(.leading)
Text(question.questionText)
.font(.largeTitle)
.multilineTextAlignment(.leading)
.bold()
.background(textBackgroundColor)
Spacer()
.frame(height: 200)
VStack(spacing: 20){
ForEach(0..<question.possibleAnswers.count) { answerIndex in
Button(action: {
print("Tapped on option with the text: \(question.possibleAnswers[answerIndex])")
mainColor = answerIndex == question.correctAnswerIndex ? .green : .red
textBackgroundColor = answerIndex == question.correctAnswerIndex ? .green : .red
print(textBackgroundColor)
}, label: {
ChoiceTextView(choiceText: question.possibleAnswers[answerIndex])
})
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ChoiceTextView:
import SwiftUI
struct ChoiceTextView: View {
let choiceText: String
let accentColor = Color(red: 48/255, green: 105/255, blue: 240/255)
#State var textBackgroundColor = Color.white
var body: some View {
Text(choiceText)
.frame(width:250)
.foregroundColor(.black)
.padding()
// make background white to put shadow later
.background(textBackgroundColor)
.cornerRadius(10)
.shadow(color: Color(hue: 1.0, saturation: 0.0, brightness: 0.869), radius: 5, x: 0, y: 5)
}
}
struct ChoiceTextView_Previews: PreviewProvider {
static var previews: some View {
ChoiceTextView(choiceText: "Choice Text!", textBackgroundColor: Color.white)
}
}
Please help me figure this out!
I think this code does what you are trying to achieve.
Basically, avoid using the color to the main view's background and instead, put it in the button background modifier.
Also, instead of using the index of the array I use the value. I think it's easier to identify plus it avoids the ForEach warning that shows up for using a non fixed Int value.
struct Question {
let questionText: String
let possibleAnswers: [String]
let correctAnswer: String
}
struct ContentView: View {
let question = Question(
questionText: "What was the first computer bug?",
possibleAnswers: ["Ant", "Beetle", "Moth", "Fly"],
correctAnswer: "Beetle"
)
var body: some View {
ZStack {
VStack{
Text("Question 1 of 10")
.font(.callout)
.foregroundColor(.gray)
.padding(.bottom, 1)
.padding(.trailing, 170)
.multilineTextAlignment(.leading)
Text(question.questionText)
.font(.largeTitle)
.multilineTextAlignment(.leading)
.bold()
Spacer()
.frame(height: 200)
VStack(spacing: 20){
ForEach(question.possibleAnswers, id: \.self) { answer in
ChoiceButton(
answer: answer,
question: question
)
}
}
}
}
}
}
struct ChoiceButton: View {
let answer: String
let question: Question
#State private var mainColor = Color(red: 255/255, green: 255/255, blue: 255/255)
var body: some View {
Button {
print("Tapped on option with the text: \(answer)")
mainColor = answer == question.correctAnswer ? .green : .red
} label: {
Text(answer)
.frame(width:250)
.foregroundColor(.black)
.padding()
// make background white to put shadow later
.background(mainColor)
.cornerRadius(10)
.shadow(color: Color(hue: 1.0, saturation: 0.0, brightness: 0.869), radius: 5, x: 0, y: 5)
}
}
}
I use a systemImage, with a onTapGesture function attached to it, to switch a boolean Variable to true. When that boolean variable is true, the view is changed. I positioned that systemImage at the top left part of the screen, using position(x:,y:) function. However, onTapGesture does not work when the value for "y" is bellow 100.
The code:
import SwiftUI
import FirebaseFirestoreSwift
import Firebase
struct ChatView: View {
#Environment(\.presentationMode) var presentationMode
#StateObject var homeData:HomeModel
#State var queryView:Bool = false
#EnvironmentObject var model:ContentModel
var user = Auth.auth().currentUser
let db = Firestore.firestore()
//If it is the first time when user scrolls
#State var scrolled = false
// #GestureState var isLongPressed = false
var body: some View {
if queryView == false {
VStack(spacing: 0) {
Text("\(homeData.query) Global Chat").foregroundColor(Color(#colorLiteral(red: 0.5951357484, green: 0.5694860816, blue: 1, alpha: 1))).font(.title3).padding(.top, 30)
Text("Welcome \(model.firstName) \(model.secondName) !").foregroundColor(Color(#colorLiteral(red: 0.5951357484, green: 0.5694860816, blue: 1, alpha: 1))).font(.callout)
Image(systemName: "arrow.backward.square")
.position(x: 30, y: 0)
.foregroundColor(Color(#colorLiteral(red: 0.5951357484, green: 0.5694860816, blue: 1, alpha: 1)))
.font(.system(size: 30, weight: .regular))
.onTapGesture {
withAnimation(.easeOut) {
queryView = true
}
print("TAPPED")
}
ScrollViewReader { reader in
ScrollView{
VStack(spacing: 15) {
ForEach(homeData.msgs) { msg in
ChatRow(chatData: msg, firstName: model.firstName, secondName: model.secondName).onAppear(){
if msg.id == self.homeData.msgs.last!.id && !scrolled {
reader.scrollTo(homeData.msgs.last!.id, anchor: .bottom)
scrolled = true
}
// print(model.firstName)
// print(model.secondName)
}
}.onChange(of: homeData.msgs) { value in
reader.scrollTo(homeData.msgs.last!.id, anchor: .bottom)
}
}
}.padding(.vertical)
}.frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height - 135)
HStack(spacing:15) {
TextField("Enter message", text: $homeData.txt)
.padding(.horizontal)
//Fixed height for animation...
.frame(height: 45)
.foregroundColor(Color(.black))
.background(Color(#colorLiteral(red: 0.5951357484, green: 0.5694860816, blue: 1, alpha: 1)).opacity(1.0))
.clipShape(Capsule())
if homeData.txt != "" {
Button {
homeData.writeAllMessages()
} label: {
Image(systemName: "paperplane.fill")
.font(.system(size: 22))
.foregroundColor(.white)
.frame(width: 45, height: 45)
.background(Color(#colorLiteral(red: 0.5951357484, green: 0.5694860816, blue: 1, alpha: 1)))
.clipShape(Circle())
}
}
}.animation(.default)
.padding()
Spacer().frame(height: 100)
}.background(Color(.black).scaledToFill().frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height).ignoresSafeArea())
.navigationBarBackButtonHidden(true)
} else {
QueryView(query: homeData.query)
}
}
}
What shall I do in order to make that TapGesture work anywhere on the screen?
To provide more info,I use the systemImage with that tapgesture function because when I use the NavigationLink back button the transition to its parent view is too slow and laggy.
It's probably because the NavigationBar is still at the top of your View even though the Back button is hidden.
Try adding .navigationBarHidden(true) instead of .navigationBarBackButtonHidden(true).
I'm trying to create my own PhoneTextField in and I have found a tutorial in here it's working fine if don't call it inside ZStack let's see the code example as below
countryCode View
struct CountryCodes: View {
#Binding var countryCode : String
#Binding var countryFlag : String
#Binding var y : CGFloat
var body: some View {
GeometryReader { geo in
List(self.countryDictionary.sorted(by: <), id: \.key) { key , value in
HStack {
Text("\(self.flag(country: key))")
Text("\(self.countryName(countryCode: key) ?? key)")
Spacer()
Text("+\(value)").foregroundColor(.secondary)
}.background(Color.white)
.font(.system(size: 20))
.onTapGesture {
self.countryCode = value
self.countryFlag = self.flag(country: key)
withAnimation(.spring()) {
self.y = 350
}
}
}
.padding(.bottom)
.frame(width: geo.size.width, height: 500)
.position(x: geo.frame(in: .global).midX, y: geo.frame(in: .global).maxY - -200)
}
}
let countryDictionary = ["AF":"93","AL":"355","DZ":"213","US":"1",
"AD":"376","AO":"244","AI":"1","AG":"1","AR":"54",
"AM":"374","AW":"297","AU":"61","AT":"43","AZ":"994",
"BS":"1","BH":"973","BD":"880","BB":"1","BY":"375",
"BE":"32","BZ":"501","BJ":"229","BM":"1","BT":"975",
"BA":"387","BW":"267","BR":"55","IO":"246","BG":"359",
"BF":"226","BI":"257","KH":"855","CM":"237","CA":"1",
"CV":"238","KY":"345","CF":"236","TD":"235","CL":"56","CN":"86",
"CX":"61","CO":"57","KM":"269","CG":"242","CK":"682","CR":"506",
"HR":"385","CU":"53","CY":"537","CZ":"420","DK":"45","DJ":"253",
"DM":"1","DO":"1","EC":"593","EG":"20","SV":"503","GQ":"240",
"ER":"291","EE":"372","ET":"251","FO":"298","FJ":"679","FI":"358",
"FR":"33","GF":"594","PF":"689","GA":"241","GM":"220","GE":"995",
"DE":"49","GH":"233","GI":"350","GR":"30","GL":"299","GD":"1",
"GP":"590","GU":"1","GT":"502","GN":"224","GW":"245","GY":"595","HT":"509",
"HN":"504","HU":"36","IS":"354","IN":"91","ID":"62","IQ":"964",
"IE":"353","IL":"972","IT":"39","JM":"1","JP":"81","JO":"962",
"KZ":"77","KE":"254","KI":"686","KW":"965","KG":"996","LV":"371",
"LB":"961","LS":"266","LR":"231","LI":"423","LT":"370","LU":"352",
"MG":"261","MW":"265","MY":"60","MV":"960","ML":"223",
"MT":"356","MH":"692","MQ":"596","MR":"222","MU":"230","YT":"262",
"MX":"52","MC":"377","MN":"976", "ME":"382","MS":"1","MA":"212",
"MM":"95","NA":"264","NR":"674","NP":"977","NL":"31","NC":"687",
"NZ":"64","NI":"505","NE":"227","NG":"234","NU":"683",
"NF":"672","MP":"1","NO":"47","OM":"968","PK":"92","PW":"680",
"PA":"507","PG":"675","PY":"595","PE":"51","PH":"63","PL":"48",
"PT":"351","PR":"1","QA":"974","RO":"40","RW":"250","WS":"685",
"SM":"378","SA":"966","SN":"221","RS":"381","SC":"248",
"SL":"232","SG":"65","SK":"421","SI":"386","SB":"677",
"ZA":"27","GS":"500","ES":"34","LK":"94","SD":"249","SR":"597",
"SZ":"268","SE":"46","CH":"41","TJ":"992","TH":"66","TG":"228",
"TK":"690","TO":"676","TT":"1","TN":"216","TR":"90",
"TM":"993","TC":"1","TV":"688","UG":"256","UA":"380",
"AE":"971","GB":"44","AS":"1","UY":"598","UZ":"998",
"VU":"678","WF":"681","YE":"967","ZM":"260",
"ZW":"263","BO":"591","BN":"673","CC":"61",
"CD":"243","CI":"225","FK":"500","GG":"44",
"VA":"379","HK":"852","IR":"98","IM":"44",
"JE":"44","KP":"850","KR":"82","LA":"856",
"LY":"218","MO":"853","MK":"389","FM":"691",
"MD":"373","MZ":"258","PS":"970","PN":"872",
"RE":"262","RU":"7","BL":"590","SH":"290","KN":"1",
"LC":"1","MF":"590","PM":"508","VC":"1","ST":"239",
"SO":"252","SJ":"47","SY":"963","TW":"886","TZ":"255",
"TL":"670","VE":"58","VN":"84","VG":"284","VI":"340"]
func countryName(countryCode: String) -> String? {
let current = Locale(identifier: "en_US")
return current.localizedString(forRegionCode: countryCode)
}
func flag(country:String) -> String {
let base : UInt32 = 127397
var flag = ""
for v in country.unicodeScalars {
flag.unicodeScalars.append(UnicodeScalar(base + v.value)!)
}
return flag
}
}
RegistrationView
//
// RegisterView.swift
// Instagram
//
// Created by Admin on 4/4/21.
//
import SwiftUI
import Combine
struct RegistrationView: View {
#State var isEditing: Bool = false
// navigate to verification view
#State private var selection: String? = nil
#State private var email:String = ""
#State private var phone:String = ""
#State private var fullname = ""
#State private var username = ""
#State private var password = ""
#State private var selectedImage:UIImage?
#State private var image:Image?
#Environment(\.presentationMode) var mode
#State var imagePickerPresented = false
#EnvironmentObject var viewModel: AuthViewModel
#State var isVerification = false
#State var phoneNumber = ""
#State var y : CGFloat = 350
#State var countryCode = ""
#State var countryFlag = ""
#State var isInputPhoneNumber = true
#State var isInputEmail = false
#State var tempData = DataTempo()
#State var isShowSignInButton = true
// var successClosure: (Bool) -> Void {
//
// }
#EnvironmentObject var mobileVerificationViewModel: MobileVerificationNvm
var body: some View {
ZStack {
LinearGradient(gradient: Gradient(colors: [Color.purple, Color("PrimaryColor")]), startPoint: .top, endPoint: .bottom)
.ignoresSafeArea()
VStack{
// Spacer()
VStack(spacing:20){
VStack(alignment:.leading){
Text("Sign Up")
.font(.largeTitle)
.bold()
.foregroundColor(.white)
}.padding(.horizontal,32).frame(width: UIScreen.main.bounds.width, alignment: .leading)
Toggle( isInputEmail ? "Use Email" : "Use Phone Number", isOn: $isInputEmail)
.foregroundColor(.white)
.toggleStyle(SwitchToggleStyle(tint: Color(#colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1))))
.padding(.horizontal,32)
if isInputEmail == true {
CustomTextField(text: $email, placeholder: Text("Email"), imageName: "envelope")
.padding()
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
.foregroundColor(.white)
.padding(.horizontal,32)
.onChange(of: email) {
// self.autocomplete($0) // like this
if viewModel.isValidEmail(testStr: $0){
print("email")
//phone number
}
}
}
if isInputEmail == false{
ZStack(alignment:.center) {
HStack (spacing: 0) {
Text(countryCode.isEmpty ? "🇹🇠+66" : "\(countryFlag) +\(countryCode)")
.frame(width: 100, height: 50)
// .background(Color.secondary.opacity(0.2))
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
// .foregroundColor(countryCode.isEmpty ? .secondary : .black)
.foregroundColor(.white)
.onTapGesture {
isShowSignInButton = false
withAnimation (.spring()) {
self.y = 0
}
}
TextField("Phone Number", text: $phone)
.onChange(of: phone) { newValue in
if !phone.isEmpty {
self.isShowSignInButton = true
if viewModel.validatePhoneNumber(value:newValue){
print("phone")
//phone number
self.isInputPhoneNumber = true
self.isInputEmail = false
}
}
else{
self.isShowSignInButton = false
}
}
.frame(width: 250, height: 50)
.keyboardType(.phonePad)
.background(Color(.init(white: 1, alpha: 0.15)))
.foregroundColor(.white)
}
CountryCodes(countryCode: $countryCode, countryFlag: $countryFlag, y: $y)
.offset(y: y)
RoundedRectangle(cornerRadius: 10).stroke()
.frame(width: 350, height: 50)
.foregroundColor(.white)
} .zIndex(1).frame( height: 50, alignment: .center)
}
CustomTextField(text: $username, placeholder: Text("Username"), imageName: "person")
.padding()
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
.foregroundColor(.white)
.padding(.horizontal,32)
CustomTextField(text: $fullname, placeholder: Text("Full Name"), imageName: "person")
.padding()
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
.foregroundColor(.white)
.padding(.horizontal,32)
CustomSecureField(text: $password, placeholder: Text("Password"))
.padding()
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
.foregroundColor(.white)
.padding(.horizontal,32)
}
.alert(isPresented: $isInputPhoneNumber, content: {
Alert(title: Text("Message"), message: Text("you're using phone number to register"), dismissButton: .default(Text("Bye alert!"), action: {
isInputPhoneNumber = true
}))
})
.alert(isPresented: mobileVerificationViewModel.alertBinding) {
Alert(title: Text(mobileVerificationViewModel.errorMessage ?? "(unknown)"), message: nil, dismissButton: .cancel())
}
HStack{
Spacer()
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/, label: {
Text("Forgot Password")
})
.font(.system(size: 13, weight: .semibold))
.foregroundColor(.white)
.padding(.top)
.padding(.trailing,28)
}
ZStack{
NavigationLink(destination:VerificationView { (pin, successClosure) in
print(pin)
mobileVerificationViewModel.codeVerification(phoneNumber: isInputEmail ? tempData.email : tempData.phone, pinCode: pin, email: email, password: password, fullname: fullname, username: username)
//navigation back
self.mode.wrappedValue.dismiss()
// print(successClosure)
}, tag: "verification", selection: $selection) { EmptyView() }
Button(action: {
self.selection = "verification"
var phoneWithCountryCode = ""
// if countryCode == "" {
// phoneWithCountryCode = "+66\(phone)"
// }
// if countryCode != ""{
// phoneWithCountryCode = "+\(countryCode)\(phone)"
// }
//
// // "+\(countryCode == "" ? "66" : countryCode + phone)"
// if (!email.isEmpty || !phone.isEmpty) && !password.isEmpty{
//
// if isInputEmail {
// MobileVerificationNvm.shared.sendVerification(phoneNumber:email)
// isVerification = true
// tempData.email = email
// }
//
// else{
// DispatchQueue.main.async {
// // print("phone:\(phoneWithCountryCode)")
// MobileVerificationNvm.shared.sendVerification(phoneNumber: phoneWithCountryCode)
// isVerification = true
// tempData.phone = phone
// }
//
// }
//
// }
// viewModel.register(withEmail: email, password: password,image: selectedImage,fullname: fullname,username: username)
}, label: {
Text("Sign Up")
}).zIndex(0)
// Spacer()
}
.font(.headline)
.frame(width: 360, height: 50)
.foregroundColor(.white)
.background(Color(#colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)))
// .clipShape(Capsule())
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.padding()
.alert(isPresented: $isInputEmail, content: {
Alert(title: Text("Message"), message: Text("Now! you're using Email to register but you also register with email just swtich on togle button"), dismissButton: .default(Text("Bye alert!"), action: {
isInputEmail = true
}))
})
Button(action: {
mode.wrappedValue.dismiss()
// isVerification = true
}, label: {
Text("Already have an account? ")
.font(.system(size: 14))
+ Text("Sign In")
.font(.system(size: 14, weight: .bold))
})
.foregroundColor(.white)
.padding(.bottom,32)
}
}
.ignoresSafeArea()
}
}
extension RegistrationView{
func loadImage() {
guard let selectedImage = selectedImage else {
return
}
image = Image(uiImage: selectedImage)
}
}
//
struct RegistrationView_Previews: PreviewProvider {
static var previews: some View {
RegistrationView()
}
}
this is my issue when I tap on country code to choose the country code you will see the button sign in over countries code list
you also can check from [here]:github.com/PhanithNoch/SwiftUIPhoneTextField this is the repo that I tried to separate code from my real project as an example
I stuck with this a few days ago hope anyone can help, please.
I'm stuck trying to figure out how to dismiss an existing TabView in my SwiftUI application. Basically, it works like this:
The SceneDelegate launches a view called MainScreenView()
struct MainScreenView: View {
#State var isLoadingViewEnabled = false
#State var isSuccessViewEnabled = false
#State var showLoopingBlobs = false
#State var showTabView = false
#ObservedObject var user = UsrAccount()
#ObservedObject var authManager = FirebaseAuthManager()
#ObservedObject var keyboardResponder = KeyboardResponder()
var body: some View {
Group {
if !showTabView {
ZStack {
Color(#colorLiteral(red: 0.1647058824, green: 0.6862745098, blue: 0.9490196078, alpha: 1))
.edgesIgnoringSafeArea(.all)
ZStack {
VStack {
LogoView()
LoginView(user: user)
.padding(.bottom, 100)
LoginButton(
isLoadingViewEnabled: $isLoadingViewEnabled,
isSuccessViewEnabled: $isSuccessViewEnabled,
showTabView: $showTabView,
user: user,
authManager: authManager
)
ForgotPassword(user: user, authManager: authManager)
.padding(.top, 20)
RegisterNewUserView(authManager: authManager, user: user)
.padding(.top, 20)
Spacer()
}
.background(
Color(#colorLiteral(red: 0.1647058824, green: 0.6862745098, blue: 0.9490196078, alpha: 1))
.edgesIgnoringSafeArea(.all)
// BlobRotationView(showLoopingBlobs: $showLoopingBlobs)
)
if isLoadingViewEnabled {
LoadingView()
}
if isSuccessViewEnabled {
LoginSuccessView()
}
}
.frame(maxWidth: .infinity)
.frame(maxHeight: .infinity)
}
.offset(y: -keyboardResponder.currentHeight*0.20)
.animation(.easeInOut(duration: 0.85))
.onTapGesture { KeyboardInteraction.hideKeyboard() }
} else {
TabBar(user: user, authManager: authManager, keyboardResponder: keyboardResponder)
.animation(.easeInOut)
}
}
}
}
Once there is a successful login to Firebase (code not shown), a tab view will get shown:
import SwiftUI
struct TabBar: View {
#State private var selectedTab = 0
#ObservedObject var user: UsrAccount
#ObservedObject var authManager: FirebaseAuthManager
#ObservedObject var keyboardResponder: KeyboardResponder
var body: some View {
TabView(selection: $selectedTab) {
HomeView(user: user, authManager: authManager, keyboardResponder: keyboardResponder)
.onTapGesture {
self.selectedTab = 0
}
.tabItem {
Image(systemName: "house")
.font(.title)
Text("Home")
}
.tag(0)
ScheduleLessonsView()
.onTapGesture {
self.selectedTab = 1
}
.tabItem {
Image(systemName: "calendar.badge.clock")
.font(.title)
Text("Schedule")
}
.tag(1)
PurchaseLessonsView()
.onTapGesture {
self.selectedTab = 2
}
.tabItem {
Image(systemName: "cart.fill.badge.plus")
.font(.title)
Text("Purchase")
}
.tag(2)
UpdateProfileView(authManager: authManager, user: user)
.onTapGesture {
self.selectedTab = 3
}
.tabItem {
Image(systemName: "person.fill")
.font(.title)
Text("Profile")
}
.tag(3)
AboutUsView()
.onTapGesture {
self.selectedTab = 4
}
.tabItem {
Image(systemName: "questionmark.circle")
.font(.title)
Text("About Us")
}
.tag(4)
}
.edgesIgnoringSafeArea(.top)
}
}
struct TabBar_Previews: PreviewProvider {
static var previews: some View {
TabBar(user: UsrAccount(), authManager: FirebaseAuthManager(), keyboardResponder: KeyboardResponder())
}
}
As you can see from the TabView, the HomeView is the initial tab that is shown. I have setup an exit button in that view to log the user out of Firebase and send the user back to the login screen (MainScreenView).
struct HomeView: View {
#ObservedObject var user: UsrAccount
#ObservedObject var authManager: FirebaseAuthManager
#ObservedObject var keyboardResponder: KeyboardResponder
#State var isCardSelected = false
#State var didSignOutUser = false
var body: some View {
Group {
if didSignOutUser {
MainScreenView()
} else {
ZStack {
Color(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1))
.edgesIgnoringSafeArea(.all)
VStack {
HStack {
Spacer()
Button(action: {
didSignOutUser = authManager.signOut()
print("**** Sign Out status: \(didSignOutUser)")
}) {
Text("X")
.foregroundColor(Color(#colorLiteral(red: 0.08235294118, green: 0.2274509804, blue: 0.4078431373, alpha: 1)))
.fontWeight(.bold)
.font(.system(size: 40))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.leading)
}
.animation(.easeInOut)
}
.padding(.horizontal)
HStack {
VStack {
Text("Welcome!")
.font(.system(.title))
.bold()
.foregroundColor(Color(#colorLiteral(red: 0.07843137255, green: 0.3294117647, blue: 0.4078431373, alpha: 1)))
}
Spacer()
}
.padding(.horizontal, 16)
.padding(.top, 25)
.padding(.bottom, 10)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(homeCardData) { item in
GeometryReader { geometry in
HomeCardView(homeCard: item, isCardSelected: self.$isCardSelected)
.rotation3DEffect(Angle(degrees:
Double(geometry.frame(in: .global).minX - 30) / -30
), axis: (x: 0, y: 10, z: 0))
}
.frame(width: 200, height: 200)
}
}
.padding(30)
.padding(.bottom, 30)
}
HStack {
VStack {
Text("Here are your upcoming classes:")
.font(.system(.body))
.bold()
.foregroundColor(Color(#colorLiteral(red: 0.07843137255, green: 0.3294117647, blue: 0.4078431373, alpha: 1)))
}
Spacer()
}
.padding(.horizontal, 16)
.padding(.bottom, 5)
NoClassView()
Spacer()
}
}
}
}
}
}
The problem is that the TabView still shows up at the bottom of the MainScreenView, which is wrong. It also seems that the user isn't even logged out.
The SignOut method is called on my FirebaseAuthManager class:
func signOut() -> Bool {
do {
try Auth.auth().signOut()
self.user = nil
return true
} catch {
return false
}
}
The above method is returning "true", so it seems that it is signing the user out.
So.. 2 questions:
How can I confirm the user is signed out when the user is sent back to MainScreenView?
How can I make sure the TabView gets destroyed when the user chooses to exit from one of the screens in the TabView?
I have 10 different choices in my view. I want to enable "Devam Et" button when any of 10 choices are made. It sounds easy but critical part is as following...When I click any of the first 8 buttons, I want to disable the last 2 buttons if they are selected and also if I select any of the last two options, I want to disable all the other 8 options if they are selected.
The code of the first 3 button configuration lines is as following...Remaining ones are same as these.
VStack{
HStack {
Button(action: {
self.tap1.toggle()
}) {
ZStack {
Rectangle()
.fill(self.tap1 ? Color(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1)) : Color(#colorLiteral(red: 0.936548737, green: 0.936548737, blue: 0.936548737, alpha: 1)))
.frame(width: 25, height: 25)
if self.tap1 {
Image(systemName: "checkmark")
}
}.padding(.leading, 40)
}
Spacer()
Text("Diyabet")
.font(.system(size: 20, weight: .regular, design: .rounded))
.padding(.trailing, 200)
Spacer()
}.padding(.bottom, 10)
HStack {
Button(action: {
self.tap2.toggle()
}) {
ZStack {
Rectangle()
.fill(self.tap2 ? Color(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1)) : Color(#colorLiteral(red: 0.936548737, green: 0.936548737, blue: 0.936548737, alpha: 1)))
.frame(width: 25, height: 25)
if self.tap2 {
Image(systemName: "checkmark")
}
}.padding(.leading, 40)
}
Spacer()
Text("Yüksek Tansiyon")
.font(.system(size: 20, weight: .regular, design: .rounded))
.padding(.trailing, 130)
Spacer()
}.padding(.bottom, 10)
HStack {
Button(action: {
self.tap3.toggle()
}) {
ZStack {
Rectangle()
.fill(self.tap3 ? Color(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1)) : Color(#colorLiteral(red: 0.936548737, green: 0.936548737, blue: 0.936548737, alpha: 1)))
.frame(width: 25, height: 25)
if self.tap3 {
Image(systemName: "checkmark")
}
}.padding(.leading, 40)
}
button1
}
The code for "Devam Et" button is as following...
var button1: some View{
return Button(action: {
if self.tap1 == true || self.tap2 == true || self.tap3 == true || self.tap4 == true || self.tap5 == true || self.tap6 == true || self.tap7 == true || self.tap8 == true {
self.tap11.toggle()
}
else if self.tap9 == true {
self.tap11.toggle()
}
else if self.tap10 == true {
self.tap11.toggle()
}
}) {
Text("Devam Et")
.font(.system(size: 20, weight: .regular, design: .rounded))
.foregroundColor(Color.white)
.frame(width: 200, height: 30)
.padding()
.background(Color(#colorLiteral(red: 0.3101329505, green: 0.193462044, blue: 0.3823927939, alpha: 1)))
.cornerRadius(40)
.shadow(color: .gray, radius: 20.0, x: 20, y: 10)
.padding(.bottom, 70)
}.background(
NavigationLink(destination: destinationView, isActive: $tap11) {
EmptyView()
}
.hidden()
)
}
#ViewBuilder
var destinationView: some View {
if tap1 || tap2 || tap3 || tap4 || tap5 || tap6 || tap7 || tap8 || tap9 || tap10{
entrance5()
}
}
TL;DR: Result video: https://imgur.com/wRx2Ezg
Result output: https://imgur.com/H8fWwg0
Alright, this is a bit of a lengthy answer, before that I'll comment on some of your previous code:
It's not really a good choice to keep every boolean in their respective field, as you noticed it's really hard to keep track of them. Instead, you could have some sort of a struct that keeps track of every choice that you are providing.
What you also need is a proper state tracking, currently you need 3 type of state:
Standard state: Nothing has selected, yet.
Multiple choice state: There can be multiple selections, except the exclusive selections.
Exclusive choice state: Only one can be selected, in their respective group.
You also need to integrate it to your choices, to determine which kind you have selected in the list.
Last but not least, it should be handled outside of your current view, like in an interactor.
Here is the full code, ready to be tested in Playgrounds:
import SwiftUI
import PlaygroundSupport
enum ContentType {
case standard
case exclusive
}
enum ContentState {
case none
case multipleChoice
case exclusiveChoice
}
struct ContentChoice: Identifiable {
var id: String { title }
let title: String
let type: ContentType
var isSelected: Bool = false
var isDisabled: Bool = false
}
class ContentInteractor: ObservableObject, ContentChoiceViewDelegate {
#Published var choices: [ContentChoice] = []
#Published var state: ContentState = .none {
didSet {
print("state is now: \(state)")
switch state {
case .none:
exclusiveChoices.forEach { choices[$0].isDisabled = false }
standardChoices.forEach { choices[$0].isDisabled = false }
case .multipleChoice:
exclusiveChoices.forEach { choices[$0].isDisabled = true }
case .exclusiveChoice:
standardChoices.forEach { choices[$0].isDisabled = true }
}
}
}
private var exclusiveChoices: [Int] {
choices.indices.filter { choices[$0].type == .exclusive }
}
private var standardChoices: [Int] {
choices.indices.filter { choices[$0].type == .standard }
}
private var isExclusiveChoiceSelected: Bool {
choices.filter { $0.type == .standard && $0.isSelected }.count > 0
}
private var selectedMultipleChoiceCount: Int {
choices.filter { $0.type == .standard && $0.isSelected }.count
}
func didToggleChoice(_ choice: ContentChoice) {
guard let index = choices.firstIndex(where: { $0.id == choice.id }) else {
fatalError("No choice found with the given id.")
}
// This is where the whole algorithm lies.
switch state {
// Phase 1:
// If the user has not made any choice (state == .none),
// Enabling a `.standard` choice should lock the `.exclusive` choices.
// And vice versa.
case .none:
choices[index].isSelected.toggle()
switch choice.type {
case .standard:
state = .multipleChoice
case .exclusive:
state = .exclusiveChoice
}
// Phase 2:
// If the user is in multiple choice state,
// They can only select multiple choices. If any of the multiple choice
// is still selected, it should stay as is.
// If every choice is deselected, it should return the state to `.none`.
case .multipleChoice:
choices[index].isSelected.toggle()
switch choice.type {
case .standard:
if selectedMultipleChoiceCount == 0 {
state = .none
}
case .exclusive:
preconditionFailure("Unexpected choice selection.")
}
// Phase 3:
// If the user is in a not-answering state,
// They can only change it within themselves.
// If every choice is deselected, it should return the state to `.none`.
// Also, every exclusive choice is, exclusive.
// Hence, if one of them is selected, the others should be deselected.
case .exclusiveChoice:
switch choice.type {
case .standard:
preconditionFailure("Unexpected choice selection.")
case .exclusive:
let isSelecting = !choices[index].isSelected
if isSelecting {
exclusiveChoices.forEach { choices[$0].isSelected = false }
} else {
state = .none
}
}
choices[index].isSelected.toggle()
}
}
func didSelectSubmit() {
print("Current selection:", choices.filter { $0.isSelected }.map { $0.title })
}
}
protocol ContentChoiceViewDelegate: AnyObject {
func didToggleChoice(_ choice: ContentChoice)
func didSelectSubmit()
}
struct ContentChoiceView: View {
let choice: ContentChoice
weak var delegate: ContentChoiceViewDelegate!
init(choice: ContentChoice, delegate: ContentChoiceViewDelegate) {
self.choice = choice
self.delegate = delegate
}
var body: some View {
HStack {
Button(action: {
self.delegate.didToggleChoice(self.choice)
}) {
ZStack {
Rectangle()
.fill(self.choice.isSelected ? Color(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1)) : Color(#colorLiteral(red: 0.936548737, green: 0.936548737, blue: 0.936548737, alpha: 1)))
.frame(width: 25, height: 25)
if self.choice.isSelected {
Image(systemName: "checkmark")
}
}.padding(.leading, 40)
}.disabled(self.choice.isDisabled)
Spacer()
Text(choice.title)
.font(.system(size: 20, weight: .regular, design: .rounded))
// .padding(.trailing, 200)
Spacer()
}.padding(.bottom, 10)
}
}
struct ContentView: View {
#ObservedObject var interactor: ContentInteractor
var body: some View {
VStack {
ForEach(interactor.choices) { choice in
ContentChoiceView(choice: choice, delegate: self.interactor)
}
Spacer()
submitButton
}.padding(.vertical, 70)
}
var submitButton: some View {
Button(action: {
self.interactor.didSelectSubmit()
}) {
Text("Devam Et")
.font(.system(size: 20, weight: .regular, design: .rounded))
.foregroundColor(Color.white)
.frame(width: 200, height: 30)
.padding()
.background(Color(#colorLiteral(red: 0.3101329505, green: 0.193462044, blue: 0.3823927939, alpha: 1)))
.cornerRadius(40)
.shadow(color: .gray, radius: 20.0, x: 20, y: 10)
}
}
}
let interactor = ContentInteractor()
["Diyabet", "Yuksek Tansiyon", "Astim"].forEach { title in
interactor.choices.append(ContentChoice(title: title, type: .standard))
}
["Soylememeyi Tercih Ederim", "Hicbirini Gecirmedim"].forEach { title in
interactor.choices.append(ContentChoice(title: title, type: .exclusive))
}
let contentView = ContentView(interactor: interactor)
PlaygroundPage.current.setLiveView(contentView)