Jak przechwycić kliknięcie linku w UITextView?

101

Czy jest możliwe wykonanie niestandardowej akcji, gdy użytkownik dotknie automatycznie wykrytego łącza telefonicznego w UITextView. Nie radzę używać zamiast tego UIWebView.

I proszę, nie powtarzaj tekstu z referencji klas Apple - z pewnością już go przeczytałem.

Dzięki.

Vladimir
źródło

Odpowiedzi:

144

Aktualizacja: od ,

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

Z a Later UITextViewma metodę delegata:

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

przechwytywania kliknięć prowadzących do linków. I to jest najlepszy sposób na zrobienie tego.

Dla a wcześniej dobrym sposobem na zrobienie tego jest UIApplicationutworzenie podklasy i nadpisanie-(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

Będziesz musiał zaimplementować openURL:w swoim delegacie.

Teraz, aby aplikacja zaczęła się z nową podklasą UIApplication, zlokalizuj plik main.m w swoim projekcie. W tym małym pliku, który ładuje Twoją aplikację, zwykle znajduje się następująca linia:

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

Trzeci parametr to nazwa klasy Twojej aplikacji. Tak więc, zamieniając tę ​​linię na:

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

To załatwiło sprawę dla mnie.

słaby
źródło
Nareszcie prawdziwa odpowiedź! Prosty i fajny pomysł. Dzięki, działa. To było dawno temu, więc już sobie bez niego radziłem. Nadal może pomóc w przyszłości. Jednak mała poprawka, wynik z gałęzi super in else powinien zostać zwrócony: return [super openURL: url];
Vladimir,
2
Możesz również kategoryzować UIApplicationi zastępować implementację openURL. Chociaż w ten sposób odniesienie do oryginalnej implementacji jest trudne (ale nie niemożliwe).
mxcl
1
Do Twojej wiadomości - opublikowałem BrowserViewController na GitHubie, który w pełni to implementuje, a także obsługuje linki klikane z poziomu UIWebView tutaj: github.com/nbuggia/Browser-View-Controller--iPhone- .
Nathan Buggia
3
Wydaje się, że działa to tylko w przypadku linków internetowych, a nie automatycznie formatowanych numerów telefonów.
azdev
W jaki sposób mogę ustawić delegata mojej aplikacji?
ratsimihah
50

W iOS 7 lub nowszym

Możesz użyć następującej metody delegata UITextView:

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

Widok tekstowy wywołuje tę metodę, jeśli użytkownik dotknie lub długo naciśnie łącze URL. Wdrożenie tej metody jest opcjonalne. Domyślnie widok tekstowy otwiera aplikację odpowiedzialną za obsługę typu adresu URL i przekazuje jej adres URL. Możesz użyć tej metody, aby wyzwolić alternatywną akcję, taką jak wyświetlenie treści WWW pod adresem URL w widoku sieci Web w bieżącej aplikacji.

Ważny:

Łącza w widokach tekstu są interaktywne tylko wtedy, gdy widok tekstu jest wybieralny, ale nie można go edytować. Oznacza to, że jeśli wartość UITextView, wybieralna właściwość to TAK, a właściwość isEditable ma wartość NO.

Rajan Balana
źródło
Cieszę się, że dodali to do UITextViewDelegate.
słaby
Niestety, nadal będziesz używać, UIWebViewjeśli chcesz, aby jakiś inny tekst był linkiem, a nie samym adresem URL. W takim przypadku <a>tag jest nadal najlepszym rozwiązaniem.
MLQ
Jeśli inni to zobaczą, możesz teraz utworzyć link w innym tekście.
Schematyczny
10

Dla 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
    }
}

lub jeśli celem jest> = IOS 10

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
Ryan Heitner
źródło
7

W Swift 5 i iOS 12 możesz użyć jednego z trzech poniższych wzorców w celu interakcji z linkami w UITextView.


# 1. Korzystanie UITextViewz dataDetectorTypeswłaściwości.

Najprostszym sposobem interakcji z numerami telefonów, adresami URL lub adresami w domenie UITextViewjest użycie dataDetectorTypeswłasności. Poniższy przykładowy kod pokazuje, jak go zaimplementować. Za pomocą tego kodu, gdy użytkownik stuka w numer telefonu, UIAlertControllerpojawi się ikona.

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. Używając UITextViewDelegate'stextView(_:shouldInteractWith:in:interaction:) metody

Jeśli chcesz wykonać niestandardową akcję zamiast tworzyć plik UIAlertController wyskakujące okienko po dotknięciu numeru telefonu podczas korzystania dataDetectorTypes, musisz UIViewControllerdostosować się do UITextViewDelegateprotokołu i zaimplementować textView(_:shouldInteractWith:in:interaction:). Poniższy kod pokazuje, jak to zaimplementować:

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. Za pomocąNSAttributedString iNSAttributedString.Key.link

Alternatywnie możesz użyć NSAttributedStringi ustawić URLdla jego NSAttributedString.Key.linkatrybutu. Przykładowy kod poniżej pokazuje możliwą implementację tego atrybutu. W przypadku tego kodu, gdy użytkownik dotyka przypisanego ciągu, UIAlertControllerpojawia się ikona.

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
źródło
6

Wersja Swift:

Twoja standardowa konfiguracja UITextView powinna wyglądać mniej więcej tak, nie zapomnij o delegacie i 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)

Po zakończeniu zajęć dodaj ten fragment:

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
źródło
1

Swift 4:

1) Utwórz następującą klasę (podklasę 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) Gdziekolwiek ustawisz widok tekstu, zrób to:

//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")
    }
}


Jeśli używasz scenorysu, upewnij się, że widok tekstu wygląda tak w inspektorze tożsamości w prawym okienku:
wprowadź opis obrazu tutaj



Voila! Teraz otrzymujesz natychmiastowe dotknięcie łącza zamiast tego, kiedy adres URL powinienInteractWith URL

Josh O'Connor
źródło
także: jeśli chcesz, aby adres URL nie był obsługiwany, po prostu ustaw metodę shouldInteractWith na wartość false
Josh O'Connor
Pomyśl, że wiąże się to z wieloma problemami, takimi jak to, co się dzieje, gdy nie klikniesz linku. Tzn. Nie sądzę, aby widok tekstu działał już normalnie, ponieważ zwracane jest nil. Wybór zmieni się również po dotknięciu łącza, ponieważ w takim przypadku zwracana jest wartość self.
Drew McCormack
u mnie działa świetnie, takie sprawy trzeba załatwić do swoich potrzeb
Josh O'Connor
słaby var linkDetectDelegate: QuickDetectLinkTextViewDelegate?
maslovsa
@vyachaslav nie dla mnie, pewnie robisz coś złego
Josh O'Connor
0

Nie próbowałem tego osobiście, ale możesz spróbować zaimplementować application:handleOpenURL:metodę w swoim delegacie aplikacji - wygląda na to, że wszystkie openURLżądania przechodzą przez to wywołanie zwrotne.

Vladimir
źródło
0

Nie jestem pewien, jak przechwycić wykryte łącze danych lub jakiego typu funkcji musisz uruchomić. Ale możesz skorzystać z metody didBeginEditing TextField, aby przeprowadzić test / skanowanie przez pole tekstowe, jeśli wiesz, czego szukasz, na przykład porównując ciągi tekstowe zgodne z formatem ### - ### - ####, lub zacznij od „www”. aby pobrać te pola, ale musiałbyś napisać mały kod, aby przeszukać łańcuch textfields, zrekonstruować to, czego potrzebujesz, a następnie wyodrębnić go do użycia przez funkcję. Nie sądzę, żeby to było takie trudne, gdy już zawęzisz dokładnie to, czego chciałeś, a następnie skupisz filtry instrukcji if () do bardzo konkretnego wzoru dopasowania tego, czego potrzebujesz.

Oczywiście oznacza to, że użytkownik ma zamiar dotknąć pola tekstowego, aby aktywować didBeginEditing (). Jeśli nie jest to typ interakcji użytkownika, którego szukałeś, możesz po prostu użyć wyzwalacza Timer, który uruchamia się na ViewDidAppear () lub innym w zależności od potrzeb i przechodzi przez ciąg tekstowy, a na końcu przebiega przez ciąg pola tekstowego metod, które stworzyłeś, po prostu wyłączasz Timer.

Newbyman
źródło
0

application:handleOpenURL:jest wywoływana, gdy inna aplikacja otwiera plik aplikację, otwierając adres URL ze schematem obsługiwanym przez Twoją aplikację. Nie jest wywoływana, gdy aplikacja zaczyna otwierać adres URL.

Myślę, że jedynym sposobem na zrobienie tego, czego chce Vladimir, jest użycie UIWebView zamiast UITextView. Spraw, aby kontroler widoku zaimplementował UIWebViewDelegate, ustaw delegata UIWebView na kontroler widoku oraz w implementacji kontrolera widoku, webView:shouldStartLoadWithRequest:navigationType:aby otworzyć [request URL]w widoku zamiast zamykania aplikacji i otwierania jej w Mobile Safari.

A. Jesse Jiryu Davis
źródło