Tengo un problema que «boundingRectForGlyphRange» siempre devuelve CGRect.cero «0.0, 0.0, 0.0, 0.0». «boundingRectForGlyphRange» no está funcionando. Por ejemplo, yo soy de codificación para tocar en una parte del texto de UILabel característica. Mi texto tiene la primera parte es «cualquier texto» y la segunda es el «LEER MÁS». Quiero que la toque reconocedor sólo funciona cuando me toque «LEER MÁS». Si yo toque en cualquier punto de UILabel, «CGRectContainsPoint» siempre devolverá true,entonces la acción de

Aquí mi código:

override func viewDidLoad() {
super.viewDidLoad()
//The full string

let firstPart:NSMutableAttributedString = NSMutableAttributedString(string: "Lorem ipsum dolor set amit ", attributes: [NSFontAttributeName: UIFont.systemFontOfSize(13)])
firstPart.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(),
range: NSRange(location: 0, length: firstPart.length))
info.appendAttributedString(firstPart)
//The "Read More" string that should be touchable
        let secondPart:NSMutableAttributedString = NSMutableAttributedString(string: "READ MORE", attributes: [NSFontAttributeName: UIFont.systemFontOfSize(14)])
secondPart.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(),
range: NSRange(location: 0, length: secondPart.length))
info.appendAttributedString(secondPart)
lblTest.attributedText = info
//Store range of chars we want to detect touches for
        moreStringRange = NSMakeRange(firstPart.length, secondPart.length)
print("moreStringRange\(moreStringRange)")
tapRec.addTarget(self, action: "didTap:")
lblTest.addGestureRecognizer(tapRec)
}
func didTap(sender:AnyObject) {
//Storage class stores the string, obviously
        let textStorage:NSTextStorage = NSTextStorage(attributedString: info)
//The storage class owns a layout manager
        let layoutManager:NSLayoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
//Layout manager owns a container which basically
        //defines the bounds the text should be contained in
        let textContainer:NSTextContainer = NSTextContainer(size: lblTest.frame.size)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = lblTest.lineBreakMode
//Begin computation of actual frame
        //Glyph is the final display representation
        var glyphRange = NSRange()
//Extract the glyph range
        layoutManager.characterRangeForGlyphRange(moreStringRange!, actualGlyphRange: &glyphRange)
//Compute the rect of glyph in the text container
        print("glyphRange\(glyphRange)")
print("textContainer\(textContainer)")
let glyphRect:CGRect = layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer: textContainer)
//Final rect relative to the textLabel.
        print("\(glyphRect)")
//Now figure out if the touch point is inside our rect
        let touchPoint:CGPoint = tapRec.locationOfTouch(0, inView: lblTest)
if CGRectContainsPoint(glyphRect, touchPoint) {
print("User tapped on Read More. So show something more")
}
}

Es sólo una demo para probar lo que quiero hacer:

Swift : toca en una parte del texto de UILabel

Cualquier ayuda sería muy apreciada.

  • Espero que esto le ayudará leer más
  • Cómo sobre el uso de dos de las etiquetas?
  • Es solo una demo. Mi texto tiene muchas partes con los archivos adjuntos. No se pueden utilizar muchos etiqueta.
  • Como ya he dicho, es sólo una demo, «leer más» (puede ser otra cosa) no puede, en el final de la cadena.
  • A continuación, marque uno stackoverflow.com/questions/32175144/…
  • Por favor, compruebe la imagen. imagen entender.
  • Otra manera posible de hacer esto: stackoverflow.com/questions/20541676/…

InformationsquelleAutor Ashley | 2016-03-16

6 Comentarios

  1. 22

    Después de tener varios problemas con este tipo de cosas, el uso de un montón de diferentes librairies, etc… he encontrado una solución interesante:
    http://samwize.com/2016/03/04/how-to-create-multiple-tappable-links-in-a-uilabel/

    Se trata de extender UITapGestureRegonizer y detectar si la llave está en el rango de la cadena cuando se activa.

    Aquí es la actualización de los Swift 4 de la versión de esta extensión:

    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 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)
    return NSLocationInRange(indexOfCharacter, targetRange)
    }
    }

    Para simplificar la gama de la conversión, usted también necesita esta ampliación de la Gama de

    extension Range where Bound == String.Index {
    var nsRange:NSRange {
    return NSRange(location: self.lowerBound.encodedOffset,
    length: self.upperBound.encodedOffset -
    self.lowerBound.encodedOffset)
    }
    }

    Una vez que usted tiene esta extensión, puede agregar un toque gesto a su etiqueta:

    let tap = UITapGestureRecognizer(target: self, action: #selector(tapLabel(tap:)))
    self.yourLabel.addGestureRecognizer(tap)
    self.yourLabel.isUserInteractionEnabled = true

    Aquí es la función de la manija del grifo:

    @objc func tapLabel(tap: UITapGestureRecognizer) {
    guard let range = self.yourLabel.text?.range(of: "Substring to detect")?.nsRange else {
    return
    }
    if tap.didTapAttributedTextInLabel(label: self.yourLabel, inRange: range) {
    //Substring tapped
        }
    }
    • No trabajo para mí
    • No es de trabajo.
    • mismo no funciona para mí también. Creo que la respuesta de sólo una línea de la etiqueta no para el multilínea etiqueta.
    • Para Multi-línea de texto, establezca la etiqueta de salto de línea en el modo «Truncar la cola».
    • No contacto en UITableViewCell de encargo de la etiqueta de texto
    • pruebe este self.yourLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapLabel(tap:)))) va a trabajar.

  2. 11

    swift 4.2

    Por favor a encontrar aquí la solución para obtener un texto específico action de Label.

    Swift : toca en una parte del texto de UILabel

    1) Etiqueta de declaración de

    @IBOutlet weak var lblTerms: UILabel!

    2) Establecer atribuye el texto a la etiqueta

    let text = "Please agree for Terms & Conditions."
    lblTerms.text = text
    self.lblTerms.textColor =  UIColor.white
    let underlineAttriString = NSMutableAttributedString(string: text)
    let range1 = (text as NSString).range(of: "Terms & Conditions.")
    underlineAttriString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range1)
    underlineAttriString.addAttribute(NSAttributedString.Key.font, value: UIFont.init(name: Theme.Font.Regular, size: Theme.Font.size.lblSize)!, range: range1)
    underlineAttriString.addAttribute(NSAttributedString.Key.foregroundColor, value: Theme.color.primaryGreen, range: range1)
    lblTerms.attributedText = underlineAttriString
    lblTerms.isUserInteractionEnabled = true
    lblTerms.addGestureRecognizer(UITapGestureRecognizer(target:self, action: #selector(tapLabel(gesture:))))

    Se ve como la imagen de arriba.

    3) Agregar la tapLable método de acción para el controlador de

    @IBAction func tapLabel(gesture: UITapGestureRecognizer) {
    let termsRange = (text as NSString).range(of: "Terms & Conditions")
    //comment for now
       //let privacyRange = (text as NSString).range(of: "Privacy Policy")
    
    if gesture.didTapAttributedTextInLabel(label: lblTerms, inRange: termsRange) {
    print("Tapped terms")
    } else if gesture.didTapAttributedTextInLabel(label: lblTerms, inRange: privacyRange) {
    print("Tapped privacy") 
    } else {                
    print("Tapped none")
    }
    }

    4) Agregar UITapGestureRecognizer extensión

    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 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 = 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)
    return NSLocationInRange(indexOfCharacter, targetRange)
    }
    }

    ¡Buena suerte! 🙂

    • ¿tiene usted alguna idea acerca de varias audicencia? Por ejemplo, tengo el texto como este: «Hola mundo! hola». Hay alguna forma de detectar toque en la 2ª palabra «hola»?
    • Hey @R4j lo siento por la respuesta tardía, sí funciona.
    • es un trabajo de varias líneas?
    • Por qué esto no es aceptado la respuesta? 😮 Funciona como un encanto para mí!
  3. 6

    Swift 3. He desarrollado una extensión:

     extension UILabel {
    ///Find the index of character (in the attributedText) at point
            func indexOfAttributedTextCharacterAtPoint(point: CGPoint) -> Int {
    assert(self.attributedText != nil, "This method is developed for attributed string")
    let textStorage = NSTextStorage(attributedString: self.attributedText!)
    let layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)
    let textContainer = NSTextContainer(size: self.frame.size)
    textContainer.lineFragmentPadding = 0
    textContainer.maximumNumberOfLines = self.numberOfLines
    textContainer.lineBreakMode = self.lineBreakMode
    layoutManager.addTextContainer(textContainer)
    let index = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    return index
    } 
    }

    Y ahora puedo comprobar si el roscado personaje está en el rango de:

            let range = SOME_RANGE
    let tapLocation = gesture.location(in: MY_TEXT_LABEL)
    let index = textLbl.indexOfAttributedTextCharacterAtPoint(point: tapLocation)
    if index > range.location && index < range.location + range.length {
    //YES, THE TAPPED CHARACTER IS IN RANGE
            }
    • Gracias. Esto es realmente útil. En varias líneas de texto, en la parte inferior de la línea no es aprovechado cuando ponemos en práctica esta extensión. Además, tratamos de let textContainer = NSTextContainer(size: CGSize(width: (self.label?.frame.width)!, height: (self.label.frame.height)+100)), pero ninguna posibilidad.
    • Buen enfoque. Pero no se trata de no-alineado a la izquierda en el texto (ex. centro o a la derecha). Parece que no hay manera de pasar de la alineación de UILabel a NSTextContainer
    • este no es trabajo para el multilínea etiqueta.
  4. 6

    Esta es una real alternativa fácil para cualquier persona que está dispuesto a utilizar un textView. Me doy cuenta de que esta pregunta es acerca de un UILabel pero si usted lee los comentarios de algunas de las respuestas que no funcionan para algunas personas y algunos de ellos son muy código pesado, que no es muy bueno para los principiantes. Usted puede hacer esto en 11 sencillos pasos si estás dispuesto a intercambiar un UILabel para un UITextView.

    Puede utilizar NSMutableAttributedString y un UITextView. El UITextView tiene un método delegado func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {. Una vez establecida la parte de la cadena que se quiere hacer para tocar el delegado método para activarla.

    Los 11 pasos que se enumeran a continuación en los comentarios por encima de cada trozo de código.

    //1st **BE SURE TO INCLUDE** UITextViewDelegate to the view controller's class
    class VewController: UIViewController, UITextViewDelegate {
    //2nd use a programmatic textView or use the textView from your storyboard
        let yourTextView: UITextView = {
    let textView = UITextView()
    textView.textAlignment = .center
    textView.isEditable = false
    textView.showsVerticalScrollIndicator = false
    return textView
    }()
    override func viewDidLoad() {
    super.viewDidLoad()
    //3rd in viewDidLoad set the textView's delegate
            yourTextView.delegate = self
    //4th create the first piece of the string you don't want to be tappable
            let regularText = NSMutableAttributedString(string: "any text ", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17), NSAttributedStringKey.foregroundColor: UIColor.black])
    //5th create the second part of the string that you do want to be tappable. I used a blue color just so it can stand out.
            let tappableText = NSMutableAttributedString(string: "READ MORE")
    tappableText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 17), range: NSMakeRange(0, tappableText.length))
    tappableText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.blue, range: NSMakeRange(0, tappableText.length))
    //6th this ISN'T NECESSARY but this is how you add an underline to the tappable part. I also used a blue color so it can match the tappableText and used the value of 1 for the height. The length of the underline is based on the tappableText's length using NSMakeRange(0, tappableText.length)
            tappableText.addAttribute(NSAttributedString.Key.underlineStyle, value: 1, range: NSMakeRange(0, tappableText.length))
    tappableText.addAttribute(NSAttributedString.Key.underlineColor, value: UIColor.blue, range: NSMakeRange(0, tappableText.length))
    //7th this is the important part that connects the tappable link to the delegate method in step 11
            //use NSAttributedString.Key.link and the value "makeMeTappable" to link the NSAttributedString.Key.link to the method. FYI "makeMeTappable" is a name I choose for clarity, you can use anything like "anythingYouCanThinkOf"
            tappableText.addAttribute(NSAttributedString.Key.link, value: "makeMeTappable", range: NSMakeRange(0, tappableText.length))
    //8th *** important append the tappableText to the regularText ***
            regularText.append(tappableText)
    //9th set the regularText to the textView's attributedText property
            yourTextView.attributedText = regularText 
    }
    //10th add the textView's delegate method that activates urls. Make sure to return false for the tappable part
       func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
    //11th use the value from the 7th step to trigger the url inside this method
            if URL.absoluteString == "makeMeTappable"{
    //in this situation I'm using the tappableText to present a view controller but it can be used for whatever you trying to do
                let someVC = SomeController()
    let navVC = UINavigationController(rootViewController: someVC)
    present(navVC, animated: true, completion: nil)
    return false //return false for this to work
            }
    return true
    }
    }
  5. 3

    Su texto kit de pila es defectuoso. Usted se olvidó de agregar el contenedor de texto para el gestor de diseño! Por lo tanto, no hay ningún texto de la disposición, y el gestor de diseño no puede informar de cualquier glifo rect. Por lo tanto, que el glifo rect es NSRectZero, que es la razón por la que nunca se puede informar de un grifo dentro de ella.

    Otro problema es que usted está llamando characterRangeForGlyphRange cuando usted debe llamar a glyphRangeForCharacterRange, y que no parecen saber cómo utilizar el resultado (de hecho, desecha el resultado).

    Aquí está trabajando código que muestra sólo la parte sobre el uso del texto de la pila. Puedo empezar con la cadena «Hola a ti». Voy a mostrar cómo aprender en la rect para «a» es:

    let s = "Hello to you"
    let ts = NSTextStorage(
    attributedString: NSAttributedString(string:s))
    let lm = NSLayoutManager()
    ts.addLayoutManager(lm)
    let tc = NSTextContainer(size: CGSizeMake(4000,400))
    lm.addTextContainer(tc) //****
    tc.lineFragmentPadding = 0
    let toRange = (s as NSString).rangeOfString("to")
    let gr = lm.glyphRangeForCharacterRange(
    toRange, actualCharacterRange: nil) //****
    let glyphRect = lm.boundingRectForGlyphRange(
    gr, inTextContainer: tc)

    El resultado es {x 30.68 y 0 w 10.008 h 13.8}. Ahora podemos proceder a probar si un toque está en que rect. Vayan Y Hagan Lo Mismo.

    • donde es la etiqueta?
  6. 3

    Para multi-etiquetas de línea, usted tiene que fijar el textStorage de la fuente o el rango incorrecto será devuelto

    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)

    Hay un montón de respuestas a esta pregunta. Sin embargo, hay mucha gente quejándose de que el grifo de la falla de múltiples etiquetas de línea, y que es correcto para la mayoría de las respuestas en esta página. El rango incorrecto por el toque, se devuelve porque el textStorage no tiene la fuente correcta.

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

    Se puede solucionar este problema de forma rápida mediante la adición de la fuente correcta para su textStorage ejemplo:

    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)

    Poniendo todo junto se puede conseguir algo como esto:

    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<Index>] {
    var ranges: [Range<Index>] = []
    while let range = self.range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex) ..< self.endIndex, locale: locale) {
    ranges.append(range)
    }
    return ranges
    }
    }

Dejar respuesta

Please enter your comment!
Please enter your name here