Automatically adjustable view height based on text height in SwiftUI
Asked Answered
N

2

7

I am trying to create a view in SwiftUI where the background of the image on the left should scale vertically based on the height of the text on the right.

I tried a lot of different approaches, from GeometryReader to .layoutPriority(), but I haven't managed to get any of them to work.

Current state:

Current state

Desired state:

enter image description here

I know that I could imitate the functionality by hardcoding the .frame(100) for the example I posted, but as text on the right is dynamic, that wouldn't work.

This is full code for the view in the screenshot:

import SwiftUI

struct DynamicallyScalingView: View {
    var body: some View {
        HStack(spacing: 20) {
            Image(systemName: "snow")
                .font(.system(size: 32))
                .padding(20)
                .background(Color.red.opacity(0.4))
                .cornerRadius(8)

            VStack(alignment: .leading, spacing: 8) {
                Text("My Title")
                    .foregroundColor(.white)
                    .font(.system(size: 13))
                    .padding(5)
                    .background(Color.black)
                    .cornerRadius(8)
                Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
                    .font(.system(size: 13))
            }
        }
        .padding(.horizontal)
    }
}

struct DailyFactView_Previews: PreviewProvider {
    static var previews: some View {
        DynamicallyScalingView()
    }
}
Nonbelligerent answered 18/6, 2020 at 13:22 Comment(0)
I
14

Here is a solution based on view preference key. Tested with Xcode 11.4 / iOS 13.4

demo

struct DynamicallyScalingView: View {
    @State private var labelHeight = CGFloat.zero     // << here !!

    var body: some View {
        HStack(spacing: 20) {
            Image(systemName: "snow")
                .font(.system(size: 32))
                .padding(20)
                .frame(minHeight: labelHeight)       // << here !!
                .background(Color.red.opacity(0.4))
                .cornerRadius(8)

            VStack(alignment: .leading, spacing: 8) {
                Text("My Title")
                    .foregroundColor(.white)
                    .font(.system(size: 13))
                    .padding(5)
                    .background(Color.black)
                    .cornerRadius(8)
                Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
                    .font(.system(size: 13))
            }
            .background(GeometryReader {      // << set right side height
                Color.clear.preference(key: ViewHeightKey.self, 
                    value: $0.frame(in: .local).size.height) 
            })
        }
        .onPreferenceChange(ViewHeightKey.self) { // << read right side height
            self.labelHeight = $0        // << here !!
        }
        .padding(.horizontal)
    }
}

struct ViewHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = value + nextValue()
    }
}

Idolize answered 18/6, 2020 at 13:53 Comment(2)
What is the reason to set .frame(minHeight: labelHeight) instead of .frame(height: labelHeight)?Nonbelligerent
I have seen you in, at least, on more post using Preference Key. I really do not understand why you are making things even hard for other developers. What you did here will mislead lost of beginners including myself. Please, do not answer just for the sake of answering. if you are not sure about something let the question "rot", I am quite sure someone will find a proper solution to problem without ugly workarounds. You can check the correct answer below. https://mcmap.net/q/356239/-automatically-adjustable-view-height-based-on-text-height-in-swiftuiDekow
D
13

This is the answer without workaround.

struct DynamicallyScalingView: View {
    var body: some View {
        HStack(spacing: 20) {
            Image(systemName: "snow")
                .frame(maxHeight: .infinity)          // Add this
                .font(.system(size: 32))
                .padding(20)
                .background(Color.red.opacity(0.4))
                .cornerRadius(8)

            VStack(alignment: .leading, spacing: 8) {
                Text("My Title")
                    .foregroundColor(.white)
                    .font(.system(size: 13))
                    .padding(5)
                    .background(Color.black)
                    .cornerRadius(8)
                Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
                    .font(.system(size: 13))
            }
            .frame(maxHeight: .infinity)              // Add this
        }
        .padding(.horizontal)
        .fixedSize(horizontal: false, vertical: true) // Add this
    }
}

enter image description here

Dekow answered 6/1, 2023 at 18:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.