8

アプリからスクリーンショットを保存する必要があるため、次のようなコードを設定しました。

- (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...
}

ただし、画面イメージが複雑になると (多くのビュー)、renderInContext は iPad 3 で最大 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 を返します。

次に、この SO コメントを読み、バックグラウンド操作が実行される前にレイヤーが変化する可能性があることを述べ、代わりにレイヤーのコピーをバックグラウンド操作に送信することを提案しました。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. インターフェイスをロックせずに複雑なレイヤーをレンダリングする他の方法はありますか?

更新: initWithLayer を使用するという Rob Napier の提案に基づいて、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
4

1 に答える 1

2

この目的のために、initWithLayer:独自のコピー メソッドを作成するのではなく、使用します。initWithLayer:明示的に「たとえば、presentationLayer メソッド用のレイヤーのシャドウ コピー」を作成するためのものです。

サブレイヤーのコピーを作成する必要がある場合もあります。それがあなたに当てはまるかどうか、すぐには思い出せinitWithLayer:ません。しかしinitWithLayer:、コア アニメーションのしくみなので、このような問題に最適化されています。

于 2012-08-29T20:43:18.787 に答える