GitHub

Default Cell Configuration

Style fonts, colours, alignment, and backgrounds for every cell with a simple callback.

The defaultCellConfiguration callback gives you access to every cell as it's configured, letting you change fonts, colours, alignment, backgrounds — anything a UILabel and UICollectionViewCell can do.

For most styling needs, this handles everything with zero boilerplate. Need images, buttons, or entirely custom layouts? See the Custom Cells article.

What You Can Customise

The default DataCell contains a single UILabel called dataLabel. Through defaultCellConfiguration, you have full access to:

Label Properties (cell.dataLabel)

PropertyWhat It Controls
fontFont family, size, weight
textColorText colour
textAlignmentLeft, center, right, natural, justified
numberOfLinesLine limit (0 for unlimited with wrapping)
lineBreakModeHow text truncates or wraps
attributedTextRich text with multiple styles
adjustsFontSizeToFitWidthShrink text to fit
minimumScaleFactorHow much to shrink (0.5 = 50% minimum)

Cell Properties

PropertyWhat It Controls
cell.backgroundColorCell background colour
cell.contentView.backgroundColorContent area background
cell.layer.borderWidthBorder thickness
cell.layer.borderColorBorder colour (use .cgColor)
cell.layer.cornerRadiusRounded corners
cell.alphaTransparency

The Four Parameters

Your callback receives everything you need to make styling decisions:

config.defaultCellConfiguration = { cell, value, indexPath, isHighlighted in
    // cell: The DataCell instance to configure
    // value: The DataTableValueType being displayed
    // indexPath: .section = column index, .item = row index
    // isHighlighted: true if this cell is in the currently sorted column
}
ParameterTypeUse For
cellDataCellSetting visual properties
valueDataTableValueTypeConditional styling based on content
indexPathIndexPathPer-column or per-row styling
isHighlightedBoolStyling the sorted column differently

Working with Values

DataTableValueType is an enum with cases for different data types. Extract the underlying value for conditional styling:

config.defaultCellConfiguration = { cell, value, _, _ in
    // Get the string representation (works for all types)
    let text = value.stringRepresentation
    
    // Extract typed values (returns nil if type doesn't match)
    if let number = value.doubleValue {
        // Style based on numeric value
    }
    
    if let intValue = value.intValue {
        // Style based on integer
    }
}

Styling Examples

Custom Font Throughout

config.defaultCellConfiguration = { cell, _, _, _ in
    cell.dataLabel.font = UIFont(name: "Avenir-Medium", size: 14)
}

Monospaced Numbers

Numbers in tables look better when digits align vertically:

config.defaultCellConfiguration = { cell, value, _, _ in
    // Use monospaced digits for numeric columns
    if value.doubleValue != nil {
        cell.dataLabel.font = .monospacedDigitSystemFont(ofSize: 14, weight: .regular)
    } else {
        cell.dataLabel.font = .systemFont(ofSize: 14)
    }
}

Colour-Code Negative Numbers

config.defaultCellConfiguration = { cell, value, _, _ in
    if let number = value.doubleValue {
        if number < 0 {
            cell.dataLabel.textColor = .systemRed
        } else if number > 0 {
            cell.dataLabel.textColor = .systemGreen
        } else {
            cell.dataLabel.textColor = .secondaryLabel
        }
    } else {
        cell.dataLabel.textColor = .label
    }
}

Per-Column Styling

indexPath.section is the column index. Style specific columns differently:

config.defaultCellConfiguration = { cell, _, indexPath, _ in
    switch indexPath.section {
    case 0:  // ID column - monospaced, muted
        cell.dataLabel.font = .monospacedSystemFont(ofSize: 12, weight: .regular)
        cell.dataLabel.textColor = .secondaryLabel
        
    case 2:  // Status column - centered, bold
        cell.dataLabel.textAlignment = .center
        cell.dataLabel.font = .systemFont(ofSize: 14, weight: .semibold)
        
    case 5:  // Price column - right-aligned
        cell.dataLabel.textAlignment = .right
        
    default:
        cell.dataLabel.font = .systemFont(ofSize: 14)
        cell.dataLabel.textColor = .label
        cell.dataLabel.textAlignment = .natural
    }
}

Alternating Row Colours (Zebra Stripes)

indexPath.item is the row index. Use modulo for alternating patterns:

config.defaultCellConfiguration = { cell, _, indexPath, _ in
    if indexPath.item % 2 == 0 {
        cell.contentView.backgroundColor = .systemBackground
    } else {
        cell.contentView.backgroundColor = .secondarySystemBackground
    }
}

Highlight Sorted Column

isHighlighted is true for cells in the column currently being sorted:

config.defaultCellConfiguration = { cell, _, indexPath, isHighlighted in
    if isHighlighted {
        cell.contentView.backgroundColor = .systemYellow.withAlphaComponent(0.15)
        cell.dataLabel.font = .systemFont(ofSize: 14, weight: .medium)
    } else {
        cell.contentView.backgroundColor = indexPath.item % 2 == 0 
            ? .systemBackground 
            : .secondarySystemBackground
        cell.dataLabel.font = .systemFont(ofSize: 14, weight: .regular)
    }
}

Status Badges with Background Colours

config.defaultCellConfiguration = { cell, value, indexPath, _ in
    // Only apply badge styling to the Status column (index 3)
    guard indexPath.section == 3 else { return }
    
    let status = value.stringRepresentation.lowercased()
    
    switch status {
    case "active":
        cell.dataLabel.textColor = .systemGreen
        cell.contentView.backgroundColor = .systemGreen.withAlphaComponent(0.1)
    case "pending":
        cell.dataLabel.textColor = .systemOrange
        cell.contentView.backgroundColor = .systemOrange.withAlphaComponent(0.1)
    case "inactive":
        cell.dataLabel.textColor = .systemGray
        cell.contentView.backgroundColor = .systemGray.withAlphaComponent(0.1)
    default:
        break
    }
    
    cell.dataLabel.textAlignment = .center
    cell.dataLabel.font = .systemFont(ofSize: 12, weight: .semibold)
}

Shrink-to-Fit Long Text

config.defaultCellConfiguration = { cell, _, _, _ in
    cell.dataLabel.adjustsFontSizeToFitWidth = true
    cell.dataLabel.minimumScaleFactor = 0.7  // Shrink to 70% minimum
}

Rich Text with AttributedString

config.defaultCellConfiguration = { cell, value, indexPath, _ in
    // Only for the description column
    guard indexPath.section == 2 else { return }
    
    let text = value.stringRepresentation
    let attributed = NSMutableAttributedString(string: text)
    
    // Highlight "URGENT" in red
    if let range = text.range(of: "URGENT") {
        let nsRange = NSRange(range, in: text)
        attributed.addAttributes([
            .foregroundColor: UIColor.systemRed,
            .font: UIFont.boldSystemFont(ofSize: 14)
        ], range: nsRange)
    }
    
    cell.dataLabel.attributedText = attributed
}

Combining with Colour Arrays

SwiftDataTables has built-in support for alternating row colours via highlightedAlternatingRowColors and unhighlightedAlternatingRowColors. These work in layers:

  • Layer 1: Colour arrays — Applied first as baseline row colours
  • Layer 2: defaultCellConfiguration — Applied second, can override backgrounds

This means you can use colour arrays for basic zebra striping while using defaultCellConfiguration for text styling:

var config = DataTableConfiguration()

// Layer 1: Alternating backgrounds (applied automatically)
config.highlightedAlternatingRowColors = [
    .systemYellow.withAlphaComponent(0.1),
    .systemYellow.withAlphaComponent(0.2)
]
config.unhighlightedAlternatingRowColors = [
    .systemBackground,
    .secondarySystemBackground
]

// Layer 2: Text styling only (doesn't touch backgrounds)
config.defaultCellConfiguration = { cell, value, indexPath, _ in
    // Monospaced for numbers
    if value.doubleValue != nil {
        cell.dataLabel.font = .monospacedDigitSystemFont(ofSize: 14, weight: .regular)
        cell.dataLabel.textAlignment = .right
    }
    
    // Red for negative
    if let num = value.doubleValue, num < 0 {
        cell.dataLabel.textColor = .systemRed
    }
}

Override order If you set cell.contentView.backgroundColor in defaultCellConfiguration, it overrides the colour arrays. Leave backgrounds alone if you want the arrays to work.

Resetting to Defaults

Cells are reused. If you conditionally set properties (e.g., red text for negative numbers), you must also set the default case, or stale styles persist:

// BAD: Stale red color persists when cell is reused
config.defaultCellConfiguration = { cell, value, _, _ in
    if let num = value.doubleValue, num < 0 {
        cell.dataLabel.textColor = .systemRed  // Set for negatives...
    }
    // ...but never reset for positives!
}

// GOOD: Always set both cases
config.defaultCellConfiguration = { cell, value, _, _ in
    if let num = value.doubleValue, num < 0 {
        cell.dataLabel.textColor = .systemRed
    } else {
        cell.dataLabel.textColor = .label  // Explicit reset
    }
}

The cell's prepareForReuse() resets font, textColor, textAlignment, and backgroundColor to defaults. But if you set properties conditionally, always handle both the "on" and "off" cases.

Performance Considerations

defaultCellConfiguration is called for every visible cell during scrolling. Keep it fast:

  • Avoid heavy computation — Don't do network calls, complex calculations, or string parsing
  • Cache fonts — Create UIFont instances once, store in a property
  • Simple conditionals — Switch statements and if/else are fine
  • No layout changes — Don't modify constraints; that's what custom cells are for
class MyViewController: UIViewController {
    // Cache fonts instead of creating them in the callback
    private let monoFont = UIFont.monospacedDigitSystemFont(ofSize: 14, weight: .regular)
    private let bodyFont = UIFont.systemFont(ofSize: 14)
    
    private lazy var config: DataTableConfiguration = {
        var config = DataTableConfiguration()
        config.defaultCellConfiguration = { [weak self] cell, value, _, _ in
            guard let self = self else { return }
            cell.dataLabel.font = value.doubleValue != nil ? self.monoFont : self.bodyFont
        }
        return config
    }()
}

When You Need Custom Cells Instead

defaultCellConfiguration can handle most styling needs, but switch to custom cells when you need:

  • Multiple UI elements — Images + text, icon + label, stacked views
  • Interactive controls — Buttons, switches, sliders
  • Different layouts per column — Avatar in column 1, text in column 2, button in column 3
  • Complex Auto Layout — Anything beyond a single label

If all you need is styling (fonts, colours, alignment, backgrounds), stick with defaultCellConfiguration. It's simpler and performs better.