如何在Swift 4的UILabel中准确检测是否单击了链接? [英] How can I accurately detect if a link is clicked inside UILabels in Swift 4?

查看:195
本文介绍了如何在Swift 4的UILabel中准确检测是否单击了链接?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

修改

有关完整的解决方案,请参见我的答案:

See my answer for a full working solution:

我设法通过使用UITextView而不是UILabel解决了这个问题.我编写了一个类,使UITextView的行为类似于UILabel,但具有完全准确的链接检测.

I managed to solve this myself by using a UITextView instead of a UILabel. I wrote a class that makes the UITextView behave like a UILabel but with fully accurate link detection.

我已经成功地使用NSMutableAttributedString设置了链接样式,但是我无法准确检测到单击了哪个字符.我已经尝试过此问题(我可以将其转换为Swift 4代码),但是没有运气.

I have managed to style the links without problem using NSMutableAttributedString but I am unable to accurately detect which character has been clicked. I have tried all the solutions in this question (that I could convert to Swift 4 code) but with no luck.

以下代码有效,但无法准确检测到单击了哪个字符并获得了错误的链接位置:

The following code works but fails to accurately detect which character has been clicked and gets the wrong location of the link:

func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
    // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
    let layoutManager = NSLayoutManager()
    let textContainer = NSTextContainer(size: CGSize.zero)
    let textStorage = NSTextStorage(attributedString: label.attributedText!)

    // Configure layoutManager and textStorage
    layoutManager.addTextContainer(textContainer)
    textStorage.addLayoutManager(layoutManager)

    // Configure textContainer
    textContainer.lineFragmentPadding = 0.0
    textContainer.lineBreakMode = label.lineBreakMode
    textContainer.maximumNumberOfLines = label.numberOfLines
    let labelSize = label.bounds.size
    textContainer.size = labelSize

    // Find the tapped character location and compare it to the specified range
    let locationOfTouchInLabel = self.location(in: label)
    let textBoundingBox = layoutManager.usedRect(for: textContainer)
    let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
    let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
    let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    print(indexOfCharacter)
    return NSLocationInRange(indexOfCharacter, targetRange)
}

推荐答案

我设法通过使用UITextView而不是UILabel来解决此问题.我本来不想使用UITextView,因为我需要该元素的行为像UILabel一样,并且UITextView可能会导致滚动问题,并且打算将其用作可编辑文本.我编写的以下类使UITextView的行为类似于UILabel,但具有完全准确的单击检测并且没有滚动问题:

I managed to solve this by using a UITextView instead of a UILabel. I originally, didn't want to use a UITextView because I need the element to behave like a UILabel and a UITextView can cause issues with scrolling and it's intended use, is to be editable text. The following class I wrote makes a UITextView behave like a UILabel but with fully accurate click detection and no scrolling issues:

import UIKit

class ClickableLabelTextView: UITextView {
    var delegate: DelegateForClickEvent?
    var ranges:[(start: Int, end: Int)] = []
    var page: String = ""
    var paragraph: Int?
    var clickedLink: (() -> Void)?
    var pressedTime: Int?
    var startTime: TimeInterval?

    override func awakeFromNib() {
        super.awakeFromNib()
        self.textContainerInset = UIEdgeInsets.zero
        self.textContainer.lineFragmentPadding = 0
        self.delaysContentTouches = true
        self.isEditable = false
        self.isUserInteractionEnabled = true
        self.isSelectable = false
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        startTime = Date().timeIntervalSinceReferenceDate
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let clickedLink = clickedLink {
            if let startTime = startTime {
                self.startTime = nil
                if (Date().timeIntervalSinceReferenceDate - startTime <= 0.2) {
                    clickedLink()
                }
            }
        }
    }

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        var location = point
        location.x -= self.textContainerInset.left
        location.y -= self.textContainerInset.top
        if location.x > 0 && location.y > 0 {
            let index = self.layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
            var count = 0
            for range in ranges {
                if index >= range.start && index < range.end {
                    clickedLink = {
                        self.delegate?.clickedLink(page: self.page, paragraph: self.paragraph, linkNo: count)
                    }
                    return self
                }
                count += 1
            }
        }
        clickedLink = nil
        return nil
    }
}

函数hitTest会被多次调用,但不会造成任何问题,因为clickedLink()每次单击只会被调用一次.我尝试为不同的视图禁用isUserInteractionEnabled,但这不是没有帮助,也是不必要的.

The function hitTest get's called multiple times but that never causes a problem, as clickedLink() will only ever get called once per click. I tried disabling isUserInteractionEnabled for different views but didn't that didn't help and was unnecessary.

要使用该类,只需将其添加到您的UITextView中.如果您在Xcode编辑器中使用autoLayout,请在编辑器中为UITextView禁用Scrolling Enabled以避免布局警告.

To use the class, simply add it to your UITextView. If you're using autoLayout in the Xcode editor, then disable Scrolling Enabled for the UITextView in the editor to avoid layout warnings.

Swift文件中,该文件包含与xib文件一起提供的代码(在我的情况下是UITableViewCell的类,您需要为可点击的textView设置以下变量:

In the Swift file that contains the code to go with your xib file (in my case a class for a UITableViewCell, you need to set the following variables for your clickable textView:

  • ranges-每个具有UITextView
  • 的可点击链接的开始和结束索引
  • page-一个String来标识包含UITextView
  • 的页面或视图
  • paragraph-如果您有多个可点击的UITextView,请为每个数字分配一个数字
  • delegate-将点击事件委派到您能够对其进行处理的任何地方.
  • ranges - the start and end index of every clickable link with the UITextView
  • page - a String to identify the page or view that contains the the UITextView
  • paragraph - If you have multiple clickable UITextView, assign each one with an number
  • delegate - to delegate the click events to where ever you are able to process them.

然后您需要为delegate创建协议:

You then need to create a protocol for your delegate:

protocol DelegateName {
    func clickedLink(page: String, paragraph: Int?, linkNo: Int?)
}

传递给clickedLink的变量为您提供了您需要知道单击哪个链接的所有信息.

The variables passed into clickedLink give you all the information you need to know which link has been clicked.

这篇关于如何在Swift 4的UILabel中准确检测是否单击了链接?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆