Cla*_*aus 24 objective-c ios7 textkit
对于a UILabel
,我想知道从触摸事件接收的特定点处的哪个字符索引.我想使用Text Kit为iOS 7解决这个问题.
由于UILabel不提供对它的访问NSLayoutManager
,我根据这样UILabel
的配置创建了自己的:
- (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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码在UILabel
子类中,UITapGestureRecognizer
配置为调用textTapped:
(Gist).
生成的字符索引是有意义的(从左到右点击时增加),但不正确(最后一个字符大约是标签宽度的一半).看起来可能没有正确配置字体大小或文本容器大小,但找不到问题.
我真的很想让我的班级成为一个子类,UILabel
而不是使用UITextView
.有人解决了这个问题UILabel
吗?
更新:我在这个问题上花了一张DTS票,Apple工程师建议用一个使用我自己的布局管理器的实现覆盖UILabel
' drawTextInRect:
ss,类似于这段代码:
- (void)drawTextInRect:(CGRect)rect
{
[yourLayoutManager drawGlyphsForGlyphRange:NSMakeRange(0, yourTextStorage.length) atPoint:CGPointMake(0, 0)];
}
Run Code Online (Sandbox Code Playgroud)
我认为让我自己的布局管理器与标签的设置保持同步会有很多工作,所以UITextView
尽管我有偏好,我也可能会这样做UILabel
.
更新2:UITextView
毕竟我决定使用.所有这一切的目的是检测文本中嵌入的链接上的点击.我尝试使用NSLinkAttributeName
,但是这个设置在快速点击链接时没有触发委托回调.相反,你必须按下链接一段时间 - 非常烦人.所以我创建了没有这个问题的CCHLinkTextView.
Kai*_*rdt 41
我玩弄了Alexey Ishkov的解决方案.最后我得到了解决方案!在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];
Run Code Online (Sandbox Code Playgroud)
希望这会帮助那里的一些人!
war*_*rly 18
我得到了与你相同的错误,索引增加了快速的方式,所以它最终不准确.导致此问题的原因是self.attributedText
不包含整个字符串的完整字体信息.
当UILabel渲染时,它使用指定的字体self.font
并将其应用于整个attributedString.将attributionText分配给textStorage时不是这种情况.因此,您需要自己做:
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
[attributedText addAttributes:@{NSFontAttributeName: self.font} range:NSMakeRange(0, self.attributedText.string.length];
Run Code Online (Sandbox Code Playgroud)
斯威夫特4
let attributedText = NSMutableAttributedString(attributedString: self.attributedText!)
attributedText.addAttributes([.font: self.font], range: NSMakeRange(0, attributedText.string.count))
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助 :)
Swift 4,由许多来源合成,包括这里的好答案.我的贡献是正确处理插入,对齐和多行标签.(大多数实现将尾随空格点击作为点击行中的最后一个字符)
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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
小智 6
在这里,您是我对同一问题的实现.我需要在水龙头上标记#hashtags
并做出@usernames
反应.
我没有覆盖,drawTextInRect:(CGRect)rect
因为默认方法很完美.
此外,我发现了以下很好的实现https://github.com/Krelborn/KILabel.我也使用了这个示例中的一些想法.
@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
Run Code Online (Sandbox Code Playgroud)