Skip to content

Commit

Permalink
Added capability to attachment enabling selection without display in …
Browse files Browse the repository at this point in the history
…container. Also added ability to get focussed child view
  • Loading branch information
rajdeep committed Aug 3, 2024
1 parent d7947b3 commit 9428a0e
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 8 deletions.
40 changes: 33 additions & 7 deletions Proton/Sources/Swift/Attachment/Attachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ open class Attachment: NSTextAttachment, BoundsObserving {
private(set) var cachedContainerSize: CGSize?
private var indexInContainer: Int?
private let backgroundColor: UIColor?
private var showSelectionViewWhenSelected = true

var cachedBounds: CGRect?

Expand Down Expand Up @@ -182,6 +183,19 @@ open class Attachment: NSTextAttachment, BoundsObserving {
isRenderingAsync && isAsyncRendered == false
}

/// Returns `true` if any of the child views is first Responder, else false
public var isFocussed: Bool {
contentView?.firstResponderChildView() != nil
}

/// Returns the child view that is first Responder, else nil
public var firstResponderChildView: UIView? {
contentView?.firstResponderChildView()
}

/// Determines if attachment is in selected range in the container `EditorView`
public var isInSelectedRange: Bool { isSelected }

var isImageBasedAttachment: Bool {
self.view == nil
}
Expand All @@ -190,12 +204,10 @@ open class Attachment: NSTextAttachment, BoundsObserving {
return view?.superview != nil
}

/// Determines if attachment is in selected range in the container `EditorView`
public var isInSelectedRange: Bool { isSelected }

var isSelected: Bool = false {
didSet {
guard let view = self.view else { return }
guard let view = self.view,
showSelectionViewWhenSelected else { return }
if isSelected {
selectionView.addTo(parent: view)
} else {
Expand Down Expand Up @@ -400,20 +412,30 @@ open class Attachment: NSTextAttachment, BoundsObserving {
}

/// Selects the attachment in Editor.
/// - Parameter isSelected: `true` to set selected, else `false`
public func setSelected(_ isSelected: Bool) {
/// - Parameters:
/// - isSelected: `true` to set selected, else `false`
/// - displaySelection: If `true` when setting selected, displays attachment itself as selected. If false, only the range in contained is set as selected
/// but the attachment itself is not shown as selected. When `true`, also makes the container the first responder. Default is `true`.
public func setSelected(_ isSelected: Bool, displaySelection: Bool = true) {
guard let containerEditor = containerEditorView,
let range = rangeInContainer() else { return }

showSelectionViewWhenSelected = displaySelection

self.isSelected = isSelected

if isSelected {
containerEditor.setFocus()
if displaySelection {
containerEditor.setFocus()
}

containerEditor.selectedRange = range
} else {
containerEditor.setFocus()
containerEditor.selectedRange = NSRange(location: range.endLocation, length: 0)
}

showSelectionViewWhenSelected = true
}

/// Causes invalidation of layout of the attachment when the containing view bounds are changed
Expand Down Expand Up @@ -666,6 +688,10 @@ extension UIView {
containerAttachmentFor(view: self)
}

func firstResponderChildView() -> UIView? {
return isFirstResponder ? self : subviews.compactMap { $0.firstResponderChildView() }.first
}

private func containerAttachmentFor(view: UIView?) -> AttachmentContentView? {
guard view != nil else { return nil }
guard let attachmentView = view as? AttachmentContentView else {
Expand Down
112 changes: 111 additions & 1 deletion Proton/Tests/Attachments/ViewAttachmentSnapshotTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ import SnapshotTesting
@testable import Proton

class ViewAttachmentSnapshotTests: SnapshotTestCase {
var attachmentOffset = CGPoint(x: 0, y: -3)

override func setUp() {
super.setUp()
attachmentOffset = CGPoint(x: 0, y: -3)
}

func testMatchContentRendering() {
let viewController = EditorTestViewController()
let textView = viewController.editor
Expand Down Expand Up @@ -110,7 +117,100 @@ class ViewAttachmentSnapshotTests: SnapshotTestCase {
assertSnapshot(matching: viewController.view, as: .image, record: recordMode)
}

func testSetsSelectionWithDisplay() {
let viewController = EditorTestViewController()
let textView = viewController.editor

let attachment1 = makeTextFieldAttachment(text: NSAttributedString(string: "Test text"))

textView.replaceCharacters(in: .zero, with: "Short text ")
textView.insertAttachment(in: textView.textEndRange, attachment: attachment1)

textView.selectedRange = .zero

attachment1.setSelected(true)
viewController.render(size: CGSize(width: 300, height: 120))

XCTAssertNotNil(attachment1.rangeInContainer())
XCTAssertTrue(attachment1.isSelected)
XCTAssertEqual(attachment1.containerEditorView?.selectedRange, attachment1.rangeInContainer())
assertSnapshot(matching: viewController.view, as: .image, record: recordMode)

Check failure on line 137 in Proton/Tests/Attachments/ViewAttachmentSnapshotTests.swift

View workflow job for this annotation

GitHub Actions / Xcode test results

Assertion Failure

failed - Snapshot does not match reference. ksdiff "/Users/runner/work/proton/proton/Proton/Tests/Attachments/__Snapshots__/ViewAttachmentSnapshotTests/testSetsSelectionWithDisplay.1.png" "/Users/runner/Library/Developer/CoreSimulator/Devices/DBA0F657-5F43-4018-AFEB-C5E2B7A4318C/data/tmp/ViewAttachmentSnapshotTests/testSetsSelectionWithDisplay.1.png" Newly-taken snapshot does not match reference.
}

func testSetsSelectionWithoutDisplay() {
let viewController = EditorTestViewController()
let textView = viewController.editor

let attachment1 = makeTextFieldAttachment(text: NSAttributedString(string: "Test text"))

textView.replaceCharacters(in: .zero, with: "Short text ")
textView.insertAttachment(in: textView.textEndRange, attachment: attachment1)

textView.selectedRange = .zero

attachment1.setSelected(true, displaySelection: false)
viewController.render(size: CGSize(width: 300, height: 120))

XCTAssertNotNil(attachment1.rangeInContainer())
XCTAssertTrue(attachment1.isSelected)
XCTAssertEqual(attachment1.containerEditorView?.selectedRange, attachment1.rangeInContainer())
assertSnapshot(matching: viewController.view, as: .image, record: recordMode)

Check failure on line 157 in Proton/Tests/Attachments/ViewAttachmentSnapshotTests.swift

View workflow job for this annotation

GitHub Actions / Xcode test results

Assertion Failure

failed - Snapshot does not match reference. ksdiff "/Users/runner/work/proton/proton/Proton/Tests/Attachments/__Snapshots__/ViewAttachmentSnapshotTests/testSetsSelectionWithoutDisplay.1.png" "/Users/runner/Library/Developer/CoreSimulator/Devices/DBA0F657-5F43-4018-AFEB-C5E2B7A4318C/data/tmp/ViewAttachmentSnapshotTests/testSetsSelectionWithoutDisplay.1.png" Newly-taken snapshot does not match reference.
}

func testGetsFocussedChildView() {
let window = UIWindow(frame: CGRect(origin: .zero, size: CGSize(width: 300, height: 800)))
window.makeKeyAndVisible()

let viewController = EditorTestViewController()
window.rootViewController = viewController

let textView = viewController.editor
let attachment1 = makeTextFieldAttachment(text: NSAttributedString(string: "Test text"))

textView.replaceCharacters(in: .zero, with: "Short text ")
textView.insertAttachment(in: textView.textEndRange, attachment: attachment1)

textView.selectedRange = .zero

viewController.render(size: CGSize(width: 300, height: 120))

XCTAssertTrue((attachment1.contentView as? AutogrowingTextField)?.becomeFirstResponder() ?? false)
XCTAssertTrue(attachment1.isFocussed)

XCTAssertTrue(attachment1.firstResponderChildView is AutogrowingTextField)

assertSnapshot(matching: viewController.view, as: .image, record: recordMode)

Check failure on line 182 in Proton/Tests/Attachments/ViewAttachmentSnapshotTests.swift

View workflow job for this annotation

GitHub Actions / Xcode test results

Assertion Failure

failed - Snapshot does not match reference. ksdiff "/Users/runner/work/proton/proton/Proton/Tests/Attachments/__Snapshots__/ViewAttachmentSnapshotTests/testGetsFocussedChildView.1.png" "/Users/runner/Library/Developer/CoreSimulator/Devices/DBA0F657-5F43-4018-AFEB-C5E2B7A4318C/data/tmp/ViewAttachmentSnapshotTests/testGetsFocussedChildView.1.png" Newly-taken snapshot does not match reference.
}

func testReturnsNilForNonFocussedChildView() {
let window = UIWindow(frame: CGRect(origin: .zero, size: CGSize(width: 300, height: 800)))
window.makeKeyAndVisible()

let viewController = EditorTestViewController()
window.rootViewController = viewController

let textView = viewController.editor
let attachment1 = makeTextFieldAttachment(text: NSAttributedString(string: "Test text"))

textView.replaceCharacters(in: .zero, with: "Short text ")
textView.insertAttachment(in: textView.textEndRange, attachment: attachment1)

textView.selectedRange = .zero

viewController.render(size: CGSize(width: 300, height: 120))

XCTAssertTrue((attachment1.contentView as? AutogrowingTextField)?.becomeFirstResponder() ?? false)
XCTAssertTrue(attachment1.isFocussed)
XCTAssertTrue((attachment1.contentView as? AutogrowingTextField)?.resignFirstResponder() ?? false)

XCTAssertFalse(attachment1.isFocussed)
XCTAssertNil(attachment1.firstResponderChildView)

assertSnapshot(matching: viewController.view, as: .image, record: recordMode)

Check failure on line 209 in Proton/Tests/Attachments/ViewAttachmentSnapshotTests.swift

View workflow job for this annotation

GitHub Actions / Xcode test results

Assertion Failure

failed - Snapshot does not match reference. ksdiff "/Users/runner/work/proton/proton/Proton/Tests/Attachments/__Snapshots__/ViewAttachmentSnapshotTests/testReturnsNilForNonFocussedChildView.1.png" "/Users/runner/Library/Developer/CoreSimulator/Devices/DBA0F657-5F43-4018-AFEB-C5E2B7A4318C/data/tmp/ViewAttachmentSnapshotTests/testReturnsNilForNonFocussedChildView.1.png" Newly-taken snapshot does not match reference.
}

private func makeDummyAttachment(text: String, size: AttachmentSize) -> Attachment {
attachmentOffset = CGPoint(x: 0, y: -3)
let textView = RichTextAttachmentView(context: RichTextViewContext())
textView.textContainerInset = .zero
textView.layoutMargins = .zero
Expand All @@ -120,10 +220,20 @@ class ViewAttachmentSnapshotTests: SnapshotTestCase {
attachment.offsetProvider = self
return attachment
}

private func makeTextFieldAttachment(text: NSAttributedString) -> Attachment {
attachmentOffset = CGPoint(x: 0, y: -5)
let textField = AutogrowingTextField()
let textFieldAttachment = Attachment(textField, size: .matchContent)
textFieldAttachment.offsetProvider = self
textField.attributedText = text
textField.borderStyle = .roundedRect
return textFieldAttachment
}
}

extension ViewAttachmentSnapshotTests: AttachmentOffsetProviding {
func offset(for attachment: Attachment, in textContainer: NSTextContainer, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGPoint {
return CGPoint(x: 0, y: -3)
return attachmentOffset
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9428a0e

Please sign in to comment.