Как перехватить переход по ссылке в UITextView?

101

Возможно ли выполнить настраиваемое действие, когда пользователь касается автоматически обнаруженной телефонной ссылки в UITextView. Пожалуйста, не советуйте использовать UIWebView вместо этого.

И, пожалуйста, не повторяйте текст из справочника по классам Apple - конечно, я его уже читал.

Спасибо.

Владимир
источник

Ответы:

144

Обновление: от ,

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

Из и Позже UITextViewесть метод делегата:

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

перехватывать переходы по ссылкам. И это лучший способ сделать это.

Для и ранее хороший способ сделать это - создать подкласс UIApplicationи перезаписать-(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

Вам нужно будет реализовать openURL:в своем делегате.

Теперь, чтобы приложение запускалось с вашим новым подклассом UIApplication, найдите файл main.m в своем проекте. В этом небольшом файле, который загружает ваше приложение, обычно есть такая строка:

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

Третий параметр - это имя класса вашего приложения. Итак, заменив эту строку на:

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

Это помогло мне.

fsaint
источник
Наконец-то настоящий ответ! Простая и крутая идея. Спасибо, это работает. Это было давно, так что я уже обошлась без него. Тем не менее может помочь в будущем. Небольшая поправка: результат super в ветке else должен быть возвращен: return [super openURL: url];
Владимир
2
Вы также можете классифицировать UIApplicationи заменить реализацию openURL. Хотя таким образом сложно (но не невозможно) ссылаться на исходную реализацию.
mxcl 06
1
К вашему сведению - я разместил BrowserViewController на GitHub, который полностью реализует это, а также поддерживает ссылки, нажимаемые из UIWebView здесь: github.com/nbuggia/Browser-View-Controller--iPhone- .
Nathan Buggia 05
3
Кажется, это работает только для веб-ссылок, а не для автоформатированных телефонных номеров.
azdev 05
Каким способом я могу установить делегата моего приложения?
ratsimihah
50

В iOS 7 или новее

Вы можете использовать следующий метод делегата UITextView:

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

Текстовое представление вызывает этот метод, если пользователь нажимает или удерживает URL-ссылку. Реализация этого метода не является обязательной. По умолчанию текстовое представление открывает приложение, отвечающее за обработку типа URL-адреса, и передает ему URL-адрес. Этот метод можно использовать для запуска альтернативного действия, такого как отображение веб-содержимого по URL-адресу в веб-представлении в текущем приложении.

Важный:

Ссылки в текстовых представлениях являются интерактивными, только если текстовое представление доступно для выбора, но не редактируется. То есть, если значение UITextView свойство selectable - YES, а свойство isEditable - NO.

Раджан Балана
источник
Я рад, что они добавили это в UITextViewDelegate.
fsaint
К сожалению , вы все еще будете в конечном итоге с помощью , UIWebViewесли вы хотите сделать какой -либо другой текст ссылку , а не сам URL. В этом случае лучше всего использовать метку. <a>
MLQ
Если это увидят другие люди, теперь вы можете сделать ссылку другим текстом.
Схема
10

Для 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
    }
}

или если цель> = IOS 10

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
Райан Хайтнер
источник
7

В Swift 5 и iOS 12 вы можете использовать один из трех следующих шаблонов для взаимодействия со ссылками в UITextView.


№1. Использование UITextView«s dataDetectorTypesсобственности.

Самый простой способ взаимодействия с телефонными номерами, URL-адресами или адресами в a UITextView- использовать dataDetectorTypesсвойство. В приведенном ниже примере кода показано, как это реализовать. С этим кодом, когда пользователь нажимает на номер телефона, появляется UIAlertControllerвсплывающее окно.

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. Использование UITextViewDelegate«S textView(_:shouldInteractWith:in:interaction:)метод

Если вы хотите выполнить какое-то настраиваемое действие вместо того, чтобы создавать UIAlertControllerвсплывающее окно, когда вы нажимаете номер телефона во время использования dataDetectorTypes, вы должны обеспечить UIViewControllerсоответствие UITextViewDelegateпротоколу и реализовать textView(_:shouldInteractWith:in:interaction:). В приведенном ниже коде показано, как это реализовать:

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. Использование NSAttributedStringиNSAttributedString.Key.link

В качестве альтернативы вы можете использовать NSAttributedStringи установить URLдля его NSAttributedString.Key.linkатрибута. В приведенном ниже примере кода показана его возможная реализация. С помощью этого кода, когда пользователь нажимает на атрибутивную строку, появляется UIAlertControllerвсплывающее окно.

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
    }

}
Иману Пети
источник
6

Быстрая версия:

Ваша стандартная настройка UITextView должна выглядеть примерно так, не забудьте делегат и 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)

По окончании урока добавьте этот кусок:

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
источник
1

Swift 4:

1) Создайте следующий класс (подкласс 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) Где бы вы ни настраивали текстовое представление, сделайте следующее:

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


Если вы используете раскадровку, убедитесь, что ваше текстовое представление в инспекторе идентичности правой панели выглядит так:
введите описание изображения здесь



Вуаля! Теперь вы получаете переход по ссылке сразу, а не тогда, когда URL должен взаимодействовать с методом URL.

Джош О'Коннор
источник
также: если вы хотите, чтобы URL-адрес не обрабатывался, просто установите метод shouldInteractWith так, чтобы он возвращал false
Джош О'Коннор,
Думаю, у этого есть куча проблем, например, что происходит, когда вы не нажимаете ссылку. Т.е. я не думаю, что текстовое представление больше будет нормально работать, поскольку возвращается nil. Выбор также изменится, когда вы нажмете ссылку, потому что в этом случае возвращается self.
Дрю МакКормак,
отлично работает для меня, вам нужно обрабатывать такие случаи для ваших нужд
Джош О'Коннор
слабый var linkDetectDelegate: QuickDetectLinkTextViewDelegate?
maslovsa
@vyachaslav не для меня, ты что-то делаешь не так
Джош О'Коннор
0

Я сам этого не пробовал, но вы можете попробовать реализовать application:handleOpenURL: метод в делегате приложения - похоже, что весь openURLзапрос проходит через этот обратный вызов.

Владимир
источник
0

Не уверен, как бы вы перехватили обнаруженный канал данных или какой тип функции вам нужно запустить. Но вы можете использовать метод didBeginEditing TextField для запуска теста / сканирования текстового поля, если вы знаете, что ищете… например, сравнение текстовых строк, соответствующих формату ### - ### - ####, или начните с www. чтобы захватить эти поля, но вам нужно будет написать небольшой код, чтобы обнюхать строку текстовых полей, повторно согласовать то, что вам нужно, а затем извлечь его для использования вашей функцией. Я не думаю, что это будет так сложно, если вы сузите именно то, что вы хотите, а затем сфокусируете свой оператор if () на фильтрах до очень конкретного шаблона соответствия того, что вам нужно.

Конечно, это означает, что пользователь коснется текстового поля, чтобы активировать didBeginEditing (). Если это не тот тип взаимодействия с пользователем, который вы искали, вы можете просто использовать триггер Timer, который запускается на ViewDidAppear () или другом в зависимости от необходимости и проходит через строку текстовых полей, а затем в конце вы запускаете строку текстового поля методы, которые вы создали, просто выключите таймер.

Newbyman
источник
0

application:handleOpenURL:вызывается, когда другое приложение открывает ваше приложение, открывая URL-адрес со схемой, поддерживаемой вашим приложением. Он не вызывается, когда ваше приложение начинает открывать URL-адрес.

Я думаю, что единственный способ сделать то, что хочет Владимир, - использовать UIWebView вместо UITextView. Сделайте свой контроллер представления реализацией UIWebViewDelegate, установите делегат UIWebView на контроллер представления и в реализации контроллера представления, webView:shouldStartLoadWithRequest:navigationType:чтобы он открывался [request URL]в представлении вместо выхода из приложения и его открытия в Mobile Safari.

А. Джесси Джирью Дэвис
источник