Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - how know number of lines in Text?

Tags:

xcode

swiftui

I have a dynamic text, it can be small or large I what by default show only 3 lines and only if needed add "more" button. When the user tap on this button ("More") - I will show all test.

I ask, how to know in SwiftUI if text it more of 3 lines or not?

like image 272
israel_b_2012 Avatar asked Dec 26 '19 07:12

israel_b_2012


People also ask

How do I set the number of lines in a text in SwiftUI?

To force a minimum number of lines to a text view, you append a new line character ( \n ) to your text.

Is text a view in SwiftUI?

Text in SwiftUI is a view that lets you display one or more lines of text. This is suitable for read-only information that's not editable. To display a line of text, you initialize Text and set a String value.

How do you justify text in SwiftUI?

You can do this via the modifier . multilineTextAlignment(. center) . Save this answer.


2 Answers

You can use a GeometryReader to determine the width of the text field and then use that together with information about the font to calculate the size of the bounding rect that would be required to show the entire text. If that height exceeds the text view then we know that the text has been truncated.

struct LongText: View {

    /* Indicates whether the user want to see all the text or not. */
    @State private var expanded: Bool = false

    /* Indicates whether the text has been truncated in its display. */
    @State private var truncated: Bool = false

    private var text: String

    init(_ text: String) {
        self.text = text
    }

    private func determineTruncation(_ geometry: GeometryProxy) {
        // Calculate the bounding box we'd need to render the
        // text given the width from the GeometryReader.
        let total = self.text.boundingRect(
            with: CGSize(
                width: geometry.size.width,
                height: .greatestFiniteMagnitude
            ),
            options: .usesLineFragmentOrigin,
            attributes: [.font: UIFont.systemFont(ofSize: 16)],
            context: nil
        )

        if total.size.height > geometry.size.height {
            self.truncated = true
        }
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            Text(self.text)
                .font(.system(size: 16))
                .lineLimit(self.expanded ? nil : 3)
                // see https://swiftui-lab.com/geometryreader-to-the-rescue/, 
                // and https://swiftui-lab.com/communicating-with-the-view-tree-part-1/
                .background(GeometryReader { geometry in
                    Color.clear.onAppear {
                        self.determineTruncation(geometry)
                    }
                })

            if self.truncated {
                self.toggleButton
            }
        }
    }

    var toggleButton: some View {
        Button(action: { self.expanded.toggle() }) {
            Text(self.expanded ? "Show less" : "Show more")
                .font(.caption)
        }
    }

}

This is then how it looks for both long and short texts:

Usage example preview

Hope this helps.

like image 109
bhuemer Avatar answered Sep 19 '22 13:09

bhuemer


Building on the excellent work from bhuemer, this version respects SwiftUI's local Font rather than requiring a hard-coded UIFont. Rather than reading the size of the "full" text using String layout, this renders Text three times: once for real, once with a line limit, and once without a line limit. It then uses two GRs to compare the last two.

struct LongText: View {

    /* Indicates whether the user want to see all the text or not. */
    @State private var expanded: Bool = false

    /* Indicates whether the text has been truncated in its display. */
    @State private var truncated: Bool = false

    private var text: String

    var lineLimit = 3

    init(_ text: String) {
        self.text = text
    }

    var body: some View {
        VStack(alignment: .leading) {
            // Render the real text (which might or might not be limited)
            Text(text)
                .lineLimit(expanded ? nil : lineLimit)

                .background(

                    // Render the limited text and measure its size
                    Text(text).lineLimit(lineLimit)
                        .background(GeometryReader { displayedGeometry in

                            // Create a ZStack with unbounded height to allow the inner Text as much
                            // height as it likes, but no extra width.
                            ZStack {

                                // Render the text without restrictions and measure its size
                                Text(self.text)
                                    .background(GeometryReader { fullGeometry in

                                        // And compare the two
                                        Color.clear.onAppear {
                                            self.truncated = fullGeometry.size.height > displayedGeometry.size.height
                                        }
                                    })
                            }
                            .frame(height: .greatestFiniteMagnitude)
                        })
                        .hidden() // Hide the background
            )

            if truncated { toggleButton }
        }
    }

    var toggleButton: some View {
        Button(action: { self.expanded.toggle() }) {
            Text(self.expanded ? "Show less" : "Show more")
                .font(.caption)
        }
    }
}

The following shows the behavior with surrounding views. Note that this approach supports LongText(...).font(.largeTitle) just like a regular Text.

struct ContentView: View {
    let longString = "This is very long text designed to create enough wrapping to force a More button to appear. Just a little more should push it over the edge and get us to one more line."

    var body: some View {
        VStack {
            Text("BEFORE TEXT")
            LongText(longString).font(.largeTitle)
            LongText(longString).font(.caption)
            Text("AFTER TEXT")
        }
    }
}

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

Screenshot of preview showing More button for largeTitle text, but no More button for caption text.

like image 40
Rob Napier Avatar answered Sep 17 '22 13:09

Rob Napier