Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Content hugging priority behaviour in SwiftUI

Tags:

swiftui

I have a List made of cells, each containing an image, and a column of text, which I wish laid out in a specific way. Image on the left, taking up a quarter of the width. The rest of the space given to the text, which is left-aligned.

Here's the code I got:

struct TestCell: View {
    let model: ModelStruct

    var body: some View {
        HStack {
            Image("flag")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: UIScreen.main.bounds.size.width * 0.25)
            VStack(alignment: .leading, spacing: 5) {
                Text("Country: Moldova")
                Text("Capital: Chișinău")
                Text("Currency: Leu")
            }
            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
        }
    }
}

struct TestCell_Previews: PreviewProvider {
    static var previews: some View {
        TestCell()
            .previewLayout(.sizeThatFits)
            .previewDevice("iPhone 11")
    }
}

And here are 2 examples:

As you can see, the height of the whole cell varies based on the aspect ratio of the image.

$1M question - How can we make the cell height hug the text (like in the second image) and not vary, but rather shrink the image in a scaleAspectFit manner inside the allocated rectangle

Note!

  1. The text's height can vary, so no hardcoding.
  2. Couldn't make it work with PreferenceKeys, as the cells will be part of a List, and there's some peculiar behaviour I'm trying to grasp around cell reusage, and onPreferenceChange not being called when 2 consecutive cells have the same height. To exhibit all this combined behaviour, make sure your model varies between cells when you test it.
like image 482
Sergiu Todirascu Avatar asked Nov 06 '22 06:11

Sergiu Todirascu


People also ask

What is content hugging priority in Swift?

The content-hugging priority represents the resistance a view has to grow larger than its intrinsic content size. Conversely, the compression-resistance priority represents the resistance a view has to shrink beyond its intrinsic content size.

What is horizontal hugging priority?

horizontal content hugging priority: green(252)- blue(251) So, larger the content hugging priority , the views bound will hug to the intrinsic content more tightly preventing the view to grow beyond its intrinsic content size.

What is setContentHuggingPriority?

setContentHuggingPriority(_:for:)Sets the priority with which a view resists being made larger than its intrinsic size. iOS 6.0+ iPadOS 6.0+ Mac Catalyst 13.1+ tvOS 9.0+


1 Answers

Here is a possible solution, however it uses GeometryReader inside the background property of the VStack, to detect their height. That height is being applied to the Image then. I used SizePreferenceKey from this solution.

struct SizePreferenceKey: PreferenceKey {
    typealias Value = CGSize
    static var defaultValue: Value = .zero

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

struct ContentView6: View {

    @State var childSize: CGSize = .zero
    
    var body: some View {
        HStack {
            Image("image1")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: UIScreen.main.bounds.size.width * 0.25, height: self.childSize.height)
            VStack(alignment: .leading, spacing: 5) {
                Text("Country: Moldova")
                Text("Capital: Chișinău")
                Text("Currency: Leu")
            }
            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
            .background(
                  GeometryReader { proxy in
                    Color.clear.preference(key: SizePreferenceKey.self, value: proxy.size)
                   }
                 )
        }
        .onPreferenceChange(SizePreferenceKey.self) { preferences in
          self.childSize = preferences
        }
        .border(Color.yellow)

    }
      

}

Will look like this.. you can apply different aspect ratios for the Image of course.

like image 122
davidev Avatar answered Dec 07 '22 20:12

davidev