Swift : tap on a part of text of UILabel

前端 未结 7 990
北海茫月
北海茫月 2020-12-02 21:36

I have a problem that \"boundingRectForGlyphRange\" always returns CGRect.zero \"0.0, 0.0, 0.0, 0.0\". \"boundingRectForGlyphRange\" is not working. For example, I am coding

7条回答
  •  春和景丽
    2020-12-02 22:08

    For multi-line labels you have to set the textStorage font or the incorrect range will be returned

    guard let attributedString = self.attributedText else { return }
    
    let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
    mutableAttribString.addAttributes([NSAttributedString.Key.font: myFont], range: NSRange(location: 0, length: attributedString.length))
    
    let textStorage = NSTextStorage(attributedString: mutableAttribString)
    

    There are a lot of answers to this question. However, there are many people complaining that the tap fails for multi-line labels and that is correct for most answers on this page. The incorrect range for the tap is returned because the textStorage doesn't have the correct font.

    let textStorage = NSTextStorage(attributedString: label.attributedText!)
    

    You can fix this quickly by adding the correct font to your textStorage instance:

    guard let attributedString = self.attributedText else { return -1 }
    
    let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
    mutableAttribString.addAttributes([NSAttributedString.Key.font: myFont], range: NSRange(location: 0, length: attributedString.length))
    
    let textStorage = NSTextStorage(attributedString: mutableAttribString)
    

    Putting it all together you get something like this:

    protocol AtMentionsLabelTapDelegate: class {
      func labelWasTappedForUsername(_ username: String)
    }
    
    class AtMentionsLabel: UILabel {
      private var tapGesture: UITapGestureRecognizer = UITapGestureRecognizer()
      weak var tapDelegate: AtMentionsLabelTapDelegate?
    
      var mentions: [String] = [] // usernames to style
    
      override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
      }
    
      required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
      }
    
      func commonInit() {
        isUserInteractionEnabled = true
    
        lineBreakMode = .byWordWrapping
        tapGesture = UITapGestureRecognizer()
        tapGesture.addTarget(self, action: #selector(handleLabelTap(recognizer:)))
        tapGesture.numberOfTapsRequired = 1
        tapGesture.isEnabled = true
        addGestureRecognizer(tapGesture)
      }
    
    
      @objc func handleLabelTap(recognizer: UITapGestureRecognizer) {
        let tapLocation = recognizer.location(in: self)
        let tapIndex = indexOfAttributedTextCharacterAtPoint(point: tapLocation)
    
        for username in mentions {
          if let ranges = self.attributedText?.rangesOf(subString: username) {
            for range in ranges {
              if tapIndex > range.location && tapIndex < range.location + range.length {
                tapDelegate?.labelWasTappedForUsername(username)
                return
              }
            }
          }
        }
      }
    
      func indexOfAttributedTextCharacterAtPoint(point: CGPoint) -> Int {
        guard let attributedString = self.attributedText else { return -1 }
    
        let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
        // Add font so the correct range is returned for multi-line labels
        mutableAttribString.addAttributes([NSAttributedString.Key.font: font], range: NSRange(location: 0, length: attributedString.length))
    
        let textStorage = NSTextStorage(attributedString: mutableAttribString)
    
        let layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)
    
        let textContainer = NSTextContainer(size: frame.size)
        textContainer.lineFragmentPadding = 0
        textContainer.maximumNumberOfLines = numberOfLines
        textContainer.lineBreakMode = lineBreakMode
        layoutManager.addTextContainer(textContainer)
    
        let index = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        return index
      }
    }
    
    extension NSAttributedString {
      func rangesOf(subString: String) -> [NSRange] {
        var nsRanges: [NSRange] = []
        let ranges = string.ranges(of: subString, options: .caseInsensitive, locale: nil)
    
        for range in ranges {
          nsRanges.append(range.nsRange)
        }
    
        return nsRanges
      }
    }
    
    extension String {
      func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range] {
        var ranges: [Range] = []
        while let range = self.range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex) ..< self.endIndex, locale: locale) {
          ranges.append(range)
        }
        return ranges
      }
    }
    

提交回复
热议问题