Reputation: 178
I'm trying to build a simple loading spinner view on a login page and the button that is used to call the LoginAPI is within a VStack and then a HStack -> placing the Login Button on the bottom of the screen. I also have the screen created all on different extracted views meaning that I do not have all the context on the main contentView
For context purposes the information to trigger the loading spinner view (success/failed login) is on a child view at the bottom of the screen and then spinner view gets centred according to the bounds of its parent view -> at the bottom.
My final goal is to place the loading spinner view on the center of the screen. Is there a way to overlay all the constraints from the parent views and force a child view to be on top of all the other views and centred ?
The current output:
LOADING SPINNER VIEW:
struct LoadingIndicator: View {
@State var animate = false
var body : some View {
GeometryReader{ geo in
VStack{
Circle()
.trim(from: 0, to: 0.8)
.stroke(AngularGradient(gradient: .init(colors: [.orange,.yellow]), center: .center), style: StrokeStyle(lineWidth: 8, lineCap: .round))
.frame(width: 45, height: 45,alignment: .center)
.rotationEffect(.init(degrees: self.animate ? 360 : 0))
.animation(Animation.linear(duration: 0.7).repeatForever(autoreverses: false))
Text("Please Wait...")
.padding(.top)
}.padding(20)
.background(Color.white)
.cornerRadius(15)
.onAppear{
self.animate.toggle()
}
}
.background(Color.black.opacity(0.45).edgesIgnoringSafeArea(.all))
.edgesIgnoringSafeArea(.all)
}
}
LOGIN BUTTON VIEW - TRIGGERS THE LOADING SPINNER VIEW
struct LoginButton: View {
@Binding var email: String
@Binding var pwd: String
@Binding var token: String
@Binding var userIdentification: String
@Binding var isAuthorized: Bool
@State var animateLoading = false
let accountModuleLoginAPI = APIGlobalVariables.AccountModule.Login
var body: some View {
ZStack{
VStack{
// disable back button from navigation view
NavigationLink(destination: DriverLocationMapView()
.onAppear(){
self.animateLoading = false
}
.navigationBarBackButtonHidden(true)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all), isActive: self.$isAuthorized){
Text("")
}
Button(action: {
// payload data initialization
let loginData = LoginPayload(username: self.email, password: self.pwd)
let postRequest = APIRequest(endpoint: self.accountModuleLoginAPI)
print("login data: \(loginData.username) and \(loginData.password)")
print("post request: \(postRequest)")
self.animateLoading = true
postRequest.loginRequest(loginData, completion: { result in
switch result{
case .success(let message):
print("Success Message: \(message)")
do{
self.token = try result.get().token
self.userIdentification = try result.get().userIdentification
self.isAuthorized = true
print("userIdentification: \(self.userIdentification)")
print("token: \(self.token)")
}catch{
print("catch")
self.animateLoading = false
self.isAuthorized = false
}
case .failure(let failure):
print("Failure Message: \(failure)")
self.animateLoading = false
self.isAuthorized = false
}
})
})
{
Text("Login")
.font(.headline)
.frame(width: 140, height: 35)
.background(Color.black)
.foregroundColor(Color.white)
.clipShape(Capsule())
.padding(10)
} } if self.animateLoading {
LoadingIndicator()
} } } }
MAIN CONTENT VIEW - CREATES THE LAYOUT AND POSITIONS THE LOGIN BUTTON
struct ContentView: View {
@State var email = "";
@State var pwd = "";
@State var height: CGFloat = 0;
@State var token = "";
@State var AccessTypeIndex = 0
@State var userIdentification = ""
@State var isAuthorized = false
var body: some View {
NavigationView {
ScrollView(self.height == 0 ? .init() : .vertical, showsIndicators: false){
ZStack {
BackgroundImage()
VStack{
//LogoImage()
AccessType(AccessTypeIndex: self.$AccessTypeIndex)
if AccessTypeIndex==0 {
LoginInputFields(email: self.$email, pwd: self.$pwd)
LoginButton(email: self.$email, pwd: self.$pwd, token: self.$token, userIdentification: self.$userIdentification, isAuthorized: self.$isAuthorized)
}else if AccessTypeIndex==1{
// register fieds
SignUpView()
}
}
.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)
.padding(.bottom, UIApplication.shared.windows.first?.safeAreaInsets.bottom)
}
}.padding(.bottom, self.height)
.background(Color.black.opacity(0.03).edgesIgnoringSafeArea(.all))
.edgesIgnoringSafeArea(.all)
.onAppear{
// keyboard did show
NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: .main) { (not) in
let data = not.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! NSValue
let keyboardheight = data.cgRectValue.height
self.height = keyboardheight - (UIApplication.shared.windows.first?.safeAreaInsets.bottom)!
print(self.height)
}
// keyboard did hide
NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, object: nil, queue: .main) { (_) in
self.height = 0
print(self.height)
}
}
}
}
}
Thank you, Daniel
Upvotes: 1
Views: 973
Reputation: 467
Here are two ways to achieve that:
ZStack {
VStack(spacing: 0) {
}
if self.loading {
LoadingIndicator()
}
}
Now since we have wrapped everything in a ZStack, whatever is at the bottom will appear on top of everything (ignoring it). So your LoadingIndicator will appear centered to its parent (which is the whole screen now). I have wrapped it in if, because I would like to only display that indicator if some variable called loading is set to true. You can try to search about ZStack example usages I believe there are plenty out there.
VStack(spacing: 0) {
}
.overlay(
if self.loading {
LoadingIndicator()
}
)
It works on the same principle. Note that you might need to wrap the if statement in Group {} or VStack{} or else.
Upvotes: 2