2012-08-29 54 views
7

我需要從我的應用程序保存截圖,所以我已經設置了這樣的代碼,它的工作原理:如何呈現的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 answerthis 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]; 
} 

當我運行這個代碼時,所有的屏幕圖像都是純白色的。顯然我的複製方法不正確。那麼有人可以幫助我解決以下任何可能的解決方案嗎?

  1. 如何爲CALayer編寫一個真正有效的複製方法?
  2. 如何檢查傳入後臺進程的圖層是否爲renderInContext的有效目標?
  3. 任何其他方式來呈現複雜的圖層而不鎖定界面?

更新:我根據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 
+2

您的後臺線程*無法觸摸'UIWindow'。您需要直接傳遞大小。 –

+0

好的,謝謝,爲了我的直接測試目的,我正在對窗口大小進行硬編碼。根據我最終得到的解決方案,我會找出一個很好的方式來動態傳遞它。但是,這並不影響我當前的問題。 – arlomedia

+0

你最終得到了一個很好的解決方案嗎? –

回答

2

爲此,我會使用initWithLayer:,而不是創建自己的複製方法。 initWithLayer:明確用於創建「圖層的陰影副本,例如,用於presentationLayer方法」。

您可能還需要創建子圖層的副本。我不記得是否initWithLayer:爲你做。但是initWithLayer:是Core Animation的工作原理,所以它針對這樣的問題進行了優化。

+0

我自己嘗試了initWithLayer,而不是我的複製方法,仍然以純白色圖像結束。我也在我的複製方法中嘗試使用它而不是[CALayer圖層],然後像以前一樣複製所有屬性,但仍獲得純白色圖像。該文檔說「不要用這種方法來初始化一個新圖層與現有圖層的內容,」所以我不知道這是否值得追求。你認爲我應該嘗試迭代原始圖層的子圖層,並使用initWithLayer將它們複製到新圖層中? – arlomedia

+0

我試着複製所有的子層,但仍然有白色的輸出。我用我試過的代碼更新了這個問題。 – arlomedia