Search code examples
iosswiftswiftui

How to align text to centre of a button while using a custom hstack length?


I am a novice to iOS and swift programming language. I am trying to create a home page for an app and I have two buttons in it.

import SwiftUI

struct HomePage: View {

  var body: some View {
    ZStack {
        // Background gradient
        LinearGradient(gradient: Gradient(colors: [.blue, .green]), startPoint: .topLeading, endPoint: .bottomTrailing)
        .ignoresSafeArea()
        let footballLoginButton = CustomButton()
        let baseballLoginButton = CustomButton()
        VStack(spacing: 40) {
            // App Logo (replace with your actual logo)
            Image("your_app_logo")
              .resizable()
              .scaledToFit()
              .frame(width: 150, height: 70)

            Text("Select your sport:")
              .font(.title)
              .fontWeight(.bold)
              .foregroundColor(.white)

            footballLoginButton.createBlueButton(withTitle: "football Login", height: 70, width: 250, color: "blue", systemName: "football", action: {
              // Your football login action code here
            })

            footballLoginButton.createGreenButton(withTitle: "baseball Login", height: 70, width: 250, color: "green", systemName: "baseball", action: {
              // Your football login action code here
            })

        }
    }
  }
}

struct HomePage_Previews: PreviewProvider {
  static var previews: some View {
    HomePage()
  }
}

I am using the below class to create buttons.

class CustomButton {
    
    func createBlueButton(withTitle title: String, height: CGFloat, width: CGFloat, color: String, systemName: String, action: @escaping () -> Void) -> some View {
        Button(action: {}) {
            HStack(spacing: 275) {
                Image(systemName: systemName)
                    .foregroundColor(.white)
                Text(title)
                    .fontWeight(.bold)
                    .foregroundColor(.white)
            }
            .padding()
            .background(Color.blue)
            .clipShape(RoundedRectangle(cornerRadius: 20)) // Squircle shape
            .frame(width: width, height: height) // Adjusted button size
        }
    }
    
    func createGreenButton(withTitle title: String, height: CGFloat, width: CGFloat, color: String, systemName: String, action: @escaping () -> Void) -> some View {
        Button(action: {}) {
            HStack(spacing: 275) {
                Image(systemName: systemName)
                    .foregroundColor(.white)
                Text(title)
                    .fontWeight(.bold)
                    .foregroundColor(.white)
            }
            .padding()
            .background(Color.green)
            .clipShape(RoundedRectangle(cornerRadius: 20)) // Squircle shape
            .frame(width: width, height: height) // Adjusted button size
        }
    }
}

But the app preview on XCode IDE shows buttons without text in it as below. enter image description here

If I reduce the HStack's spacing to 75 like HStack(spacing: 75) I am able to see the text in button but the buttons are very small. Could anyone let me know what is the mistake I am making here and how can I correct it?

Edit1

Also tried: .frame(width: width, height: height, alignment: .center) in the button class but the result is same.


Solution

  • I would suggest the following changes:

    • Instead of applying spacing to the HStack, use one or more Spacer() to provide the spacing:
    HStack {
        Image(systemName: systemName)
        Spacer()
        Text(title)
        Spacer()
    }
    
    • The reason why your attempts to set a frame width didn't seem to be working may be because you need to set the width before you apply the background color. So the .frame modifier needs to come before the .background and .clipShape.

    • Your class CustomButton appears to be a button factory. However, there is no need to create an instance for each button. In fact, you have created two instances called footballLoginButton and baseballLoginButton, but you then use footballLoginButton to create both of the actual buttons, baseballLoginButton is not being used. So I would suggest changing the functions into static functions, and it can be a struct instead of a class too.

    • Instead of using a fixed width and height, just let the content find its own size and then use padding to add spacing where needed.

    • When it comes to button styling, it works well to define your own ButtonStyle.

    • The modifier .foregroundColor is deprecated, use .foregroundStyle instead.

    Here's how it can all come together:

    struct HomePage: View {
    
        var body: some View {
            ZStack {
                // Background gradient
                LinearGradient(gradient: Gradient(colors: [.blue, .green]), startPoint: .topLeading, endPoint: .bottomTrailing)
                    .ignoresSafeArea()
                VStack(spacing: 40) {
                    // App Logo (replace with your actual logo)
                    Image("your_app_logo")
                        .resizable()
                        .scaledToFit()
                        .frame(width: 150, height: 70)
    
                    Text("Select your sport:")
                        .font(.title)
                        .fontWeight(.bold)
                        .foregroundStyle(.white)
    
                    CustomButton.createBlueButton(withTitle: "football Login", systemName: "football") {
                        // Your football login action code here
                    }
    
                    CustomButton.createGreenButton(withTitle: "baseball Login", systemName: "baseball") {
                        // Your baseball login action code here
                    }
                }
                .padding(.horizontal, 30)
            }
        }
    }
    
    class CustomButton {
    
        private struct CustomButtonStyle: ButtonStyle {
            let color: Color
    
            func makeBody(configuration: Configuration) -> some View {
                configuration.label
                    .foregroundStyle(.white)
                    .fontWeight(.bold)
                    .padding()
                    .background(color)
                    .clipShape(RoundedRectangle(cornerRadius: 20)) // Squircle shape
            }
        }
    
        private static func createButton(withTitle title: String, systemName: String, color: Color, action: @escaping () -> Void) -> some View {
            Button(action: action) {
                HStack {
                    Image(systemName: systemName)
                    Spacer()
                    Text(title)
                    Spacer()
                }
            }
            .buttonStyle(CustomButtonStyle(color: color))
        }
    
        static func createBlueButton(withTitle title: String, systemName: String, action: @escaping () -> Void) -> some View {
            CustomButton.createButton(withTitle: title, systemName: systemName, color: .blue, action: action)
        }
    
        static func createGreenButton(withTitle title: String, systemName: String, action: @escaping () -> Void) -> some View {
            CustomButton.createButton(withTitle: title, systemName: systemName, color: .green, action: action)
        }
    }
    

    Screenshot