DTCoreText Documentation

repository·main·Indexed 27 days ago

https://github.com/cocoanetics/dtcoretext

A library for generating NSAttributedString and SwiftUI AttributedString from HTML on iOS 16.0+, macOS 13.0+, and tvOS 16.0+. It leverages CoreText for layout and rendering to enable rich text display without a web view, supporting various HTML tags, custom CSS, and specialized UI components like DTAttributedTextView and DTAttributedLabel.

Tokens
5.8K
Snippets
16
Records
34
Agent score
91%

What's inside DTCoreText

  1. Overview of DTCoreText

    main

    DTCoreText is a library for generating NSAttributedString and SwiftUI AttributedString from HTML on iOS, macOS, and tvOS. It leverages CoreText for layout and rendering, allowing for rich text display without the overhead of a web view.

    It provides two main capabilities:

    1. Parsing and layout: Converting HTML into attributed strings and interfacing with CoreText.
    2. User interface: Providing specialized UI components like DTAttributedTextView and DTAttributedLabel for rendering rich text.
  2. Customize HTML output with options

    main

    Control the output of the HTML parsing process by passing an options dictionary. You can specify properties such as DTDefaultFontFamily and NSTextSizeMultiplierDocumentOption to manage fonts and text scaling.

    let options: [String: Any] = [
        DTDefaultFontFamily: "Helvetica Neue",
        NSTextSizeMultiplierDocumentOption: 1.5
    ]
    let attributed = NSAttributedString(htmlData: data, options: options, documentAttributes: nil)
  3. Use specific font faces via HTML or Code

    main

    You can specify a specific font face in two ways:

    1. Via HTML: Use the font tag with the PostScript font face name in the face attribute.
    2. Via Code: Use CoreTextFontDescriptor.setOverrideFontName(_:forFontFamily:bold:italic:) to map a font family to a specific font face name.
    <!-- Variant 1: HTML -->
    <p><font face="HelveticaNeue-Light">HelveticaNeue-Light</font></p>
    // Variant 2: Code override
    CoreTextFontDescriptor.setOverrideFontName(
        "HelveticaNeue-Light",
        forFontFamily: "Helvetica Neue",
        bold: false,
        italic: false
    )
  4. Install DTCoreText via Swift Package Manager

    main

    DTCoreText is distributed as a Swift package. You can install it using Xcode or by manually editing your Package.swift file.

    Via Xcode: Go to File > Add Package Dependencies… and enter the repository URL: https://github.com/Cocoanetics/DTCoreText.git

    Via Package.swift: Add the following dependency to your manifest and link the DTCoreText product to your target.

    .package(url: "https://github.com/Cocoanetics/DTCoreText.git", from: "2.0.0")
  5. Configure DTCoreText in Package.swift

    main

    To add DTCoreText as a dependency in a Swift Package, add the repository URL to your dependencies array and include the DTCoreText product in your target's dependencies list.

    dependencies: [
        .package(url: "https://github.com/Cocoanetics/DTCoreText.git", from: "2.0.0")
    ]
    
    // ...
    
    .target(
        name: "YourTarget",
        dependencies: [
            .product(name: "DTCoreText", package: "DTCoreText")
        ]
    )
  6. Understand how macOS represents HTML tables in attributed strings

    main

    On macOS, NSAttributedString(data:options:documentAttributes:) with NSHTMLTextDocumentType represents HTML tables using three specific AppKit classes. These objects are stored within the NSParagraphStyle.textBlocks array of every paragraph inside a table cell.

    Core AppKit Classes

    • NSTextBlock: The base class. It carries dimensions (width, height, min/max), per-edge widths for three layers (padding, border, margin), per-edge border colors, a background color, and vertical alignment.
    • NSTextTable (subclass of NSTextBlock): Represents the table itself. It includes numberOfColumns, layoutAlgorithm (automatic/fixed), collapsesBorders, and hidesEmptyCells.
    • NSTextTableBlock (subclass of NSTextBlock): Represents an individual cell. It contains a reference to its parent table and its grid position: startingRow, rowSpan, startingColumn, and columnSpan.

    Key Structural Behaviors

    • No dedicated characters: Tables do not add characters (like tabs or delimiters) to the string. The structure exists solely in the textBlocks of the paragraph styles.
    • Cell Content: Each cell becomes one or more ordinary paragraphs terminated by \n. An empty cell is a bare "\n" paragraph carrying an NSTextTableBlock.
    • Grouping: All paragraphs in a single cell share one NSTextTableBlock instance. All cells in a table point to one shared NSTextTable instance. Use object identity (not equality) to group cells and paragraphs.
    • Nested Tables: For nested tables, the textBlocks array contains both blocks in order: [outerCellBlock, innerCellBlock].
  7. Display remote images in text

    main

    To display remote images, use DTLazyImageView within the DTAttributedTextContentView delegate method viewForAttachment(_:frame:). When the image loads, you must update the attachment's originalSize and trigger a relayout on the content view.

    // 1. Return the lazy image view in the delegate
    func attributedTextContentView(
        _ attributedTextContentView: DTAttributedTextContentView,
        viewForAttachment attachment: DTTextAttachment,
        frame: CGRect
    ) -> UIView? {
        guard let imageAttachment = attachment as? DTImageTextAttachment else { return nil }
    
        let imageView = DTLazyImageView(frame: frame)
        imageView.delegate = self
        imageView.url = imageAttachment.contentURL
        return imageView
    }
    
    // 2. Handle image load and relayout
    func lazyImageView(_ lazyImageView: DTLazyImageView, didChangeImageSize size: CGSize) {
        guard let url = lazyImageView.url else { return }
        let pred = NSPredicate(format: "contentURL == %@", url as CVarArg)
    
        for attachment in attributedTextContentView.layoutFrame.textAttachments(with: pred) {
            attachment.originalSize = size
        }
    
        attributedTextContentView.layayer = nil
        attributedTextContentView.relayoutText()
    }