Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added capability to auto resolve container scrollview for table viewp… #346

Merged
merged 2 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -487,12 +487,15 @@ extension CommandsExampleViewController: TableViewDelegate {
viewport.size.width -= (offsetX * 2)
viewport.size.height -= (offsetY * 2)

Utility.drawRect(rect: CGRect(origin: CGPoint(x: offsetX, y: offsetY), size: viewport.size), color: .red, in: editor)
return viewport
}

var containerScrollView: UIScrollView? {
editor.scrollView
nil
}

var resolvedViewportBorderDisplay: ViewportBorderDisplay {
.visible(color: .red, borderWidth: 1)
}

func tableView(_ tableView: TableView, didReceiveKey key: EditorKey, at range: NSRange, in cell: TableCell) { }
Expand Down
8 changes: 4 additions & 4 deletions Proton/Sources/Swift/Helpers/Utility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ import UIKit
public class Utility {
private init() { }

public static func drawRect(rect: CGRect, color: UIColor, in view: UIView, name: String = "rect_layer") {
public static func drawRect(rect: CGRect, color: UIColor, borderWidth: CGFloat = 1.0, in view: UIView, name: String = "rect_layer") {
let path = UIBezierPath(rect: rect).cgPath
drawPath(path: path, color: color, in: view, name: name)
drawPath(path: path, color: color, borderWidth: borderWidth, in: view, name: name)
}

public static func drawPath(path: CGPath, color: UIColor, in view: UIView, name: String = "path_layer") {
public static func drawPath(path: CGPath, color: UIColor, borderWidth: CGFloat = 1.0, in view: UIView, name: String = "path_layer") {
let existingLayer = view.layer.sublayers?.first(where: { $0.name == name}) as? CAShapeLayer
let shapeLayer = existingLayer ?? CAShapeLayer()
shapeLayer.path = path
shapeLayer.strokeColor = color.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 1.0
shapeLayer.lineWidth = borderWidth
shapeLayer.name = name

if existingLayer == nil {
Expand Down
68 changes: 63 additions & 5 deletions Proton/Sources/Swift/Table/TableView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,24 @@ public protocol TableCellLifeCycleObserver: AnyObject {
func tableView(_ tableView: TableView, didRemoveCellFromViewport cell: TableCell)
}


public enum ViewportBorderDisplay {
case hidden
case visible(color: UIColor, borderWidth: CGFloat)
}

/// An object capable of handing `TableView` events
public protocol TableViewDelegate: AnyObject {
var containerScrollView: UIScrollView? { get }

var viewport: CGRect? { get }

/// Governs whether resolved viewport is displayed
/// - Note: This may be used for debugging purposes.
/// - Important: It is responsibility of consumer of the API to ensure that this is not displayed in app if not intended to. i.e. display of viewport does not
/// check for DEBUG flags and would be displayed based on value provided.
var resolvedViewportBorderDisplay: ViewportBorderDisplay { get }

/// Invoked when `EditorView` within the cell receives focus
/// - Parameters:
/// - tableView: TableView containing cell
Expand Down Expand Up @@ -145,6 +157,10 @@ public protocol TableViewDelegate: AnyObject {
func tableView(_ tableView: TableView, didRemoveCellFromViewport cell: TableCell)
}

public extension TableViewDelegate {
var resolvedViewportBorderDisplay: ViewportBorderDisplay { .hidden }
}

/// A view that provides a tabular structure where each cell is an `EditorView`.
/// Since the cells contains an `EditorView` in itself, it is capable of hosting any attachment that `EditorView` can host
/// including another `TableView` as an attachment.
Expand All @@ -165,6 +181,12 @@ public class TableView: UIView {

private let repository = TableCellRepository()

private weak var _containerScrollView: UIScrollView? {
didSet {
_containerScrollView != nil ? setupScrollObserver() : removeScrollObserver()
}
}

private lazy var columnRightBorderView: UIView = {
makeSelectionBorderView()
}()
Expand Down Expand Up @@ -345,6 +367,40 @@ public class TableView: UIView {
}
}

public override func didMoveToWindow() {
guard window != nil else { return }

// Only try to auto resolve container scrollview, if not already provided by the delegate
guard self.containerScrollView == nil else { return }

// If table has the Editor which is scrollable, use that as container for viewport
let containerEditorView = self.containerAttachment?.containerEditorView
if let scrollView = containerEditorView?.scrollView, scrollView.isScrollEnabled {
_containerScrollView = scrollView
return
}

// Else, find the next available scrollview up the hierarchy
if let scrollView = getScrollContainer(from: containerEditorView) {
_containerScrollView = scrollView
}

// If there's still none, default to container editor scrollview
// This would typically be the case where the Editor starts off as non-scrollable but becomes scrollable
// as the content overflows in which case this should resolve correctly.
if _containerScrollView == nil {
_containerScrollView = containerEditorView?.scrollView
}
}

private func getScrollContainer(from view: UIView?) -> UIScrollView? {
guard view != nil else { return nil }
guard let scrollView = view as? UIScrollView else {
return getScrollContainer(from: view?.superview)
}
return scrollView
}

/// 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
Expand Down Expand Up @@ -399,7 +455,7 @@ public class TableView: UIView {
}

private func setupScrollObserver() {
observation = delegate?.containerScrollView?.observe(\.bounds, options: [.new, .old]) { [weak self] container, change in
observation = containerScrollView?.observe(\.bounds, options: [.new, .old]) { [weak self] container, change in
self?.viewportChanged()
}
}
Expand Down Expand Up @@ -450,7 +506,7 @@ public class TableView: UIView {
// ensure editor is not hidden e.g. inside an Expand in collapsed state
attachmentContentView.attachment?.containerEditorView?.isHidden == false,
tableView.bounds != .zero,
let containerScrollView = delegate?.containerScrollView,
let containerScrollView = self.containerScrollView,
let rootEditorView = containerAttachment?.containerEditorView?.rootEditor else {
cellsInViewport = []
return
Expand All @@ -472,8 +528,10 @@ public class TableView: UIView {
// Convert the visible rectangle back to the nestedView's coordinate space
let visibleRectOfNestedView = rootEditorView.convert(visibleRectOfNestedViewInScrollView, from: containerScrollView)

// Uncomment following line to show the resolved viewport
// Utility.drawRect(rect: visibleRectOfNestedView, color: .red, in: rootEditorView, name: "viewport")
if let viewportBorder = delegate?.resolvedViewportBorderDisplay,
case let ViewportBorderDisplay.visible(color, borderWidth) = viewportBorder {
Utility.drawRect(rect: visibleRectOfNestedView, color: color, borderWidth: borderWidth, in: rootEditorView, name: "viewport")
}

let adjustedViewport = visibleRectOfNestedView.offsetBy(dx: tableView.bounds.minX, dy: tableView.bounds.minY)

Expand Down Expand Up @@ -834,7 +892,7 @@ extension TableView: UIScrollViewDelegate {

extension TableView: TableContentViewDelegate {
var containerScrollView: UIScrollView? {
delegate?.containerScrollView
delegate?.containerScrollView ?? _containerScrollView
}

var viewport: CGRect? {
Expand Down
26 changes: 26 additions & 0 deletions Proton/Tests/Table/TableViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,32 @@ class TableViewTests: XCTestCase {
window.rootViewController = viewController
}

func testResolvesContainerScrollView() throws {
delegate.containerScrollView = nil

let viewport = CGRect(x: 0, y: 100, width: 350, height: 200)
delegate.viewport = viewport

let attachment = AttachmentGenerator.makeTableViewAttachment(
id: 1,
numRows: 20,
numColumns: 5,
initialRowHeight: 100
)
let tableView = attachment.view

tableView.delegate = delegate

editor.replaceCharacters(in: .zero, with: "Some text in editor")
editor.insertAttachment(in: editor.textEndRange, attachment: attachment)
editor.replaceCharacters(in: editor.textEndRange, with: "Text after grid")

viewController.render()

XCTAssertNil(delegate.containerScrollView)
XCTAssertEqual(attachment.view.containerScrollView, editor.scrollView)
}

func testReusesTextFromPreRenderedCells() throws {
delegate.containerScrollView = editor.scrollView

Expand Down
Loading