SwiftUI text layout bug

I'm tracing down a swiftUI text layout issue and created a small sample app reproducing it (tested on iOS 15, iPhone max size simulator) which I've filed as a radar. Looking for a workaround until it is fixed. NavigationView, List and PlainListStyle are the important ingredients.

Edit:
Expected Behaviour: text height adjusted appropriately and there is no text truncation. The presence of slider doesn't effect things.
Actual Behaviour: text height is not adjusted appropriately and there is text truncation. Uncommenting slider magically fixes the bug.

import SwiftUI

class Model: ObservableObject {
    @Published var value: CGFloat = 0
    @Published var text = "t in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis"
    private var on = false
    
    init() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [self] _ in
            on.toggle()
            value = on ? 0.99 : 0
        }
    }
}

struct ContentView: View {
    @StateObject var model = Model()
    
    var body: some View {
        NavigationView {
            List {
                // Uncomment - and there is no bug
                // Slider(value: $model.value)
                HStack() {
                    Color.green.frame(width: model.value * 200, height: 1)
                    Text(model.text)
                }
            }
            .listStyle(PlainListStyle())
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

@main
struct TextBugApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

So I think the bug is Text got elited instead of expand the height more to show the whole?

Add this to Text fix it:

.fixedSize(horizontal: false, vertical: true)

I duplicated the HStack to add an extra item in the List and it still works :slight_smile:

yep, the text is truncated like this:

with your workaround the text is not truncated... but extends beyond the list cell boundaries:

I don't see the problem: Text.fixedSize() fix - Album on Imgur

Xcode Version 13.2 (13C90)

I just copy your code and add fixedSize():

import SwiftUI

class Model: ObservableObject {
    @Published var value: CGFloat = 0
    @Published var text = "t in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis"
    private var on = false

    init() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [self] _ in
            on.toggle()
            value = on ? 0.99 : 0
        }
    }
}

struct ContentView: View {
    @StateObject var model = Model()

    var body: some View {
        NavigationView {
            List {
                // Uncomment - and there is no bug
//                Slider(value: $model.value)
                HStack() {
                    Color.green.frame(width: model.value * 200, height: 1)
                    Text(model.text)
                        .fixedSize(horizontal: false, vertical: true)
                }
                HStack() {
                    Color.green.frame(width: model.value * 200, height: 1)
                    Text(model.text)
                        .fixedSize(horizontal: false, vertical: true)
                }
            }
            .listStyle(PlainListStyle())
        }
    }
}

I think the fact you are duplicating HStack/Text makes it redraw twice and correct any incorrectness that would otherwise surface... try this to see the issue:

HStack() {
    Color.green.frame(width: model.value * 200, height: 1)
    Text(model.text)
        .fixedSize(horizontal: false, vertical: true)
}
Color.blue

Then you need to "invalidated" List item with a new id: List item invalidated - Album on Imgur

import SwiftUI

class Model: ObservableObject {
    @Published var id = 0
    @Published var value: CGFloat = 0
    @Published var text = "t in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis"
    private var on = false

    init() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [self] _ in
            on.toggle()
            value = on ? 0.99 : 0
            id += 1
        }
    }
}

struct ContentView: View {
    @StateObject var model = Model()

    var body: some View {
        NavigationView {
            List {
                // Uncomment - and there is no bug
//                Slider(value: $model.value)
                HStack() {
                    Color.green.frame(width: model.value * 200, height: 1)
                    Text(model.text)
                        .fixedSize(horizontal: false, vertical: true)
                }
                .id(model.id)
//                HStack() {
//                    Color.green.frame(width: model.value * 200, height: 1)
//                    Text(model.text)
//                        .fixedSize(horizontal: false, vertical: true)
//                }
                Color.blue
            }
            .listStyle(PlainListStyle())
        }
    }
}

Check this to see if there is better approach: id(_): Identifying SwiftUI Views - The SwiftUI Lab

1 Like

wow, thanks a lot. It did it and fixedSize trick is no required, just id.

Whenever you post about a bug, you need to describe the expected behavior and the actual behavior that you observe.

Edited the expected / actual behaviours in the original post.

Also this variation of @young version works:

class Model: ObservableObject {
    @Published var value: CGFloat = 0
    @Published var text = "t in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis"
    var id: String {
        String(Double(value)) + text
    }

Edit:

Interestingly this simpler variant also works:

    HStack {
        Color.green.frame(width: model.value * 200, height: 1)
        Text(model.text)
    }
    .id(model.value)

and changing either the text variable or value variable correctly updates UI. :thinking: