我需要将UIWebView的CALayers镜像到较小的CALayer。较小的CALayer本质上是较大的UIWebView的一部分。我很难做到这一点。唯一接近的是CAReplicatorLayer,但考虑到原始版本和副本必须将CAReplicatorLayer作为父级,我不能在不同的屏幕上拆分原件和副本。
说明我正在尝试做什么:
用户需要能够与较小的CALayer进行交互,并且两者都需要同步。
我尝试过使用renderInContext和CADisplayLink。不幸的是,有一些滞后/口吃,因为它试图重新绘制每一帧,每秒60次。我需要一种方法来进行镜像而无需在每个帧上重新绘制,除非实际发生了某些变化。所以我需要一种方法来了解CALayer(或儿童CALayers)何时变脏。
我不能简单地拥有两个UIWebView,因为两个页面可能不同(时间关闭,背景不同等等)。我无法控制正在显示的网页。我也无法显示整个iPad屏幕,因为屏幕上还有其他元素不应显示在外部屏幕上。
较大的CALayer和较小的“pip”CALayer都需要在iOS 6中逐帧匹配。我不需要支持早期版本。
解决方案需要适用于商店。
答案 0 :(得分:5)
如评论中所述,如果主要需要知道何时更新图层(而不是如何),我会在“OLD ANSWER”行之后移动我的原始答案,并添加评论中讨论的内容:
首先(100%Apple Review Safe; - )
第二:表现友好且“理论上”审查安全......但不确定: - /
我试着解释一下我是如何达到这个代码的:
主要目标是了解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:
?然后只需相应地更新其他图层。