GitHub

Text Wrapping

Enable multi-line text cells when content exceeds column width.

By default, when cell text is too long for the column width, it gets truncated with an ellipsis: "This is a very long descrip...". Users see part of the content but lose the rest. For short labels this is fine, but for descriptions, notes, or addresses, truncation hides important information.

Text wrapping lets content flow onto multiple lines. The row expands vertically to fit, showing the complete text without truncation.

What Users See

With wrapping enabled:

  • Text that exceeds the column width breaks onto a new line
  • The row height grows automatically to accommodate all lines
  • Each row can have a different height based on its content
  • Short text stays on one line — only long text wraps

The result is a table where every cell shows its complete content, with rows sized to fit.

Enabling Text Wrapping

Two configuration options work together:

var config = DataTableConfiguration()
config.textLayout = .wrap                         // Allow text to flow onto multiple lines
config.rowHeightMode = .automatic(estimated: 60)  // Let rows grow to fit content

Both are required textLayout = .wrap tells the label to use multiple lines. rowHeightMode = .automatic tells the row to expand. Without automatic heights, wrapped text gets clipped at the fixed row height.

The Default: Single Line with Truncation

If you don't enable wrapping, text stays on one line and truncates when it exceeds the column width. You can control where the truncation happens:

Truncation ModeExampleUse Case
.byTruncatingTail"Hello Wor..."Default — most natural for reading
.byTruncatingHead"...llo World"File paths, URLs (keep the end)
.byTruncatingMiddle"Hel...orld"Long identifiers (keep both ends)
// Default: truncate at the end
config.textLayout = .singleLine()

// Truncate at the start (good for file paths)
config.textLayout = .singleLine(truncation: .byTruncatingHead)

// Truncate in the middle (good for UUIDs, long IDs)
config.textLayout = .singleLine(truncation: .byTruncatingMiddle)

Real-World Examples

Notes or Comments

User-generated content like notes, comments, or descriptions often vary wildly in length. Wrapping ensures nothing is hidden.

struct Note: Identifiable {
    let id: UUID
    let title: String
    let content: String
    let createdAt: Date
}

var config = DataTableConfiguration()
config.textLayout = .wrap
config.rowHeightMode = .automatic(estimated: 80)
config.maxColumnWidth = 300  // Prevent content column from getting too wide

let columns: [DataTableColumn<Note>] = [
    .init("Title", \.title),
    .init("Content", \.content),  // Will wrap
    .init("Created") { $0.createdAt.formatted(date: .abbreviated, time: .omitted) }
]

Addresses

Addresses are often long and multiline by nature. Wrapping displays them naturally.

struct Contact: Identifiable {
    let id: String
    let name: String
    let address: String  // "123 Main St\nApt 4B\nNew York, NY 10001"
    let phone: String
}

var config = DataTableConfiguration()
config.textLayout = .wrap
config.rowHeightMode = .automatic(estimated: 70)

let columns: [DataTableColumn<Contact>] = [
    .init("Name", \.name),
    .init("Address", \.address),  // Multiline addresses display naturally
    .init("Phone", \.phone)
]

Error Logs

Error messages and stack traces are often long. Wrapping lets users see the full error without expanding each row manually.

struct LogEntry: Identifiable {
    let id: UUID
    let timestamp: Date
    let level: String
    let message: String  // Can be very long
}

var config = DataTableConfiguration()
config.textLayout = .wrap
config.rowHeightMode = .automatic(estimated: 60)

let columns: [DataTableColumn<LogEntry>] = [
    .init("Time") { $0.timestamp.formatted(date: .omitted, time: .standard) },
    .init("Level", \.level),
    .init("Message", \.message)  // Full error messages visible
]

Controlling Column Width

With wrapping enabled, wide columns produce few lines while narrow columns produce many lines. Use maxColumnWidth to cap how wide columns can grow:

config.maxColumnWidth = 250  // Columns won't exceed 250 points

// Or control specific columns
config.columnWidthModeProvider = { columnIndex in
    if columnIndex == 1 {  // Description column
        return .fixed(width: 200)  // Fixed width forces more wrapping
    }
    return nil  // Other columns use default sizing
}

Narrower columns mean more line breaks and taller rows. Find the balance that works for your content.

Performance with Large Datasets

Wrapped text requires measuring each cell to determine row heights. For large datasets (10,000+ rows), this measurement happens lazily:

config.textLayout = .wrap
config.rowHeightMode = .automatic(estimated: 60, prefetchWindow: 10)
  • estimated — Initial height before measurement. Rows start at this height and adjust when measured.
  • prefetchWindow — How many rows ahead to measure during scrolling. Higher values mean smoother scrolling but more upfront work.

Rows are measured on-demand as they scroll into view. The table preserves scroll position when heights change, so users don't experience jumps.

When Not to Use Wrapping

Wrapping isn't always the right choice:

  • Dense data tables — When users need to scan many rows quickly, uniform row heights help. Wrapping creates visual noise.
  • Numeric data — Numbers rarely need wrapping. Fixed heights with truncation work better.
  • Known-length content — If all values are short (names, dates, IDs), wrapping adds overhead with no benefit.

Use wrapping for variable-length content where showing the full text matters. Use single-line for scannable, uniform data.