2017-05-12 8 views
0

私は、そのbeginUpdatesendUpdatesのメソッドを使用して展開および折りたたんで、そのように表示されるドロップシャドーを表示しようとしているUITableViewを持っています。私のカスタムUITableViewCellので、私はlayoutSubviewsでのシャドウを作成する層を持っている:CALayerの影を同時にアニメーション化するUITableviewCell height animates

self.shadowLayer.shadowColor = self.shadowColor.CGColor; 
self.shadowLayer.shadowOffset = CGSizeMake(self.shadowOffsetWidth, self.shadowOffsetHeight); 
self.shadowLayer.shadowOpacity = self.shadowOpacity; 
self.shadowLayer.masksToBounds = NO; 
self.shadowLayer.frame = self.layer.bounds; 
// this is extremely important for performance when drawing shadows 
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.shadowLayer.frame cornerRadius:self.cornerRadius]; 
self.shadowLayer.shadowPath = shadowPath.CGPath; 

私はviewDidLoadでのUITableViewCellにこのレイヤーを追加します。

self.shadowLayer = [CALayer layer]; 
self.shadowLayer.backgroundColor = [UIColor whiteColor].CGColor; 
[self.layer insertSublayer:self.shadowLayer below:self.contentView.layer]; 

私が呼ぶとき、私は、それを理解したようbeginUpdatesの場合、暗黙的なCALayerTransactionが存在しない場合は、現在の実行ループ用に作成されます。さらに、layoutSubviewsも呼び出されます。ここでの問題は、結果として生じる影が、UITableViewCellの新しいサイズに基づいて即座に描画されることです。実際のレイヤーがアニメーション化されているので、私は実際に予想どおりにキャストを続けるために影を付ける必要があります。

作成したレイヤーはバッキングCALayerではないため、明示的にCATransactionを指定することなくアニメーション化されます。しかし、私が理解しているように、私は実際には、beginUpdates/endUpdates CATransactionを保持し、その中でアニメーションを実行するための何らかの方法が必要です。どうすればいいですか?

+0

シャドウは1つのセルの周りまたはテーブル全体の周りにあると想定されていますか? –

答えて

0

UITableViewCATransactionを作成していない可能性があります。ある場合は、更新を終了するまで待機しています。私の理解では、テーブルビューはそれらの関数間のすべての変更をまとめて、必要に応じてアニメーションを作成するというものです。実際に何が起きたかわからないので、コミットしている実際のアニメーションパラメータを処理する方法はありません。コンテンツオフセットの変更をUIScrollViewでアニメートすると、同じことが起こります。システムは、アニメーション自体に関するコンテキストを提供しないので、イライラします。システムに現在のCATransactionを照会する方法もありません。

恐らくあなたができることは、UITableViewが作成しているアニメーションを検査し、自分のアニメーションで同じタイミングパラメータを模倣することだけです。 Swizzling add(_:forKey:)CALayerに追加すると、追加するすべてのアニメーションを検査することができます。確かにこれを実際に出荷したくないのですが、このテクニックをデバッグに使用して、どのアニメーションが追加されているのか、そのプロパティが何であるかを把握することがよくあります。

適切なセルについては、自分のシャドウレイヤアニメーションをtableView(_:willDisplayCell:for:row:)にコミットする必要があると思われます。

5

だから私はあなたがこのような何かを持っていると思います:

bad animation

(私はシミュレータで「デバッグ>スローアニメーション」オン。)そして、あなたは影がそのにジャンプ道を好きではありません新しいサイズ。あなたはこの代わりにしたい:

good animation

あなたは私のテストプロジェクトin this github repositoryを見つけることができます。

アニメーションパラメータを取得し、テーブルビューのアニメーションブロックにアニメーションを追加するのは難しいことではありませんが、不可能ではありません。最も難しいのは、影付きのビュー自体のlayoutSubviewsメソッドまたは影付きのビューの即時スーパービューのメソッドshadowPathを更新する必要があることです。私のデモビデオでは、は、緑色のボックスビューまたは緑色ボックスの即時のスーパービューのlayoutSubviewsメソッドで更新する必要があることを示しています。

ShadowingViewクラスを作成して、そのサブビューの1つの影を描き、アニメーション化することを唯一の仕事としました。ここでは、インターフェイスがあります:

@interface ShadowingView : UIView 

@property (nonatomic, strong) IBOutlet UIView *shadowedView; 

@end 

ShadowingViewを使用するには、私は私のストーリーボードの私の細胞ビューにこれを追加しました。実際には、セル内のスタックビュー内にネストされています。次に、緑のボックスをShadowingViewのサブビューとして追加し、緑のボックスにshadowedViewのコンセントを接続しました。

ShadowingView実装には3つの部分があります。

場合のように、この方法は、(アニメーションブロック内で実行される
@implementation ShadowingView 

- (void)layoutSubviews { 
    [super layoutSubviews]; 

    CALayer *layer = self.layer; 
    layer.backgroundColor = nil; 

    CALayer *shadowedLayer = self.shadowedView.layer; 
    if (shadowedLayer == nil) { 
     layer.shadowColor = nil; 
     return; 
    } 

    NSAssert(shadowedLayer.superlayer == layer, @"shadowedView must be my direct subview"); 

    layer.shadowColor = UIColor.blackColor.CGColor; 
    layer.shadowOffset = CGSizeMake(0, 1); 
    layer.shadowOpacity = 0.5; 
    layer.shadowRadius = 3; 
    layer.masksToBounds = NO; 

    CGFloat radius = shadowedLayer.cornerRadius; 
    layer.shadowPath = CGPathCreateWithRoundedRect(shadowedLayer.frame, radius, radius, nil); 
} 

表:一つは、それ自身の層の上に層シャドウプロパティを設定し、そのlayoutSubviews方法は、そのshadowedViewサブビューの周りに影を描画しますビューはセルのサイズの変化をアニメーション化します)、メソッドはshadowPathに設定され、コアアニメーションはshadowPathの更新後に実行するアクションを探します。それが見える方法の1つは、actionForLayer:forKey:をレイヤのデリゲートに送信し、デリゲートはShadowingViewです。可能であれば適切な処置を行うため、actionForLayer:forKey:を上書きします。できない場合は、superに電話するだけです。

Core Animationのが実際shadowPathの値を変更する前に、shadowPathセッター内部からアクションを要求することを理解することが重要です。

アクションを提供するには、キーが@"shadowPath"であること、既存の値がshadowPathであること、レイヤーに既にbounds.sizeのアニメーションがあることを確認します。既存のbounds.sizeのアニメーションを探す理由は何ですか?既存のアニメーションには、アニメーションに使用する期間とタイミング機能があるため、shadowPathです。すべてが順序であれば、我々は既存のshadowPathをつかむ、アクションに保存し、アニメーションのコピーを作成し、アクションを返す:

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event { 
    if (![event isEqualToString:@"shadowPath"]) { return [super actionForLayer:layer forKey:event]; } 

    CGPathRef priorPath = layer.shadowPath; 
    if (priorPath == NULL) { return [super actionForLayer:layer forKey:event]; } 

    CAAnimation *sizeAnimation = [layer animationForKey:@"bounds.size"]; 
    if (![sizeAnimation isKindOfClass:[CABasicAnimation class]]) { return [super actionForLayer:layer forKey:event]; } 

    CABasicAnimation *animation = [sizeAnimation copy]; 
    animation.keyPath = @"shadowPath"; 
    ShadowingViewAction *action = [[ShadowingViewAction alloc] init]; 
    action.priorPath = priorPath; 
    action.pendingAnimation = animation; 
    return action; 
} 

@end 

は、アクションがどのようなものが見えますか?

@interface ShadowingViewAction : NSObject <CAAction> 
@property (nonatomic, strong) CABasicAnimation *pendingAnimation; 
@property (nonatomic) CGPathRef priorPath; 
@end 

実装にはrunActionForKey:object:arguments:メソッドが必要です。この方法では、actionForLayer:forKey:で作成したアニメーションを、保存済みの古いshadowPathと新しいshadowPathを使用して更新し、アニメーションをレイヤーに追加します。

ARCはCGPathオブジェクトを管理しないため、保存されたパスの保持カウントも管理する必要があります。

@implementation ShadowingViewAction 

- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict { 
    if (![anObject isKindOfClass:[CALayer class]] || _pendingAnimation == nil) { return; } 
    CALayer *layer = anObject; 
    _pendingAnimation.fromValue = (__bridge id)_priorPath; 
    _pendingAnimation.toValue = (__bridge id)layer.shadowPath; 
    [layer addAnimation:_pendingAnimation forKey:@"shadowPath"]; 
} 

- (void)setPriorPath:(CGPathRef)priorPath { 
    CGPathRetain(priorPath); 
    CGPathRelease(_priorPath); 
    _priorPath = priorPath; 
} 

- (void)dealloc { 
    CGPathRelease(_priorPath); 
} 

@end 
+1

ロブ、これは素晴らしいです!これはまさに私が探していた解決策でした。私はそれをSwiftに翻訳し、ここにも掲載します。 – horseshoe7

1

これはSwiftで書かれたRob Mayoffの答えです。誰かを救うことができました。

これをupvoteしないでください。 Rob Mayoffの解決策をUpvoteしてください。それは素晴らしい、そして正しい。

import UIKit 

class AnimatingShadowView: UIView { 

    struct DropShadowParameters { 
     var shadowOpacity: Float = 0 
     var shadowColor: UIColor? = .black 
     var shadowRadius: CGFloat = 0 
     var shadowOffset: CGSize = .zero 

     static let defaultParameters = DropShadowParameters(shadowOpacity: 0.15, 
                  shadowColor: .black, 
                  shadowRadius: 5, 
                  shadowOffset: CGSize(width: 0, height: 1)) 
    } 

    @IBOutlet weak var contentView: UIView! // no sense in have a shadowView without content! 

    var shadowParameters: DropShadowParameters = DropShadowParameters.defaultParameters 

    private func apply(dropShadow: DropShadowParameters) { 
     let layer = self.layer 
     layer.shadowColor = dropShadow.shadowColor?.cgColor 
     layer.shadowOffset = dropShadow.shadowOffset 
     layer.shadowOpacity = dropShadow.shadowOpacity 
     layer.shadowRadius = dropShadow.shadowRadius 
     layer.masksToBounds = false 
    } 

    override func layoutSubviews() { 
     super.layoutSubviews() 
     let layer = self.layer 
     layer.backgroundColor = nil 

     let contentLayer = self.contentView.layer 
     assert(contentLayer.superlayer == layer, "contentView must be a direct subview of AnimatingShadowView!") 

     self.apply(dropShadow: self.shadowParameters) 

     let radius = contentLayer.cornerRadius 
     layer.shadowPath = UIBezierPath(roundedRect: contentLayer.frame, cornerRadius: radius).cgPath 
    } 

    override func action(for layer: CALayer, forKey event: String) -> CAAction? { 
     guard event == "shadowPath" else { 
      return super.action(for: layer, forKey: event) 
     } 

     guard let priorPath = layer.shadowPath else { 
      return super.action(for: layer, forKey: event) 
     } 

     guard let sizeAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation else { 
      return super.action(for: layer, forKey: event) 
     } 

     let animation = sizeAnimation.copy() as! CABasicAnimation 
     animation.keyPath = "shadowPath" 
     let action = ShadowingViewAction() 
     action.priorPath = priorPath 
     action.pendingAnimation = animation 
     return action 
    } 
} 


private class ShadowingViewAction: NSObject, CAAction { 
    var pendingAnimation: CABasicAnimation? = nil 
    var priorPath: CGPath? = nil 

    // CAAction Protocol 
    func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { 
     guard let layer = anObject as? CALayer, let animation = self.pendingAnimation else { 
      return 
     } 

     animation.fromValue = self.priorPath 
     animation.toValue = layer.shadowPath 
     layer.add(animation, forKey: "shadowPath") 
    } 
} 
関連する問題