CAShapeLayer(UIViewサブクラスのレイヤー)があり、ビューのpath
サイズが変更されるたびにbounds
を更新する必要があります。このために、レイヤーの_setBounds:
_メソッドをオーバーライドしてパスをリセットしました。
_@interface CustomShapeLayer : CAShapeLayer
@end
@implementation CustomShapeLayer
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
self.path = [[self shapeForBounds:bounds] CGPath];
}
_
これは、境界の変更をアニメーション化するまでうまく機能します。 (UIViewアニメーションブロックで)アニメーション化された境界の変更に沿ってパスをアニメーション化して、シェイプレイヤーが常にそのサイズに合わせられるようにしたいと考えています。
path
プロパティはデフォルトではアニメーション化しないため、これを思いつきました。
レイヤーの_addAnimation:forKey:
_メソッドをオーバーライドします。その中で、境界アニメーションがレイヤーに追加されているかどうかを確認します。その場合は、メソッドに渡される境界アニメーションからすべてのプロパティをコピーして、境界に沿ってパスをアニメーション化する2つ目の明示的なアニメーションを作成します。コードで:
_- (void)addAnimation:(CAAnimation *)animation forKey:(NSString *)key
{
[super addAnimation:animation forKey:key];
if ([animation isKindOfClass:[CABasicAnimation class]]) {
CABasicAnimation *basicAnimation = (CABasicAnimation *)animation;
if ([basicAnimation.keyPath isEqualToString:@"bounds.size"]) {
CABasicAnimation *pathAnimation = [basicAnimation copy];
pathAnimation.keyPath = @"path";
// The path property has not been updated to the new value yet
pathAnimation.fromValue = (id)self.path;
// Compute the new value for path
pathAnimation.toValue = (id)[[self shapeForBounds:self.bounds] CGPath];
[self addAnimation:pathAnimation forKey:@"path"];
}
}
}
_
このアイデアは、DavidRönnqvistの View-Layer Synergyの記事 を読んで得ました。 (このコードはiOS 8用です。iOS7では、アニメーションのkeyPath
を `@" bounds.size "ではなく_@"bounds"
_に対してチェックする必要があるようです。)
ビューのアニメーション化された境界の変更をトリガーする呼び出しコードは、次のようになります。
_[UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.3 initialSpringVelocity:0.0 options:0 animations:^{
self.customShapeView.bounds = newBounds;
} completion:nil];
_
これはほとんど機能しますが、2つの問題があります。
前のアニメーションの進行中にこのアニメーションをトリガーすると、 編集:気にしないでください。 CGPathApply()
で(_EXC_BAD_ACCESS
_)がクラッシュすることがあります。これが私の特定の実装と関係があるかどうかはわかりません。UIBezierPath
をCGPathRef
に変換するのを忘れました。 ありがとう@antonioviero !
標準のUIViewスプリングアニメーションを使用すると、ビューの境界とレイヤーのパスのアニメーションがわずかに同期しなくなります。つまり、パスアニメーションも弾力的な方法で実行されますが、ビューの境界に正確には従いません。
より一般的に、これは最善のアプローチですか?パスが境界サイズに依存し、任意のオブジェクトと同期してアニメーション化する必要があるシェイプレイヤーがあるようです境界の変更はうまくいくはずですが™、私は苦労しています。もっと良い方法があるはずだと思います。
path
プロパティのカスタムアニメーションオブジェクトを返すには、レイヤーの_actionForKey:
_またはビューの_actionForLayer:forKey:
_をオーバーライドします。これが望ましい方法だと思いますが、外側のアニメーションブロックで設定する必要があるトランザクションプロパティを取得する方法が見つかりませんでした。アニメーションブロック内から呼び出された場合でも、_[CATransaction animationDuration
_]などは常にデフォルト値を返します。(a)現在アニメーションブロック内にいることを確認する方法、および(b)そこに設定されているアニメーションプロパティ(期間、アニメーションカーブなど)を取得する方法はありますか?ブロック?
アニメーションは次のとおりです。オレンジ色の三角形は、シェイプレイヤーのパスです。黒のアウトラインは、シェイプレイヤーをホストするビューのフレームです。
GitHubのサンプルプロジェクトをご覧ください 。 (このプロジェクトはiOS 8用であり、実行するにはXcode 6が必要です。申し訳ありません。)
Jose Luis Piedrahita 私を指摘この記事はNick Lockwood で、次のアプローチを示唆しています。
ビューの_actionForLayer:forKey:
_またはレイヤーの_actionForKey:
_メソッドをオーバーライドし、このメソッドに渡されたキーがアニメーション化するものであるかどうかを確認します(_@"path"
_)。
その場合、レイヤーの「通常の」アニメート可能なプロパティ(_@"bounds"
_など)の1つでsuper
を呼び出すと、アニメーションブロック内にいるかどうかが暗黙的に通知されます。ビュー(レイヤーのデリゲート)が(nil
またはNSNull
ではなく)有効なオブジェクトを返す場合、そのとおりです。
_[super actionForKey:]
_から返されたアニメーションからパスアニメーションのパラメーター(期間、タイミング関数など)を設定し、パスアニメーションを返します。
これは確かにiOS 7.xでうまく機能します。ただし、iOS 8では、_actionForLayer:forKey:
_から返されるオブジェクトは標準(および文書化)_CA...Animation
_ではなく、プライベート__UIViewAdditiveAnimationAction
_クラス(NSObject
サブクラス)のインスタンスです。このクラスのプロパティはドキュメント化されていないため、パスアニメーションを作成するためにそれらを簡単に使用することはできません。
_Edit:または、結局はうまくいくかもしれません。 Nickが彼の記事で述べたように、backgroundColor
のようないくつかのプロパティは、iOS 8でも標準の_CA...Animation
_を返します。試してみる必要があります。`
私はこれが古い質問であることを知っていますが、ロックウッド氏のアプローチと同様に見える解決策を提供できます。悲しいことに、ここのソースコードはSwiftなので、ObjCに変換する必要があります。
前述のように、レイヤーがビューのバッキングレイヤーである場合は、ビュー自体のCAActionをインターセプトできます。ただし、これは、たとえば、バッキングレイヤーが複数のビューで使用されている場合には便利ではありません。
良いニュースは、actionForLayer:forKey:が実際にバッキングレイヤーのactionForKey:を呼び出すことです。
これはactionForKeyにあります。これらの呼び出しをインターセプトして、パスが変更されたときのアニメーションを提供できるバッキングレイヤーにあります。
Swiftで記述されたレイヤーの例は次のとおりです。
class AnimatedBackingLayer: CAShapeLayer
{
override var bounds: CGRect
{
didSet
{
if !CGRectIsEmpty(bounds)
{
path = UIBezierPath(roundedRect: CGRectInset(bounds, 10, 10), cornerRadius: 5).CGPath
}
}
}
override func actionForKey(event: String) -> CAAction?
{
if event == "path"
{
if let action = super.actionForKey("backgroundColor") as? CABasicAnimation
{
let animation = CABasicAnimation(keyPath: event)
animation.fromValue = path
// Copy values from existing action
animation.autoreverses = action.autoreverses
animation.beginTime = action.beginTime
animation.delegate = action.delegate
animation.duration = action.duration
animation.fillMode = action.fillMode
animation.repeatCount = action.repeatCount
animation.repeatDuration = action.repeatDuration
animation.speed = action.speed
animation.timingFunction = action.timingFunction
animation.timeOffset = action.timeOffset
return animation
}
}
return super.actionForKey(event)
}
}
レイヤーのフレームとパスを同時に遊んでいるので問題があると思います。
私は、カスタムのdrawRectを持つCustomViewを使用します。
[UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.3 initialSpringVelocity:0.0 options:0 animations:^{
self.customView.bounds = newBounds;
} completion:nil];
またはパスを使用する必要はまったくありません
これが私がこのアプローチを使って得たものです https://dl.dropboxusercontent.com/u/73912254/triangle.mov
path
がアニメーション化されているときにCAShapeLayer
のbounds
をアニメーション化するための解決策が見つかりました:
typedef UIBezierPath *(^PathGeneratorBlock)();
@interface AnimatedPathShapeLayer : CAShapeLayer
@property (copy, nonatomic) PathGeneratorBlock pathGenerator;
@end
@implementation AnimatedPathShapeLayer
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key {
if ([key rangeOfString:@"bounds.size"].location == 0) {
CAShapeLayer *presentationLayer = self.presentationLayer;
CABasicAnimation *pathAnim = [anim copy];
pathAnim.keyPath = @"path";
pathAnim.fromValue = (id)[presentationLayer path];
pathAnim.toValue = (id)self.pathGenerator().CGPath;
self.path = [presentationLayer path];
[super addAnimation:pathAnim forKey:@"path"];
}
[super addAnimation:anim forKey:key];
}
- (void)removeAnimationForKey:(NSString *)key {
if ([key rangeOfString:@"bounds.size"].location == 0) {
[super removeAnimationForKey:@"path"];
}
[super removeAnimationForKey:key];
}
@end
//
@interface ShapeLayeredView : UIView
@property (strong, nonatomic) AnimatedPathShapeLayer *layer;
@end
@implementation ShapeLayeredView
@dynamic layer;
+ (Class)layerClass {
return [AnimatedPathShapeLayer class];
}
- (instancetype)initWithGenerator:(PathGeneratorBlock)pathGenerator {
self = [super init];
if (self) {
self.layer.pathGenerator = pathGenerator;
}
return self;
}
@end
境界とパスアニメーションが同期していないのは、UIVIewスプリングとCABasicAnimationのタイミング関数が異なるためです。
代わりに、アニメーション変換を試すこともできます。パスも変換する必要があります(テストされていない)。アニメーションが終了したら、境界を設定できます。
もう1つの可能な方法は、パスのスナップショットを取り、それをレイヤーのコンテンツとして設定し、境界をアニメーション化することです。コンテンツはアニメーションに従う必要があります。