Обнаружение касания для UILabel с включенным автоматическим сжатием до минимального размера шрифта - PullRequest
1 голос
/ 07 мая 2020

Я использую следующий 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)
    }

}

Я установил метку таким образом, чтобы обнаруживать клики, как в других ответах do:

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 и соответствующим образом устанавливаю стиль абзаца и межстрочный интервал (у меня есть подозрение, что это может быть проблемой, но я не знаю, как это проверить) et c ...

Наконец, я есть моя функция метки касания

 @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

Часть, в которой он вычисляет поле ограничения текста или смещение текстового контейнера или locationoftouchintextcontainer, составляет , скорее всего, это точки ошибки , но я не уверен, что именно, так как это сложно чтобы понять.

Я могу сказать, что он работает неправильно, потому что в моей строке это не где-то рядом с концом, и он всегда говорит indexofcharacter = 36 независимо от того, где я щелкаю мимо него, что означает, что он не вычисляет что-то правильно с одним переменных, о которых я упоминал выше.

Любая помощь приветствуется, если они знают, почему это не работает.

Чтобы дать представление о том, как сейчас выглядит текст, я покажу вам ниже. Моя конечная цель - сделать так, чтобы я мог щелкать по частям в формуле, чтобы отображались значения, чтобы можно было сказать интерактивные ссылки.

enter image description here

ОБНОВЛЕНИЕ ::

Я забыл упомянуть, что когда я обновил 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

Вы заметите, что текстовое поле больше похоже на размер ярлыка. Я где-то читал, что проблема может быть в том, что вы используете разные шрифты, поэтому я попытался добавить это, чтобы убедиться, что он работает правильно.

Также интересно, что добавление этого в 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

** ОСНОВНОЕ ОБНОВЛЕНИЕ *****

Ну отличные новости! Оказывается, исправление шрифта было частью исправления, но поскольку у меня было включено автоматическое сжатие с минимальным размером шрифта и максимальным размером шрифта, оно никогда не вычислялось правильно! Когда я установил фиксированный размер шрифта, все работает!

Теперь возникает вопрос: как заставить его работать с включенным автоматическим сжатием? Обратите внимание, что вам нужно установить размер на что-то очень большое, чтобы доказать, что это работает, например, я установил свой на 55, поэтому он автоматически сжимается, а затем вы видите ошибку, если размер по умолчанию меньше, то, похоже, он работает нормально, что говорит мне, что ошибка как-то связана с размером может быть изначально очень большим?

enter image description here

1 Ответ

0 голосов
/ 09 мая 2020

Итак, я, наконец, нашел приемлемый ответ.

Ключ - это код ниже:

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)

Этот код делает dr aws строку, как если бы она имела определенный размер шрифта, дающий вам границы по ширине и высоте. В моем случае меня интересует только ширина благодаря уловке с тем, как вы настраиваете UILabel в построителе интерфейса.

enter image description here

Чтобы эта стратегия работала это ДОЛЖНО быть установлено на вашем 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)
    }

}

Не стесняйтесь удалите комментарии, но я оставил их там, чтобы вы могли видеть, что он выбирает правильный индекс строк.

Также обратите внимание, что у меня был параметр абзаца с межстрочным интервалом 6 и центром выравнивания, так что почувствуйте вы можете изменить их в соответствии с вашим вариантом использования, но не меняйте режим разрыва строки! Это критично для того, чтобы система автоматически находила оптимальный размер шрифта, когда он dr aws.

Также в этом коде есть ссылка на текущий шрифт меток, это была новая часть, которую я добавил, потому что без нее текст -счет хранения будет далек от того, как указано в других ответах на переполнение стека.

Сложите его вместе с функцией для жеста касания

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

и, конечно же, установите жест касания в UILabel в viewDidLoad или, где это необходимо ...

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

И у вас есть рабочий

  • Multiline Friendly
  • Autoshrink Friendly
  • Дружественный к клику

UILabel!

...