Joa*_*olt 13 iphone objective-c uilabel ipad ios
我想检查一下我UILabel是否感动了.但我还需要更多.文字被触动了吗?现在我只有在UILabel使用这个触摸框架时才会得到true/false :
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    if (CGRectContainsPoint([self.currentLetter frame], [touch locationInView:self.view]))
    {
        NSLog(@"HIT!");
    }
}
有没有办法检查这个?一旦我触摸到信件外面的某个地方,UILabel我就会想要归还.
我想知道实际黑色渲染的"文字像素"何时被触及.
谢谢!
Dav*_*ist 20
tl; dr:你可以点击测试文本的路径.这里有Gist.
我要采用的方法是检查分接点是否在文本路径内.在详细介绍之前,让我先介绍一下这些步骤.
pointInside:withEvent:以确定是否应考虑内部点.这种方法的最大优点是它可以精确地遵循字体,并且您可以修改生长"可命中"区域的路径,如下所示.黑色和橙色部分都是可点击的,但标签中只会绘制黑色部分.

我创建了一个UILabel被调用的子类,TextHitTestingLabel并为文本路径添加了一个私有属性.
@interface TextHitTestingLabel (/*Private stuff*/)
@property (assign) CGPathRef textPath;
@end
由于iOS标签可以有一个text或attributedText两个,所以我将这两个方法子类化,并使它们调用一个方法来更新文本路径.
- (void)setText:(NSString *)text {
    [super setText:text];
    [self textChanged];
}
- (void)setAttributedText:(NSAttributedString *)attributedText {
    [super setAttributedText:attributedText];
    [self textChanged];
}
此外,可以从NIB/Storyboard创建标签,在这种情况下,文本将立即设置.在那种情况下,我从nib中检查唤醒的初始文本.
- (void)awakeFromNib {
    [self textChanged];
}
Core Text是一个低级框架,可让您完全控制文本呈现.您必须添加CoreText.framework到项目并将其导入到您的文件中
#import <CoreText/CoreText.h>
我在里面做的第一件事textChanged就是获取文本.根据它是iOS 6还是更早,我还必须检查属性文本.标签只有其中一个.
// Get the text
NSAttributedString *attributedString = nil;
if ([self respondsToSelector:@selector(attributedText)]) { // Available in iOS 6
    attributedString = self.attributedText; 
}
if (!attributedString) { // Either earlier than iOS6 or the `text` property was set instead of `attributedText`
    attributedString = [[NSAttributedString alloc] initWithString:self.text
                                                       attributes:@{NSFontAttributeName: self.font}];
}
接下来,我为所有字母字形创建一个新的可变路径.
// Create a mutable path for the paths of all the letters.
CGMutablePathRef letters = CGPathCreateMutable();
核心文本适用于文本行和字形以及字形运行.例如,如果我有文本:"Hello",其属性类似于" Hel lo"(为了清晰起见,添加了空格).然后,这将是一行文本,其中包含两个字形运行:一个粗体,一个常规.第一个字形运行包含3个字形,第二个字符包含2个字形.
我枚举所有字形运行及其字形并获取路径CTFontCreatePathForGlyph().然后将每个单独的字形路径添加到可变路径中.
// Create a line from the attributed string and get glyph runs from that line
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attributedString);
CFArrayRef runArray = CTLineGetGlyphRuns(line);
// A line with more then one font, style, size etc will have multiple fonts.
// "Hello" formatted as " *Hel* lo " (spaces added for clarity) is two glyph
// runs: one italics and one regular. The first run contains 3 glyphs and the
// second run contains 2 glyphs.
// Note that " He *ll* o " is 3 runs even though "He" and "o" have the same font.
for (CFIndex runIndex = 0; runIndex < CFArrayGetCount(runArray); runIndex++)
{
    // Get the font for this glyph run.
    CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex);
    CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
    // This glyph run contains one or more glyphs (letters etc.)
    for (CFIndex runGlyphIndex = 0; runGlyphIndex < CTRunGetGlyphCount(run); runGlyphIndex++)
    {
        // Read the glyph itself and it position from the glyph run.
        CFRange glyphRange = CFRangeMake(runGlyphIndex, 1);
        CGGlyph glyph;
        CGPoint position;
        CTRunGetGlyphs(run, glyphRange, &glyph);
        CTRunGetPositions(run, glyphRange, &position);
        // Create a CGPath for the outline of the glyph
        CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyph, NULL);
        // Translate it to its position.
        CGAffineTransform t = CGAffineTransformMakeTranslation(position.x, position.y);
        // Add the glyph to the 
        CGPathAddPath(letters, &t, letter);
        CGPathRelease(letter);
    }
}
CFRelease(line);
与常规UIView坐标系相比,核心文本坐标系是颠倒的,因此我翻转路径以匹配我们在屏幕上看到的内容.
// Transform the path to not be upside down
CGAffineTransform t = CGAffineTransformMakeScale(1, -1); // flip 1
CGSize pathSize = CGPathGetBoundingBox(letters).size; 
t = CGAffineTransformTranslate(t, 0, -pathSize.height); // move down
// Create the final path by applying the transform
CGPathRef finalPath = CGPathCreateMutableCopyByTransformingPath(letters, &t);
// Clean up all the unused path
CGPathRelease(letters);
self.textPath = finalPath;
现在我有一个完整的标签文本CGPath.
pointInside:withEvent:要自定义标签认为内部本身的哪些点,我会覆盖内部的点,并检查该点是否在文本路径内.UIKit的其他部分将调用此方法进行命中测试.
// Override -pointInside:withEvent to determine that ourselves.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // Check if the points is inside the text path.
    return CGPathContainsPoint(self.textPath, NULL, point, NO);
}
现在一切都设置为正常触摸处理.我在NIB中为我的标签添加了一个tap识别器,并将其连接到我的视图控制器中的方法.
- (IBAction)labelWasTouched:(UITapGestureRecognizer *)sender {
    NSLog(@"LABEL!");
}
这就是全部.如果你在这里完全向下滚动并且不想采用不同的代码片段并将它们粘贴在一起,我将整个.m文件放在Gist中,您可以下载和使用它.
请注意,与触摸精度(44px)相比,大多数字体都非常非常薄,当触摸被视为"未命中"时,您的用户很可能非常沮丧.话虽如此:快乐的编码!
为了让用户更好一点,您可以描边用于命中测试的文本路径.这提供了一个更大的区域,可以点击,但仍然感觉你正在点击文本.
CGPathRef endPath = CGPathCreateMutableCopyByTransformingPath(letters, &t);
CGMutablePathRef finalPath = CGPathCreateMutableCopy(endPath);
CGPathRef strokedPath = CGPathCreateCopyByStrokingPath(endPath, NULL, 7, kCGLineCapRound, kCGLineJoinRound, 0);
CGPathAddPath(finalPath, NULL, strokedPath);
// Clean up all the unused paths
CGPathRelease(strokedPath);
CGPathRelease(letters);
CGPathRelease(endPath);
self.textPath = finalPath;
现在,下图中的橙色区域也是可以点亮的.这仍然感觉您正在触摸文本,但对您的应用程序的用户来说不那么烦人.
如果你想要你可以更进一步,使它更容易点击文本,但在某些时候,它会觉得整个标签是可点击的.

根据我的理解,问题是检测在UILabel中构成文本的其中一个字形上发生敲击(触摸)的时间.如果触摸落在任何字形的路径之外,则不计算.
这是我的解决方案.它假设一个UILabel*名为_label 的ivar,并UITapGestureRecognizer与包含该标签的视图相关联.
- (IBAction) onTouch: (UITapGestureRecognizer*) tgr
{
    CGPoint p = [tgr locationInView: _label];
    // in case the background of the label isn't transparent...
    UIColor* labelBackgroundColor = _label.backgroundColor;
    _label.backgroundColor = [UIColor clearColor];
    // get a UIImage of the label
    UIGraphicsBeginImageContext( _label.bounds.size );
    CGContextRef c = UIGraphicsGetCurrentContext();
    [_label.layer renderInContext: c];
    UIImage* i = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    // restore the label's background...
    _label.backgroundColor = labelBackgroundColor;
    // draw the pixel we're interested in into a 1x1 bitmap
    unsigned char pixel = 0x00;
    c = CGBitmapContextCreate(&pixel,
                              1, 1, 8, 1, NULL,
                              kCGImageAlphaOnly);
    UIGraphicsPushContext(c);
    [i drawAtPoint: CGPointMake(-p.x, -p.y)];
    UIGraphicsPopContext();
    CGContextRelease(c);
    if ( pixel != 0 )
    {
        NSLog( @"touched text" );
    }
}
| 归档时间: | 
 | 
| 查看次数: | 5154 次 | 
| 最近记录: |