【问题标题】:Detect tap for UILabel with Autoshrink to minimum font size enabled在启用自动缩小到最小字体大小的情况下检测 UILabel 的点击
【发布时间】:2020-08-22 09:13:59
【问题描述】:

我正在使用我修改的以下 UITapGestureRecognizer,以便其他人可以复制和粘贴以查看与我相同的输出。

extension UITapGestureRecognizer {

    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 mutableAttribString = NSMutableAttributedString(attributedString: label.attributedText!)
        //mutableAttribString.addAttributes([NSAttributedString.Key.font: label.font!], range: NSRange(location: 0, length: label.attributedText!.length))

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

        // 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
        print("LabelSize=",labelSize)

        textContainer.size = labelSize
        print("TextContainerSize=",textContainer.size)

        // Find the tapped character location and compare it to the specified range
        let locationOfTouchInLabel = self.location(in: label)
        print("LocationOfTouchInLabel=",locationOfTouchInLabel)

        let textBoundingBox = layoutManager.usedRect(for: textContainer)
        print("TextBoundingBox=",textBoundingBox)
        //let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                              //(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
        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)
        print("textContainerOffset",textContainerOffset)

        //let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
                                                        // locationOfTouchInLabel.y - textContainerOffset.y);
        let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
        print("LocationOfTouchInTextContainer",locationOfTouchInTextContainer)

        let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        print("IndexOfCharacter=",indexOfCharacter)

        print("TargetRange=",targetRange)
        return NSLocationInRange(indexOfCharacter, targetRange)
    }

}

我这样设置标签以检测其他答案所说的点击:

formulaLabel.addGestureRecognizer(UITapGestureRecognizer(target:self, action: #selector(tapLabel(gesture:))))

设置为属性化。

如果重要的话,我有以下代码可以用文本执行动画。

func doFormulaAnimation(){
        //animTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true, block: {_ in
            //Draw correct formula based on mode.

        var formula = ""
        var highlight_ranges:[NSRange] = []

        if(stateController?.tdfvariables.ebrtMode == true){
            formulaLabel.numberOfLines = 1
            formula = "BED = N * d * [ RBE + ( d / (α/β) ) ]"

            self.formulaLabel.attributedText = NSMutableAttributedString(string: formula, attributes: [ NSAttributedString.Key.foregroundColor: UIColor.label ])

            if(self.dosePerFractionSelected==true){
                highlight_ranges.append(NSRange(location: 10, length: 1))
                highlight_ranges.append(NSRange(location: 24, length: 1))
            }

            if(self.alphaBetaSelected==true){
                highlight_ranges.append(NSRange(location: 29, length: 3))
            }

            if(self.totalDoseSelected==true){
                highlight_ranges.append(NSRange(location: 6, length: 5))
            }

            if(self.numFractionsSelected==true){
                highlight_ranges.append(NSRange(location: 6, length: 1))
            }
        }

        if(stateController?.tdfvariables.ldrMode == true){
            formulaLabel.numberOfLines = 3
            formula = "BED = R * T * [RBE + (( g * R * T) / (α/β) ) ]\ng = ( 2 / μT ) * ( 1 - ( ( 1 - exp(-μT) / μT ) )\nμ = 0.693 / Thalf repair"
        }

        if(stateController?.tdfvariables.permMode == true){
            formulaLabel.numberOfLines = 2
            formula = "BED = ( R0 / L ) * [ RBE + ( R0 / ( ( u + L) * (α/β) ) ) ]\n"
        }

        UIView.transition(with: self.formulaLabel, duration: 0.5, options: .transitionCrossDissolve, animations: {
                let colorAttribute = [ NSAttributedString.Key.foregroundColor: UIColor.label ]
                let attributedTextFormula = NSMutableAttributedString(string: formula, attributes: colorAttribute)

                let paragraphStyle = NSMutableParagraphStyle()
                paragraphStyle.lineSpacing = 6
                paragraphStyle.lineBreakMode = .byTruncatingTail
                //paragraphStyle.alignment = .center

                attributedTextFormula.addAttribute(
                    .paragraphStyle,
                    value: paragraphStyle,
                    range: NSRange(location: 0, length: attributedTextFormula.length
                ))


                self.formulaLabel.attributedText = attributedTextFormula
                //
            }, completion: { finished in
                print("finished first transition")

                UIView.transition(with: self.formulaLabel, duration: 0.5, options: .transitionCrossDissolve, animations: {
                    let colorAttribute = [ NSAttributedString.Key.foregroundColor: UIColor.label ]
                    let attributedTextFormula = NSMutableAttributedString(string: formula, attributes: colorAttribute)
                    for range in highlight_ranges {
                        attributedTextFormula.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.red, range: range)
                    }

                    let paragraphStyle = NSMutableParagraphStyle()
                    paragraphStyle.lineSpacing = 6
                    paragraphStyle.lineBreakMode = .byTruncatingTail
                    //paragraphStyle.alignment = .center

                    attributedTextFormula.addAttribute(
                        .paragraphStyle,
                        value: paragraphStyle,
                        range: NSRange(location: 0, length: attributedTextFormula.length
                    ))

                    self.formulaLabel.attributedText = attributedTextFormula
                    //self.formulaLabel.addInterlineSpacing(spacingValue: 1)
                }, completion: { finished in
                    print("finished second transition")
                })

        })
    }

上面的代码演示了我相应地创建 NSMutableString 并相应地设置段落样式和行间距(我有预感这可能是问题,但不确定如何测试)等等...

终于有了我的点击标签功能

 @IBAction func tapLabel(gesture: UITapGestureRecognizer) {
        guard let text = formulaLabel.attributedText?.string else {
            return
        }

        let AB_Range = text.range(of: "(α/β)")

        //let AB_Range = NSRange(location: 29, length: 3)
        if gesture.didTapAttributedTextInLabel(label: formulaLabel, inRange: NSRange(AB_Range!, in: text)) {
           print("Tapped a/b")
        } else {
           print("Tapped none")
        }
    }

目前它没有点击正确的位置,但在很大程度上偏离了。调试后,它为什么不工作变得很明显,但我不确定如何修复代码或哪里出错了。

当我点击 A/B 时,我应该得到它作为调试信息。

LabelSize= (315.0, 43.0)
TextContainerSize= (315.0, 43.0)
LocationOfTouchInLabel= (266.0, 28.0)
TextBoundingBox= (0.0, 0.0, 185.373046875, 13.8)
textContainerOffset (64.8134765625, 14.6)
LocationOfTouchInTextContainer (201.1865234375, 13.4)
IndexOfCharacter= 36
TargetRange= {28, 5}
Tapped none

它计算 textbounding box 或 textcontaineroffset 或 locationoftouchintextcontainer 的部分很可能是错误点,但我不确定是哪一个,因为这很难理解。

我可以说它工作不正常,因为在我的字符串中它没有接近结尾,无论我在哪里点击它,它总是说 indexofcharacter=36,这意味着它没有用我上面提到的变量之一正确计算某些东西.

如果他们知道为什么这不起作用,我们将不胜感激。

为了让您了解文本现在的外观,我将在下面向您展示。我的最终目标是做到这一点,以便我可以单击公式中的部分以显示值,因此您可以说可点击的链接。

更新::

我忘了提到当我更新 UITapGestureRecognizer 并添加这些行时:

let mutableAttribString = NSMutableAttributedString(attributedString: label.attributedText!)
        mutableAttribString.addAttributes([NSAttributedString.Key.font: label.font!], range: NSRange(location: 0, length: label.attributedText!.length))

并将其设置为文本存储,let textStorage = NSTextStorage(attributedString: mutableAttribString) 我确实看到数字有所改善,但仍有一段距离。

LabelSize= (315.0, 43.0)
TextContainerSize= (315.0, 43.0)
LocationOfTouchInLabel= (269.0, 25.5)
TextBoundingBox= (7.505859375, 0.0, 299.98828125, 42.9609375)
textContainerOffset (0.0, 0.01953125)
LocationOfTouchInTextContainer (269.0, 25.48046875)
IndexOfCharacter= 17
TargetRange= {28, 5}
Tapped none

您会注意到 textboundingbox 至少更类似于标签大小。我在某处读到,如果您使用不同的字体可能会出现问题,所以这就是为什么我尝试添加它以确保它正常工作。

另外有趣的是,将它添加到 UITapGestureRecognizer 似乎也有帮助:

let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = 6
        paragraphStyle.lineBreakMode = .byTruncatingTail
        //paragraphStyle.alignment = .center

    mutableAttribString.addAttribute(
        .paragraphStyle,
        value: paragraphStyle,
        range: NSRange(location: 0, length: mutableAttribString.length
    ))

我注意到当我这样做时文本边界框正确显示 0,0 而不是 7.50

LabelSize= (315.0, 43.0)
TextContainerSize= (315.0, 43.0)
LocationOfTouchInLabel= (262.5, 31.0)
TextBoundingBox= (0.0, 0.0, 299.98828125, 42.9609375)
textContainerOffset (7.505859375, 0.01953125)
LocationOfTouchInTextContainer (254.994140625, 30.98046875)
IndexOfCharacter= 17
TargetRange= {28, 5}
Tapped none

**主要更新*****

好消息!事实证明,字体修复是修复的一部分,但是因为我启用了 autoshrink 并启用了最小字体大小和最大字体大小,所以它从未正确计算! 当我设置为固定字体大小时,一切正常!

现在的问题是如何使其在启用自动收缩的情况下工作?请注意,您必须将大小设置为非常大的值才能证明这有效,例如我将我的设置为 55,因此它会相应地自动收缩并且然后你会看到错误,如果默认大小更小,那么它似乎工作正常,这告诉我错误可能与最初的大小非常大有关?

【问题讨论】:

    标签: uilabel multiline swift5 autoshrink


    【解决方案1】:

    所以我终于想出了一个可以接受的答案。

    关键是下面的代码:

    let formulaLabelWidth = formulaLabel.bounds.size.width
    
    var font_size:CGFloat = 36.0 //Change this to a higher number if you need to.
    var stringSize = NSString(string: formula).size(withAttributes: [.font : self.formulaLabel.font.withSize(font_size)])
    while(stringSize.width>formulaLabelWidth){
        font_size = font_size - 1
        stringSize = NSString(string: formula).size(withAttributes: [.font : self.formulaLabel.font.withSize(font_size)])
    }
    
    
     formulaLabel.font = formulaLabel.font.withSize(font_size)
    

    这段代码所做的是绘制字符串,就好像它具有特定的字体大小一样,为您提供宽度和高度的边界。就我而言,我只关心宽度,这要归功于您如何在界面构建器中设置 UILabel。

    要使此策略生效,必须在您的 UILabel 上进行设置,因为它会尽一切努力找到适合文本的完美尺寸。

    确实适用于多行,我在我的许多公式中都使用了它。这些行用 \n 字符分隔并自动计算在内,因为它只是增加了使用.size(withAttributes) 函数绘制的高度。

    他们曾经有一个 sizeForFont 但它已被弃用,所以我研究了这个函数作为一个可能的解决方案,确实它确实可以通过一些聪明的想法工作。

    至于检测点击,请使用我创建的以下修改后的 UITapGestureRecognizer。

    extension UITapGestureRecognizer {
    
        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 mutableAttribString = NSMutableAttributedString(attributedString: label.attributedText!)
            mutableAttribString.addAttributes([NSAttributedString.Key.font: label.font!], range: NSRange(location: 0, length: label.attributedText!.length))
    
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.lineSpacing = 6
            paragraphStyle.lineBreakMode = .byTruncatingTail
            paragraphStyle.alignment = .center
            mutableAttribString.addAttributes([.paragraphStyle: paragraphStyle], range: NSMakeRange(0, mutableAttribString.string.count))
    
            let textStorage = NSTextStorage(attributedString: mutableAttribString)
    
            // Configure textContainer
            textContainer.lineFragmentPadding = 0.0
            textContainer.lineBreakMode = label.lineBreakMode
            textContainer.maximumNumberOfLines = label.numberOfLines
    
            // Configure layoutManager and textStorage
            layoutManager.addTextContainer(textContainer)
    
            textStorage.addLayoutManager(layoutManager)
    
            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 = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                                  //(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
            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 = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
                                                            // locationOfTouchInLabel.y - textContainerOffset.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=",indexOfCharacter)
    
            print("TargetRange=",targetRange)
            return NSLocationInRange(indexOfCharacter, targetRange)
        }
    
    }
    

    请随意删除 cmets,但我将它们留在那里,以便您可以看到它正在选择正确的字符串索引。

    另外请注意,我在其中设置了一个段落设置,行距为 6,对齐中心,因此请随意将其更改为您的用例,但不要更改换行模式!这对于系统在绘制时自动找到最佳字体大小至关重要。

    在此代码中还引用了当前标签字体,这是我添加的新部分,因为没有它,文本存储计算将与堆栈溢出的其他答案中指出的那样相去甚远。

    将它与点击手势的功能放在一起

    @IBAction func tapLabel(gesture: UITapGestureRecognizer) {
            guard let text = formulaLabel.attributedText?.string else {
                return
            }
    
            let AB_Range = text.range(of: "(α/β)")
    
            //let AB_Range = NSRange(location: 29, length: 3)
            if gesture.didTapAttributedTextInLabel(label: formulaLabel, inRange: NSRange(AB_Range!, in: text)) {
               print("Tapped a/b")
            } else {
               print("Tapped none")
            }
        }
    

    当然还有在 viewDidLoad 或适当的地方将点击手势设置为 UILabel...

    formulaLabel.addGestureRecognizer(UITapGestureRecognizer(target:self, action: #selector(tapLabel(gesture:))))
    

    你有自己的工作

    • 多行友好
    • 自动收缩友好
    • 可点击友好

    UILabel!

    【讨论】:

      猜你喜欢
      • 2014-10-07
      • 1970-01-01
      • 1970-01-01
      • 2013-04-16
      • 2015-10-08
      • 2011-02-10
      • 2012-05-15
      • 2013-05-14
      • 2015-09-14
      相关资源
      最近更新 更多