简体   繁体   English

如何检查UILabel文字是否被触摸?

[英]How to check if UILabel text was touched?

I want to check if my UILabel was touched. 我想检查我的UILabel被触摸。 But i need even more than that. 但是我甚至需要更多。 Was the text touched? 文字是否被感动? Right now I only get true/false if the UILabel frame was touched using this: 现在,只有使用以下方式触摸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!");
    }
}

Is there any way to check this? 有什么办法可以检查吗? As soon as I touch somewhere outside the letter in the UILabel I want false to get returned. 一旦我触摸UILabel字母外的某个地方,我就希望返回false。

I want to know when the actual black rendered "text pixles" has been touched. 我想知道实际的黑色渲染“文本像素”何时被触摸。

Thanks! 谢谢!

tl;dr: You can hit test the path of the text. tl; dr:您可以点击测试文本的路径。 Gist is available here . 要点可以在这里找到


The approach I would go with is to check if the tap point is inside the path of the text or not. 我要使用的方法是检查点击点是否在文本路径内。 Let me give you a overview of the steps before going into detail. 在详细介绍之前,让我先概述一下这些步骤。

  1. Subclass UILabel 子类UILabel
  2. Use Core Text to get the CGPath of the text 使用核心文字获取文字的CGPath
  3. Override pointInside:withEvent: to be able to determine if a point should be considered inside or not. 覆盖pointInside:withEvent:以确定是否应考虑一个点在内部。
  4. Use any "normal" touch handling like for example a tap gesture recognizer to know when a hit was made. 使用任何“常规”触摸处理(例如轻击手势识别器)来知道何时进行了点击。

The big advantage of this approach is that it follows the font precisely and that you can modify the path to grow the "hittable" area like seen below. 这种方法的最大优点是它可以精确地跟随字体,并且您可以修改路径以增大“可命中”区域,如下所示。 Both the black and the orange parts are tappable but only the black parts will be drawn in the label. 黑色和橙色部分都是可轻敲的,但只有黑色部分会在标签中绘制。

龙头面积

Subclass UILabel 子类UILabel

I created a subclass of UILabel called TextHitTestingLabel and added a private property for the text path. 我创建了一个名为TextHitTestingLabelUILabel子类,并为文本路径添加了一个私有属性。

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

Since iOS labels can have either a text or an attributedText so I subclassed both these methods and made them call a method to update the text path. 由于iOS标签可以包含textattributedText因此我将这两种方法都子类化,并使其调用一种方法来更新文本路径。

- (void)setText:(NSString *)text {
    [super setText:text];

    [self textChanged];
}

- (void)setAttributedText:(NSAttributedString *)attributedText {
    [super setAttributedText:attributedText];

    [self textChanged];
}

Also, a label can be created from a NIB/Storyboard in which case the text will be set right away. 同样,可以从NIB / Storyboard创建标签,在这种情况下,将立即设置文本。 In that case I check for the initial text in awake from nib. 在这种情况下,我会从笔尖唤醒后检查初始文本。

- (void)awakeFromNib {
    [self textChanged];
}

Use Core Text to get the path of the text 使用核心文字获取文字的路径

Core Text is a low level framework that gives you full control over the text rendering. 核心文本是一个低级框架,可让您完全控制文本呈现。 You have to add CoreText.framework to your project and import it to your file 您必须将CoreText.framework添加到您的项目中并将其导入到文件中

#import <CoreText/CoreText.h>

The first thing I do inside textChanged is to get the text. 我在textChanged内部做的第一件事是获取文本。 Depending on if it's iOS 6 or earlier I also have to check the attributed text. 根据是否是iOS 6或更早版本,我还必须检查属性文本。 A label will only have one of these. 标签将只有其中之一。

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

Next I create a new mutable path for all the letter glyphs. 接下来,我为所有字母字形创建一个新的可变路径。

// Create a mutable path for the paths of all the letters.
CGMutablePathRef letters = CGPathCreateMutable();

Core Text "magic" 核心文字“魔术”

Core Text works with lines of text and glyphs and glyph runs. 核心文字适用于文本行和字形以及字形运行。 For example, if I have the text: "Hello" with attributes like this " Hel lo " (spaces added for clarity). 例如,如果我有文本:“ Hello”,其属性类似于“ Hel lo”(为清楚起见添加了空格)。 Then that is going to be one line of text with two glyph runs: one bold and one regular. 然后,这将是一行带有两个字形的文本:一个为粗体,一个为常规。 The first glyph run contains 3 glyphs and the second run contains 2 glyphs. 第一个字形运行包含3个字形,第二个字形运行包含2个字形。

I enumerate all the glyph runs and their glyphs and get the path with CTFontCreatePathForGlyph() . 我列举了所有字形运行及其字形,并使用CTFontCreatePathForGlyph()获取路径。 Each individual glyph path is then added to the mutable path. 然后将每个单独的字形路径添加到可变路径。

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

The core text coordinate system is upside down compared to the regular UIView coordinate system so I then flip the path to match what we see on screen. 与常规的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;

And now I have a complete CGPath for the text of the label. 现在,我有了用于标签文本的完整CGPath。

Override pointInside:withEvent: 覆盖pointInside:withEvent:

To customize what points the label consider as inside itself I override point inside and have it check if the point is inside the text path. 要自定义标签认为在内部的点,我将覆盖内部的点,并检查该点是否在文本路径内。 Other parts of UIKit is going to call this method for hit testing. 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);
}

Normal touch handling 正常的触摸处理

Now everything is setup to work with normal touch handling. 现在,所有设置都可以正常触摸处理。 I added a tap recognizer to my label in a NIB and connected it to a method in my view controller. 我在NIB的标签上添加了一个点击识别器,并将其连接到视图控制器中的方法。

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

That is all it takes. 这就是全部。 If you scrolled all the way down here and don't want to take the different pieces of code and paste them together I have the entire .m file in a Gist that you can download and use . 如果您一直向下滚动到这里,并且不想采用不同的代码片段并将它们粘贴在一起,则我将整个.m文件保存在Gist中,可以下载和使用

A note, most fonts are very, very thin compared to the precision of a touch (44px) and your users will most likely be very frustrated when the touches are considered "misses". 请注意,与触摸的精度(44像素)相比,大多数字体非常非常细,当将触摸视为“缺失”时,您的用户很可能会感到沮丧。 That being said: happy coding! 话虽这么说:编码愉快!


Update: 更新:

To be slightly nicer to the user you can stroke the text path that you use for hit testing. 为了对用户更好一点,您可以描画用于命中测试的文本路径。 This gives a larger area that hit tappable but still gives the feeling that you are tapping the text. 这将提供较大的区域,使其可以点击,但仍给人以您正在点击文本的感觉。

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;

Now the orange area in the image below is going to be tappable as well. 现在,下图中的橙色区域也将可以被点击。 This still feels like you are touching the text but is less annoying to the users of your app. 仍然感觉像您在触摸文本,但对应用程序的用户却没有那么令人讨厌。 龙头面积

If you want you can take this even further to make it even easier to hit the text but at some point it is going to feel like the entire label is tappable. 如果您愿意,可以更进一步,以使其更容易命中文本,但是在某些时候,感觉就像整个标签都是可轻敲的。

巨大的水龙头面积

The problem, as I understand it, is to detect when a tap (touch) happens on one of the glyphs that comprise the text in a UILabel. 据我了解,问题是要检测一下何时在包含UILabel中的文本的字形之一上发生了敲击(触摸)。 If a touch lands outside the path of any of the glyphs then it isn't counted. 如果触摸降落在任何字形的路径之外,则不会计算在内。

Here's my solution. 这是我的解决方案。 It assumes a UILabel* ivar named _label, and a UITapGestureRecognizer associated with the view containing the label. 它假定一个名为_label的UILabel* 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" );
    }
}

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

Specifically, I guess you'd like to use the UITapGestureRecognizer . 具体来说,我想您想使用UITapGestureRecognizer If you want to recognize when the text frame is touched, then the easiest would be to make the size of your frame to fit the text with [yourLabel sizeToFit] . 如果您想识别何时触摸了文本框 ,那么最简单的方法是使用[yourLabel sizeToFit]来调整文本框的大小以适合文本。

Anyway, to do so I will go to use a UIButton , it's the easiest option. 无论如何,为此,我将使用UIButton ,这是最简单的选项。

In case you need to detect only when the actual text and not the entire UITextField frame is tapped then it becomes much more difficult. 万一您只需要检测实际文本而不是整个UITextField框架时,它就会变得更加困难。 One approach is detecting the darkness of the pixel the user tapped, but this involves some ugly code . 一种方法是检测用户点击的像素的暗度,但这涉及一些难看的代码 Anyway, depending on the expected interaction within your application in can work out. 无论如何,取决于应用程序内部的预期交互作用。 Check this SO question: 检查这个SO问题:

iOS -- detect the color of a pixel? iOS-检测像素的颜色吗?

I would take in consideration that not all the rendered pixel will be 100% black, so I would play with a threshold to achieve better results. 我会考虑到并非所有渲染的像素都将是100%黑色的,因此我将使用阈值来达到更好的效果。

I think he wants to know whether the letter within the label is touched, not other parts of the label. 我认为他想知道标签内的字母是否被触摸,而不是标签的其他部分。 Since you are willing to use a transparent image to achieve this, I would suggest that, for example you have the letter "A" with transparent background, if the color of the letter if monotonous, let's say red in this case, you could grab a CGImage of the UIImage, get the provider and render it as bitmap and sample whether the color of the point being touched is red. 由于您愿意使用透明的图像来实现此目的,因此建议您,例如,使用透明背景的字母“ A”,如果字母的颜色单调,那么在这种情况下,我们说红色UIImage的CGImage,获取提供程序并将其渲染为位图,并采样所触摸点的颜色是否为红色。 For other colors, you could simply sample that color using an online image editor and grab its RGB value and check against that. 对于其他颜色,您可以简单地使用在线图像编辑器对该颜色进行采样,并获取其RGB值并进行检查。

You could use an UIButton instead of a label : 您可以使用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];
}

When the Button is pressed do something here : 当按下按钮时,请在此处执行以下操作:

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

I hope it helped ;) 希望对您有所帮助;)

Assuming UILabel instance which you want to track is userInteractionEnabled. 假设要跟踪的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);
    }
}

First of all create and attach tap gesture recognizer and allow user interactions: 首先创建并附加点击手势识别器,并允许用户交互:

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

Now implement -tapGesture: 现在实现-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();
}

This will works on UILabel with transparent background, if this is not what you want you can compare alpha, red, green, blue with self.label.backgroundColor ... 这将在具有透明背景的UILabel上工作,如果这不是您想要的,则可以将alpha,红色,绿色,蓝色与self.label.backgroundColor ...

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:) 在viewDidLoad中创建标签,或通过IB创建标签,并使用下面的代码和选择器添加tapGesture,然后在您点击标签日志时将被打印(在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 如果找到,请标记为积极的快乐编码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM