镜像UIWebView的最佳方式

时间:2013-02-18 02:22:42

标签: ios uiwebview calayer cadisplaylink

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

说明我正在尝试做什么:

enter image description here

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

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

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

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

解决方案需要适用于商店。

2 个答案:

答案 0 :(得分:5)

如评论中所述,如果主要需要知道何时更新图层(而不是如何),我会在“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类]”将肯定给你一个拒绝(苹果使用静态分析来检查使用私有API的) 。你能做什么?

像......那样棘手......比较所涉及的图层大小(调用layerSetNeedsDisplay :)的大小与UIWebView大小相比较? ; - )

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

最后一件事:layerSetNeedsDisplay:不总是调用时你只需滚动的UIWebView(如果该层已建),所以你必须使用UIWebViewDelegate拦截滚动/缩放

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

老答案

我不确定这是否足够友好,我只在同一台设备上使用两种视图进行了尝试,并且效果非常好......你应该尝试使用Airplay。

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

要检测视图是否发生变化并避免在无线链路上进行流量,您可以逐字节比较两个uiimages(旧帧和新帧),如果它与前一个不同,则设置新的。 (是的,它有效!; - )

我没今晚的唯一的事情就是让这个比较快:如果你看一下连接的示例代码,你会看到比较确实是CPU密集型(因为它使用UIImagePNGRepresentation()来转换的UIImage在NSData中)并使整个应用程序变得如此缓慢。如果你不使用比较(复制每一帧)应用程序是快速和流畅的(至少在我的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];
}

答案 1 :(得分:0)

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

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