Search code examples
swiftswiftuiframemobile-development

How can I expand the height of a shape based on the content of its overlay in SwiftUI


I am new to SwiftUI and have what seems like a relatively straight forward issue. I have a RecipeCard component that has a image and bottom rectangular section which has the title of the recipe and name of the author.

Right now, if the title or author name goes beyond the max width of the text box it cuts the overflow with "Part of Name...". I want the title and author name to expand over different lines (I've achieved this) but also push out the height of the bottom rectangular section.

My main approach has been to set a max height for the Rectangle. However, when I set a max height, the rectangle always seems to go to the max height, even if no content pushes it down. While it's obvious in the below code that the Spacer() in the VStack maxes out the vertical space, I've tried other variations but have the same problem. Does anyone have any easy ways to adjust the height of the bottom rectangle (while also pushing out the outer rounded rectangle) when the text requires new lines?

enter image description here

Here is my component code:

        RoundedRectangle(cornerRadius: 10)
            .strokeBorder(Color.black, lineWidth: 1)
            .background(RoundedRectangle(cornerRadius: 10).fill(.white))
            .frame(width: 132, height: 180)
            .padding(-1)
            .overlay {
                VStack (spacing: 0) {
                    Image("coq-au-vin")
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(height: 132)
                        .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                        .padding(.bottom, -16)
                        
                    Rectangle()
                        .fill(Color(.white))
                        .frame(height: 48)
                        .overlay {
                            VStack (alignment: .leading, spacing: 4) {
                                Text(recipeName)
                                    .font(.custom("Inter-Regular", size: 13))
//                                        .lineLimit(nil)
//                                        .fixedSize(horizontal: false, vertical: true)
                                Text(recipeAuthor)
                                    .font(.custom("Inter-Regular", size: 11))
//                                        .lineLimit(nil)
//                                        .fixedSize(horizontal: false, vertical: true)
                            }
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .padding([.top, .leading, .trailing], 8)
                        }
                        
                    Spacer()
                }
            }

Solution

  • The tip by lorem ipsum is a good one - it probably works better if you let the main content of your view find its own size with as few .frame constraints as possible. Then apply a background (if needed) behind it and the border as an overlay. The background and overlay automatically adopt the size of the View they are applied to.

    Hopefully the following is close to what you were asking for:

    struct ContentView: View {
        let recipeName = "Coq Au Vin With Lots Of Tasty Bits"
        let recipeAuthor = "Recipe Tin Eats With All His Mates"
        var body: some View {
            VStack(spacing: 0) {
                Image(systemName: "fork.knife.circle")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(height: 132)
                    .background(Color.yellow)
                VStack(alignment: .leading, spacing: 4) {
                    Text(recipeName)
                        .font(.custom("Inter-Regular", size: 13))
                    Text(recipeAuthor)
                        .font(.custom("Inter-Regular", size: 11))
                }
                // Force multi-line instead of truncation of text
                .fixedSize(horizontal: false, vertical: true)
                .padding(8)
            }
            .frame(width: 132)
            .background(Color.white)
            .cornerRadius(10)
            .overlay(
                RoundedRectangle(cornerRadius: 10)
                    .strokeBorder(Color.black, lineWidth: 1)
            )
        }
    }
    

    Screenshot