我正在尝试构建自定义文本视图,我正在寻找正确方法的一些帮助.我是一个iOS n00b,所以主要是寻找有关如何最好地解决问题的想法.
我想构建一个自定义文本视图,具有以下行为:
>视图从小开始.如果它收到一个点击它会增长(动画)为父视图的大小作为模态窗口(左上图,然后右上图)
>在这个大状态中,如果单击一个单词,则单词以某种方式突出显示,并且委托方法称为传递包含被轻敲的字符串的字符串(左下图)
New Text View http://telliott.net/static/NewTextView.png
我怀疑复杂性将在于识别被点击的单词,所以让我们从那里开始.我可以想到几种尝试这种方法:
> UILabel子类.添加触摸手势识别器.识别触摸时,以某种方式获取触摸点的(x.y)坐标,查看视图显示的字符串,并根据位置确定必须按下哪个字.然而,我可以通过自动换行很快看到这变得非常复杂.我不确定如何获得触摸的(x,y)坐标(虽然我认为这很简单),或者如何获得每个角色等设备相关的文本宽度.我宁愿不去这条路线,除非有人可以说服我这不会太可怕!
> UIView子类,并通过为每个单词添加UILabel来伪造句子.测量每个UILabel的宽度并自己铺设.这似乎是一种更明智的方法,虽然我担心以这种方式布置文本也比我开始尝试时的想法更难.
有更明智的方法吗?你可以用其他方式检索UILabel中触及的单词吗?
如果我选择选项2,那么我认为动画来自小 – >大文本可能很复杂,因为单词将以有趣的方式包装.所以我想要另一个子视图 – 这次是UITextView – 将句子保持在小状态.将此动画设置为大,然后将其隐藏,同时以每个单词的一个视图显示我的UIView.
任何想法都赞赏.提前致谢 :)
解决方法
由于在CoreText中添加了NSLayoutManager,因此iOS 7更容易实现.如果您正在处理UITextView,则可以将布局管理器作为视图的属性进行访问.在我的情况下,我想坚持使用UILabel,所以你必须创建一个大小相同的布局管理器,即:
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:labelText]; NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; [textStorage addLayoutManager:layoutManager]; CGRect bounds = label.bounds; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:bounds.size]; [layoutManager addTextContainer:textContainer];
现在你只需要找到被点击的字符的索引,这很简单!
NSUInteger characterIndex = [layoutManager characterIndexForPoint:location inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:NULL];
这使得找到这个词本身变得微不足道:
if (characterIndex < textStorage.length) { [labelText.string enumerateSubstringsInRange:NSMakeRange(0,textStorage.length) options:NSStringEnumerationByWords usingBlock:^(NSString *substring,NSRange substringRange,NSRange enclosingRange,BOOL *stop) { if (NSLocationInRange(characterIndex,enclosingRange)) { // Do your thing with the word,at range 'enclosingRange' *stop = YES; } }]; }
原始答案,适用于iOS< 7 感谢@JP Hribovsek提供了一些有关此工作的提示,我设法为我的目的解决了这个问题.它感觉有点hacky,对于大型文本可能不会很好,但对于一次一段(这是我需要的),它很好. 我创建了一个简单的UILabel子类,允许我设置插入值:
#import "WWLabel.h" #define WWLabelDefaultInset 5 @implementation WWLabel @synthesize topInset,leftInset,bottomInset,rightInset; - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.topInset = WWLabelDefaultInset; self.bottomInset = WWLabelDefaultInset; self.rightInset = WWLabelDefaultInset; self.leftInset = WWLabelDefaultInset; } return self; } - (void)drawTextInRect:(CGRect)rect { UIEdgeInsets insets = {self.topInset,self.leftInset,self.bottomInset,self.rightInset}; return [super drawTextInRect:UIEdgeInsetsInsetRect(rect,insets)]; }
然后我创建了一个包含我的自定义标签的UIView子类,然后在标签上构建了标签中每个单词的文本大小,直到大小超过了点击位置的大小 – 这是被点击的单词.这不是完美的,但现在效果还不错.
然后我使用一个简单的NSAttributedString来突出显示文本:
#import "WWPhoneticTextView.h" #import "WWLabel.h" #define WWPhoneticTextViewInset 5 #define WWPhoneticTextViewDefaultColor [UIColor blackColor] #define WWPhoneticTextViewHighlightColor [UIColor yellowColor] #define UILabelMagicTopMargin 5 #define UILabelMagicLeftMargin -5 @implementation WWPhoneticTextView { WWLabel *label; NSMutableAttributedString *labelText; NSRange tappedRange; } // ... skipped init methods,very simple,just call through to configureView - (void)configureView { if(!label) { tappedRange.location = NSNotFound; tappedRange.length = 0; label = [[WWLabel alloc] initWithFrame:[self bounds]]; [label setLineBreakMode:NSLineBreakByWordWrapping]; [label setNumberOfLines:0]; [label setBackgroundColor:[UIColor clearColor]]; [label setTopInset:WWPhoneticTextViewInset]; [label setLeftInset:WWPhoneticTextViewInset]; [label setBottomInset:WWPhoneticTextViewInset]; [label setRightInset:WWPhoneticTextViewInset]; [self addSubview:label]; } // Setup tap handling UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; singleFingerTap.numberOfTapsrequired = 1; [self addGestureRecognizer:singleFingerTap]; } - (void)setText:(NSString *)text { labelText = [[NSMutableAttributedString alloc] initWithString:text]; [label setAttributedText:labelText]; } - (void)handleSingleTap:(UITapGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateEnded) { // Get the location of the tap,and normalise for the text view (no margins) CGPoint tapPoint = [sender locationInView:sender.view]; tapPoint.x = tapPoint.x - WWPhoneticTextViewInset - UILabelMagicLeftMargin; tapPoint.y = tapPoint.y - WWPhoneticTextViewInset - UILabelMagicTopMargin; // Iterate over each word,and check if the word contains the tap point in the correct line __block NSString *partialString = @""; __block NSString *lineString = @""; __block int currentLineHeight = label.font.pointSize; [label.text enumerateSubstringsInRange:NSMakeRange(0,[label.text length]) options:NSStringEnumerationByWords usingBlock:^(NSString* word,NSRange wordRange,BOOL* stop){ CGSize sizeForText = CGSizeMake(label.frame.size.width-2*WWPhoneticTextViewInset,label.frame.size.height-2*WWPhoneticTextViewInset); partialString = [NSString stringWithFormat:@"%@ %@",partialString,word]; // Find the size of the partial string,and stop if we've hit the word CGSize partialStringSize = [partialString sizeWithFont:label.font constrainedToSize:sizeForText lineBreakMode:label.lineBreakMode]; if (partialStringSize.height > currentLineHeight) { // Text wrapped to new line currentLineHeight = partialStringSize.height; lineString = @""; } lineString = [NSString stringWithFormat:@"%@ %@",lineString,word]; CGSize lineStringSize = [lineString sizeWithFont:label.font constrainedToSize:label.frame.size lineBreakMode:label.lineBreakMode]; lineStringSize.width = lineStringSize.width + WWPhoneticTextViewInset; if (tapPoint.x < lineStringSize.width && tapPoint.y > (partialStringSize.height-label.font.pointSize) && tapPoint.y < partialStringSize.height) { NSLog(@"Tapped word %@",word); if (tappedRange.location != NSNotFound) { [labelText addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:tappedRange]; } tappedRange = wordRange; [labelText addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:tappedRange]; [label setAttributedText:labelText]; *stop = YES; } }]; } }