Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
5
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

UITextViewのLink選択以外のイベントを制御する

はじめに

UILabelでURLなどのリンクを装飾して、WebViewやSafariに遷移させたい場合、昔はOHAttributedLabelTTTAttributedLabelによくお世話になっていました。

UITextViewのdataDetectorTypesで自動でリンクや電話番号などを選択できるような機能がありますが、UITextViewの性質上コピーや範囲選択などができてしまうので、それらを除外したものを作れば色々と使える場面が出てくるのではないかと思い、作ってみました。

実装

public class LinkTextView : UITextView, UITextViewDelegate {

    //MARK: - Properties
    //MARK: Public
    public var linkEnabled : Bool = true {
        didSet{
            self.dataDetectorTypes = (linkEnabled) ? .Link : .None
        }
    }

    public var clickHandler : ((url : NSURL) -> Void)?

    //MARK: Private

    //MARK: - Initializer
    public init(frame : CGRect) {
        super.init(frame: frame, textContainer: nil)

        self.backgroundColor = UIColor.clearColor()
        self.delegate = self
        self.scrollEnabled = false
        self.editable = false
        self.selectable = true
        self.textContainer.lineFragmentPadding = 0
        self.textContainerInset = UIEdgeInsetsZero
        self.dataDetectorTypes = .Link
    }

    required public init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    //MARK: - Public method
    public func setLinkClickHandler(handler : ((NSURL) -> Void)?) {
        self.clickHandler = handler
    }

    //MARK: - Touch event
    public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        var p = point
        p.y = p.y - self.textContainerInset.top
        p.x = p.x - self.textContainerInset.left

        let i = self.layoutManager.characterIndexForPoint(p, inTextContainer: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        let attr = self.textStorage.attributesAtIndex(i, effectiveRange: nil)

        var touchingLink = false
        if attr[NSLinkAttributeName] != nil {
            let glyphIndex = self.layoutManager.glyphIndexForCharacterAtIndex(i)
            self.layoutManager.enumerateLineFragmentsForGlyphRange(NSMakeRange(glyphIndex, 1), usingBlock: { (recr, usedRect, container, glyphRange, stop) -> Void in
                if CGRectContainsPoint(usedRect, p) {
                    touchingLink = true
                    stop.initialize(true)
                }
            })
            return (touchingLink) ? self : nil
        }
        return nil
    }

    //MARK: - UITextViewDelegate
    public func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
        self.clickHandler?(url : URL)
        return false
    }
}

まず、initializerでUITextViewのプロパティの設定をします。
ここで注意するのが、selectableをfalseにしてしまうとリンクを選択した時のイベントも無効になってしまいます。

hitTestメソッドをオーバーライドしてリンクを選択した時以外のイベントをスルーするようにします。

UITextViewDelegateshouldInteractWithURLでリンクを選択した時のイベントが取得できますが、デフォルトではSafariに遷移するようになっているので、そのままでよければここの実装は不要です。
今回はhandlerで選択したリンクを受け取りたかったので、handlerで値を返して、delegateにはfalseを返しています。

使い方

UITextViewのデフォルトで付いてるpaddingも0にしているので、UILabelのように使えます。
sizeToFitboundingRectWithSizeでラベルの高さをフィットさせることもできます。

let textView = LinkTextView(frame: CGRectMake(0, 0, frame.width, 100))
textView.text = "https://google.com"
textView.setLinkClickHandler({ (url) in
   print("url : \(url.absoluteString)")
})
self.addSubview(textView)

終わりに

UILabelでdataDetectorTypesを指定できるようになればいいなぁと作りながら思っていました。

こんなことしなくても便利ライブラリを使ってしまえばいいのですが、Objective-Cの頃に比べてSwiftはバージョン依存なども多く、バージョンが上がると使えなくなったりアップデートされていないと手詰まりになったりするので、Swiftで外部ライブラリを使うのは毎回気を使うので、自作できるものは極力自作するようにしています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
5
Help us understand the problem. What are the problem?