How to Add a Tap Gesture to Part of a Concatenated SwiftUI Text View?

181 Views Asked by At

I'm working on a SwiftUI view where I need to concatenate two Text views and make only one part of the concatenated text tappable. Here is a simplified example.

Text("Hello, ") + Text("world!").bold()

I want to add a tap gesture such that only the "world!" part is tappable. However, I am aware that after concatenation with the + operator, the text becomes a single Text view, making it challenging to add a gesture recognizer to just a part of it.

Is there a way in SwiftUI to add a tap gesture recognizer to only a part of a concatenated Text view? Or is there an alternative approach to achieve a similar layout with part of the text being tappable?

My end goal is to create a toggle-able truncation such as this: Spotify demo

1

There are 1 best solutions below

0
Benzy Neez On

Another problem you are going to face in this particular case is finding a way to truncate the first text with ellipses and then append the "see more" without ellipses.

If you look closely at the demo, I don't think the ellipses are attached to the first text. So one way to implement is to include the ellipses with the button and appy it as an overlay in the bottom corner using alignment .trailingLastTextBaseline. A gradient can be used to mask out the text that flows behind the button.

To constrain the height of the long text, you can either set a fixed height, or just set a line limit. By setting a line limit, there is no excess vertical space and it adapts to different font sizes automatically.

@State private var isExpanded = false
let loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

var body: some View {
    VStack(alignment: .leading, spacing: 20) {
        Text(loremIpsum)
            .lineLimit(isExpanded ? nil : 4)
            .overlay(alignment: .trailingLastTextBaseline) {
                if !isExpanded {
                    Text("... see more")
                        .bold()
                        .padding(.leading, 20)
                        .background {
                            LinearGradient(
                                colors: [.clear, Color(UIColor.systemBackground)],
                                startPoint: .leading,
                                endPoint: UnitPoint(x: 0.15, y: 0.5)
                            )
                        }
                        .onTapGesture { isExpanded = true }
                }
            }
        if isExpanded {
            Text("See less")
                .bold()
                .onTapGesture { isExpanded = false }
        }
    }
    .padding()
    .animation(.easeInOut, value: isExpanded)
    .frame(height: 400, alignment: .top)
}

Animation