2

まず、SO の他の場所からCALayerへのこの非常に便利な拡張機能を確認してください。レイヤーのコンテンツに割り当てられたCGImageRefのポイントが透明か透明でないかを判断するのに役立ちます。

注意: レイヤがCGImageRefcontentsであるかのように表現可能または応答するという保証はありません。(これは、上記で参照されている拡張機能の広範な使用に影響を与える可能性があります。) ただし、私の場合、テストしているレイヤーにはCGImageRefが割り当てられていることがわかっています。(うまくいけば、これは配属後も私の下から変わることはありません! さらに、それが保持されていることに気付きました。)contentscontents

さて、当面の問題に戻りましょう。拡張機能の使用方法は次のとおりです。まず、セレクターを からcontainsPoint:に変更しましたcontainsNonTransparentPoint:(元のメソッドを維持する必要があります)。

これで、 7 つのCALayerオブジェクトを使用するUIImageViewサブクラスができました。これらは、不透明度ベースのアニメーション (パルス/グロー効果およびオン/オフ状態) に使用されます。これらの 7 つのレイヤーのそれぞれに既知のCGImageRefがあり、ビュー全体の一部を独自の色帯で効果的に「カバー」(エア クォート) します。それぞれのレイヤー内の各画像の残りの部分は透明です。contents

サブクラスでは、シングル タップ ジェスチャを登録します。1 つが到着すると、レイヤーを調べて、効果的にタップされたレイヤー (つまり、タップした場所が不透明なポイントがあり、最初に見つかったレイヤーが勝つ) を確認してから、必要なことを何でも実行できます。

ジェスチャを処理する方法は次のとおりです。

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

良いニュース?これらはすべて、iOS 4.3.2 の iPhone シミュレーターで美しく動作します。(FWIW、私は Xcode 4.1 を実行している Lion を使用しています。)

しかし、私の iPhone 4 (iOS 4.3.3 を搭載したもの) では、それにさえ近づきません! 私のタップはどれも、私が期待するレイヤーのいずれとも一致しないようです。

1x1 ピクセル コンテキストに描画するときにCGContextSetBlendModeを使用するという提案を試みても、さいころはありません。

パイロットのエラーだといいのですが、何が違うのかはまだわかりません。タップにはパターンがありますが、識別できるものではありません。

おそらく、データ境界の問題があります。おそらく、y座標を画像の左下に反転する以外のことをしなければなりません。まだわかりません。

誰かが間違っている可能性があることに光を当てることができれば、私は最も感謝しています!

更新、2011 年 9 月 22 日:最初のあははの瞬間を獲得しました! 問題は Simulator-vs-iPhone ではありません。網膜対非網膜です!Retina 版を使用している場合、シミュレーターで同じ症状が発生します。おそらく、ソリューションは何らかの方法/形状/形式でのスケーリング (CTM?) を中心にしています。Quartz 2D Programming Guide は、「iOS アプリケーションはUIGraphicsBeginImageContextWithOptionsを使用する必要がある」ともアドバイスしています。私はここで解決策に非常に近いと感じています!

4

1 に答える 1

1

わかった!まず、問題は Simulator-vs-iPhone ではありませんでした。むしろ、Retina 対 Non-Retina でした。Retina 版を使用している場合、シミュレーターで同じ症状が発生します。すぐに、解決策はスケーリングに関係していると考え始めます。

Apple Dev Quartz 2D フォーラムの非常に役立つ投稿 (同様の「スケーリングに注意してください」行に沿って) は、私を解決策に導きました。さて、私が最初に認めたのは、このソリューションはきれいではありませんが、Retina および非 Retina のケースでは機能します。

これで、前述のCALayer 拡張機能の修正されたコードが次のようになります。

//
// 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 デバイスと非 Retina デバイスで機能するようになりました。

これについて私が気に入らないのは、現在containsNonTransparentPoint:Scale:と呼ばれている貧弱なセレクターを混乱させることです。質問で述べたように、レイヤーのコンテンツに何が含まれるかは保証されません。私の場合、CGImageRef を含むレイヤーでのみこれを使用するように注意していますが、より一般的/再利用可能なケースではうまくいきません。

結局のところ、CALayer がこの特定の拡張機能にとって最適な場所ではないのではないかと思います。少なくともこの新しい化身では。おそらく、いくつかのレイヤースマートが投入されたCGImageは、よりクリーンになるでしょう。CGImage でヒット テストを実行し、その時点で不透明なコンテンツを含む最初のレイヤーの名前を返すことを想像してください。どのレイヤーに CGImageRefs があるかわからないという問題がまだ残っているため、いくつかのヒントが必要になる場合があります。(本当にあなたと読者のための演習として残してください!)

更新: Apple の開発者と話し合った結果、この方法でレイヤーをいじることは、実際にはお勧めできません。以前に学んだこととは反対に (間違って?)、UIView 内にカプセル化された複数の UIImageView がここに進む方法です。(自分の見解を最小限に抑えたいと思っていることをいつも覚えています。おそらく、この場合はそれほど大したことではありません。)それにもかかわらず、私はこの回答をここに残しておきますが、正しいとはマークしません. 他の手法を試して検証したら、ここで共有します。

于 2011-09-22T17:20:26.960 に答える