23

UIWebView の CALayer をより小さな CALayer にミラーリングする必要があります。小さい方の CALayer は、本質的に大きい方の UIWebView のピップです。私はこれを行うのに苦労しています。近いのは CAReplicatorLayer だけですが、オリジナルとコピーが CAReplicatorLayer を親として持つ必要があるため、オリジナルとコピーを別の画面に分割することはできません。

私がやろうとしていることの実例:

ここに画像の説明を入力

ユーザーは小さい方の CALayer とやり取りできる必要があり、両方が同期している必要があります。

renderInContext と CADisplayLink でこれをやってみました。残念ながら、1 秒間に 60 回、すべてのフレームを再描画しようとしているため、ラグやスタッターが発生します。何かが実際に変更されていない限り、各フレームで再描画せずにミラーリングを行う方法が必要です。そのため、CALayer (または子 CALayer) がいつダーティになるかを知る方法が必要です。

2 つのページが異なる可能性があるため (タイミングがずれている、背景が異なるなど)、単純に 2 つの UIWebView を持つことはできません。表示されている Web ページを制御できません。外部画面に表示されるべきではない他の要素が画面にあるため、iPad 画面全体を表示することもできません。

iOS 6 では、大きい方の CALayer と小さい方の "pip" CALayer の両方が、フレームごとにスムーズに一致する必要があります。以前のバージョンをサポートする必要はありません。

ソリューションは、アプリストアでまずまずのものである必要があります。

4

2 に答える 2

6

コメントに書かれているように、主な必要性がいつレイヤーを更新するか (方法ではなく) を知ることである場合、元の回答を「OLD ANSWER」行の後に移動し、コメントで説明した内容を追加します。

まず (100% Apple Review Safe ;-)

  • 元の UIView の定期的な「スクリーンショット」を取得し、結果の NSData (古いものと新しいもの) を比較できます --> データが異なる場合、レイヤーのコンテンツが変更されます。フル解像度のスクリーンショットを比較する必要はありませんが、小さい方のスクリーンショットで比較すると、パフォーマンスが向上します。

2番目:パフォーマンスに優しく、「理論的に」レビューは安全ですが、よくわかりません:-/

このコードにたどり着いた方法を説明しようとします。

主な目標は、TileLayer (UIWebView で使用される CALayer のプライベート サブクラス) がいつダーティになるかを理解することです。

問題は、直接アクセスできないことです。ただし、swizzle メソッドを使用して、すべての CALayer およびサブクラスで layerSetNeedsDisplay: メソッドの動作を変更できます。

元の動作を大幅に変更することは避け、メソッドが呼び出されたときに「通知」を追加するために必要なことだけを行う必要があります。

各 layerSetNeedsDisplay: 呼び出しを正常に検出したら、あとは関連する CALayer が「どれか」を理解するだけです。つまり、それが内部 UIWebView TileLayer である場合は、「isDirty」通知をトリガーします。

しかし、UIWebView のコンテンツを反復処理して TileLayer を見つけることはできません。たとえば、単に「isKindOfClass:[TileLayer class]」を使用すると確実に拒否されます (Apple は静的アナライザーを使用してプライベート API の使用をチェックします)。あなたは何ができますか?

たとえば...関連するレイヤーのサイズ(layerSetNeedsDisplay:を呼び出しているもの)とUIWebViewのサイズを比較するなどのトリッキーなものはありますか?;-)

さらに、UIWebView が子の TileLayer を変更して新しいものを使用する場合があるため、このチェックを何度も行う必要があります。

最後に: layerSetNeedsDisplay: は、単に UIWebView をスクロールするだけでは呼び出されないため (レイヤーが既に構築されている場合)、UIWebViewDelegate を使用してスクロール/ズームをインターセプトする必要があります。

メソッドのスウィズルが一部のアプリで拒否される理由であることがわかりますが、それは常に「オブジェクトの動作を変更した」という動機で行われています。この場合、何かの動作を変更するのではなく、メソッドが呼び出されたときにインターセプトするだけです。よくわからない場合は、試してみるか、Apple サポートに連絡して合法かどうかを確認してください。

古い答え

これがパフォーマンスに十分に適しているかどうかはわかりません。同じデバイスの両方のビューでのみ試してみましたが、かなりうまく機能します...Airplayを使用して試してみてください.

解決策は非常に簡単です。UIGraphicsGetImageFromCurrentImageContext を使用して UIWebView / MKMapView の「スクリーンショット」を撮ります。これを 1 秒間に 30/60 回行い、結果を UIImageView にコピーします (2 番目のディスプレイに表示され、好きな場所に移動できます)。

ビューが変更されたかどうかを検出し、ワイヤレス リンクでのトラフィックを回避するには、2 つの uiimage (古いフレームと新しいフレーム) をバイト単位で比較し、以前と異なる場合にのみ新しいフレームを設定します。(ええ、うまくいきます!;-)

今夜私ができなかった唯一のことは、この比較を高速化することです: 添付のサンプル コードを見ると、比較が実際に CPU を集中的に使用することがわかります (UIImagePNGRepresentation() を使用して NSData の UIImage を変換するため)。アプリ全体が非常に遅くなります。比較 (すべてのフレームをコピー) を使用しない場合、アプリは高速でスムーズです (少なくとも私の iPhone 5 では)。しかし、それを解決する可能性は非常に高いと思います...たとえば、4〜5フレームごとに比較を行うか、バックグラウンドでNSDataの作成を最適化します

サンプル プロジェクトを添付します: http://www.lombax.it/documents/ImageMirror.zip

プロジェクトでは、フレーム比較が無効になっています (コメントが付けられている場合)。今後の参考のために、ここにコードを添付します。

// here you start a timer, 50fps
// the timer is started on a background thread to avoid blocking it when you scroll the webview
- (IBAction)enableMirror:(id)sender {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); //0ul --> unsigned long
    dispatch_async(queue, ^{

        // 0.04f --> 25 fps
        NSTimer __unused *timer = [NSTimer scheduledTimerWithTimeInterval:0.02f target:self selector:@selector(copyImageIfNeeded) userInfo:nil repeats:YES];

        // need to start a run loop otherwise the thread stops
        CFRunLoopRun();
    });
}

// this method create an UIImage with the content of the given view
- (UIImage *) imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

// the method called by the timer
-(void)copyImageIfNeeded
{
    // this method is called from a background thread, so the code before the dispatch is executed in background
    UIImage *newImage = [self imageWithView:self.webView];

    // the copy is made only if the two images are really different (compared byte to byte)
    // this comparison method is cpu intensive

    // UNCOMMENT THE IF AND THE {} to enable the frame comparison

    //if (!([self image:self.mirrorView.image isEqualTo:newImage]))
    //{
        // this must be called on the main queue because it updates the user interface
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_async(queue, ^{
            self.mirrorView.image = newImage;
        });
    //}
}

// method to compare the two images - not performance friendly
// it can be optimized, because you can "store" the old image and avoid
// converting it more and more...until it's changed
// you can even try to generate the nsdata in background when the frame
// is created?
- (BOOL)image:(UIImage *)image1 isEqualTo:(UIImage *)image2
{
    NSData *data1 = UIImagePNGRepresentation(image1);
    NSData *data2 = UIImagePNGRepresentation(image2);

    return [data1 isEqual:data2];
}
于 2013-02-26T23:08:25.583 に答える
0

CADisplayLink を使用するというあなたのアイデアは良いと思います。主な問題は、すべてのフレームを更新しようとしていることです。このframeIntervalプロパティを使用して、フレーム レートを自動的に下げることができます。または、このプロパティを使用してtimestamp、最後の更新がいつ行われたかを知ることができます。

うまくいくかもしれない別のオプション: レイヤーが汚れているかどうかを知るために、オブジェクトをすべてのレイヤーのデリゲートにしてみませんdrawLayer:inContext:か? 次に、それに応じて他のレイヤーを更新します。

于 2013-02-26T18:10:13.280 に答える