Scrollable Plain Text with SwiftUI
January 09, 2025 at 6:28AMI 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 ;-)