Skip to content

Commit 0b40571

Browse files
Improve testability
1 parent 88025ef commit 0b40571

5 files changed

Lines changed: 377 additions & 68 deletions

File tree

Framework/Sources/LayoutEngine.swift

Lines changed: 187 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,25 @@
99
import UIKit
1010

1111
final class LayoutEngine {
12-
private let spreadsheetView: SpreadsheetView
13-
private let scrollView: ScrollView
12+
struct Layouter: ViewLayouter {
13+
let scrollView: ScrollView
1414

15-
private let intercellSpacing: CGSize
16-
private let defaultGridStyle: GridStyle
17-
private let circularScrollingOptions: CircularScrolling.Configuration.Options
18-
private let blankCellReuseIdentifier: String
19-
private let highlightedIndexPaths: Set<IndexPath>
20-
private let selectedIndexPaths: Set<IndexPath>
15+
func layout(cell: Cell) {
16+
scrollView.insertSubview(cell, at: 0)
17+
}
18+
}
19+
var layouter: ViewLayouter
2120

22-
private let frozenColumns: Int
23-
private let frozenRows: Int
21+
private let spreadsheetView: SpreadsheetView
22+
private let scrollView: ScrollView
2423

25-
private let columnWidthCache: [CGFloat]
26-
private let rowHeightCache: [CGFloat]
24+
private let spreadsheetViewConfiguration: SpreadsheetViewConfiguration
25+
private let dataSourceSnapshot: DataSourceSnapshot
26+
private let scrollViewConfiguration: ScrollViewConfiguration
2727

2828
private let visibleRect: CGRect
2929
private var cellOrigin: CGPoint
3030

31-
private let startColumn: Int
32-
private let startRow: Int
33-
private let numberOfColumns: Int
34-
private let numberOfRows: Int
35-
private let columnCount: Int
36-
private let rowCount: Int
37-
private let insets: CGPoint
38-
39-
private let columnRecords: [CGFloat]
40-
private let rowRecords: [CGFloat]
41-
4231
private var mergedCellAddresses = Set<Address>()
4332
private var mergedCellRects = [Address: CGRect]()
4433

@@ -54,41 +43,133 @@ final class LayoutEngine {
5443
init(spreadsheetView: SpreadsheetView, scrollView: ScrollView) {
5544
self.spreadsheetView = spreadsheetView
5645
self.scrollView = scrollView
46+
self.layouter = Layouter(scrollView: scrollView)
47+
48+
spreadsheetViewConfiguration = SpreadsheetViewConfiguration(intercellSpacing: spreadsheetView.intercellSpacing,
49+
defaultGridStyle: spreadsheetView.gridStyle,
50+
circularScrollingOptions: spreadsheetView.circularScrollingOptions,
51+
circularScrollScalingFactor: spreadsheetView.circularScrollScalingFactor,
52+
blankCellReuseIdentifier: spreadsheetView.blankCellReuseIdentifier,
53+
highlightedIndexPaths: spreadsheetView.highlightedIndexPaths,
54+
selectedIndexPaths: spreadsheetView.selectedIndexPaths)
55+
dataSourceSnapshot = DataSourceSnapshot(frozenColumns: spreadsheetView.layoutProperties.frozenColumns, frozenRows: spreadsheetView.layoutProperties.frozenRows,
56+
columnWidthCache: spreadsheetView.layoutProperties.columnWidthCache, rowHeightCache: spreadsheetView.layoutProperties.rowHeightCache)
57+
58+
scrollViewConfiguration = ScrollViewConfiguration(startColumn: scrollView.layoutAttributes.startColumn, startRow: scrollView.layoutAttributes.startRow,
59+
numberOfColumns: scrollView.layoutAttributes.numberOfColumns, numberOfRows: scrollView.layoutAttributes.numberOfRows,
60+
columnCount: scrollView.layoutAttributes.columnCount, rowCount: scrollView.layoutAttributes.rowCount,
61+
insets: scrollView.layoutAttributes.insets,
62+
columnRecords: scrollView.columnRecords, rowRecords: scrollView.rowRecords)
63+
64+
visibleRect = CGRect(origin: scrollView.state.contentOffset, size: scrollView.state.frame.size)
65+
cellOrigin = .zero
66+
}
5767

58-
intercellSpacing = spreadsheetView.intercellSpacing
59-
defaultGridStyle = spreadsheetView.gridStyle
60-
circularScrollingOptions = spreadsheetView.circularScrollingOptions
61-
blankCellReuseIdentifier = spreadsheetView.blankCellReuseIdentifier
62-
highlightedIndexPaths = spreadsheetView.highlightedIndexPaths
63-
selectedIndexPaths = spreadsheetView.selectedIndexPaths
68+
init(spreadsheetViewConfiguration: SpreadsheetViewConfiguration, dataSourceSnapshot: DataSourceSnapshot, scrollViewConfiguration: ScrollViewConfiguration, scrollViewState: ScrollView.State) {
69+
class DataSource: SpreadsheetViewDataSource {
70+
func numberOfColumns(in spreadsheetView: SpreadsheetView) -> Int {
71+
return 0
72+
}
6473

65-
frozenColumns = spreadsheetView.layoutProperties.frozenColumns
66-
frozenRows = spreadsheetView.layoutProperties.frozenRows
67-
columnWidthCache = spreadsheetView.layoutProperties.columnWidthCache
68-
rowHeightCache = spreadsheetView.layoutProperties.rowHeightCache
74+
func numberOfRows(in spreadsheetView: SpreadsheetView) -> Int {
75+
return 0
76+
}
6977

70-
visibleRect = CGRect(origin: scrollView.state.contentOffset, size: scrollView.state.frame.size)
71-
cellOrigin = CGPoint.zero
72-
73-
startColumn = scrollView.layoutAttributes.startColumn
74-
startRow = scrollView.layoutAttributes.startRow
75-
numberOfColumns = scrollView.layoutAttributes.numberOfColumns
76-
numberOfRows = scrollView.layoutAttributes.numberOfRows
77-
columnCount = scrollView.layoutAttributes.columnCount
78-
rowCount = scrollView.layoutAttributes.rowCount
79-
insets = scrollView.layoutAttributes.insets
80-
81-
columnRecords = scrollView.columnRecords
82-
rowRecords = scrollView.rowRecords
78+
func spreadsheetView(_ spreadsheetView: SpreadsheetView, widthForColumn column: Int) -> CGFloat {
79+
return 0
80+
}
81+
82+
func spreadsheetView(_ spreadsheetView: SpreadsheetView, heightForRow column: Int) -> CGFloat {
83+
return 0
84+
}
85+
}
86+
spreadsheetView = SpreadsheetView()
87+
scrollView = ScrollView()
88+
struct CellInfo {
89+
let frame: CGRect
90+
let indexPath: IndexPath
91+
}
92+
93+
struct DebugLayouter: ViewLayouter, CustomStringConvertible {
94+
let dataSource = DataSource()
95+
var cells = [CellInfo]()
96+
97+
mutating func layout(cell: Cell) {
98+
cells.append(CellInfo(frame: cell.frame, indexPath: cell.indexPath))
99+
}
100+
101+
var description: String {
102+
let sortedCells = cells.sorted { (lhs, rhs) -> Bool in
103+
if lhs.indexPath.row < rhs.indexPath.row {
104+
return true
105+
}
106+
if lhs.indexPath.row > rhs.indexPath.row {
107+
return false
108+
}
109+
return lhs.indexPath.column < rhs.indexPath.column
110+
}
111+
var string = ""
112+
var previousRow = 0
113+
if sortedCells.count > 0 {
114+
previousRow = sortedCells[0].indexPath.row
115+
}
116+
for cell in sortedCells {
117+
let indexPath = cell.indexPath
118+
if previousRow != indexPath.row {
119+
string += "|\n"
120+
previousRow = indexPath.row
121+
}
122+
string += "|"
123+
let location = "R\(indexPath.row)C\(indexPath.column)"
124+
string += String(repeating: " ", count: 6 - location.characters.count)
125+
string += location
126+
let frame = "\(cell.frame)"
127+
string += String(repeating: " ", count: 27 - frame.characters.count)
128+
string += frame
129+
}
130+
string += "|\n"
131+
return string
132+
}
133+
}
134+
let debugLayouter = DebugLayouter()
135+
layouter = debugLayouter
136+
spreadsheetView.dataSource = debugLayouter.dataSource
137+
138+
self.spreadsheetViewConfiguration = SpreadsheetViewConfiguration(intercellSpacing: spreadsheetViewConfiguration.intercellSpacing,
139+
defaultGridStyle: spreadsheetViewConfiguration.defaultGridStyle,
140+
circularScrollingOptions: spreadsheetViewConfiguration.circularScrollingOptions,
141+
circularScrollScalingFactor: spreadsheetView.circularScrollScalingFactor,
142+
blankCellReuseIdentifier: spreadsheetView.blankCellReuseIdentifier,
143+
highlightedIndexPaths: spreadsheetViewConfiguration.highlightedIndexPaths,
144+
selectedIndexPaths: spreadsheetViewConfiguration.selectedIndexPaths)
145+
self.dataSourceSnapshot = dataSourceSnapshot
146+
self.scrollViewConfiguration = scrollViewConfiguration
147+
148+
visibleRect = CGRect(origin: scrollViewState.contentOffset, size: scrollViewState.frame.size)
149+
cellOrigin = .zero
83150
}
84151

85152
func layout() {
153+
let startColumn = scrollViewConfiguration.startColumn
154+
let startRow = scrollViewConfiguration.startRow
155+
let numberOfRows = scrollViewConfiguration.numberOfRows
156+
let columnCount = scrollViewConfiguration.columnCount
157+
let rowCount = scrollViewConfiguration.rowCount
158+
let insets = scrollViewConfiguration.insets
159+
160+
let rowRecords = scrollViewConfiguration.rowRecords
161+
86162
guard startColumn != columnCount && startRow != rowCount else {
87163
return
88164
}
89165

90-
let startRowIndex = spreadsheetView.findIndex(in: scrollView.rowRecords, for: visibleRect.origin.y - insets.y)
91-
cellOrigin.y = insets.y + scrollView.rowRecords[startRowIndex] + intercellSpacing.height
166+
let intercellSpacing = spreadsheetViewConfiguration.intercellSpacing
167+
let circularScrollingOptions = spreadsheetViewConfiguration.circularScrollingOptions
168+
let frozenRows = dataSourceSnapshot.frozenRows
169+
let rowHeightCache = dataSourceSnapshot.rowHeightCache
170+
171+
let startRowIndex = spreadsheetView.findIndex(in: rowRecords, for: visibleRect.origin.y - insets.y)
172+
cellOrigin.y = insets.y + rowRecords[startRowIndex] + intercellSpacing.height
92173

93174
for rowIndex in (startRowIndex + startRow)..<rowCount {
94175
let row = rowIndex % numberOfRows
@@ -111,6 +192,20 @@ final class LayoutEngine {
111192
}
112193

113194
private func enumerateColumns(currentRow row: Int, currentRowIndex rowIndex: Int) -> Bool {
195+
let intercellSpacing = spreadsheetViewConfiguration.intercellSpacing
196+
let circularScrollingOptions = spreadsheetViewConfiguration.circularScrollingOptions
197+
let frozenColumns = dataSourceSnapshot.frozenColumns
198+
let columnWidthCache = dataSourceSnapshot.columnWidthCache
199+
let rowHeightCache = dataSourceSnapshot.rowHeightCache
200+
201+
let startColumn = scrollViewConfiguration.startColumn
202+
let startRow = scrollViewConfiguration.startRow
203+
let numberOfColumns = scrollViewConfiguration.numberOfColumns
204+
let columnCount = scrollViewConfiguration.columnCount
205+
let insets = scrollViewConfiguration.insets
206+
let columnRecords = scrollViewConfiguration.columnRecords
207+
let rowRecords = scrollViewConfiguration.rowRecords
208+
114209
let startColumnIndex = spreadsheetView.findIndex(in: columnRecords, for: visibleRect.origin.x - insets.x)
115210
cellOrigin.x = insets.x + columnRecords[startColumnIndex] + intercellSpacing.width
116211

@@ -264,6 +359,10 @@ final class LayoutEngine {
264359
return
265360
}
266361

362+
let blankCellReuseIdentifier = spreadsheetViewConfiguration.blankCellReuseIdentifier
363+
let highlightedIndexPaths = spreadsheetViewConfiguration.highlightedIndexPaths
364+
let selectedIndexPaths = spreadsheetViewConfiguration.selectedIndexPaths
365+
267366
let gridlines: Gridlines?
268367
let border: (borders: Borders?, hasBorders: Bool)
269368

@@ -291,7 +390,7 @@ final class LayoutEngine {
291390
gridlines = cell.gridlines
292391
border = (cell.borders, cell.hasBorder)
293392

294-
scrollView.insertSubview(cell, at: 0)
393+
layouter.layout(cell: cell)
295394
scrollView.visibleCells[address] = cell
296395
}
297396

@@ -350,6 +449,8 @@ final class LayoutEngine {
350449
}
351450

352451
private func renderHorizontalGridlines() {
452+
let intercellSpacing = spreadsheetViewConfiguration.intercellSpacing
453+
353454
for (address, gridLayout) in horizontalGridLayouts {
354455
var frame = CGRect.zero
355456
frame.origin = gridLayout.origin
@@ -385,6 +486,8 @@ final class LayoutEngine {
385486
}
386487

387488
private func renderVerticalGridlines() {
489+
let intercellSpacing = spreadsheetViewConfiguration.intercellSpacing
490+
388491
for (address, gridLayout) in verticalGridLayouts {
389492
var frame = CGRect.zero
390493
frame.origin = gridLayout.origin
@@ -440,6 +543,8 @@ final class LayoutEngine {
440543
}
441544

442545
private func extractGridStyle(style: GridStyle) -> (width: CGFloat, color: UIColor, priority: CGFloat) {
546+
let defaultGridStyle = spreadsheetViewConfiguration.defaultGridStyle
547+
443548
let gridWidth: CGFloat
444549
let gridColor: UIColor
445550
let priority: CGFloat
@@ -512,6 +617,35 @@ final class LayoutEngine {
512617
}
513618
}
514619

620+
struct SpreadsheetViewConfiguration {
621+
let intercellSpacing: CGSize
622+
let defaultGridStyle: GridStyle
623+
let circularScrollingOptions: CircularScrolling.Configuration.Options
624+
var circularScrollScalingFactor: (horizontal: Int, vertical: Int)
625+
let blankCellReuseIdentifier: String
626+
let highlightedIndexPaths: Set<IndexPath>
627+
let selectedIndexPaths: Set<IndexPath>
628+
}
629+
630+
struct DataSourceSnapshot {
631+
let frozenColumns: Int
632+
let frozenRows: Int
633+
let columnWidthCache: [CGFloat]
634+
let rowHeightCache: [CGFloat]
635+
}
636+
637+
struct ScrollViewConfiguration {
638+
let startColumn: Int
639+
let startRow: Int
640+
let numberOfColumns: Int
641+
let numberOfRows: Int
642+
let columnCount: Int
643+
let rowCount: Int
644+
let insets: CGPoint
645+
let columnRecords: [CGFloat]
646+
let rowRecords: [CGFloat]
647+
}
648+
515649
struct LayoutProperties {
516650
let numberOfColumns: Int
517651
let numberOfRows: Int
@@ -574,3 +708,7 @@ struct GridLayout {
574708
let edge: RectEdge
575709
let priority: CGFloat
576710
}
711+
712+
protocol ViewLayouter {
713+
mutating func layout(cell: Cell)
714+
}

0 commit comments

Comments
 (0)