Question

How to make Image center of the screen and below it a label in swiftUI

I am working on a simple design in SwiftUI, but I am finding it quite challenging. Here is the design I am trying to achieve: Image centered the screen text label blow the Image

the design seems to be easy but unfortunately it very challenging in swift The goal is to center an image with a height and width of 140 points in the middle of the screen. Below the image, there should be a text label with a top padding of 50 points. Here is the code I have so far:

struct ContentView: View {
   var body: some View {
        VStack {
            Spacer()
            VStack(spacing: 0) {
                Image("SwiftLogo")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 140, height: 140)

                Text("Hello Swift")
                    .font(.system(size: 32, weight: .semibold))
                    .foregroundColor(.black)
                    .background(Color.green)
                    .padding(.top, 50)
            }
            Spacer()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .edgesIgnoringSafeArea(.all)
    }
}

However, this code centers the entire view, which is not what I want. How can I modify it to achieve the desired design?

The goal is to center an image with a height and width of 140 points in the middle of the screen. Below the image, there should be a text label with a top padding of 50 points.

 3  118  3
1 Jan 1970

Solution

 1

You can put the VStack inside an full-height HStack, then adjust the center alignmentGuide of the VStack to be the top of the vertical centre of the image.

HStack {
    VStack(spacing: 0) {
        Image("my_image")
            .resizable()
            .scaledToFill()
            .frame(width: 140, height: 140)
        Text("Hello Swift")
            .font(.system(size: 32, weight: .semibold))
            .foregroundColor(.black)
            .background(Color.green)
            .padding(.top, 50)
    }
    .alignmentGuide(VerticalAlignment.center) { dimension in
        // the vertical centre of the image is 70pt downwards from the top of the VStack
        dimension[.top] + 70
    }
}
.frame(maxHeight: .infinity)
//.ignoresSafeArea() // if you want centre of the screen instead of centre of safe area

Alternatively, put the text as an overlay of the image, then use alignmentGuide to move it 50pt below the image.

var body: some View {
    VStack {
        Image("my_image")
            .resizable()
            .scaledToFit()
            .frame(width: 140, height: 140)
            .frame(maxWidth: .infinity)
            .overlay(alignment: .bottom) {
                Text("Hello Swift")
                    .font(.system(size: 32, weight: .semibold))
                    .foregroundColor(.black)
                    .background(Color.green)
                    .alignmentGuide(.bottom) { dimension in
                        // 50pt upwards from the top of the text should align with the bottom of the image
                        dimension[.top] - 50
                    }
            }
    }
    .frame(maxHeight: .infinity)
    //.ignoresSafeArea() // if you want centre of the screen instead of centre of safe area
}

Note that I put another .frame(maxWidth: .infinity) around the existing 140x140 frame. This is so that the overlay can expand to the full width of the screen, instead of limited by the 140pt width of the image.

Output:

enter image description here

2024-07-15
Sweeper

Solution

 0

Here are two quite simple ways to achieve the layout:

1. Add a hidden version of the label above the image

The hidden version balances out the visible version below the image, so the image remains centered:

var body: some View {
    VStack(spacing: 0) {
        Text("Hello Swift")
            .hidden()
        Image("SwiftLogo")
            .resizable()
            .scaledToFit()
            .frame(width: 140, height: 140)
            .padding(.vertical, 50)
        Text("Hello Swift")
            .foregroundStyle(.black)
            .background(.green)
    }
    .font(.system(size: 32, weight: .semibold))
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .ignoresSafeArea()
}

2. Show the text in an overlay with a y-offset

The overlay is added with alignment: .top. The offset is then computed as (image height + gap to label).

Doing it this way, you will want to expand the image to the full width before applying the overlay, so that the label can be wider than the image if it needs to be:

var body: some View {
    Image("SwiftLogo")
        .resizable()
        .scaledToFit()
        .frame(width: 140, height: 140)
        .frame(maxWidth: .infinity)
        .overlay(alignment: .top) {
            Text("Hello Swift")
                .font(.system(size: 32, weight: .semibold))
                .foregroundStyle(.black)
                .background(.green)
                .offset(y: 140 + 50)
        }
        .frame(maxHeight: .infinity)
        .ignoresSafeArea()
}

The result looks the same in both cases:

Screenshot

Ps. .foregroundColor and .edgesIgnoringSafeArea are both deprecated, use .foregroundStyle and .ignoresSafeArea instead.

2024-07-15
Benzy Neez

Solution

 0

Below is what works fine.

struct ContentView: View {
    var body: some View {
        ZStack {
            VStack {
                Image( "all-out-donuts-thumb")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 140, height: 140)
            }
            Text("Hello Swift")
                .padding(.top, (140+50))
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .ignoresSafeArea()
    }
}

padding is (140+50) is because 140 is the height of the image and 50 is what you want to give.

Note : I have not used any formatting here of font etc.


Don't use numbers 140 directly as not all devices width are same.

You can use logic I explained here

2024-07-15
Fahim Parkar

Solution

 0

Here's an advanced way to achieve the design you want, in which you can use a dynamic font and the image will always remain in the center of screen:

struct TextHeightPreferenceKey: PreferenceKey {

  typealias Value = CGFloat

  static var defaultValue: CGFloat = 0

  static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
    value = nextValue()
  }
}

struct ContentView: View {
      
      @State private var textHeight: CGFloat = 0
      
      private let imageSize: CGFloat = 140
      
      private let kPadding: CGFloat = 50
      
      var body: some View {
        VStack(spacing: kPadding) {
          Image("SwiftLogo")
            .resizable()
            .scaledToFit()
            .frame(width: imageSize, height: imageSize)
          
          Text("Hello Swift")
            .font(.system(size: 32, weight: .semibold))
            .background(GeometryReader { geometry in
              Color.clear
                .preference(key: TextHeightPreferenceKey.self, value: geometry.size.height)
            })
            .onPreferenceChange(TextHeightPreferenceKey.self, perform: { value in
              self.textHeight = value
            })
        }
        .offset(y: textHeight/2 + kPadding/2)
      }
    }
2024-07-16
Mohammed