Fix text selection

This commit is contained in:
Peter
2019-08-30 02:05:58 +04:00
parent 628eb10f2e
commit 749a35b11c
7 changed files with 2218 additions and 2164 deletions

View File

@@ -3,6 +3,7 @@ import UIKit
import UIKit.UIGestureRecognizerSubclass
import AsyncDisplayKit
import Display
import TelegramPresentationData
private func findScrollView(view: UIView?) -> UIScrollView? {
if let view = view {
@@ -174,7 +175,11 @@ private final class TextSelectionGetureRecognizer: UIGestureRecognizer, UIGestur
}
public final class TextSelectionNodeView: UIView {
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return self.hitTestImpl?(point, event)
}
}
public enum TextSelectionAction {
@@ -185,6 +190,7 @@ public enum TextSelectionAction {
public final class TextSelectionNode: ASDisplayNode {
private let theme: TextSelectionTheme
private let strings: PresentationStrings
private let textNode: TextNode
private let updateIsActive: (Bool) -> Void
private let present: (ViewController, Any?) -> Void
@@ -199,8 +205,9 @@ public final class TextSelectionNode: ASDisplayNode {
public let highlightAreaNode: ASDisplayNode
public init(theme: TextSelectionTheme, textNode: TextNode, updateIsActive: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, rootNode: ASDisplayNode, performAction: @escaping (String, TextSelectionAction) -> Void) {
public init(theme: TextSelectionTheme, strings: PresentationStrings, textNode: TextNode, updateIsActive: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, rootNode: ASDisplayNode, performAction: @escaping (String, TextSelectionAction) -> Void) {
self.theme = theme
self.strings = strings
self.textNode = textNode
self.updateIsActive = updateIsActive
self.present = present
@@ -233,19 +240,14 @@ public final class TextSelectionNode: ASDisplayNode {
override public func didLoad() {
super.didLoad()
(self.view as? TextSelectionNodeView)?.hitTestImpl = { [weak self] point, event in
return self?.hitTest(point, with: event)
}
let recognizer = TextSelectionGetureRecognizer(target: nil, action: nil)
recognizer.knobAtPoint = { [weak self] point in
guard let strongSelf = self else {
return nil
}
if !strongSelf.leftKnob.alpha.isZero, strongSelf.leftKnob.frame.insetBy(dx: -4.0, dy: -8.0).contains(point) {
return (.left, strongSelf.leftKnob.frame.offsetBy(dx: 0.0, dy: strongSelf.leftKnob.frame.width / 2.0).center)
}
if !strongSelf.rightKnob.alpha.isZero, strongSelf.rightKnob.frame.insetBy(dx: -4.0, dy: -8.0).contains(point) {
return (.right, strongSelf.rightKnob.frame.offsetBy(dx: 0.0, dy: -strongSelf.rightKnob.frame.width / 2.0).center)
}
return nil
return self?.knobAtPoint(point)
}
recognizer.moveKnob = { [weak self] knob, point in
guard let strongSelf = self, let cachedLayout = strongSelf.textNode.cachedLayout, let _ = cachedLayout.attributedString, let currentRange = strongSelf.currentRange else {
@@ -326,6 +328,16 @@ public final class TextSelectionNode: ASDisplayNode {
self.view.addGestureRecognizer(recognizer)
}
public func updateLayout() {
if let currentRange = self.currentRange {
let updatedMin = currentRange.0
let updatedMax = currentRange.1
let updatedRange = NSRange(location: min(updatedMin, updatedMax), length: max(updatedMin, updatedMax) - min(updatedMin, updatedMax))
self.updateSelection(range: updatedRange, animateIn: false)
}
}
private func updateSelection(range: NSRange?, animateIn: Bool) {
var rects: [CGRect]?
@@ -390,6 +402,22 @@ public final class TextSelectionNode: ASDisplayNode {
}
}
private func knobAtPoint(_ point: CGPoint) -> (Knob, CGPoint)? {
if !self.leftKnob.alpha.isZero, self.leftKnob.frame.insetBy(dx: -4.0, dy: -8.0).contains(point) {
return (.left, self.leftKnob.frame.offsetBy(dx: 0.0, dy: self.leftKnob.frame.width / 2.0).center)
}
if !self.rightKnob.alpha.isZero, self.rightKnob.frame.insetBy(dx: -4.0, dy: -8.0).contains(point) {
return (.right, self.rightKnob.frame.offsetBy(dx: 0.0, dy: -self.rightKnob.frame.width / 2.0).center)
}
if !self.leftKnob.alpha.isZero, self.leftKnob.frame.insetBy(dx: -14.0, dy: -14.0).contains(point) {
return (.left, self.leftKnob.frame.offsetBy(dx: 0.0, dy: self.leftKnob.frame.width / 2.0).center)
}
if !self.rightKnob.alpha.isZero, self.rightKnob.frame.insetBy(dx: -14.0, dy: -14.0).contains(point) {
return (.right, self.rightKnob.frame.offsetBy(dx: 0.0, dy: -self.rightKnob.frame.width / 2.0).center)
}
return nil
}
private func dismissSelection() {
self.currentRange = nil
self.updateSelection(range: nil, animateIn: false)
@@ -409,15 +437,15 @@ public final class TextSelectionNode: ASDisplayNode {
let text = (attributedString.string as NSString).substring(with: range)
var actions: [ContextMenuAction] = []
actions.append(ContextMenuAction(content: .text(title: "Copy", accessibilityLabel: "Copy"), action: { [weak self] in
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in
self?.performAction(text, .copy)
self?.dismissSelection()
}))
actions.append(ContextMenuAction(content: .text(title: "Look Up", accessibilityLabel: "Look Up"), action: { [weak self] in
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuLookUp, accessibilityLabel: self.strings.Conversation_ContextMenuLookUp), action: { [weak self] in
self?.performAction(text, .lookup)
self?.dismissSelection()
}))
actions.append(ContextMenuAction(content: .text(title: "Share...", accessibilityLabel: "Share"), action: { [weak self] in
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in
self?.performAction(text, .share)
self?.dismissSelection()
}))
@@ -428,4 +456,14 @@ public final class TextSelectionNode: ASDisplayNode {
return (strongSelf, completeRect, rootNode, rootNode.bounds)
}, bounce: false))
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.knobAtPoint(point) != nil {
return self.view
}
if self.bounds.contains(point) {
return self.view
}
return nil
}
}