0

I have a simple login screen with two textfield and a button. It should look like this. The two textfields closer together and the button a little ways down.

enter image description here

Here is my code.

struct ContentView: View {
    var body: some View {
        VStack {
            Spacer()
            InputTextField(title: "First Name", text: .constant(""))
            InputTextField(title: "Last Name", text: .constant(""))
            Spacer()
            ActionButton(title: "Login", action: {})
            Spacer()
        }
    }
}


struct InputTextField: View {
    let title: String
    
    @Binding var text: String
    
    var body: some View {
        VStack(alignment: .leading) {
            Text(title)
                .foregroundColor(.primary)
                .fontWeight(.medium)
                .font(.system(size: 18))
            
            HStack {
                TextField("", text: $text)
                    .frame(height: 54)
                    .textFieldStyle(PlainTextFieldStyle())
                    .cornerRadius(10)
            }
            .padding([.leading, .trailing], 10)
            .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 0.6))
        }
        .padding()
    }
}


struct ActionButton: View {
    let title: String
    var action: () -> Void

    var body: some View {
        Button(title) {
            action()
        }
        .frame(minWidth: 100, idealWidth: 100, maxWidth: .infinity, minHeight: 60, idealHeight: 60)
        .font(.system(size: 24, weight: .bold))
        .foregroundColor(.white)
        .background(Color.blue)
        .cornerRadius(10)
        .padding([.leading, .trailing])
        .shadow(color: Color.gray, radius: 2, x: 0, y: 2)
    }
}

I wanted to embed this inside a ScrollView so that user can scroll up and down when the keyboard comes up.

struct ContentView: View {
    var body: some View {
        ScrollView {
            VStack {
                Spacer()
                InputTextField(title: "First Name", text: .constant(""))
                InputTextField(title: "Last Name", text: .constant(""))
                Spacer()
                ActionButton(title: "Login", action: {})
                Spacer()
            }
        }
    }
}

Here is where I'm coming across this issue. When I add the VStack inside a ScrollView, all the content kind of shrinks and shows clumped together. Seems like the Spacers have no effect when inside a ScrollView.

enter image description here

How can I fix this?

Demo project

Isuru
  • 30,617
  • 60
  • 187
  • 303

2 Answers2

4

Here, You need to make the content stretch to fill the whole scroll view by giving minimum height as below

struct ContentView: View {
  var body: some View {
    GeometryReader { gr in
        ScrollView {
            VStack {
                Spacer()
                InputTextField(title: "First Name", text: .constant(""))
                InputTextField(title: "Last Name", text: .constant(""))
                Spacer()
                ActionButton(title: "Login", action: {})
                Spacer()
            }
            .frame(minHeight: gr.size.height)
        }
    }
  }
}

Here is output:

enter image description here

Jignesh Mayani
  • 6,937
  • 1
  • 20
  • 36
  • Great solution. I was looking for something like this for over an hour. Now I can sleep in peace for the rest of the night. – Fahim Rahman Sep 04 '21 at 21:20
0

As you have found, Spacers behave differently when they are in a ScrollView or not, or put differently, when the axis they can expand on is infinite or finite.

If what you want is for your content to be centered vertically when it fits and scroll when it's larger than the screen, I would do something like this:

struct ContentView: View {
    var body: some View {
        VStack { // This new stack would vertically center the content
                 // (I haven't actually tried it though)
            ScrollView {
                VStack {
                    Spacer().size(height: MARGIN) // The minimum margin you want
                    InputTextField(title: "First Name", text: .constant(""))
                    InputTextField(title: "Last Name", text: .constant(""))
                    Spacer().size(height: SPACING)
                    ActionButton(title: "Login", action: {})
                    Spacer().size(height: MARGIN)
                }
            }
        }
    }
}
EmilioPelaez
  • 18,758
  • 6
  • 46
  • 50