garysimpson.dev
Mobile development with swift and flutter

Scrollable Plain Text with SwiftUI

January 09, 2025 at 6:28AM

I wanted two main features with this change.

  • A Text view that scrolls.
  • Height based on the lineLimit.

This approach gae me the added benefit of getting height adjustments for free when text size settings change in code or as part of system changes. 😎

Might be able to refactor in future but this gets me everything I need at the moment.


ScrollableText



import SwiftUI
import UIKit

/// Custom SwiftUI Text view that  vertically scrolls and sets maxHeight to always show correct number of lines (lineLimit).
/// NOTE: The **font** is used  to check the UIFont.TextStyle **lineHeight** for the height calculations using a private extension.
struct ScrollableText: View {
    let title: String
    var font: Font = .title
    var padding: CGFloat = 0
    var lineLimit: CGFloat = 1
    
    var body: some View {
        ScrollView {
            HStack(spacing: 0) {
                textLabel
                Spacer(minLength: 1) // force full width
            }
        }
        .frame(maxHeight: getHeight())
        .padding(.all, padding)
    }
    
    var textLabel: some View {
        return Text(title)
            .font(font)
            .foregroundStyle(.secondary)
    }
    
    func getHeight() -> CGFloat {
        let lineHeight = UIFont.preferredFont(forTextStyle: font.textStyle).lineHeight
        return (lineHeight * lineLimit)
    }
}

Font Extension

The key part to this change was an Extenstion on Font that allowed me to use the lineHeight property of UIFont.TextStyle to calculate the heaght per row in my getHeight() function above.



private extension Font {
    var textStyle: UIFont.TextStyle {
        switch self {
        case .body:
            return .body
        case .callout:
            return .callout
        case .caption:
            return .caption1
        case .caption2:
            return .caption2
        case .footnote:
            return .footnote
        case .headline:
            return .headline
        case .largeTitle:
            return .largeTitle
        case .subheadline:
            return .subheadline
        case .title:
            return .title1
        case .title2:
            return .title2
        case .title3:
            return .title3
        default:
            return .body
        }
    }
}

File this one away under things I wish we got for free but was fun to solve.


Happy Coding ;-)