9

this gives me dynamic text height correctly

import SwiftUI
struct ContentView : View {
    var body: some View {
        List {
            Text("This is some very long text can we can see scrolls past two lines ").lineLimit(nil)   
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

enter image description here

But the following truncates the Text. How do i get dynamic height with the following?

import SwiftUI
struct ContentView : View {
    var body: some View {
        GeometryReader { reader in
            ScrollView {
                Text("This is some very long text can we can see scrolls past two lines ")
                    .lineLimit(nil)
                    .frame(width: reader.size.width)
            }
        }
    }
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

enter image description here

Just a coder
  • 15,480
  • 16
  • 85
  • 138
  • There's a good working workaround on this SO post: https://stackoverflow.com/questions/56593120/how-do-you-create-a-multi-line-text-inside-a-scrollview-in-swiftui – GRosay Oct 06 '19 at 15:53

6 Answers6

14

You need to prevent truncation using fixedSize modifier. In your case it is like:

Text("This is some very long text can we can see scrolls past two lines ")
 .lineLimit(nil)
 .fixedSize(horizontal: false, vertical: true) //** It keeps 'width size' and expands 'height size'
 .frame(width: reader.size.width)
FRIDDAY
  • 3,781
  • 1
  • 29
  • 43
6
import SwiftUI

struct ContentView : View {

    let veryLongText = "Very long text..."
    let fontName = "System Font"
    let fontSize: Length = 12

    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                Text(veryLongText)
                    .lineLimit(nil)
                    .font(.custom(fontName, size: fontSize))
                    .frame(width: geometry.size.width, height: veryLongText.textHeightFrom(width: geometry.size.width, fontName: fontName, fontSize: fontSize))
            }
        }
        .padding(.horizontal, 0.5 * fontSize)
    }
}

extension String {

    func textHeightFrom(width: CGFloat, fontName: String = "System Font", fontSize: CGFloat = .systemFontSize) -> CGFloat {

        #if os(macOS) 

        typealias UXFont = NSFont
        let text: NSTextField = .init(string: self)
        text.font = NSFont.init(name: fontName, size: fontSize)

        #else

        typealias UXFont = UIFont
        let text: UILabel = .init()
        text.text = self
        text.numberOfLines = 0

        #endif

        text.font = UXFont.init(name: fontName, size: fontSize)
        text.lineBreakMode = .byWordWrapping
        return text.sizeThatFits(CGSize.init(width: width, height: .infinity)).height
    }
}
4

Might be a ScrollView bug. I'm honestly not sure why your code doesn't work, but you can get the desired result using:

struct ContentView : View {
    var body: some View {
        GeometryReader { reader in
            ScrollView(alwaysBounceVertical: true) {
                ZStack(alignment: .top) {
                    Color.clear.edgesIgnoringSafeArea(.all)
                    Text("This is some very long text can we can see scrolls past two lines ")
                        .lineLimit(nil)
                }.frame(width: reader.size.width, height: reader.size.height)
            }
        }
    }
}

I added alwaysBounceVertical to the scroll view so you can see that the view scrolls. It's not necessary though.

JWK
  • 3,345
  • 2
  • 19
  • 27
  • + for the work around. I'll leave the question up a while still in case this is not a bug. Because I need this view to get much more complex and I am not sure the workaround might work – Just a coder Jun 30 '19 at 16:38
1

In my opinion this is a bug, as I cannot find the rationale of its behavior.

Let me explain:

If you do not have a ScrollView, we know that forcing the width of a Text view to a specific width and using the lineLimit(nil), will make the text view expand vertically to accommodate the text. If instead, you have lineLimit(1), the ellipsis will show to truncate the contents of the view.

Now, if we have the Text() inside a ScrollView, and we force the width to a specific size and set lineLimit(nil), there are two acceptable outcomes:

  1. The text view respects the new width and expands vertically.
  2. The text view does not respect the width but let you scroll horizontally. Not at all a good choice, but it could be a decision made by who developed the framework.

However, what actually happens, is that the text view respects the new width size (as made evident by the ellipsis), but it won't expand vertically in spite of the lineLimit(nil). There is no reason for that, since that behavior can already be achieved by using lineLimit(1). This is why I think this is definitely a bug.

You should file a bug report with Apple.

kontiki
  • 37,663
  • 13
  • 111
  • 125
  • And also note that if you force the height, to let's say 100 points, the text then does extend to more than 1 line. The problem is that you do not know in advance how much you will need. – kontiki Jun 30 '19 at 20:23
  • Agreed, this is most definitely a bug and it's very easy to reproduce. Simply create a new SwiftUI file, wrap the existing "Hello World" `Text()` inside a `ScrollView`, and create the text with a long string. I have submitted a Feedback on this, and really encourage everyone to do the same. – Andy Ibanez Jul 15 '19 at 00:54
  • I can just confirm that this issue still persists in beta 5. I have the same use case and need to wrap a long text inside a scroll view. It seems that ScrollView is very buggy atm. It also breaks `.sheet` modifiers. Also filed a bug report for this. – grahan Aug 19 '19 at 19:37
0

This appears to have been addressed with iOS 15.0. But if you want to support users on iOS 14, then you could add a hack view modifier

@available(iOS 14.0, *)
fileprivate struct LineLimitHackForiOS14: ViewModifier {

    func body(content: Content) -> some View {
        if #available(iOS 15.0, *) {
            return AnyView(content)
        }
        return AnyView(content.fixedSize(horizontal: false, vertical: true))
    }
}

@available(iOS 14.0, *)
extension View {
    fileprivate func applyHackToPreventTextTruncationInScrollViews() -> some View {
        self.lineLimit(nil).modifier(LineLimitHackForiOS14())
    }
}

Where code could be used as

Text(self.text).applyHackToPreventTextTruncationInScrollViews()
Nish
  • 51
  • 1
  • 7
0

WORKAROUND #1

I was able to get around this issue by adding a new line to the end of the string:

        ScrollView {
            Text("This is some very long text can we can see scrolls past two lines\n")
        }

With this workaround, I didn't need to add .lineLimit(nil) or set a fixed width.

WORKAROUND #2

I was also able to get around this by simply setting a fixed font size:

    ScrollView {
        Text("This is some very long text can we can see scrolls past two lines")
            .font(.custom("CustomText", fixedSize: 14.0))
    }
NineToeNerd
  • 307
  • 5
  • 17