I have a login page where the views are embedded in a ZStack
the behavior that I am aiming for is when I click on either the email
and password
fields the ZStack should offset -300 vertically
so that it wouldn't be covered by the keyboard. I achieved this behavior by setting a state inside the onTapGesture
of the text fields. It works perfectly fine on the preview device, but when I run it on the simulator and my physical device (iPhone xs latest OS ver), the behavior of the Color("background2")
and the HStack
for the login button
subviews changes and does not offset properly (see image below for reference).
Can anyone explain why this behavior happens and some workarounds for this problem?
Whole Code of the file:
import SwiftUI
import Firebase
struct LoginView: View {
@State var email = ""
@State var password = ""
@State var isFocused = false
@State var showAlert = false
@State var alertMessage = "Something went wrong."
@State var isLoading = false
@State var isSuccessful = false
func login() {
self.hideKeyboard()
self.isFocused = false
self.isLoading = true
Auth.auth().signIn(withEmail: email, password: password) { result, error in
self.isLoading = false
if error != nil {
self.alertMessage = error?.localizedDescription ?? ""
self.showAlert = true
} else {
self.isSuccessful = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.email = ""
self.password = ""
self.isSuccessful = false
}
}
}
}
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
ZStack(alignment: .top) {
// Gray background
Color("background2")
.clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous))
.edgesIgnoringSafeArea(.bottom)
// Design card-like view
CoverView()
// Email and Password fields
VStack {
HStack {
Image(systemName: "person.crop.circle.fill")
.foregroundColor(Color(#colorLiteral(red: 0.6549019608, green: 0.7137254902, blue: 0.862745098, alpha: 1)))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.leading)
TextField("Your Email".uppercased(), text: $email)
.keyboardType(.emailAddress)
.font(.subheadline)
// .textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.leading)
.frame(height: 44)
.onTapGesture {
self.isFocused = true
}
}
Divider().padding(.leading, 80)
HStack {
Image(systemName: "lock.fill")
.foregroundColor(Color(#colorLiteral(red: 0.6549019608, green: 0.7137254902, blue: 0.862745098, alpha: 1)))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.leading)
SecureField("Password".uppercased(), text: $password)
.keyboardType(.default)
.font(.subheadline)
// .textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.leading)
.frame(height: 44)
.onTapGesture {
self.isFocused = true
}
}
}
.frame(height: 136)
.frame(maxWidth: .infinity)
.background(BlurView(style: .systemMaterial))
.clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 20, x: 0, y: 20)
.padding(.horizontal)
.offset(y: 460)
// Forgot password and Login Button view
HStack {
Text("Forgot password?")
.font(.subheadline)
Spacer()
Button(action: {
self.login()
}) {
Text("Log in").foregroundColor(.black)
}
.padding(12)
.padding(.horizontal, 30)
.background(Color(#colorLiteral(red: 0, green: 0.7529411765, blue: 1, alpha: 1)))
.clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
.shadow(color: Color(#colorLiteral(red: 0, green: 0.7529411765, blue: 1, alpha: 1)).opacity(0.3), radius: 20, x: 0, y: 20)
.alert(isPresented: $showAlert) {
Alert(title: Text("Error"), message: Text(self.alertMessage), dismissButton: .default(Text("OK")))
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.padding()
}
.offset(y: isFocused ? -300 : 0)
.animation(isFocused ? .easeInOut : nil)
.onTapGesture {
self.isFocused = false
self.hideKeyboard()
}
if isLoading {
LoadingView()
}
if isSuccessful {
SuccessView()
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
// .previewDevice("iPad Air 2")
}
}
struct CoverView: View {
@State var show = false
@State var viewState = CGSize.zero
@State var isDragging = false
var body: some View {
VStack {
GeometryReader { geometry in
Text("Quizzes that fit on your pocket.")
.font(.system(size: geometry.size.width/10, weight: .bold))
.foregroundColor(.white)
}
.frame(maxWidth: 375, maxHeight: 100)
.padding(.horizontal, 16)
.offset(x: viewState.width/15, y: viewState.height/15)
Text("Keep on learning new things!")
.font(.subheadline)
.frame(width: 250)
.offset(x: viewState.width/20, y: viewState.height/20)
Spacer()
}
.multilineTextAlignment(.center)
.padding(.top, 100)
.frame(height: 477)
.frame(maxWidth: .infinity)
.background(
ZStack {
Image(uiImage: #imageLiteral(resourceName: "Blob"))
.offset(x: -150, y: -200)
.rotationEffect(Angle(degrees: show ? 360+90 : 90))
.blendMode(.plusDarker)
// .animation(Animation.linear(duration: 120).repeatForever(autoreverses: false))
.animation(nil)
.onAppear { self.show = true }
Image(uiImage: #imageLiteral(resourceName: "Blob"))
.offset(x: -200, y: -250)
.rotationEffect(Angle(degrees: show ? 360 : 0), anchor: .leading)
.blendMode(.overlay)
// .animation(Animation.linear(duration: 120).repeatForever(autoreverses: false))
.animation(nil)
}
)
.background(
Image(uiImage: #imageLiteral(resourceName: "Card3"))
.offset(x: viewState.width/25, y: viewState.height/25)
, alignment: .bottom
)
.background(Color(#colorLiteral(red: 0.4117647059, green: 0.4705882353, blue: 0.9725490196, alpha: 1)))
.clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous))
.scaleEffect(isDragging ? 0.9 : 1)
.animation(.timingCurve(0.2, 0.8, 0.2, 1, duration: 0.8))
.rotation3DEffect(Angle(degrees: 5), axis: (x: viewState.width, y: viewState.height, z: 0))
.gesture(
DragGesture().onChanged { value in
self.viewState = value.translation
self.isDragging = true
}
.onEnded { value in
self.viewState = .zero
self.isDragging = false
}
)
}
}