ios - UITextView con enlaces seleccionables pero sin texto resaltado
objective-c ios7 (8)
Aquí hay un enfoque de subclases de UITextView que analizará sus reconocedores de gestos y solo permitirá aquellos que interactúan con texto vinculado (usando Swift 3).
func textViewDidChangeSelection(_ textView: UITextView) {
if let gestureRecognizers = textView.gestureRecognizers {
for recognizer in gestureRecognizers {
if recognizer is UILongPressGestureRecognizer {
if let index = textView.gestureRecognizers?.index(of: recognizer) {
textView.gestureRecognizers?.remove(at: index)
}
}
}
}
}
Tengo un UITextView que muestra texto no editable. Quiero que el texto analice automáticamente enlaces, números de teléfono, etc. para el usuario y para que puedan hacer clic en ellos.
Sin embargo, no quiero que el usuario pueda resaltar texto, porque quiero anular esas interacciones de pulsación larga y doble toque para hacer algo diferente.
Para que los enlaces se analicen en iOS7, el conmutador seleccionable debe estar activado para la UITextView, pero seleccionable también habilita el resaltado, que no quiero.
Intenté anular el gesto de LongPress para evitar el resaltado, pero parece que también se han desactivado los toques normales en los enlaces ...
for (UIGestureRecognizer *recognizer in cell.messageTextView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]){
recognizer.enabled = NO;
}
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]){
recognizer.enabled = YES;
}
}
Existen muchos hilos similares pero ninguno parece abordar esta pregunta específica de los enlaces habilitados, el texto no se puede resaltar.
Aunque es ciertamente frágil ante posibles cambios futuros de implementación, el enfoque de Kubík Kašpar es el único que me ha funcionado.
Pero (a) esto se puede hacer más simple si subclasifica UITextView
y (b) si la única interacción que desea permitir es el toque de enlace, puede hacer que el toque se reconozca de inmediato:
class LinkTextView: UITextView {
override func gestureRecognizerShouldBegin(_ gesture: UIGestureRecognizer) -> Bool {
let tapLocation = gesture.location(in: self).applying(CGAffineTransform(translationX: -textContainerInset.left, y: -textContainerInset.top))
let characterAtIndex = layoutManager.characterIndex(for: tapLocation, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let linkAttributeAtIndex = textStorage.attribute(NSLinkAttributeName, at: characterAtIndex, effectiveRange: nil)
// Returns true for gestures located on linked text
return linkAttributeAtIndex != nil
}
override func becomeFirstResponder() -> Bool {
// Returning false disables double-tap selection of link text
return false
}
}
Como escribí en la otra publicación, hay otra solución.
Después de algunas pruebas, encontré solución.
Si desea que los enlaces estén activos y no se habilitará la selección, debe editar los gestores de reconocimiento.
Por ejemplo, hay 3 LongPressGestureRecognizers. Uno para hacer clic en el enlace (minimumPressDuration = 0.12), segundo para hacer zoom en el modo editable (minimumPressDuration = 0.5), tercero para la selección (minimumPressDuration = 0.8). Esta solución elimina LongPressGestureRecognizer para seleccionar y segundo para hacer zoom en el modo de edición.
NSArray *textViewGestureRecognizers = self.captionTextView.gestureRecognizers;
NSMutableArray *mutableArrayOfGestureRecognizers = [[NSMutableArray alloc] init];
for (UIGestureRecognizer *gestureRecognizer in textViewGestureRecognizers) {
if (![gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
[mutableArrayOfGestureRecognizers addObject:gestureRecognizer];
} else {
UILongPressGestureRecognizer *longPressGestureRecognizer = (UILongPressGestureRecognizer *)gestureRecognizer;
if (longPressGestureRecognizer.minimumPressDuration < 0.3) {
[mutableArrayOfGestureRecognizers addObject:gestureRecognizer];
}
}
}
self.captionTextView.gestureRecognizers = mutableArrayOfGestureRecognizers;
Probado en iOS 9, pero debería funcionar en todas las versiones (iOS 7, 8, 9). ¡Espero que ayude! :)
Esto aborda el problema, ya que la selección de texto está desactivada y oculta la lupa - los enlaces seguirán funcionando.
@interface GMTextView : UITextView
@end
@implementation GMTextView
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
// discard all recognizers but the one that activates links, by just not calling super
// (in iOS 9.2.3 a short press for links is 0.12s, long press for selection is 0.75s)
if ([gestureRecognizer isMemberOfClass:UILongPressGestureRecognizer.class] &&
((UILongPressGestureRecognizer*)gestureRecognizer).minimumPressDuration < 0.25) {
((UILongPressGestureRecognizer*)gestureRecognizer).minimumPressDuration = 0.0;
[super addGestureRecognizer:gestureRecognizer];
}
}
@end
Nota: en lugar de eliminar, puede reemplazar el reconocedor por el que desee.
Esto es lo que funcionó para mí.
No pude deshacerme del cristal de aumento, pero esto le permitirá mantener la vista de texto seleccionable (para que pueda tocar los enlaces), pero deshacerse de toda la IU relacionada con la selección. Solo probado en iOS 9.
Precaución Swift abajo!
Primero, subclase UITextView
e incluye esta función:
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
return false
}
Eso deshabilitará la copia, etc. del menú. Luego incluyo un método de configuración, al que llamo desde init, donde hago un montón de tareas relacionadas con la configuración. (Solo uso estas vistas de texto de un guión gráfico, por lo tanto, el decodificador init):
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
selectable = true
editable = false
tintColor = UIColor.clearColor()
}
Seleccionable = verdadero para mantener los enlaces intercambiables, editables = falso porque los enlaces no son intercambiables en una vista de texto editable. Al especificar un tintColor
claro, se tintColor
las barras azules que aparecen al principio y al final de una selección.
Por último, en el controlador que utiliza la vista de texto subclase, asegúrese de que se UITextViewDelegate
protocolo UITextViewDelegate
, de que el delegado esté establecido textView.delegate = self
, e implemente esta función de delegado:
func textViewDidChangeSelection(textView: UITextView) {
var range = NSRange()
range.location = 0
range.length = 0
textView.selectedRange = range
}
Sin esta función, las barras de selección y el menú contextual se desactivarán, pero un fondo de color se dejará atrás del texto que seleccionó. Esta función se deshace de ese fondo de selección.
Como dije, no he encontrado una manera de deshacerme del vidrio de aumento, pero si hacen un toque largo en cualquier lugar que no sea un enlace, no quedará nada cuando el vidrio de aumento desaparezca.
Estoy trabajando exactamente en el mismo problema y lo mejor que pude hacer fue borrar instantáneamente la selección tan pronto como se realice agregando lo siguiente al delegado de UITextView:
- (void)textViewDidChangeSelection:(UITextView *)textView {
if(!NSEqualRanges(textView.selectedRange, NSMakeRange(0, 0))) {
textView.selectedRange = NSMakeRange(0, 0);
}
}
Tenga en cuenta la verificación para evitar la recursión. Esto soluciona el problema porque solo la selección está deshabilitada, los enlaces seguirán funcionando.
Otro problema tangencial es que la vista de texto aún se convertirá en el primer respondedor, que puede solucionar configurando el primer respondedor deseado después de configurar el rango seleccionado.
Nota: la única rareza visual que queda es que mantener presionado abre la lupa.
No estoy seguro de si esto funciona para su caso particular, pero tuve un caso similar en el que necesitaba hacer clic en los enlaces de vista de texto, pero no quería que se produjera la selección de texto y estaba usando la vista de texto para presentar datos en un CollectionViewCell.
Simplemente tuve que anular -canBecomeFirstResponder
y devolver NO
.
@interface MYTextView : UITextView
@end
@implementation MYTextView
- (BOOL)canBecomeFirstResponder {
return NO;
}
@end
Swift 4, Xcode 9.2
A continuación se muestra un enfoque algo diferente,
class TextView: UITextView {
//MARK: Properties
open var didTouchedLink:((URL,NSRange,CGPoint) -> Void)?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = Array(touches)[0]
if let view = touch.view {
let point = touch.location(in: view)
self.tapped(on: point)
}
}
}
extension TextView {
fileprivate func tapped(on point:CGPoint) {
var location: CGPoint = point
location.x -= self.textContainerInset.left
location.y -= self.textContainerInset.top
let charIndex = layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
guard charIndex < self.textStorage.length else {
return
}
var range = NSRange(location: 0, length: 0)
if let attributedText = self.attributedText {
if let link = attributedText.attribute(NSAttributedStringKey.link, at: charIndex, effectiveRange: &range) as? URL {
print("/n/t##-->You just tapped on ''/(link)'' withRange = /(NSStringFromRange(range))/n")
self.didTouchedLink?(link, range, location)
}
}
}
}
CÓMO UTILIZAR,
let textView = TextView()//Init your textview and assign attributedString and other properties you want.
textView.didTouchedLink = { (url,tapRange,point) in
//here goes your other logic for successfull URL location
}