13

我想检查我的是否UILabel被触摸。但我需要的还不止这些。文字被触动了吗?现在,如果UILabel使用此方法触摸框架,我只会得到真/假:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    if (CGRectContainsPoint([self.currentLetter frame], [touch locationInView:self.view]))
    {
        NSLog(@"HIT!");
    }
}

有没有办法检查这个?一旦我触摸到字母之外的某个地方,UILabel我希望返回 false。

我想知道何时触摸了实际的黑色渲染“文本像素”。

谢谢!

4

8 回答 8

21

tl; dr:您可以点击测试文本的路径。要点在这里可用


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

  1. 子类 UILabel
  2. 使用Core Text获取文本的CGPath
  3. 覆盖pointInside:withEvent:以能够确定是否应在内部考虑某个点。
  4. 使用任何“正常”的触摸处理,例如点击手势识别器来了解何时进行了点击。

这种方法的最大优点是它精确地遵循字体,并且您可以修改路径以增加“可点击”区域,如下所示。黑色和橙色部分都是可点击的,但标签中只会绘制黑色部分。

水龙头区域

子类 UILabel

我创建了一个UILabel被调用的子类,TextHitTestingLabel并为文本路径添加了一个私有属性。

@interface TextHitTestingLabel (/*Private stuff*/)
@property (assign) CGPathRef textPath;
@end

由于 iOS 标签可以有 atext或 an 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 获取文本的路径

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

核心文本“魔法”

Core Text 适用于文本行、字形和字形运行。例如,如果我有文本:“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:

为了自定义标签认为在自身内部的点,我覆盖了 point inside 并让它检查该点是否在文本路径内。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 中的标签上添加了一个点击识别器,并将其连接到我的视图控制器中的一个方法。

- (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;

现在,下图中的橙色区域也可以点击了。这仍然感觉就像您在触摸文本,但对您的应用程序的用户来说不那么烦人。水龙头区域

如果你愿意,你可以更进一步,让它更容易点击文本,但在某些时候,你会感觉整个标签都是可点击的。

巨大的水龙头区域

于 2013-06-29T11:19:00.690 回答
7

据我了解,问题是检测何时在构成 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" );
    }
}
于 2013-06-28T19:49:42.127 回答
4

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

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

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

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

iOS——检测像素的颜色?

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

于 2013-06-23T11:15:33.520 回答
2

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

于 2013-06-23T12:07:21.400 回答
0

您可以使用 UIButton 而不是 label :

- (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 !");
}

我希望它有所帮助;)

于 2013-06-23T11:41:48.610 回答
0

Create the Label in viewDidLoad or through IB and add tapGesture using below code with selector then when you tap on label log will be printed(which is in singletap:)

- (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
}

If your found it please mark it positive happy coding

于 2013-07-24T20:21:00.633 回答
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...进行比较

于 2013-07-01T08:37:46.480 回答
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);
    }
}
于 2013-06-27T10:24:52.073 回答