Skip to content

Instantly share code, notes, and snippets.

@jerrypm
Last active May 4, 2025 10:01
Show Gist options
  • Select an option

  • Save jerrypm/f73660aaa73c453089cd40cdfd5fce66 to your computer and use it in GitHub Desktop.

Select an option

Save jerrypm/f73660aaa73c453089cd40cdfd5fce66 to your computer and use it in GitHub Desktop.
attributesString minimal iOS 15
import SwiftUI
/*
Available for iOS 15
*/
struct AttributedStringView: View {
let htmlString: String
let font: Font
let textColor: Color
@Binding var isExpanded: Bool
@State private var fullText: AttributedString = .init()
@State private var truncatedText: AttributedString = .init()
@State private var isTruncated: Bool = false
// Number of lines to show when collapsed
let collapsedLineLimit: Int?
init(
htmlString: String,
font: Font = .body,
textColor: Color = .primary,
isExpanded: Binding<Bool> = .constant(false),
collapsedLineLimit: Int? = nil
) {
self.htmlString = htmlString
self.font = font
self.textColor = textColor
self._isExpanded = isExpanded
self.collapsedLineLimit = collapsedLineLimit
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(isExpanded ? fullText : truncatedText)
.font(font)
.foregroundColor(textColor)
.lineLimit(isExpanded ? nil : collapsedLineLimit)
.fixedSize(horizontal: false, vertical: true)
.animation(.easeInOut(duration: 0.3), value: isExpanded)
}
.onAppear {
// Parse the HTML on component appear
let attributedString = parseHTML(from: htmlString)
fullText = attributedString
truncatedText = attributedString
// Check if text would be truncated with the collapsed line limit
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// We use a slight delay to let the view layout first
checkIfTextIsTruncated()
}
}
}
// Function to check if text is truncated
private func checkIfTextIsTruncated() {
// This is a simplified approach - in a real app you might need a more robust solution
// to check if text is actually truncated
let estimatedTextHeight = fullText.characters.count / 40 // rough estimate of chars per line
if let lineLimit = collapsedLineLimit {
isTruncated = estimatedTextHeight > lineLimit
} else {
isTruncated = false
}
}
// Convert HTML to AttributedString
private func parseHTML(from htmlString: String) -> AttributedString {
guard let data = htmlString.data(using: .utf8) else {
return AttributedString(htmlString)
}
do {
// Convert HTML to NSAttributedString
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
]
if let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) {
if let attributedString = try? AttributedString(attributedString) {
return attributedString
}
}
}
// Fallback to plain text if HTML parsing fails
return AttributedString(htmlString)
}
}
struct ExampleView: View {
// @State private var isTruncated: Bool = false
@State private var isExpanded: Bool = false
@State private var mockData: String =
"""
<p>This is a sample paragraph demonstrating <strong>bold text</strong> and some random formatting.</p>
<p>Here is a new line <br><br> followed by more text with <strong>bold formatting</strong> as an example.</p>
<p>And here's another line to show Swift-style formatting: "This is a formatted string: ??"</p>
"""
var body: some View {
VStack {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text("Details")
.font(.title)
.fontWeight(.bold)
.padding(.top)
AttributedStringView(
htmlString: mockData,
font: .system(.body),
// textColor: .secondary
textColor: .secondary,
isExpanded: $isExpanded,
collapsedLineLimit: 4
)
Button(action: {
withAnimation(.easeInOut(duration: 0.3)) {
isExpanded.toggle()
}
}) {
HStack {
Spacer()
Text(isExpanded ? "Hide" : "Show")
.font(.system(.body))
.foregroundColor(.blue)
Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
.frame(width: 12, height: 12)
.foregroundColor(.blue)
.rotationEffect(.degrees(isExpanded ? 180 : 0))
.animation(.easeInOut(duration: 0.3), value: isExpanded)
Spacer()
}
}
.padding(.bottom, 8)
}
.padding()
.background(Color.white)
.cornerRadius(8)
.shadow(radius: 2)
Spacer()
}
.safeAreaPadding(.top, 64)
}
.padding()
.background(Color(UIColor.systemGray6))
.edgesIgnoringSafeArea(.all)
.safeAreaPadding(.top, 24)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment