rasgos que los lista héroe genios genio ejemplos distinguen cosas características caracteristicas caracter adultos objective-c ios7 textkit

objective c - que - Índice de caracteres en el punto de contacto para UILabel



lista de rasgos de caracter (5)

Aquí tienes mi implementación para el mismo problema. He necesitado marcar #hashtags y @usernames con reacción en los @usernames .

No drawTextInRect:(CGRect)rect porque el método predeterminado funciona a la perfección.

También he encontrado la siguiente implementación agradable https://github.com/Krelborn/KILabel . Usé algunas ideas de esta muestra también.

@protocol EmbeddedLabelDelegate <NSObject> - (void)embeddedLabelDidGetTap:(EmbeddedLabel *)embeddedLabel; - (void)embeddedLabel:(EmbeddedLabel *)embeddedLabel didGetTapOnHashText:(NSString *)hashStr; - (void)embeddedLabel:(EmbeddedLabel *)embeddedLabel didGetTapOnUserText:(NSString *)userNameStr; @end @interface EmbeddedLabel : UILabel @property (nonatomic, weak) id<EmbeddedLabelDelegate> delegate; - (void)setText:(NSString *)text; @end #define kEmbeddedLabelHashtagStyle @"hashtagStyle" #define kEmbeddedLabelUsernameStyle @"usernameStyle" typedef enum { kEmbeddedLabelStateNormal = 0, kEmbeddedLabelStateHashtag, kEmbeddedLabelStateUsename } EmbeddedLabelState; @interface EmbeddedLabel () @property (nonatomic, strong) NSLayoutManager *layoutManager; @property (nonatomic, strong) NSTextStorage *textStorage; @property (nonatomic, weak) NSTextContainer *textContainer; @end @implementation EmbeddedLabel - (void)dealloc { _delegate = nil; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setupTextSystem]; } return self; } - (void)awakeFromNib { [super awakeFromNib]; [self setupTextSystem]; } - (void)setupTextSystem { self.userInteractionEnabled = YES; self.numberOfLines = 0; self.lineBreakMode = NSLineBreakByWordWrapping; self.layoutManager = [NSLayoutManager new]; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:self.bounds.size]; textContainer.lineFragmentPadding = 0; textContainer.maximumNumberOfLines = self.numberOfLines; textContainer.lineBreakMode = self.lineBreakMode; textContainer.layoutManager = self.layoutManager; [self.layoutManager addTextContainer:textContainer]; self.textStorage = [NSTextStorage new]; [self.textStorage addLayoutManager:self.layoutManager]; } - (void)setFrame:(CGRect)frame { [super setFrame:frame]; self.textContainer.size = self.bounds.size; } - (void)setBounds:(CGRect)bounds { [super setBounds:bounds]; self.textContainer.size = self.bounds.size; } - (void)layoutSubviews { [super layoutSubviews]; self.textContainer.size = self.bounds.size; } - (void)setText:(NSString *)text { [super setText:nil]; self.attributedText = [self attributedTextWithText:text]; self.textStorage.attributedString = self.attributedText; [self.gestureRecognizers enumerateObjectsUsingBlock:^(UIGestureRecognizer *recognizer, NSUInteger idx, BOOL *stop) { if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) [self removeGestureRecognizer:recognizer]; }]; [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(embeddedTextClicked:)]]; } - (NSMutableAttributedString *)attributedTextWithText:(NSString *)text { NSMutableParagraphStyle *style = [NSMutableParagraphStyle new]; style.alignment = self.textAlignment; style.lineBreakMode = self.lineBreakMode; NSDictionary *hashStyle = @{ NSFontAttributeName : [UIFont boldSystemFontOfSize:[self.font pointSize]], NSForegroundColorAttributeName : (self.highlightedTextColor ?: (self.textColor ?: [UIColor darkTextColor])), NSParagraphStyleAttributeName : style, kEmbeddedLabelHashtagStyle : @(YES) }; NSDictionary *nameStyle = @{ NSFontAttributeName : [UIFont boldSystemFontOfSize:[self.font pointSize]], NSForegroundColorAttributeName : (self.highlightedTextColor ?: (self.textColor ?: [UIColor darkTextColor])), NSParagraphStyleAttributeName : style, kEmbeddedLabelUsernameStyle : @(YES) }; NSDictionary *normalStyle = @{ NSFontAttributeName : self.font, NSForegroundColorAttributeName : (self.textColor ?: [UIColor darkTextColor]), NSParagraphStyleAttributeName : style }; NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:@"" attributes:normalStyle]; NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:kWhiteSpaceCharacterSet]; NSMutableString *token = [NSMutableString string]; NSInteger length = text.length; EmbeddedLabelState state = kEmbeddedLabelStateNormal; for (NSInteger index = 0; index < length; index++) { unichar sign = [text characterAtIndex:index]; if ([charSet characterIsMember:sign] && state) { [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:token attributes:state == kEmbeddedLabelStateHashtag ? hashStyle : nameStyle]]; state = kEmbeddedLabelStateNormal; [token setString:[NSString stringWithCharacters:&sign length:1]]; } else if (sign == ''#'' || sign == ''@'') { [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:token attributes:normalStyle]]; state = sign == ''#'' ? kEmbeddedLabelStateHashtag : kEmbeddedLabelStateUsename; [token setString:[NSString stringWithCharacters:&sign length:1]]; } else { [token appendString:[NSString stringWithCharacters:&sign length:1]]; } } [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:token attributes:state ? (state == kEmbeddedLabelStateHashtag ? hashStyle : nameStyle) : normalStyle]]; return attributedText; } - (void)embeddedTextClicked:(UIGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateEnded) { CGPoint location = [recognizer locationInView:self]; NSUInteger characterIndex = [self.layoutManager characterIndexForPoint:location inTextContainer:self.textContainer fractionOfDistanceBetweenInsertionPoints:NULL]; if (characterIndex < self.textStorage.length) { NSRange range; NSDictionary *attributes = [self.textStorage attributesAtIndex:characterIndex effectiveRange:&range]; if ([attributes objectForKey:kEmbeddedLabelHashtagStyle]) { NSString *value = [self.attributedText.string substringWithRange:range]; [self.delegate embeddedLabel:self didGetTapOnHashText:[value stringByReplacingOccurrencesOfString:@"#" withString:@""]]; } else if ([attributes objectForKey:kEmbeddedLabelUsernameStyle]) { NSString *value = [self.attributedText.string substringWithRange:range]; [self.delegate embeddedLabel:self didGetTapOnUserText:[value stringByReplacingOccurrencesOfString:@"@" withString:@""]]; } else { [self.delegate embeddedLabelDidGetTap:self]; } } else { [self.delegate embeddedLabelDidGetTap:self]; } } } @end

Para una UILabel , me gustaría averiguar qué índice de caracteres se encuentra en un punto específico recibido de un evento táctil. Me gustaría resolver este problema para iOS 7 utilizando el Kit de texto.

Dado que UILabel no proporciona acceso a su NSLayoutManager , creé mi propio basado en la configuración de UILabel esta manera:

- (void)textTapped:(UITapGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateEnded) { CGPoint location = [recognizer locationInView:self]; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText]; NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; [textStorage addLayoutManager:layoutManager]; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:self.bounds.size]; [layoutManager addTextContainer:textContainer]; textContainer.maximumNumberOfLines = self.numberOfLines; textContainer.lineBreakMode = self.lineBreakMode; NSUInteger characterIndex = [layoutManager characterIndexForPoint:location inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:NULL]; if (characterIndex < textStorage.length) { NSRange range = NSMakeRange(characterIndex, 1); NSString *value = [self.text substringWithRange:range]; NSLog(@"%@, %zd, %zd", value, range.location, range.length); } } }

El código anterior está en una subclase de UILabel con un UITapGestureRecognizer configurado para llamar a textTapped: ( Gist ).

El índice de caracteres resultante tiene sentido (aumenta cuando se toca de izquierda a derecha), pero no es correcto (el último carácter se alcanza aproximadamente a la mitad del ancho de la etiqueta). Parece que tal vez el tamaño de fuente o el tamaño del contenedor de texto no está configurado correctamente, pero no puede encontrar el problema.

Realmente me gustaría mantener mi clase como una subclase de UILabel lugar de utilizar UITextView . ¿Alguien ha resuelto este problema para UILabel ?

Actualización: gasté un ticket de DTS en esta pregunta y el ingeniero de Apple recomendó anular el UILabel de drawTextInRect: con una implementación que usa mi propio administrador de diseño, similar a este fragmento de código:

- (void)drawTextInRect:(CGRect)rect { [yourLayoutManager drawGlyphsForGlyphRange:NSMakeRange(0, yourTextStorage.length) atPoint:CGPointMake(0, 0)]; }

Creo que sería mucho trabajo mantener mi propio administrador de diseño sincronizado con la configuración de la etiqueta, así que probablemente iré con UITextView pesar de mi preferencia por UILabel .

Actualización 2: Decidí usar UITextView después de todo. El propósito de todo esto fue detectar los toques en los enlaces incrustados en el texto. Intenté usar NSLinkAttributeName , pero esta configuración no NSLinkAttributeName la devolución de llamada de delegado al tocar un enlace rápidamente. En su lugar, debe presionar el enlace durante un período de tiempo determinado, muy molesto. Así que creé CCHLinkTextView que no tiene este problema.


He implementado lo mismo en swift 3. A continuación, se encuentra el código completo para encontrar el Índice de caracteres en el punto de contacto de UILabel, puede ayudar a otros que están trabajando en swift y están buscando la solución:

//here myLabel is the object of UILabel //added this from @warly''s answer //set font of attributedText let attributedText = NSMutableAttributedString(attributedString: myLabel!.attributedText!) attributedText.addAttributes([NSFontAttributeName: myLabel!.font], range: NSMakeRange(0, (myLabel!.attributedText?.string.characters.count)!)) // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: CGSize(width: (myLabel?.frame.width)!, height: (myLabel?.frame.height)!+100)) let textStorage = NSTextStorage(attributedString: attributedText) // Configure layoutManager and textStorage layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) // Configure textContainer textContainer.lineFragmentPadding = 0.0 textContainer.lineBreakMode = myLabel!.lineBreakMode textContainer.maximumNumberOfLines = myLabel!.numberOfLines let labelSize = myLabel!.bounds.size textContainer.size = labelSize // get the index of character where user tapped let indexOfCharacter = layoutManager.characterIndex(for: tapLocation, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)


Jugué con la solución de Alexey Ishkov. ¡Finalmente tengo una solución! Use este fragmento de código en su selector UITapGestureRecognizer:

UILabel *textLabel = (UILabel *)recognizer.view; CGPoint tapLocation = [recognizer locationInView:textLabel]; // init text storage NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:textLabel.attributedText]; NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; [textStorage addLayoutManager:layoutManager]; // init text container NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(textLabel.frame.size.width, textLabel.frame.size.height+100) ]; textContainer.lineFragmentPadding = 0; textContainer.maximumNumberOfLines = textLabel.numberOfLines; textContainer.lineBreakMode = textLabel.lineBreakMode; [layoutManager addTextContainer:textContainer]; NSUInteger characterIndex = [layoutManager characterIndexForPoint:tapLocation inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:NULL];

Espero que esto ayude a algunas personas por ahí!


Recibí el mismo error que usted, el índice aumentó de manera rápida, por lo que no fue preciso al final. La causa de este problema fue que self.attributedText no contenía información de fuente completa para toda la cadena.

Cuando UILabel renderiza, utiliza la fuente especificada en self.font y la aplica a la zona de todos los atributos. Este no es el caso cuando se asigna el TextText al TextStorage. Por lo tanto necesitas hacer esto tú mismo:

NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText]; [attributedText addAttributes:@{NSFontAttributeName: self.font} range:NSMakeRange(0, self.attributedText.string.length];

Swift 4

let attributedText = NSMutableAttributedString(attributedString: self.attributedText!) attributedText.addAttributes([.font: self.font], range: NSMakeRange(0, attributedText.string.count))

Espero que esto ayude :)


Swift 4, sintetizado a partir de muchas fuentes, incluyendo buenas respuestas aquí. Mi contribución es el manejo correcto de inserciones, alineación y etiquetas multilínea. (la mayoría de las implementaciones tratan un toque en espacios en blanco al final como un toque en el carácter final de la línea)

class TappableLabel: UILabel { var onCharacterTapped: ((_ label: UILabel, _ characterIndex: Int) -> Void)? func makeTappable() { let tapGesture = UITapGestureRecognizer() tapGesture.addTarget(self, action: #selector(labelTapped)) tapGesture.isEnabled = true self.addGestureRecognizer(tapGesture) self.isUserInteractionEnabled = true } @objc func labelTapped(gesture: UITapGestureRecognizer) { // only detect taps in attributed text guard let attributedText = attributedText, gesture.state == .ended else { return } // Configure NSTextContainer let textContainer = NSTextContainer(size: bounds.size) textContainer.lineFragmentPadding = 0.0 textContainer.lineBreakMode = lineBreakMode textContainer.maximumNumberOfLines = numberOfLines // Configure NSLayoutManager and add the text container let layoutManager = NSLayoutManager() layoutManager.addTextContainer(textContainer) // Configure NSTextStorage and apply the layout manager let textStorage = NSTextStorage(attributedString: attributedText) textStorage.addAttribute(NSAttributedStringKey.font, value: font, range: NSMakeRange(0, attributedText.length)) textStorage.addLayoutManager(layoutManager) // get the tapped character location let locationOfTouchInLabel = gesture.location(in: gesture.view) // account for text alignment and insets let textBoundingBox = layoutManager.usedRect(for: textContainer) var alignmentOffset: CGFloat! switch textAlignment { case .left, .natural, .justified: alignmentOffset = 0.0 case .center: alignmentOffset = 0.5 case .right: alignmentOffset = 1.0 } let xOffset = ((bounds.size.width - textBoundingBox.size.width) * alignmentOffset) - textBoundingBox.origin.x let yOffset = ((bounds.size.height - textBoundingBox.size.height) * alignmentOffset) - textBoundingBox.origin.y let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - xOffset, y: locationOfTouchInLabel.y - yOffset) // figure out which character was tapped let characterTapped = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) // figure out how many characters are in the string up to and including the line tapped let lineTapped = Int(ceil(locationOfTouchInLabel.y / font.lineHeight)) - 1 let rightMostPointInLineTapped = CGPoint(x: bounds.size.width, y: font.lineHeight * CGFloat(lineTapped)) let charsInLineTapped = layoutManager.characterIndex(for: rightMostPointInLineTapped, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) // ignore taps past the end of the current line if characterTapped < charsInLineTapped { onCharacterTapped?(self, characterTapped) } } }