如何检查是否触摸了UILabel文本?

时间:2013-06-23 10:46:49

标签: iphone ios objective-c ipad uilabel

我想检查我的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中的字母之外的某处,我想要假回来。

我想知道实际黑色渲染的“文字像素”何时被触及。

谢谢!

8 个答案:

答案 0 :(得分:20)

tl; dr:您可以点击测试文字的路径。 Gist is available here


我要采用的方法是检查分接点是否在文本路径内。在详细介绍之前,让我先概述一下这些步骤。

  1. 子类UILabel
  2. 使用核心文本获取文本的CGPath
  3. 覆盖pointInside:withEvent:以确定是否应考虑内部点。
  4. 使用任何“普通”触摸处理,例如点按手势识别器,以了解点击的时间。
  5. 这种方法的最大优点是它可以精确地遵循字体,并且您可以修改生长“可命中”区域的路径,如下所示。黑色和橙色部分都是可点击的,但标签中只会绘制黑色部分。

    tap area

    子类UILabel

    我创建了一个名为UILabel的{​​{1}}子类,并为文本路径添加了一个私有属性。

    TextHitTestingLabel

    由于iOS标签可以有@interface TextHitTestingLabel (/*Private stuff*/) @property (assign) CGPathRef textPath; @end text,因此我将这两种方法子类化,并让它们调用一种方法来更新文本路径。

    attributedText

    此外,可以从NIB / Storyboard创建标签,在这种情况下,文本将立即设置。在那种情况下,我会从nib中检查清醒的初始文本。

    - (void)setText:(NSString *)text {
        [super setText:text];
    
        [self textChanged];
    }
    
    - (void)setAttributedText:(NSAttributedString *)attributedText {
        [super setAttributedText:attributedText];
    
        [self textChanged];
    }
    

    使用核心文本获取文本的路径

    Core Text是一个低级框架,可让您完全控制文本呈现。您必须将- (void)awakeFromNib { [self textChanged]; } 添加到项目中并将其导入到您的文件中

    CoreText.framework

    我在#import <CoreText/CoreText.h> 内做的第一件事就是获取文字。根据它是iOS 6还是更早,我还必须检查属性文本。标签只会有其中一种。

    textChanged

    接下来,我为所有字母字形创建一个新的可变路径。

    // 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}];
    }
    

    核心文字“魔术”

    核心文本适用于文本行和字形以及字形运行。例如,如果我有文本:“Hello”,其属性如“ Hel lo”(为清晰起见,添加了空格)。然后,这将是一行文本,其中包含两个字形运行:一个粗体,一个常规。第一个字形运行包含3个字形,第二个运行包含2个字形。

    我枚举所有字形运行及其字形,并使用// Create a mutable path for the paths of all the letters. CGMutablePathRef letters = CGPathCreateMutable(); 获取路径。然后将每个单独的字形路径添加到可变路径中。

    CTFontCreatePathForGlyph()

    与常规UIView坐标系相比,核心文本坐标系是颠倒的,所以我翻转路径以匹配我们在屏幕上看到的内容。

    // 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);
    

    现在我有一个完整的标签文本CGPath。

    覆盖// 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;

    要自定义标签所考虑的内部点,我会覆盖内部的点,并检查该点是否在文本路径中。 UIKit的其他部分将调用此方法进行命中测试。

    pointInside:withEvent:

    正常触摸处理

    现在一切都设置为正常触摸处理。我在NIB中为我的标签添加了一个点击识别器,并将其连接到我的视图控制器中的方法。

    // 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);
    }
    

    这就是全部。如果你在这里完全向下滚动并且不想采用不同的代码片段并将它们粘贴在一起我有the entire .m file in a Gist that you can download and use

    注意,与触摸精度(44px)相比,大多数字体非常非常薄,当触摸被视为“未命中”时,您的用户很可能非常沮丧。话虽如此:快乐的编码!


    更新

    为了让用户稍微好一点,您可以描边用于命中测试的文本路径。这提供了一个更大的区域,可以点击,但仍然感觉你正在点击文本。

    - (IBAction)labelWasTouched:(UITapGestureRecognizer *)sender {
        NSLog(@"LABEL!");
    }
    

    现在,下图中的橙色区域也是可以点亮的。这仍然感觉您正在触摸文本,但对您的应用程序的用户来说不那么烦人。 tap area

    如果你想要你可以更进一步,使它更容易点击文本,但在某些时候,它会觉得整个标签是可以点击的。

    Huge tap area

答案 1 :(得分:7)

根据我的理解,问题是检测在UILabel中组成文本的其中一个字形上发生敲击(触摸)的时间。如果触摸落在任何字形的路径之外,则不计算。

这是我的解决方案。它假定UILabel* ivar命名为_label,并且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" );
    }
}

答案 2 :(得分:4)

您可以使用UIGestureRecognizerhttp://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html

具体来说,我想你想使用UITapGestureRecognizer。如果您想要识别触摸文本框的时间,那么最简单的方法是使框架的大小适合[yourLabel sizeToFit]的文字。

无论如何,要这样做,我会使用UIButton,这是最简单的选择。

如果您只需要检测实际文本而不是整个UITextField框架,则会变得更加困难。一种方法是检测用户点击的像素的暗度,但这涉及一些丑陋的代码。无论如何,取决于您的应用程序中的预期交互可以解决。检查这个问题:

iOS -- detect the color of a pixel?

我会考虑到并非所有渲染像素都是100%黑色,所以我会使用阈值来获得更好的效果。

答案 3 :(得分:2)

我想他想知道标签内的字母是否被触及,而不是标签的其他部分。既然你愿意使用透明图像来实现这一点,我建议你,例如你有一个透明背景的字母“A”,如果字母的颜色单调,让我们说红色在这种情况下,你可以抓住获取UIImage的CGImage,获取提供者并将其渲染为位图,并对被触摸点的颜色是否为红色进行采样。对于其他颜色,您可以使用在线图像编辑器简单地对该颜色进行采样,并获取其RGB值并进行检查。

答案 4 :(得分:0)

您可以使用UIButton而不是标签:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIButton *tmpButton = [[UIButton alloc] initWithFrame:CGRectMake(50, 50, 100, 20)];
    [tmpButton setTitle:@"KABOYA" forState:UIControlStateNormal];
    [tmpButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [tmpButton addTarget:self
              action:@selector(buttonPressed:)
    forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:tmpButton];
}

按下按钮时,请执行以下操作:

-(void)buttonPressed:(UIButton *)sender {
    NSLog(@"Pressed !");
}

我希望它有所帮助;)

答案 5 :(得分:0)

假设您要跟踪的UILabel实例是userInteractionEnabled。

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    UIView *touchView = touch.view;
    if([touchView isKindOfClass:[UILabel class]]){
        NSLog(@"Touch event occured in Label %@",touchView);
    }
}

答案 6 :(得分:0)

首先创建并附加点击手势识别器并允许用户互动:

UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
[self.label addGestureRecognizer:tapRecognizer];
self.label.userInteractionEnabled = YES;

现在实施-tapGesture:

- (void)tapGesture:(UITapGestureRecognizer *)recognizer
{
    // Determine point touched
    CGPoint point = [recognizer locationInView:self.label];

    // Render UILabel in new context
    UIGraphicsBeginImageContext(self.label.bounds.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.label.layer renderInContext:context];

    // Getting RGBA of concrete pixel
    int bpr = CGBitmapContextGetBytesPerRow(context);
    unsigned char * data = CGBitmapContextGetData(context);
    if (data != NULL)
    {
        int offset = bpr*round(point.y) + 4*round(point.x);
        int red = data[offset+0];
        int green = data[offset+1];
        int blue = data[offset+2];
        int alpha =  data[offset+3];

        NSLog(@"%d %d %d %d", alpha, red, green, blue);

        if (alpha == 0)
        {
            // Here is tap out of text
        }
        else
        {
            // Here is tap right into text
        }
    }

    UIGraphicsEndImageContext();
}

这将适用于具有透明背景的UILabel,如果这不是您想要的,您可以将alpha,red,green,blue与self.label.backgroundColor进行比较......

答案 7 :(得分:0)

在viewDidLoad或IB中创建Label并使用下面的代码使用选择器添加tapGesture然后当您点击标签日志时将打印(这是单声道:)

- (void)viewDidLoad
{
[super viewDidLoad];    
UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(30, 0, 150, 35)];
label.userInteractionEnabled = YES;
label.backgroundColor = [UIColor greenColor];
label.text = @"label";
label.textAlignment = NSTextAlignmentCenter;

UITapGestureRecognizer * single = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singletap:)];
[label addGestureRecognizer:single];
single.numberOfTapsRequired = 1;
[self.view addSubview:label];


}
-(void) singletap:(id)sender
{
NSLog(@"single tap");
//do your stuff here
}

如果您发现它请标记为正面 快乐的编码