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 contentBoth 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 Mode | Example | Use 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.