我需要從我的應用程序保存截圖,所以我已經設置了這樣的代碼,它的工作原理:如何呈現的CALayer背景
- (void)renderScreen {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
CGSize outputSize = keyWindow.bounds.size;
UIGraphicsBeginImageContext(outputSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CALayer *layer = [keyWindow layer];
[layer renderInContext:context];
CGContextRestoreGState(context);
UIImage *screenImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// now save the screen image, etc...
}
然而,當屏幕圖像變得複雜(大量的視圖),在iPad 3上,renderInContext可能需要0.8秒的時間,並且在此期間用戶界面鎖定,這會干擾其他一些功能。讓我感動的渲染到後臺線程,就像這樣:
- (void)renderScreen {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
CALayer *layer = [keyWindow layer];
[self performSelectorInBackground:@selector(renderLayer:) withObject:layer];
}
- (void)renderLayer:(CALayer *)layer {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
CGSize outputSize = keyWindow.bounds.size;
UIGraphicsBeginImageContext(outputSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
[layer renderInContext:context];
CGContextRestoreGState(context);
UIImage *screenImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// now save the screen image, etc...
}
這使得界面順利再次運行,但偶爾會導致對renderInContext符合EXC_BAD_ACCESS崩潰。我試着首先檢查layer!= nil和[layer respondsToSelector:@selector(renderInContext :)],這樣我就可以避免崩潰,但是兩個條件總是返回true。
然後我讀this SO comment,指出在後臺操作運行前一層可能發生變異,並建議將層的副本發送到後臺操作。 This SO answer和this one讓我開始,我結束了與該類別添加複製方法的CALayer:
#import "QuartzCore/CALayer.h"
@interface CALayer (CALayerCopyable)
- (id)copy;
@end
@implementation CALayer (CALayerCopyable)
- (id)copy {
CALayer *newLayer = [CALayer layer];
newLayer.actions = [self.actions copy];
newLayer.anchorPoint = self.anchorPoint;
newLayer.anchorPointZ = self.anchorPointZ;
newLayer.backgroundColor = self.backgroundColor;
//newLayer.backgroundFilters = [self.backgroundFilters copy]; // iOS 5+
newLayer.borderColor = self.borderColor;
newLayer.borderWidth = self.borderWidth;
newLayer.bounds = self.bounds;
//newLayer.compositingFilter = self.compositingFilter; // iOS 5+
newLayer.contents = [self.contents copy];
newLayer.contentsCenter = self.contentsCenter;
newLayer.contentsGravity = [self.contentsGravity copy];
newLayer.contentsRect = self.contentsRect;
//newLayer.contentsScale = self.contentsScale; // iOS 4+
newLayer.cornerRadius = self.cornerRadius;
newLayer.delegate = self.delegate;
newLayer.doubleSided = self.doubleSided;
newLayer.edgeAntialiasingMask = self.edgeAntialiasingMask;
//newLayer.filters = [self.filters copy]; // iOS 5+
newLayer.frame = self.frame;
newLayer.geometryFlipped = self.geometryFlipped;
newLayer.hidden = self.hidden;
newLayer.magnificationFilter = [self.magnificationFilter copy];
newLayer.mask = [self.mask copy]; // property is another CALayer
newLayer.masksToBounds = self.masksToBounds;
newLayer.minificationFilter = [self.minificationFilter copy];
newLayer.minificationFilterBias = self.minificationFilterBias;
newLayer.name = [self.name copy];
newLayer.needsDisplayOnBoundsChange = self.needsDisplayOnBoundsChange;
newLayer.opacity = self.opacity;
newLayer.opaque = self.opaque;
newLayer.position = self.position;
newLayer.rasterizationScale = self.rasterizationScale;
newLayer.shadowColor = self.shadowColor;
newLayer.shadowOffset = self.shadowOffset;
newLayer.shadowOpacity = self.shadowOpacity;
newLayer.shadowPath = self.shadowPath;
newLayer.shadowRadius = self.shadowRadius;
newLayer.shouldRasterize = self.shouldRasterize;
newLayer.style = [self.style copy];
//newLayer.sublayers = [self.sublayers copy]; // this line makes the screen go blank
newLayer.sublayerTransform = self.sublayerTransform;
//newLayer.superlayer = self.superlayer; // read-only
newLayer.transform = self.transform;
//newLayer.visibleRect = self.visibleRect; // read-only
newLayer.zPosition = self.zPosition;
return newLayer;
}
@end
然後我更新renderScreen該層的副本發送到renderLayer:
- (void)renderScreen {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
CALayer *layer = [keyWindow layer];
CALayer *layerCopy = [layer copy];
[self performSelectorInBackground:@selector(renderLayer:) withObject:layerCopy];
}
當我運行這個代碼時,所有的屏幕圖像都是純白色的。顯然我的複製方法不正確。那麼有人可以幫助我解決以下任何可能的解決方案嗎?
- 如何爲CALayer編寫一個真正有效的複製方法?
- 如何檢查傳入後臺進程的圖層是否爲renderInContext的有效目標?
- 任何其他方式來呈現複雜的圖層而不鎖定界面?
更新:我根據Rob Napier的建議使用initWithLayer重寫了我的CALayerCopyable類別。簡單地複製圖層仍然給我一個普通的白色輸出,所以我添加了一個遞歸複製所有子圖層的方法。我依然,然而,得到純白色的輸出:
#import "QuartzCore/CALayer.h"
@interface CALayer (CALayerCopyable)
- (id)copy;
- (NSArray *)copySublayers:(NSArray *)sublayers;
@end
@implementation CALayer (CALayerCopyable)
- (id)copy {
CALayer *newLayer = [[CALayer alloc] initWithLayer:self];
newLayer.sublayers = [self copySublayers:self.sublayers];
return newLayer;
}
- (NSArray *)copySublayers:(NSArray *)sublayers {
NSMutableArray *newSublayers = [NSMutableArray arrayWithCapacity:[sublayers count]];
for (CALayer *sublayer in sublayers) {
[newSublayers addObject:[sublayer copy]];
}
return [NSArray arrayWithArray:newSublayers];
}
@end
您的後臺線程*無法觸摸'UIWindow'。您需要直接傳遞大小。 –
好的,謝謝,爲了我的直接測試目的,我正在對窗口大小進行硬編碼。根據我最終得到的解決方案,我會找出一個很好的方式來動態傳遞它。但是,這並不影響我當前的問題。 – arlomedia
你最終得到了一個很好的解決方案嗎? –