Skip to content

Commit

Permalink
Added ability to scroll lock an unrendered cell
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdeep committed May 13, 2024
1 parent 1bc79e0 commit 97d0b73
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -528,9 +528,10 @@ extension CommandsExampleViewController: TableViewDelegate {
return proposedWidth > 50
}

func tableView(_ tableView: TableView, didUpdateScrollLock delta: CGPoint) {
func tableView(_ tableView: TableView, needsUpdateScrollPositionOnCell cell: TableCell, rect: CGRect, isRendered: Bool) {
if let container = tableView.delegate?.containerScrollView {
container.contentOffset = CGPoint(x: container.contentOffset.x + delta.x, y: container.contentOffset.y + delta.y)
let offset = container.convert(CGPoint(x: cell.frame.origin.x, y: cell.frame.midY), from: tableView)
container.contentOffset = offset
}
}
}
Expand Down
53 changes: 36 additions & 17 deletions Proton/Sources/Swift/Table/TableView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,18 @@ public protocol TableViewDelegate: AnyObject {
/// - tableView: TableView containing the cell.
/// - cell: Cell being laid out
func tableView(_ tableView: TableView, didLayoutCell cell: TableCell)
/// Provides a delta if the scroll locked position is updated as a result of cells getting rendered. A scroll lock can be applied using `maintainScrolledPositionLock`
/// on `TableView`.

/// Notified that the cell that is being tried to focus using `maintainScrolledPositionLock` or as a result of `scrollTo` needs to be refocussed.
/// A cell may need to be refocussed if it happens to be displayed from originally calculated position on rendering.
/// - Parameters:
/// - tableView: Tableview in which the cells are getting rendered
/// - delta: Change in `x` and `y` positions of locked `CGRect` as a result of new cells getting rendered
/// - Note:
/// This is only intended to be used in scenarios where Editor is being scrolled to a position within `TableView` and the cell that is being scrolled to may not have been rendered
/// as it lies outside viewport.
func tableView(_ tableView: TableView, didUpdateScrollLock delta: CGPoint)
/// - cell: Locked `TableCell` that may need to be scrolled to.
/// - rect: Rectangle for content within Cell to focus.
/// - isRendered: Informs if the cell is already rendered in viewport. `true` if it is. `false` if cell is not yet in viewport
/// /// - Note:
/// This is only intended to be used in scenarios where Editor is being scrolled to a position within `TableView` and the cell that is being scrolled to may
/// not have been rendered being outside viewport.
func tableView(_ tableView: TableView, needsUpdateScrollPositionOnCell cell: TableCell, rect: CGRect, isRendered: Bool)
}

/// A view that provides a tabular structure where each cell is an `EditorView`.
Expand All @@ -126,7 +128,7 @@ public class TableView: UIView {
private var resizingDragHandleLastLocation: CGPoint? = nil
private var leadingShadowConstraint: NSLayoutConstraint!

private var maintainLockOnRect: CGRect?
private var maintainLockOnCell: (cell: TableCell, rect: CGRect)?

private var observation: NSKeyValueObservation?

Expand Down Expand Up @@ -241,11 +243,15 @@ public class TableView: UIView {
public var numberOfRows: Int {
tableView.numberOfRows
}

/// Cells visible in current viewport.
public var visibleCells: [TableCell] {
cellsInViewport
}

public override var bounds: CGRect {
didSet {
guard oldValue == bounds else { return }
maintainLockOnRect = nil
}
}

Expand Down Expand Up @@ -299,10 +305,16 @@ public class TableView: UIView {
}
}

/// Maintains the scroll lock on the rect passed in if the original rect ends up moving as a result of cells getting rendered above this rect position
/// - Parameter rect: Rect to lock. `nil` to release lock.
public func maintainScrolledPositionLock(_ rect: CGRect?) {
maintainLockOnRect = rect
/// Maintains the scroll lock on the cell passed in if the original rect ends up moving as a result of cells getting rendered above this rect position
/// - Parameters:
/// - cell: Cell to lock on
/// - rect: Offset within cell with respect to origin to scroll to.
public func maintainScrolledPositionLock(on cell: TableCell?, rect: CGRect) {
guard let cell = cell else {
maintainLockOnCell = nil
return
}
maintainLockOnCell = (cell, rect)
}

private func setup() {
Expand Down Expand Up @@ -338,6 +350,14 @@ public class TableView: UIView {
])
}

public override func layoutSubviews() {
super.layoutSubviews()
if let lockedCell = maintainLockOnCell?.cell,
cellsInViewport.contains(lockedCell) {
maintainLockOnCell = nil
}
}

private func setupScrollObserver() {
observation = delegate?.containerScrollView?.observe(\.bounds, options: [.new, .old]) { [weak self] container, change in
self?.viewportChanged()
Expand Down Expand Up @@ -738,9 +758,8 @@ extension TableView: TableContentViewDelegate {
}

func tableContentView(_ tableContentView: TableContentView, didChangeContentSize contentSize: CGSize, oldContentSize: CGSize) {
if maintainLockOnRect != nil {
let delta = CGPoint(x: (contentSize.width - oldContentSize.width), y: (contentSize.height - oldContentSize.height))
delegate?.tableView(self, didUpdateScrollLock: delta)
if let maintainLockOnCell {
delegate?.tableView(self, needsUpdateScrollPositionOnCell: maintainLockOnCell.cell, rect: maintainLockOnCell.rect, isRendered: maintainLockOnCell.cell.editor != nil)
}
}

Expand Down
9 changes: 5 additions & 4 deletions Proton/Tests/Table/Mocks/MockTableViewDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MockTableViewDelegate: TableViewDelegate {
var onDidSelectCells: ((_ tableView: TableView, _ cells: [TableCell]) -> Void)?
var onDidUnselectCells: ((_ tableView: TableView, _ cells: [TableCell]) -> Void)?
var onDidLayoutCell: ((_ tableView: TableView, _ cell: TableCell) -> Void)?
var onDidUpdateScrollLock: ((_ tableView: TableView, _ delta: CGPoint) -> Void)?
var onNeedsUpdateScrollPositionOnCell: ((_ tableView: TableView, _ cell: TableCell, _ rect: CGRect, _ isRendered: Bool) -> Void)?

func tableView(_ tableView: TableView, didReceiveFocusAt range: NSRange, in cell: TableCell) {
onDidReceiveFocus?(tableView, range, cell)
Expand Down Expand Up @@ -77,10 +77,11 @@ class MockTableViewDelegate: TableViewDelegate {
onDidLayoutCell?(tableView, cell)
}

func tableView(_ tableView: TableView, didUpdateScrollLock delta: CGPoint) {
onDidUpdateScrollLock?(tableView, delta)
func tableView(_ tableView: TableView, needsUpdateScrollPositionOnCell cell: TableCell, rect: CGRect, isRendered: Bool) {
onNeedsUpdateScrollPositionOnCell?(tableView, cell, rect, isRendered)
if let container = tableView.delegate?.containerScrollView {
container.contentOffset = CGPoint(x: container.contentOffset.x + delta.x, y: container.contentOffset.y + delta.y)
let offset = container.convert(CGPoint(x: cell.frame.origin.x, y: cell.frame.midY), from: tableView)
container.contentOffset = offset
}
}
}

0 comments on commit 97d0b73

Please sign in to comment.