Как проверить, был ли затронут текст UILabel?

Я хочу проверить, был ли затронут мой UILabel. Но мне нужно еще больше. Был ли текст затронут? Прямо сейчас я получаю true/false, если коснуться фрейма 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 возвращался.

Я хочу знать, когда были затронуты фактические черные текстовые пиксели.

Спасибо!

Ответ 1

Проблема, как я понимаю, заключается в обнаружении, когда нажатие (касание) происходит на одном из глифов, которые содержат текст в UILabel. Если прикосновение выходит за пределы любого из глифов, то оно не учитывается.

Вот мое решение. Он предполагает UILabel* ivar с именем _label и a 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 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

tl; dr: Вы можете нажать тест пути к тексту. Gist доступен здесь.


Подход, с которым я столкнулся, - проверить, находится ли точка перехода внутри пути текста или нет. Позвольте мне дать вам обзор шагов, прежде чем вдаваться в подробности.

  • Подкласс UILabel
  • Используйте Core Text для получения CGPath текста
  • Переопределить pointInside:withEvent:, чтобы определить, следует ли считать точку внутри или нет.
  • Используйте любую "обычную" сенсорную обработку, такую ​​как, например, распознаватель жестов, чтобы знать, когда был сделан удар.

Большим преимуществом такого подхода является то, что он точно следует за шрифтом и что вы можете изменить путь для роста области "hittable", как показано ниже. Как черная, так и оранжевая части являются сбрасываемыми, но только черные части будут нарисованы на этикетке.

tap area

Подкласс UILabel

Я создал подкласс 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();

Основной текст "magic"

Core Text работает с линиями текста, глифов и глифов. Например, если у меня есть текст: "Привет" с атрибутами вроде этого " 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 и подключил ее к методу в моем контроллере представления.

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

Это все, что нужно. Если вы прокрутили весь путь вниз и не хотите брать разные фрагменты кода и вставлять их вместе, я весь .m файл в Gist, который вы можете скачать и использовать.

Примечание. Большинство шрифтов очень и очень тонкие по сравнению с точностью touch (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;

Теперь оранжевая область на изображении ниже также будет отображаться. Это по-прежнему кажется, что вы касаетесь текста, но менее раздражаете пользователей вашего приложения. tap area

Если вы хотите, вы можете сделать это еще глубже, чтобы еще проще нанести удар по тексту, но в какой-то момент будет ощущение, что вся метка является tappable.

Huge tap area

Ответ 3

Вы можете использовать UIGestureRecognizer: http://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html

В частности, я думаю, вы хотели бы использовать UITapGestureRecognizer. Если вы хотите узнать, когда затрагивается текстовый фрейм, самым простым было бы сделать размер вашего фрейма подходящим для текста с помощью [yourLabel sizeToFit].

В любом случае, для этого я поеду использовать UIButton, это самый простой вариант.

Если вам нужно обнаружить только тогда, когда отображается фактический текст, а не весь кадр UITextField, это становится намного сложнее. Один из подходов заключается в обнаружении темноты пикселя, который пользователь прослушивает, но это связано с некоторым уродливым кодом. В любом случае, в зависимости от ожидаемого взаимодействия внутри вашего приложения в может работать. Проверьте этот вопрос:

iOS - определить цвет пикселя?

Я бы принял во внимание, что не весь отображаемый пиксель будет на 100% черным, поэтому я бы сыграл с порогом, чтобы добиться лучших результатов.

Ответ 4

Я думаю, он хочет знать, касается ли письмо внутри ярлыка, а не другие части метки. Поскольку вы готовы использовать прозрачный образ для достижения этого, я бы предположил, что, например, у вас есть буква "A" с прозрачным фоном, если цвет буквы монотонный, допустим, красный в этом случае, вы можете захватить CGImage UIImage, получить провайдера и отобразить его как растровое изображение и попробовать, будет ли цвет касания точки красным. Для других цветов вы можете просто пробовать этот цвет с помощью онлайн-редактора изображений и получать его значение RGB и проверять на это.

Ответ 5

Вы можете использовать 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 !");
}

Я надеюсь, что это помогло;)

Ответ 6

Предполагая, что экземпляр 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);
    }
}

Ответ 7

Прежде всего создайте и прикрепите распознаватель жестов жесты и разрешите взаимодействие с пользователем:

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 с прозрачным фоном, если это не то, что вы хотите, вы можете сравнить альфа, красный, зеленый, синий с self.label.backgroundColor...

Ответ 8

Создайте ярлык в viewDidLoad или через IB и добавьте tapGesture, используя приведенный ниже код с селектором, а затем, когда вы нажмете на журнал меток, будет напечатан (который находится в singleletap:)

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

Если вы нашли его, отметьте его положительным счастливое кодирование