Ios 镜像UIWebView的最佳方式

Ios 镜像UIWebView的最佳方式,ios,uiwebview,calayer,cadisplaylink,Ios,Uiwebview,Calayer,Cadisplaylink,我需要将UIWebView的CALayer镜像到较小的CALayer。较小的CALayer本质上是较大的UIWebView的一个pip。我做这件事有困难。唯一接近的是CAReplicatorLayer,但鉴于原件和副本必须以CAReplicatorLayer为父级,我无法在不同屏幕上拆分原件和副本 我试图做的一个例子: 用户需要能够与较小的CALayer交互,并且两者都需要同步 我尝试过使用RenderContext和CADisplayLink来实现这一点。不幸的是,有一些延迟/口吃,因为它试

我需要将UIWebView的CALayer镜像到较小的CALayer。较小的CALayer本质上是较大的UIWebView的一个pip。我做这件事有困难。唯一接近的是CAReplicatorLayer,但鉴于原件和副本必须以CAReplicatorLayer为父级,我无法在不同屏幕上拆分原件和副本

我试图做的一个例子:

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

我尝试过使用RenderContext和CADisplayLink来实现这一点。不幸的是,有一些延迟/口吃,因为它试图重新绘制每一帧,每秒60次。我需要一种方法来进行镜像,而无需在每个帧上重新绘制,除非实际发生了更改。因此,我需要一种方法来知道炉匠(或儿童炉匠)何时变脏

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

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


解决方案必须是AppStore可接受的。

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


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

如注释中所述,如果主要需要知道何时更新图层(而不是如何更新),我将原始答案移动到“旧答案”行后,并添加注释中讨论的内容:

第一(100%苹果审查安全;-)

  • 您可以定期拍摄原始UIView的“屏幕截图”,并比较生成的NSData(旧数据和新数据)->如果数据不同,则图层内容会发生更改。不需要比较全分辨率的屏幕截图,但您可以使用较小的屏幕截图,以获得更好的性能
第二:性能友好且“理论上”审查安全…但不确定:-/

  • 在该链接中,您可以找到一个示例项目,每当UIWebView层变脏时,该项目都会向您发出警报;-)
我试图解释我是如何达到这一目标的:

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

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

您必须确保避免在原始行为中发生根本性的更改,并且只在调用方法时添加“通知”

成功检测到每个layerSetNeedsDisplay:call后,剩下的唯一一件事就是了解“哪个是”所涉及的CALayer-->如果是内部UIWebView TileLayer,我们将触发“isDirty”通知

但我们无法遍历UIWebView内容并找到TileLayer,例如,简单地使用“isKindOfClass:[TileLayer class]”肯定会拒绝(苹果使用静态分析器检查私有API的使用)。你能做什么

一些棘手的事情,例如…比较涉及的层大小(调用layerSetNeedsDisplay:)和UIWebView大小?;-)

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

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

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

旧答案

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

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

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

今晚我唯一没能做到的就是快速进行比较:如果你看附件中的示例代码,你会发现这种比较实际上是cpu密集型的(因为它使用UIImagePNGRepresentation()在NSData中转换UIImage),并且使整个应用程序运行得非常慢。如果不使用比较(复制每一帧),应用程序会快速流畅(至少在我的iPhone 5上)。 但我认为解决这个问题的可能性很大……例如,每4-5帧进行一次比较,或者在后台优化NSData创建

我附上一个示例项目:

在项目中,禁用帧比较(if命令
// 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];
}