¿Cómo interceptar el clic en el enlace en UITextView?

101

¿Es posible realizar una acción personalizada cuando el usuario toca el enlace del teléfono detectado automáticamente en UITextView? No aconseje utilizar UIWebView en su lugar.

Y, por favor, no repita el texto de la referencia de clases de Apple, ciertamente ya lo he leído.

Gracias.

Vladimir
fuente

Respuestas:

144

Actualización: desde ,

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction;

De y más tarde UITextViewtiene el método delegado:

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange *NS_DEPRECATED_IOS(7_0, 10_0, "Use textView:shouldInteractWithURL:inRange:forInteractionType: instead");*

para interceptar los clics a los enlaces. Y esta es la mejor forma de hacerlo.

por y anteriormente, una buena manera de hacer esto es subclasificando UIApplicationy sobrescribiendo el-(BOOL)openURL:(NSURL *)url

@interface MyApplication : UIApplication {

}

@end

@implementation MyApplication


-(BOOL)openURL:(NSURL *)url{
    if  ([self.delegate openURL:url])
         return YES;
    else
         return [super openURL:url];
}
@end

Deberá implementar openURL:en su delegado.

Ahora, para que la aplicación comience con su nueva subclase de UIApplication, ubique el archivo main.m en su proyecto. En este pequeño archivo que inicia su aplicación, generalmente hay esta línea:

int retVal = UIApplicationMain(argc, argv, nil, nil);

El tercer parámetro es el nombre de la clase de su aplicación. Entonces, reemplazando esta línea por:

int retVal = UIApplicationMain(argc, argv, @"MyApplication", nil);

Esto funcionó para mí.

fsaint
fuente
¡Por fin respuesta real! Idea simple y genial. Gracias, funciona. Fue hace mucho tiempo, así que ya me las he arreglado sin él. Todavía puede ayudar en el futuro. Sin embargo, una pequeña corrección, se debe devolver el resultado de super in else branch: return [super openURL: url];
Vladimir
2
También puede categorizar UIApplicationy reemplazar la implementación de openURL. Aunque de esta manera es complicado (pero no imposible) hacer referencia a la implementación original.
mxcl
1
FYI: publiqué un BrowserViewController en GitHub que implementa completamente esto, así como enlaces de soporte en los que se hace clic desde UIWebView aquí: github.com/nbuggia/Browser-View-Controller--iPhone- .
Nathan Buggia
3
Esto solo parece funcionar para enlaces web, no para números de teléfono formateados automáticamente.
azdev
¿En qué método puedo configurar el delegado de mi aplicación?
ratsimihah
50

En iOS 7 o posterior

Puede utilizar el siguiente método de delegado de UITextView:

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange

La vista de texto llama a este método si el usuario toca o mantiene presionado el enlace URL. La implementación de este método es opcional. Por defecto, la vista de texto abre la aplicación responsable de manejar el tipo de URL y le pasa la URL. Puede utilizar este método para activar una acción alternativa, como mostrar el contenido web en la URL en una vista web dentro de la aplicación actual.

Importante:

Los vínculos en las vistas de texto son interactivos solo si la vista de texto es seleccionable pero no editable. Es decir, si el valor de UITextView, la propiedad seleccionable es YES y la propiedad isEditable es NO.

Rajan Balana
fuente
Me alegra que hayan agregado esto a UITextViewDelegate.
fsaint
Desafortunadamente, terminará usando UIWebViewsi desea hacer otro texto en el enlace y no en la URL en sí. La <a>etiqueta sigue siendo la mejor forma de hacerlo en ese caso.
Matthew Quiros
En caso de que otras personas vean esto, ahora puede hacer otro texto en el enlace.
Schemetrical
10

Para Swift 3

textView.delegate = self

extension MyTextView: UITextViewDelegate {

    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {

        GCITracking.sharedInstance.track(externalLink: URL)
        return true
    }
}

o si el objetivo es> = IOS 10

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
Ryan Heitner
fuente
7

Con Swift 5 y iOS 12, puede utilizar uno de los tres patrones siguientes para interactuar con enlaces en un UITextView.


# 1. Usando UITextView'sdataDetectorTypes propiedad de.

La forma más sencilla de interactuar con números de teléfono, urls o direcciones en un UITextViewes usar dataDetectorTypesproperty. El código de muestra a continuación muestra cómo implementarlo. Con este código, cuando el usuario toca el número de teléfono, UIAlertControlleraparece un mensaje.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let textView = UITextView()
        textView.text = "Phone number: +33687654321"
        textView.isUserInteractionEnabled = true
        textView.isEditable = false
        textView.isSelectable = true
        textView.dataDetectorTypes = [.phoneNumber]
        textView.isScrollEnabled = false

        textView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(textView)
        textView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        textView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
    }

}

# 2. Usando UITextViewDelegate'stextView(_:shouldInteractWith:in:interaction:) método de

Si desea realizar alguna acción personalizada en lugar de hacer una UIAlertControllerventana emergente cuando toca un número de teléfono mientras lo usa dataDetectorTypes, debe UIViewControllercumplir con el UITextViewDelegateprotocolo e implementarlo textView(_:shouldInteractWith:in:interaction:). El siguiente código muestra cómo implementarlo:

import UIKit

class ViewController: UIViewController, UITextViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let textView = UITextView()
        textView.delegate = self
        textView.text = "Phone number: +33687654321"
        textView.isUserInteractionEnabled = true
        textView.isEditable = false
        textView.isSelectable = true
        textView.dataDetectorTypes = [.phoneNumber]
        textView.isScrollEnabled = false

        textView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(textView)
        textView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        textView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
    }

    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        /* perform your own custom actions here */
        print(URL) // prints: "tel:+33687654321"

        return false // return true if you also want UIAlertController to pop up
    }

}

# 3. UtilizandoNSAttributedString yNSAttributedString.Key.link

Como alternativa, puede usar NSAttributedStringy establecer un URLpara su NSAttributedString.Key.linkatributo. El código de muestra siguiente muestra una posible implementación del mismo. Con este código, cuando el usuario toca la cadena atribuida, UIAlertControlleraparece un mensaje.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let attributedString = NSMutableAttributedString(string: "Contact: ")
        let phoneUrl = NSURL(string: "tel:+33687654321")! // "telprompt://+33687654321" also works
        let attributes = [NSAttributedString.Key.link: phoneUrl]
        let phoneAttributedString = NSAttributedString(string: "phone number", attributes: attributes)
        attributedString.append(phoneAttributedString)

        let textView = UITextView()
        textView.attributedText = attributedString
        textView.isUserInteractionEnabled = true
        textView.isEditable = false
        textView.isSelectable = true
        textView.isScrollEnabled = false

        textView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(textView)
        textView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        textView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
    }

}
Imanou Petit
fuente
6

Versión rápida:

Su configuración estándar de UITextView debería verse así, no olvide el delegado y dataDetectorTypes.

var textView = UITextView(x: 10, y: 10, width: CardWidth - 20, height: placeholderHeight) //This is my custom initializer
textView.text = "dsfadsaf www.google.com"
textView.selectable = true
textView.dataDetectorTypes = UIDataDetectorTypes.Link
textView.delegate = self
addSubview(textView)

Después de que termine su clase, agregue esta pieza:

class myVC: UIViewController {
    //viewdidload and other stuff here
}

extension MainCard: UITextViewDelegate {
    func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
        //Do your stuff over here
        var webViewController = SVModalWebViewController(URL: URL)
        view.presentViewController(webViewController, animated: true, completion: nil)
        return false
    }
}
Esqarrouth
fuente
1

Rápido 4:

1) Cree la siguiente clase (subclase UITextView):

import Foundation

protocol QuickDetectLinkTextViewDelegate: class {
    func tappedLink()
}

class QuickDetectLinkTextView: UITextView {

    var linkDetectDelegate: QuickDetectLinkTextViewDelegate?

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)

    }

    required init?(coder aDecoder: NSCoder) {
         super.init(coder: aDecoder)
    }

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let glyphIndex: Int? = layoutManager.glyphIndex(for: point, in: textContainer, fractionOfDistanceThroughGlyph: nil)
        let index: Int? = layoutManager.characterIndexForGlyph(at: glyphIndex ?? 0)
        if let characterIndex = index {
            if characterIndex < textStorage.length {
                if textStorage.attribute(NSLinkAttributeName, at: characterIndex, effectiveRange: nil) != nil {
                    linkDetectDelegate?.tappedLink()
                    return self
                }
            }
        }

        return nil
    }
}


2) Dondequiera que configure su vista de texto, haga esto:

//init, viewDidLoad, etc
textView.linkDetectDelegate = self

//outlet
@IBOutlet weak var textView: QuickDetectLinkTextView!

//change ClassName to your class
extension ClassName: QuickDetectLinkTextViewDelegate {
    func tappedLink() {
        print("Tapped link, do something")
    }
}


Si está utilizando un guión gráfico, asegúrese de que su vista de texto se vea así en el inspector de identidad del panel derecho:
ingrese la descripción de la imagen aquí



¡Voila! Ahora obtiene el toque del enlace inmediatamente en lugar de cuando la URL debería interactuar con el método URL

Josh O'Connor
fuente
también: si desea que no se maneje la URL, simplemente configure el método shouldInteractWith para que devuelva falso
Josh O'Connor
Creo que esto tiene un montón de problemas, como lo que sucede cuando no tocas un enlace. Es decir, no creo que la vista de texto funcione normalmente, ya que se devuelve nil. La selección también cambiará cuando toque un enlace, porque en ese caso, se devuelve self.
Drew McCormack
funciona muy bien para mí, necesita manejar estos casos para sus necesidades
Josh O'Connor
débil var linkDetectDelegate: QuickDetectLinkTextViewDelegate?
maslovsa
@vyachaslav no es para mí, debes estar haciendo algo mal
Josh O'Connor
0

No lo he intentado yo mismo, pero puede intentar implementar el application:handleOpenURL:método en el delegado de su aplicación; parece que todas las openURLsolicitudes pasan por esta devolución de llamada.

Vladimir
fuente
0

No estoy seguro de cómo interceptaría el enlace de datos detectado o qué tipo de función necesita ejecutar. Pero es posible que pueda utilizar el método didBeginEditing TextField para ejecutar una prueba / escanear a través del campo de texto si sabe lo que está buscando ... como comparar cadenas de texto que cumplen con el formato ### - ### - ####, o comience con "www." para tomar esos campos, pero necesitaría escribir un pequeño código para rastrear la cadena de campos de texto, reconocer lo que necesita y luego extraerlo para el uso de su función. No creo que esto sea tan difícil, una vez que haya reducido exactamente lo que quería y luego haya enfocado sus filtros de declaración if () hacia un patrón de coincidencia muy específico de lo que necesita.

Por supuesto, esto implica que el usuario va a tocar el cuadro de texto para activar didBeginEditing (). Si ese no es el tipo de interacción del usuario que estaba buscando, puede usar un temporizador de activación, que comienza en ViewDidAppear () u otro según la necesidad y se ejecuta a través de la cadena de campos de texto, luego, al final, ejecute la cadena de campo de texto. métodos que usted construyó, simplemente apague el temporizador.

Newbyman
fuente
0

application:handleOpenURL:se llama cuando otra aplicación abre su aplicación abriendo una URL con un esquema que su aplicación admite. No se llama cuando su aplicación comienza a abrir una URL.

Creo que la única forma de hacer lo que Vladimir quiere es usar UIWebView en lugar de UITextView. Haga que su controlador de vista implemente UIWebViewDelegate, configure el delegado de UIWebView en el controlador de vista y, en el controlador de vista, implemente webView:shouldStartLoadWithRequest:navigationType:para abrir [request URL]en una vista en lugar de salir de su aplicación y abrirla en Mobile Safari.

A. Jesse Jiryu Davis
fuente