center child view within full screen

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:

The loading image should be placed on the middle of the screen

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

Answers (1)

DI.dev
DI.dev

Reputation: 467

Here are two ways to achieve that:

Using ZStack

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.

Using .overlay()

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

Related Questions