Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building High Performance and Testable UI component

Building High Performance and Testable UI component

Kishikawa Katsumi

September 16, 2017
Tweet

More Decks by Kishikawa Katsumi

Other Decks in Programming

Transcript

  1. Building High Performance
    and Testable UI component
    iOSDC 2017
    Kishikawa Kishikawa
    [email protected]

    View Slide

  2. Kishikawa Katsumi
    Realm Inc.
    [email protected]

    View Slide

  3. Agenda
    • ߴ଎ͳUIΛ࡞Δʹ͸
    • ෛՙͷߴ͍ॲཧΛݮΒ͢
    • ಡΈ΍͢͞ɺϝϯςφϯεͷ͠΍͢͞ͱͷτϨʔυΦϑ
    • ϝϯςφϯε͠΍͍͢ίʔυΛॻ͘ʹ͸
    • ϝϯςφϯε͠΍͍͢ʹςετ͠΍͍͢
    • UIίϯϙʔωϯτΛςετ͢Δʹ͸
    • ঢ়ଶΛϞσϧʹ෼཭͢Δ
    • Ϟσϧ͸ςετͰ͖Δʢ͠΍͍͢ʣ
    • ബ͍Ϗϡʔͱ෼ް͍Ϟσϧ
    [email protected]

    View Slide

  4. [email protected]
    https://github.com/kishikawakatsumi/SpreadsheetView

    View Slide

  5. [email protected]
    https://github.com/kishikawakatsumi/SpreadsheetView

    View Slide

  6. View Slide

  7. Writing High Performance
    UI Component
    [email protected]

    View Slide

  8. Writing High Performance
    UI Component
    [email protected]
    • ܭଌ͢Δ
    • τϨʔυΦϑ

    View Slide

  9. ෛՙͷߴ͍ॲཧΛݮΒ͢
    • UIΛ஗͘͢ΔཁҼΛ஌Δ
    • UIView͕ԿΛ͍ͯ͠Δͷ͔Λཧղ͢Δ
    • UITableView/UICollectionViewͷςΫχοΫ
    ΛֶͿ
    [email protected]

    View Slide

  10. UIView
    [email protected]
    • Ϗϡʔͷ਺͕૿͑Δ΄Ͳ஗͘ͳΔ
    • ੜ੒ͷίετ͸ൺֱతߴ͍

    View Slide

  11. View Slide

  12. [email protected]
    func layout() {
    for 0 in 0..for 0 in 0..let cellSize = CGSize(width: columnWidth, height: rowHeight)
    layoutCell(frame: CGRect(origin: cellOrigin, size: cellSize))
    cellOrigin.x += columnWidth
    }
    cellOrigin.y += rowHeight
    }
    }

    View Slide

  13. View Slide

  14. [email protected]
    func layout() {
    let startRow = spreadsheetView.findIndex(in: scrollView.rowRecords, for: visibleRect.origin.y)
    cellOrigin.y = scrollView.rowRecords[startRow]
    for row in startRow..let stop = enumerateColumns(currentRow: row, currentRowIndex: rowIndex)
    if stop {
    break
    }
    cellOrigin.y += rowHeightCache[row] + intercellSpacing.height
    }
    }
    private func enumerateColumns(currentRow row: Int, currentRowIndex rowIndex: Int) -> Bool {
    let startColumn = spreadsheetView.findIndex(in: columnRecords, for: visibleRect.origin.x)
    cellOrigin.x = columnRecords[startColumn]
    while columnIndex < columnCount {
    let columnWidth = columnWidthCache[column]
    let rowHeight = rowHeightCache[row]
    guard cellOrigin.x + columnWidth > visibleRect.minX else {
    cellOrigin.x += columnWidth + intercellSpacing.width
    continue
    }
    guard cellOrigin.x <= visibleRect.maxX else {
    cellOrigin.x += columnWidth + intercellSpacing.width
    return false
    }
    guard cellOrigin.y + rowHeight > visibleRect.minY else {
    cellOrigin.x += columnWidth + intercellSpacing.width
    continue
    }
    guard cellOrigin.y <= visibleRect.maxY else {
    return true
    }
    let address = Address(row: row, column: column, rowIndex: rowIndex, columnIndex: columnIndex)
    visibleCellAddresses.insert(address)
    let cellSize = CGSize(width: columnWidth, height: rowHeight)
    layoutCell(address: address, frame: CGRect(origin: cellOrigin, size: cellSize))
    cellOrigin.x += columnWidth }
    return false
    }

    View Slide

  15. View Slide

  16. View Slide

  17. [email protected]
    let sum = numbers.reduce(0) { $0 + $1 }
    var sum: CGFloat = 0
    for n in numbers {
    sum += n
    }

    View Slide

  18. [email protected]
    let sum = numbers.reduce(0) { $0 + $1 }
    var sum: CGFloat = 0
    for n in numbers {
    sum += n
    }

    View Slide

  19. τϨʔυΦϑΛ஌Δ
    [email protected]
    • ύϑΥʔϚϯενϡʔχϯάʹ͸ʢ΄ͱΜ
    Ͳͷ৔߹ʣτϨʔυΦϑ͕ଘࡏ͢Δ
    • ίʔυͷಡΈ΍͢͞΍ςετͷ͠΍͕͢͞
    ٘ਜ਼ʹͳΔ͜ͱ͕ଟ͍
    • ಘΔ΋ͷͱࣦ͏΋ͷͷόϥϯεͷݟۃΊ͕
    ࿹ͷݟͤͲ͜Ζ

    View Slide

  20. τϨʔυΦϑΛ஌Δ
    [email protected]
    • ύϑΥʔϚϯενϡʔχϯάʹ͸ʢ΄ͱΜ
    Ͳͷ৔߹ʣτϨʔυΦϑ͕ଘࡏ͢Δ
    • ίʔυͷಡΈ΍͢͞΍ςετͷ͠΍͕͢͞
    ٘ਜ਼ʹͳΔ͜ͱ͕ଟ͍
    • ಘΔ΋ͷͱࣦ͏΋ͷͷόϥϯεͷݟۃΊ͕
    ࿹ͷݟͤͲ͜Ζ

    View Slide

  21. ύϑΥʔϚϯεͱ
    ϝϯςφϏϦςΟʢอकੑʣͷཱ྆
    [email protected]

    View Slide

  22. ϝϯςφϯε͠΍͍͢
    UIίϯϙʔωϯτΛॻ͘ʹ͸
    [email protected]

    View Slide

  23. ϝϯςφϯε͠΍͍͢
    ςετ͠΍͍͢
    UIίϯϙʔωϯτΛॻ͘ʹ͸
    [email protected]

    View Slide

  24. UIίϯϙʔωϯτΛ
    ςετ͢Δ͜ͱ͸೉͍͠
    [email protected]

    View Slide

  25. UIͷςετ͕ࠔ೉ͳཧ༝
    [email protected]
    • ಺෦ঢ়ଶ͕ଟ͘ෳࡶ
    • ঢ়ଶΛมԽͤ͞ΔཁҼ͕ଟ͍
    • ঢ়ଶ͕૬ޓ࡞༻Λٴ΅͢
    • ਖ਼͍͠ڍಈ͕໌֬Ͱͳ͍

    View Slide

  26. Unit Testing vs UI Testing
    [email protected]
    • UIςετ͸ςετର৅ʹ௚઀ΞΫηεͰ͖
    ͳ͍
    • Ξαʔγϣϯ͕೉͍͠
    • ੒ޭɾࣦഊͷج४͕໌֬Ͱͳ͍

    View Slide

  27. [email protected]
    func add(x: Int, y: Int) -> Int {
    return x + y
    }
    func testAdd() {
    let a = 1
    let b = 2
    XCTAssertEqual(add(x: a, y: b), 3)
    }

    View Slide

  28. [email protected]
    func add(x: Int, y: Int) -> Int {
    return x + y
    }
    func testAdd() {
    let a = 1
    let b = 2
    XCTAssertEqual(add(x: a, y: b), 3)
    }
    ಺෦ঢ়ଶͳ͠ ೖྗ ग़ྗ

    View Slide

  29. ؔ਺
    ಺෦ঢ়ଶ ύϥϝʔλ ग़ྗ݁Ռ
    x, y add() Int

    View Slide

  30. View Slide

  31. ಺෦ঢ়ଶ ύϥϝʔλ

    ؔ਺

    View Slide


  32. SpreadsheetView
    dataSource
    frame
    contentOffset
    ...
    λς/Ϥί
    NavigationController
    Touch
    ...
    iPhone/iPad

    View Slide

  33. ؔ਺
    ಺෦ঢ়ଶ ύϥϝʔλ ग़ྗ݁Ռ

    View Slide

  34. ಺෦ঢ়ଶ ύϥϝʔλ
    SpreadsheetView
    dataSource
    frame
    contentOffset
    ...
    λς/Ϥί
    NavigationController
    Touch
    ...
    iPhone/iPad

    View Slide

  35. ςετ͠΍͍͢ߏ଄ͱ͸
    [email protected]
    • σʔλͷྲྀΕΛ̍ํ޲ʹ͢Δ
    • ঢ়ଶΛϞσϧʹ෼཭͢Δ
    • ৼΔ෣͍ΛϞοΫʹஔ͖׵͑Δ

    View Slide

  36. σʔλͷྲྀΕΛ̍ํ޲ʹ͢Δ
    [email protected]
    ΞΫγϣϯ ϞσϧΛมߋ ϏϡʔΛߋ৽
    λοϓ
    contentOffset
    selectedIndexPath
    εϫΠϓ
    layoutSubviews()

    View Slide

  37. ঢ়ଶΛϞσϧʹ෼཭͢Δ
    [email protected]
    ʁ

    View Slide

  38. ಺෦ঢ়ଶ
    ঢ়ଶΛϞσϧʹ෼཭͢Δ
    [email protected]
    public class SpreadsheetView: UIView {
    public var intercellSpacing = CGSize(width: 1, height: 1)
    public var gridStyle: GridStyle = .solid(width: 1, color: .lightGray)
    public protocol SpreadsheetViewDataSource: class {
    func numberOfColumns(...) -> Int
    func numberOfRows(...) -> Int
    func frozenColumns(...) -> Int
    func frozenRows(...) -> Int
    func spreadsheetView(_:widthForColumn:) -> CGFloat
    func spreadsheetView(_:heightForRow:) -> CGFloat
    func mergedCells(in:) -> [CellRange]
    }

    View Slide

  39. ύϥϝʔλ
    ঢ়ଶΛϞσϧʹ෼཭͢Δ
    [email protected]
    var contentOffset: CGPoint
    var frame: CGRect
    var orientation: UIDeviceOrientation
    ...
    ϝιου
    public override func layoutSubviews() {
    reloadDataIfNeeded()
    layoutCornerView()
    layoutRowHeaderView()
    layoutColumnHeaderView()
    layoutTableView()
    }
    ಺෦ঢ়ଶ

    View Slide

  40. [email protected]
    func testTableView() {
    let parameters = Parameters(numberOfColumns: 50, numberOfRows: 60,
    frozenColumns: 1, frozenRows: 1)
    let viewController = SpreadsheetViewController()
    viewController.numberOfColumns = { _ in return parameters.numberOfColumns }
    viewController.numberOfRows = { _ in return parameters.numberOfRows }
    viewController.widthForColumn = { return parameters.columns[$1] }
    viewController.heightForRow = { return parameters.rows[$1] }
    viewController.frozenColumns = { _ in return parameters.frozenColumns }
    viewController.frozenRows = { _ in return parameters.frozenRows }
    ...

    View Slide

  41. [email protected]
    ...
    let window = UIWindow()
    window.backgroundColor = .white
    window.rootViewController = viewController
    window.makeKeyAndVisible()
    showViewController(viewController: viewController)
    waitRunLoop()
    XCTAssertEqual(spreadsheetView.visibleCells.count, numberOfVisibleColumns(...))
    for (index, visibleCell) in spreadsheetView.visibleCells
    .sorted()
    .enumerated() {
    let column = index / numberOfVisibleRows(in: spreadsheetView, parameters: parameters)
    let row = index % numberOfVisibleRows(in: spreadsheetView, parameters: parameters)
    XCTAssertEqual(visibleCell.indexPath, IndexPath(row: row, column: column))
    }
    }

    View Slide

  42. [email protected]
    ...
    let rect = cell.convert(cell.bounds, to: spreadsheetView)
    var actual = CGPoint.zero
    var expected = CGPoint.zero
    if scrollPosition.contains(.left) {
    actual.x = rect.origin.x
    if parameters.circularScrolling.options.direction.contains(CircularScrolling.Direction.horizontally) {
    if width < frozenWidth {
    expected.x = width + parameters.intercellSpacing.width
    } else {
    expected.x = frozenWidth + parameters.intercellSpacing.width
    }
    } else {
    if width < frozenWidth {
    expected.x = width + parameters.intercellSpacing.width
    } else if width <= parameters.columnWidth - spreadsheetView.frame.width + frozenWidth {
    expected.x = frozenWidth + parameters.intercellSpacing.width
    } else {
    expected.x = spreadsheetView.frame.width - (parameters.columnWidth - width) + parameters.intercellSpacing.width
    }
    }
    }
    if scrollPosition.contains(.centeredHorizontally) {
    ...

    View Slide

  43. View Slide

  44. ಺෦ঢ়ଶ ύϥϝʔλ
    SpreadsheetView
    dataSource
    frame
    contentOffset
    ...
    λς/Ϥί
    NavigationController
    Touch
    ...
    iPhone/iPad

    View Slide

  45. ؔ਺
    ಺෦ঢ়ଶ ύϥϝʔλ ग़ྗ݁Ռ
    x, y add() Int

    View Slide

  46. ঢ়ଶΛϞσϧʹ෼཭͢Δ
    [email protected]

    View Slide

  47. [email protected]
    public class SpreadsheetView: UIView {
    public weak var dataSource: SpreadsheetViewDataSource?
    public var intercellSpacing = CGSize(width: 1, height: 1)
    public var gridStyle: GridStyle = .solid(width: 1, color: .lightGray)
    ...
    public override func layoutSubviews() {
    super.layoutSubviews()
    ...
    layoutCornerView()
    layoutRowHeaderView()
    layoutColumnHeaderView()
    layoutTableView()
    }
    func layout() {
    ...
    for rowIndex in (startRowIndex + startRow).....
    for rowIndex in (startRowIndex + startRow).....
    }
    }
    }
    }

    View Slide

  48. [email protected]
    final class LayoutEngine {
    private let spreadsheetView: SpreadsheetView
    private let intercellSpacing: CGSize
    private let defaultGridStyle: GridStyle
    ...
    private let visibleRect: CGRect
    ...
    private let numberOfColumns: Int
    private let numberOfRows: Int
    ...
    init(spreadsheetView: SpreadsheetView, scrollView: ScrollView) {
    self.spreadsheetView = spreadsheetView
    self.scrollView = scrollView
    intercellSpacing = spreadsheetView.intercellSpacing
    ...
    }
    func layout() {
    guard startColumn != columnCount && startRow != rowCount else {
    return
    }
    let startRowIndex = spreadsheetView.findIndex(in: scrollView.rowRecords, for: visibleRect.origin.y - insets.y)
    cellOrigin.y = insets.y + scrollView.rowRecords[startRowIndex] + intercellSpacing.height
    for rowIndex in (startRowIndex + startRow).....
    }
    ...

    View Slide

  49. [email protected]
    final class LayoutEngine {
    private let spreadsheetView: SpreadsheetView
    private let scrollView: ScrollView
    private let intercellSpacing: CGSize
    private let defaultGridStyle: GridStyle
    ...
    private let visibleRect: CGRect
    ...
    private let numberOfColumns: Int
    private let numberOfRows: Int
    ...
    init(spreadsheetView: SpreadsheetView, scrollView: ScrollView) {
    self.spreadsheetView = spreadsheetView
    self.scrollView = scrollView
    intercellSpacing = spreadsheetView.intercellSpacing
    ...
    }
    func layout() {
    guard startColumn != columnCount && startRow != rowCount else {
    return
    }
    let startRowIndex = spreadsheetView.findIndex(in: scrollView.rowRecords, for: visibleRect.origin.y - insets.y)
    cellOrigin.y = insets.y + scrollView.rowRecords[startRowIndex] + intercellSpacing.height
    for rowIndex in (startRowIndex + startRow).....
    }
    ...

    View Slide

  50. [email protected]
    final class LayoutEngine {
    private let spreadsheetView: SpreadsheetView
    private let scrollView: ScrollView
    private let intercellSpacing: CGSize
    private let defaultGridStyle: GridStyle
    ...
    private let visibleRect: CGRect
    ...
    private let numberOfColumns: Int
    private let numberOfRows: Int
    ...
    init(spreadsheetView: SpreadsheetView, scrollView: ScrollView) {
    self.spreadsheetView = spreadsheetView
    self.scrollView = scrollView
    intercellSpacing = spreadsheetView.intercellSpacing
    ...
    }
    func layout() {
    guard startColumn != columnCount && startRow != rowCount else {
    return
    }
    let startRowIndex = spreadsheetView.findIndex(in: scrollView.rowRecords, for: visibleRect.origin.y - insets.y)
    cellOrigin.y = insets.y + scrollView.rowRecords[startRowIndex] + intercellSpacing.height
    for rowIndex in (startRowIndex + startRow).....
    }
    ...

    View Slide

  51. [email protected]
    final class LayoutEngine {
    private let intercellSpacing: CGSize
    private let defaultGridStyle: GridStyle
    ...
    private let visibleRect: CGRect
    ...
    private let numberOfColumns: Int
    private let numberOfRows: Int
    ...
    init(spreadsheetView: SpreadsheetView, scrollView: ScrollView) {
    self.spreadsheetView = spreadsheetView
    self.scrollView = scrollView
    intercellSpacing = spreadsheetView.intercellSpacing
    ...
    }
    func layout() {
    guard startColumn != columnCount && startRow != rowCount else {
    return
    }
    let startRowIndex = spreadsheetView.findIndex(in: scrollView.rowRecords, for: visibleRect.origin.y - insets.y)
    cellOrigin.y = insets.y + scrollView.rowRecords[startRowIndex] + intercellSpacing.height
    for rowIndex in (startRowIndex + startRow).....
    }
    ...

    View Slide

  52. [email protected]
    struct SpreadsheetViewConfiguration {
    let intercellSpacing: CGSize
    let defaultGridStyle: GridStyle
    let circularScrollingOptions: CircularScrolling.Configuration.Options
    let circularScrollScalingFactor: (horizontal: Int, vertical: Int)
    let blankCellReuseIdentifier: String
    let highlightedIndexPaths: Set
    let selectedIndexPaths: Set
    }
    struct DataSourceSnapshot {
    let frozenColumns: Int
    let frozenRows: Int
    let columnWidthCache: [CGFloat]
    let rowHeightCache: [CGFloat]
    }
    init(spreadsheetViewConfiguration: SpreadsheetViewConfiguration,
    dataSourceSnapshot: DataSourceSnapshot,
    scrollViewConfiguration: ScrollViewConfiguration,
    scrollViewState: ScrollView.State) {
    self.spreadsheetViewConfiguration = spreadsheetViewConfiguration
    self.dataSourceSnapshot = dataSourceSnapshot
    self.scrollViewConfiguration = scrollViewConfiguration
    visibleRect = CGRect(origin: scrollViewState.contentOffset, size: scrollViewState.frame.size)
    cellOrigin = .zero
    }

    View Slide

  53. Tips
    [email protected]
    func resetContentSize(of scrollView: ScrollView) {
    scrollView.columnRecords.removeAll()
    scrollView.rowRecords.removeAll()
    let startColumn = scrollView.layoutAttributes.startColumn
    let columnCount = scrollView.layoutAttributes.columnCount
    var width: CGFloat = 0
    for column in startColumn..scrollView.columnRecords.append(width)
    let index = column % numberOfColumns
    if !circularScrollingOptions.tableStyle.contains(.columnHeaderNotRepeated) ||
    index >= startColumn {
    width += layoutProperties.columnWidthCache[index] + intercellSpacing.width
    }
    }
    ...
    scrollView.state.contentSize = CGSize(width: width + intercellSpacing.width,
    height: height + intercellSpacing.height)
    }

    View Slide

  54. Tips
    [email protected]
    func resetContentSize(of scrollView: ScrollView) {
    scrollView.columnRecords.removeAll()
    scrollView.rowRecords.removeAll()
    let startColumn = scrollView.layoutAttributes.startColumn
    let columnCount = scrollView.layoutAttributes.columnCount
    var width: CGFloat = 0
    for column in startColumn..scrollView.columnRecords.append(width)
    let index = column % numberOfColumns
    if !circularScrollingOptions.tableStyle.contains(.columnHeaderNotRepeated) ||
    index >= startColumn {
    width += layoutProperties.columnWidthCache[index] + intercellSpacing.width
    }
    }
    ...
    scrollView.state.contentSize = CGSize(width: width + intercellSpacing.width,
    height: height + intercellSpacing.height)
    }

    View Slide

  55. Tips
    [email protected]
    static func resetContentSize(of scrollView: ScrollView) {
    scrollView.columnRecords.removeAll()
    scrollView.rowRecords.removeAll()
    let startColumn = scrollView.layoutAttributes.startColumn
    let columnCount = scrollView.layoutAttributes.columnCount
    var width: CGFloat = 0
    for column in startColumn.....
    }
    ...
    scrollView.state.contentSize = CGSize(width: width + intercellSpacing.width,
    height: height + intercellSpacing.height)
    }

    View Slide

  56. Tips
    [email protected]
    func resetContentSize(scrollView: ScrollView) {
    SpreadsheetView.resetContentSize(scrollView: scrollView,
    spreadsheetViewConfiguration: spreadsheetViewConfiguration,
    layoutProperties: layoutProperties)
    }
    static func resetContentSize(scrollView: ScrollView,
    spreadsheetViewConfiguration: SpreadsheetViewConfiguration,
    layoutProperties: LayoutProperties) {
    let intercellSpacing = spreadsheetViewConfiguration.intercellSpacing
    let circularScrollingOptions = spreadsheetViewConfiguration.circularScrollingOptions
    ...
    var width: CGFloat = 0
    for column in startColumn.....
    }
    ...
    scrollView.state.contentSize = CGSize(width: width + intercellSpacing.width,
    height: height + intercellSpacing.height)
    }

    View Slide

  57. Tips
    [email protected]
    func resetContentSize(...)
    static func resetContentSize(...)
    ϓϩμΫγϣϯ
    ςετ

    View Slide

  58. Tips
    [email protected]
    init(spreadsheetView: SpreadsheetView,
    scrollView: ScrollView)
    init(spreadsheetViewConfiguration: SpreadsheetViewConfiguration,
    scrollViewConfiguration: ScrollViewConfiguration)
    ϓϩμΫγϣϯ
    ςετ

    View Slide

  59. ৼΔ෣͍ΛϞοΫʹஔ͖׵͑Δ
    [email protected]

    View Slide

  60. [email protected]
    let cell = dataSource.spreadsheetView(spreadsheetView, cellForItemAt: indexPath)
    ...
    scrollView.insertSubview(cell, at: 0)
    ...
    scrollView.addSubview(border)

    View Slide

  61. [email protected]
    let cell = dataSource.spreadsheetView(spreadsheetView, cellForItemAt: indexPath)
    ...
    scrollView.insertSubview(cell, at: 0)
    ...
    scrollView.addSubview(border)

    View Slide

  62. [email protected]
    protocol ViewLayouter {
    mutating func layout(cell: Cell)
    }

    View Slide

  63. [email protected]
    scrollView.insertSubview(cell, at: 0)
    layouter.layout(cell: cell)

    View Slide

  64. [email protected]
    scrollView.insertSubview(cell, at: 0)
    struct Layouter: ViewLayouter {
    let scrollView: ScrollView
    func layout(cell: Cell) {
    scrollView.insertSubview(cell, at: 0)
    }
    }

    View Slide

  65. [email protected]
    scrollView.insertSubview(cell, at: 0)
    struct DebugLayouter: ViewLayouter {
    var cells = [CellInfo]()
    mutating func layout(cell: Cell) {
    cells.append(CellInfo(frame: cell.frame,
    indexPath: cell.indexPath))
    }
    }

    View Slide

  66. [email protected]
    func testLayout() {
    // State
    let spreadsheetViewConfiguration = SpreadsheetViewConfiguration(intercellSpacing: CGSize(width: 4, height
    defaultGridStyle: .none,
    circularScrollingOptions: CircularScrolli
    let dataSourceSnapshot = DataSourceSnapshot(frozenColumns: 0, frozenRows: 0)
    let dataSource = DataSource(spreadsheetViewConfiguration: spreadsheetViewConfiguration, dataSourceSnapsho
    // Parameters
    let contentOffset = CGPoint(x: 0, y: 0)
    let scrollViewState = ScrollView.State(frame: CGRect(x: 0, y: 0, width: 320, height: 567), contentOffset:
    // Test
    let layoutEngine = LayoutEngine(spreadsheetViewConfiguration: spreadsheetViewConfiguration,
    dataSourceSnapshot: dataSourceSnapshot,
    scrollViewConfiguration: scrollViewConfiguration,
    scrollViewState: scrollViewState)
    layoutEngine.layout()
    // Assert
    XCTAssertEqual(layoutEngine.layouter, ...)
    ...

    View Slide

  67. [email protected]
    Test Suite 'Selected tests' started at 2017-09-15 23:36:31.590
    Test Suite 'SpreadsheetViewTests.xctest' started at 2017-09-15 23:36:31.591
    Test Suite 'LayoutTests' started at 2017-09-15 23:36:31.591
    Test Case '-[SpreadsheetViewTests.LayoutTests testLayout]' started.
    | R0C0 (4.0, 4.0, 60.0, 40.0)| R0C1 (68.0, 4.0, 60.0, 40.0)| R0C2 (132.0, 4.0, 60.0, 40.0)|
    | R1C0 (4.0, 48.0, 60.0, 40.0)| R1C1 (68.0, 48.0, 60.0, 40.0)| R1C2 (132.0, 48.0, 60.0, 40.0)|
    | R2C0 (4.0, 92.0, 60.0, 40.0)| R2C1 (68.0, 92.0, 60.0, 40.0)| R2C2 (132.0, 92.0, 60.0, 40.0)|
    | R3C0 (4.0, 136.0, 60.0, 40.0)| R3C1 (68.0, 136.0, 60.0, 40.0)| R3C2 (132.0, 136.0, 60.0, 40.0)|
    | R4C0 (4.0, 180.0, 60.0, 40.0)| R4C1 (68.0, 180.0, 60.0, 40.0)| R4C2 (132.0, 180.0, 60.0, 40.0)|
    | R5C0 (4.0, 224.0, 60.0, 40.0)| R5C1 (68.0, 224.0, 60.0, 40.0)| R5C2 (132.0, 224.0, 60.0, 40.0)|
    | R6C0 (4.0, 268.0, 60.0, 40.0)| R6C1 (68.0, 268.0, 60.0, 40.0)| R6C2 (132.0, 268.0, 60.0, 40.0)|
    | R7C0 (4.0, 312.0, 60.0, 40.0)| R7C1 (68.0, 312.0, 60.0, 40.0)| R7C2 (132.0, 312.0, 60.0, 40.0)|
    | R8C0 (4.0, 356.0, 60.0, 40.0)| R8C1 (68.0, 356.0, 60.0, 40.0)| R8C2 (132.0, 356.0, 60.0, 40.0)|
    | R9C0 (4.0, 400.0, 60.0, 40.0)| R9C1 (68.0, 400.0, 60.0, 40.0)| R9C2 (132.0, 400.0, 60.0, 40.0)|
    | R10C0 (4.0, 444.0, 60.0, 40.0)| R10C1 (68.0, 444.0, 60.0, 40.0)| R10C2 (132.0, 444.0, 60.0, 40.0)|
    | R11C0 (4.0, 488.0, 60.0, 40.0)| R11C1 (68.0, 488.0, 60.0, 40.0)| R11C2 (132.0, 488.0, 60.0, 40.0)|
    | R12C0 (4.0, 532.0, 60.0, 40.0)| R12C1 (68.0, 532.0, 60.0, 40.0)| R12C2 (132.0, 532.0, 60.0, 40.0)|
    ...

    View Slide

  68. Summary
    [email protected]
    • ςετ͠΍͍͢ίʔυ͸ྑ͍ίʔυ
    • ςετ͠΍͍͢ίʔυʹ͢Δʹ͸
    • σʔλͷྲྀΕΛҰํ௨ߦʹ͢Δ
    • ঢ়ଶΛϞσϧʹ෼཭͢Δ
    • ৼΔ෣͍ΛϞοΫʹஔ͖׵͑Δ
    • ബ͍Ϗϡʔʢίϯτϩʔϥʣɺ෼ް͍Ϟσϧ

    View Slide

  69. Questions?
    Katsumi Kishikawa
    [email protected]
    www.realm.io
    @k_katsumi
    [email protected]

    View Slide