使用自动收缩到最小字体大小检测 UILabel 的点击 [英] Detect tap for UILabel with Autoshrink to minimum font size enabled

查看:109
本文介绍了使用自动收缩到最小字体大小检测 UILabel 的点击的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am using the following UITapGestureRecognizer that I modified so others could copy and paste to see the same output as me.

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

}

我像其他答案所说的那样设置标签来检测点击:

I set the label like so to detect clicks like other answers have said to do:

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

它被设置为归因.

以防万一,我有以下代码可以使用文本执行动画.

In case it matters I have the following code that performs an animation with the text.

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 并相应地设置段落样式和行距(我有预感这可能是问题,但不确定如何测试)等...

The code above demonstrates me creating the NSMutableString accordingly and setting the paragraph style and line spacing accordingly (I have a hunch this may be the issue but not sure how to test that) etc...

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

Finally I have my tap label function

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

目前它没有点击正确的位置,但在很大程度上是关闭的.调试后很明显为什么它不起作用,但我不确定如何修复代码或哪里出错了.

Currently it does not tap on the correct location but is off by a very large degree. Upon debugging it becomes obvious why it's not working but I am not sure how to fix the code or where it went wrong.

当我像我应该那样点击 A/B 时,我会得到它作为调试信息.

When i click on the A/B like I'm supposed to I get this as the debug info.

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 的部分很可能是错误点,但我不确定是哪一个,因为这很难理解.

The part where it calculates the textbounding box or textcontaineroffset or the locationoftouchintextcontainer are most likely the error points but I'm not sure which it is since this is hard to understand.

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

I can tell it's not working correctly because in my string it's no where near the end and it always says indexofcharacter=36 no matter where I click past it, which means it's not calculating something correctly with one of the variables I mentioned above.

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

Any help is appreciated if they know why this isn't working.

为了让您了解文本现在的样子,我将在下面向您展示.我的最终目标是让我可以点击公式中的部分来显示值,所以你可以说可点击的链接.

To give an idea how the text looks right now I'll show you below. My end goal is to make it so I can click on parts in the formula to get values to appear, so clickable links you could say.

更新::

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

I forgot to mention that when I updated the UITapGestureRecognizer and add these lines:

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) 我确实看到数字有一些改进,但仍有很长的路要走.

and set it to the text storage, let textStorage = NSTextStorage(attributedString: mutableAttribString) I do seem to see some improvements in the numbers but it's still way off.

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 至少与 labelsize 更相似.我在某处读到,如果您使用不同的字体可能会出现问题,这就是为什么我尝试添加它以确保它正常工作.

You will notice the textboundingbox more closely resembles the labelsize at least. I read somewhere that an issue could be if you are using different fonts so this is why I tried adding that in to be sure it's working correctly.

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

Also interestingly, adding this to the UITapGestureRecognizer seems to help also:

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

I noticed the textbounding box shows 0,0 correctly when I do this instead of 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

**主要更新*****

**MAJOR UPDATE*****

好消息!结果证明字体修复是修复的一部分,但是因为我使用最小字体大小和最大字体大小启用了自动收缩,所以它永远无法正确计算!当我设置为固定字体大小时,一切正常!

Well great news! It turns out that the font fix was part of the fix, but because I had autoshrink enabled with a minimum font size and maximum font size it was never calculating correctly! When I set to a fixed font size everything works!

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

Question is now how to make it work with autoshrink enabled? Note that you will have to set size to something very large to prove this works, like I set mine to 55 so it autoshrinks accordingly and then you see the error, if the default size is smaller then it seems to work fine, which tells me the error has something to do with the size being initially very large maybe?

推荐答案

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

So I finally figured out an acceptable answer.

关键是下面的代码:

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 的技巧.

What this code does is draws the string as if it had a certain font size giving you the boundaries in width and height. In my case I only care about the width thanks to a trick with how you set up the UILabel in interface builder.

要使此策略起作用,必须在您的 UILabel 上设置它,因为它完成了找到适合文本的完美大小的所有艰苦工作.

For this strategy to work this MUST be set on your UILabel since it does all the hard work of finding the perfect size in which the text will fit.

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

This DOES work with Multiple Lines as well as I am using that for many of my formulas. The lines are separated with \n characters and is automatically accounted for since it just adds to the height of how it would draw with the .size(withAttributes) function.

他们曾经有一个 sizeForFont,但它已被弃用,所以我研究了该函数作为一种可能的解决方案,并且确实通过一些聪明的思考确实有效.

They used to have a sizeForFont but it was deprecated so I looked into that function as a possible solution and indeed it does work with some clever thinking.

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

As for detecting a tap use the following modified UITapGestureRecognizer that I created.

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

}

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

Feel free to remove the comments, but I left them there so you could see that it is selecting the correct index of the strings.

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

Also note that I had a paragraph setting in there with line spacing of 6 and alignment center so feel free to change those to your use case but do not change the line break mode! That is critical to the system automatically finding the optimal font size when it draws.

在这段代码中还有对当前标签字体的引用,这是我添加的新部分,因为没有它,文本存储计算将与堆栈溢出的其他答案中指出的一样.

Also in this code is reference to the current labels font, this was the new part I added, because without it the text-storage calculations would be way off as pointed out in other answers on stack-overflow.

与点击手势的功能结合起来

Put it together with the function for the tap gesture

@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...

and of course set the tap gesture to the UILabel in viewDidLoad or where appropriate...

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

你有自己的工作

  • 多行友好
  • 自动收缩友好
  • 易于点击

UILabel!

这篇关于使用自动收缩到最小字体大小检测 UILabel 的点击的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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