镜像UIWebView的最佳方式

Luk*_*uke 23 uiwebview calayer ios cadisplaylink

我需要将UIWebView的CALayers镜像到较小的CALayer.较小的CALayer本质上是较大的UIWebView的一部分.我很难做到这一点.唯一接近的是CAReplicatorLayer,但鉴于原始版本和副本必须将CAReplicatorLayer作为父级,我无法在不同的屏幕上拆分原件和副本.

我正在尝试做的一个例子:

在此输入图像描述

用户需要能够与较小的CALayer进行交互,并且两者都需要同步.

我尝试过使用renderInContext和CADisplayLink.不幸的是,有一些滞后/口吃,因为它试图重新绘制每一帧,每秒60次.我需要一种方法来进行镜像而无需在每个帧上重新绘制,除非实际发生了某些变化.所以我需要一种方法来了解CALayer(或儿童CALayers)何时变脏.

我不能简单地拥有两个UIWebView,因为两个页面可能不同(时间关闭,背景不同等等).我无法控制正在显示的网页.我也无法显示整个iPad屏幕,因为屏幕上还有其他元素不应显示在外部屏幕上.

较大的CALayer和较小的"pip"CALayer都需要在iOS 6中逐帧匹配.我不需要支持早期版本.

解决方案需要应用程序存储可通过.

Lom*_*baX 6

如在评论中所写,如果主要需要知道何时更新图层(而不是如何),我会在"OLD ANSWER"行之后移动我的原始答案,并添加评论中讨论的内容:

第一(100%Apple Review Safe ;-)

  • 您可以定期拍摄原始UIView的"屏幕截图"并比较生成的NSData(旧的和新的) - >如果数据不同,则图层内容会发生变化.无需比较FULL RESOLUTION屏幕截图,但您可以使用较小的屏幕截图进行比较,以获得更好的性能

第二:表现友好,"理论上"审查安全......但不确定: - /

我试着解释一下我是如何得到这个代码的:

主要目标是了解TileLayer(UIWebView使用的CALayer的私有子类)何时变脏.

问题是您无法直接访问它.但是,您可以使用方法swizzle来更改每个CALayer和子类中的layerSetNeedsDisplay:方法的行为.

您必须确保避免对原始行为进行彻底更改,并且只在调用方法时添加"通知".

当您成功检测到每个layerSetNeedsDisplay:call时,唯一剩下的就是了解"哪个是"所涉及的CALayer - >如果它是内部UIWebView TileLayer,我们会触发"isDirty"通知.

但我们无法遍历UIWebView内容并找到TileLayer,例如只需使用"isKindOfClass:[TileLayer类]"肯定会拒绝(Apple使用静态分析器来检查私有API的使用).你能做什么?

有点像......例如...比较所涉及的图层大小(调用layerSetNeedsDisplay :)的大小与UIWebView大小相比较?;-)

此外,有时UIWebView会更改子TileLayer并使用新的TileLayer,因此您必须多次检查.

最后一点:layerSetNeedsDisplay:当您只是滚动UIWebView时(如果已经构建了图层),并不总是调用它,因此您必须使用UIWebViewDelegate来拦截滚动/缩放.

你会发现这种方法在某些应用程序中会被拒绝,但它总是以"你改变了对象的行为"为动机.在这种情况下,您不会更改某些内容的行为,而只是在调用方法时进行拦截.如果您不确定,我认为您可以尝试或联系Apple支持以检查它是否合法.

老答复

我不确定这对性能是否足够友好,我只在同一台设备上同时使用它进行了尝试并且效果非常好......你应该尝试使用Airplay.

解决方案非常简单:使用UIGraphicsGetImageFromCurrentImageContext获取UIWebView/MKMapView的"屏幕截图".您每秒执行30/60次,并将结果复制到UIImageView中(在第二个显示屏上可见,您可以将其移动到任何您想要的位置).

要检测视图是否已更改并避免在无线链路上进行通信,您可以逐字节比较两个uiimages(旧帧和新帧),并且只有在与前一个不同时才设置新的.(是的,它有效!;-)

我今晚唯一没有管理的就是快速进行这种比较:如果你看一下附带的示例代码,你会发现比较实际上是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];
}
Run Code Online (Sandbox Code Playgroud)


Rik*_*les 0

我认为你使用 CADisplayLink 的想法很好。主要问题是您试图刷新每一帧。您可以使用该frameInterval属性自动降低帧速率。或者,您可以使用该timestamp属性来了解上次更新发生的时间。

另一个可能有效的选项:要知道图层是否脏,为什么不让一个对象作为所有图层的委托,每当drawLayer:inContext:每个图层需要绘制时都会触发该对象?然后相应地更新其他层。