From fe4d5a076ca49396dfb22baf131ecfb137151b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Tue, 31 Mar 2026 11:13:16 +0700 Subject: [PATCH 1/2] perf: optimize DataGridView scroll by pre-computing column metadata and warming display cache --- TablePro/Models/Query/RowProvider.swift | 17 ++++++++++++ .../Views/Results/DataGridCellFactory.swift | 3 +-- .../Views/Results/DataGridCoordinator.swift | 26 +++++++++++++++++++ TablePro/Views/Results/DataGridView.swift | 8 ++++++ .../Extensions/DataGridView+Columns.swift | 16 ++---------- 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/TablePro/Models/Query/RowProvider.swift b/TablePro/Models/Query/RowProvider.swift index 2a81e1a1d..8af31183b 100644 --- a/TablePro/Models/Query/RowProvider.swift +++ b/TablePro/Models/Query/RowProvider.swift @@ -217,6 +217,23 @@ final class InMemoryRowProvider: RowProvider { return columnIndex < rowCache.count ? rowCache[columnIndex] : nil } + @MainActor + func preWarmDisplayCache(upTo rowCount: Int) { + let count = min(rowCount, totalRowCount) + for row in 0.. = [] + private(set) var fkColumns: Set = [] var isSyncingSortDescriptors: Bool = false /// Suppresses selection delegate callbacks during programmatic selection sync var isSyncingSelection = false @@ -263,6 +265,30 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData cachedColumnCount = rowProvider.columns.count } + func rebuildColumnMetadataCache() { + var enumSet = Set() + var fkSet = Set() + let columns = rowProvider.columns + let types = rowProvider.columnTypes + let enumValues = rowProvider.columnEnumValues + let fkKeys = rowProvider.columnForeignKeys + + for i in 0.. Date: Tue, 31 Mar 2026 11:17:01 +0700 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20compl?= =?UTF-8?q?ete=20cell=20property=20guard,=20prevent=20division=20by=20zero?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TablePro/Views/Results/DataGridCellFactory.swift | 7 ++++++- TablePro/Views/Results/DataGridView.swift | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/TablePro/Views/Results/DataGridCellFactory.swift b/TablePro/Views/Results/DataGridCellFactory.swift index d47c80913..a7f25b687 100644 --- a/TablePro/Views/Results/DataGridCellFactory.swift +++ b/TablePro/Views/Results/DataGridCellFactory.swift @@ -230,7 +230,12 @@ final class DataGridCellFactory { isNewCell = true } - if !isNewCell && cell.lineBreakMode != .byTruncatingTail { + if !isNewCell && ( + cell.lineBreakMode != .byTruncatingTail || + cell.maximumNumberOfLines != 1 || + cell.cell?.truncatesLastVisibleLine != true || + cell.cell?.usesSingleLineMode != true + ) { cell.lineBreakMode = .byTruncatingTail cell.maximumNumberOfLines = 1 cell.cell?.truncatesLastVisibleLine = true diff --git a/TablePro/Views/Results/DataGridView.swift b/TablePro/Views/Results/DataGridView.swift index a6216221c..02d6d0e3c 100644 --- a/TablePro/Views/Results/DataGridView.swift +++ b/TablePro/Views/Results/DataGridView.swift @@ -284,8 +284,11 @@ struct DataGridView: NSViewRepresentable { coordinator.rebuildColumnMetadataCache() if previousIdentity == nil || previousIdentity?.rowCount == 0 { - let visibleRows = Int(tableView.visibleRect.height / tableView.rowHeight) + 5 - coordinator.rowProvider.preWarmDisplayCache(upTo: visibleRows) + let rowH = tableView.rowHeight + if rowH > 0 { + let visibleRows = Int(tableView.visibleRect.height / rowH) + 5 + coordinator.rowProvider.preWarmDisplayCache(upTo: visibleRows) + } } coordinator.changeManager = changeManager