Альфа-обнаружение в слое OK на симуляторе, а не на iPhone - PullRequest
2 голосов
/ 22 сентября 2011

Во-первых, посмотрите это очень удобное расширение для CALayer из других мест на SO. Это помогает определить, является ли точка в назначенном содержимом слоя CGImageRef прозрачной или нет.

n.b .: Нет никакой гарантии, что contents слоя будет представлен или отвечает, как если бы это был CGImageRef . (Это может иметь значение для более широкого использования расширения, на которое есть ссылка, как указано выше.) В моем случае, однако, я знаю, что слои, которые я тестирую, имеют contents, которым был присвоен CGImageRef . (Надеюсь, это не изменится из-под меня после назначения! Плюс я замечаю, что contents сохраняется.)

Хорошо, вернемся к проблеме. Вот как я использую расширение. Для начала я изменил селектор с containsPoint: на containsNonTransparentPoint: (мне нужно сохранить оригинальный метод.)

Теперь у меня есть подкласс UIImageView , который использует семь CALayer объектов. Они используются для анимации на основе непрозрачности (пульсирующие / светящиеся эффекты и состояния включения / выключения). Каждый из этих семи слоев имеет известный CGImageRef в его contents, который эффективно «покрывает» (воздушные кавычки) одну часть всего представления своим собственным диапазоном цветов. Остальная часть каждого изображения в соответствующем слое является прозрачной.

В подклассе я регистрируюсь на жесты одним нажатием. Когда кто-то прибывает, я прохожу свои слои, чтобы увидеть, какой из них был эффективно повернут (то есть, у какого есть непрозрачная точка, где я постучал, сначала найден выигрыш), а затем я могу делать все, что нужно.

Вот как я справляюсь с жестом:

- (IBAction)handleSingleTap:(UIGestureRecognizer *)sender {
    CGPoint tapPoint = [sender locationInView:sender.view];

    // Flip y so 0,0 is at lower left. (Required by layer method below.)
    tapPoint.y = sender.view.bounds.size.height - tapPoint.y;

    // Figure out which layer was effectively tapped. First match wins.
    for (CALayer *layer in myLayers) {
        if ([layer containsNonTransparentPoint:tapPoint]) {
            NSLog(@"%@ tapped at (%.0f, %.0f)", layer.name, tapPoint.x, tapPoint.y);

            // We got our layer! Do something useful with it.
            return;
        }
    }
}

Хорошие новости? Все это прекрасно работает на iPhone Simulator с iOS 4.3.2. (FWIW, я на Lion, работающем Xcode 4.1.)

Однако на моем iPhone 4 (с iOS 4.3.3) он даже близко не подходит! Ни один из моих сигналов не совпадает с любыми слоями, которые я ожидаю от них.

Даже если я попытаюсь предложить использовать CGContextSetBlendMode при рисовании в контексте пикселя 1x1, кубик не будет.

Я надеюсь, что это ошибка пилота, но мне еще предстоит выяснить, в чем заключается несоответствие. Метчики имеют рисунок, но не различимы.

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

Если кто-нибудь может пролить свет на то, что может быть не так, я был бы очень признателен!

ОБНОВЛЕНИЕ, 22 сентября 2011 г .: Первый момент ах-ха! Проблема не в симуляторе против iPhone. Это Retina против Non-Retina! Те же симптомы возникают в симуляторе при использовании версии Retina. Возможно решение сосредоточено вокруг масштабирования (CTM?) В некотором роде / форме / форме. Руководство по программированию Quartz 2D также рекомендует, чтобы «приложения для iOS использовали UIGraphicsBeginImageContextWithOptions ». Я чувствую, что я очень близок к решению здесь!

1 Ответ

1 голос
/ 22 сентября 2011

OK! Во-первых, проблема была не в симуляторе против iPhone. Скорее, это была Retina против Non-Retina. Те же симптомы возникают в симуляторе при использовании версии Retina. Сразу начинаешь думать, что решение связано с масштабированием.

Очень полезный пост на форуме Apple Dev Quartz 2D (по аналогии с линиями «помните о масштабировании») подтолкнул меня к решению. Теперь я признаю, что это решение НЕ симпатичное, но оно работает для случаев Retina и Non-Retina.

С этим вот пересмотренный код для вышеупомянутого CALayer extension :

//
// Checks image at a point (and at a particular scale factor) for transparency.
// Point must be with origin at lower-left.
//
BOOL ImagePointIsTransparent(CGImageRef image, CGFloat scale, CGPoint point) {
    unsigned char pixel[1] = {0};

    CGContextRef context = CGBitmapContextCreate(pixel, 1, 1, 8, 1,
        NULL, kCGImageAlphaOnly);
    CGContextSetBlendMode(context, kCGBlendModeCopy);
    CGContextDrawImage(context, CGRectMake(-point.x, -point.y,
        CGImageGetWidth(image)/scale, CGImageGetHeight(image)/scale), image);

    CGContextRelease(context);
    CGFloat alpha = pixel[0]/255.0;
    return (alpha < 0.01);
}

@implementation CALayer (Extensions)

- (BOOL)containsNonTransparentPoint:(CGPoint)point scale:(CGFloat)scale {
    if (CGRectContainsPoint(self.bounds, point)) {
        if (!ImagePointIsTransparent((CGImageRef)self.contents, scale, point))
            return YES;
    }
    return NO;
}

@end

Короче, нам нужно знать о шкале . Если мы разделим ширину и высоту изображения на эту шкалу, да-да, тест попадания теперь работает на устройствах Retina и Non-Retina!

Что мне не нравится в этом, так это беспорядок, который мне пришлось создать из этого плохого селектора, который теперь называется containsNonTransparentPoint: Scale: . Как уже упоминалось в вопросе, нет никакой гарантии того, что будет содержать содержимое слоя. В моем случае я стараюсь использовать это только на слоях с CGImageRef, но в более общем / повторно используемом случае это не сработает.

Все это заставляет меня задуматься, не является ли CALayer не лучшим местом для этого конкретного расширения, по крайней мере, в этом новом воплощении. Возможно, CGImage с добавленными смартами слоя будет чище. Представьте себе, что вы выполняете тест на попадание в CGImage, но возвращаете имя первого слоя с непрозрачным содержимым в этой точке. По-прежнему существует проблема незнания того, какие слои содержат CGImageRefs, поэтому может потребоваться некоторая подсказка. (Оставлено как упражнение для вас и читателя!)

ОБНОВЛЕНИЕ: После некоторого обсуждения с разработчиком в Apple, путаница со слоями таким образом фактически опрометчива. Вопреки тому, что я выучил ранее (неправильно?), Несколько UIImageView, инкапсулированных в UIView, являются подходящим способом. (Я всегда помню, как узнал, что вы хотите свести свои взгляды к минимуму. Возможно, в этом случае это не так уж важно.) Тем не менее, я пока оставлю здесь этот ответ, но не буду отмечать его как правильный. Как только я опробую и проверим другую технику, я поделюсь этим здесь!

...