У меня есть CAShapeLayer (который является слоем подкласса UIView), чей path
должен обновляться при изменении размера представления bounds
. Для этого я переопределил метод layer setBounds:
на reset путь:
@interface CustomShapeLayer : CAShapeLayer
@end
@implementation CustomShapeLayer
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
self.path = [[self shapeForBounds:bounds] CGPath];
}
Это отлично работает, пока я не изменил границы. Я хотел бы, чтобы анимация анимации наряду с любыми изменениями анимированных границ (в блоке анимации UIView), чтобы слой формы всегда принимал его размер.
Поскольку свойство path
по умолчанию не анимируется, я придумал следующее:
Переопределить метод слоя addAnimation:forKey:
. В нем выясните, добавляется ли анимация границ в слой. Если это так, создайте вторую явную анимацию для анимации пути вдоль границ, скопировав все свойства из анимации границ, которая будет передана методу. В коде:
- (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"];
}
}
}
Я получил эту идею от чтения David Rönnqvist Статья в формате View-Layer Synergy. (Этот код для iOS 8. На iOS 7 кажется, что вам нужно проверить анимацию keyPath
на @"bounds"
, а не `@ "bounds.size".)
Вызывающий код, который вызывает изменение анимированных ограничений вида, будет выглядеть так:
[UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.3 initialSpringVelocity:0.0 options:0 animations:^{
self.customShapeView.bounds = newBounds;
} completion:nil];
Вопросы
В основном это работает, но у меня есть две проблемы:
-
Иногда я вызываю авария (Изменить: неважно. Я забыл преобразоватьEXC_BAD_ACCESS
) вCGPathApply()
при запуске этой анимации, пока предыдущая анимация все еще продолжается. Я не уверен, имеет ли это какое-либо отношение к моей конкретной реализации.UIBezierPath
вCGPathRef
. Спасибо @antonioviero! -
При использовании стандартной анимации UIView spring анимация границ обзора и путь слоя немного не синхронизированы. То есть анимация пути также выполняется пружинистым способом, но она точно не соответствует ограничениям вида.
-
В целом, это лучший подход? Кажется, что наличие слоя формы, путь которого зависит от его размера границ и который должен анимироваться в синхронизации с любыми изменениями границ, является тем, что должен просто работать ™, но мне тяжело. Я считаю, что должен быть лучший способ.
Другие вещи, которые я пробовал
- Переопределите слой
actionForKey:
или представлениеactionForLayer:forKey:
, чтобы вернуть пользовательский объект анимации для свойстваpath
. Я думаю, что это было бы предпочтительным способом, но я не нашел способ получить свойства транзакции, которые должны быть установлены приложением блока анимации. Даже если вызов изнутри блока анимации,[CATransaction animationDuration
] и т.д. Всегда возвращают значения по умолчанию.
Есть ли способ (а) определить, что вы сейчас находитесь в блоке анимации, и (б) получить свойства анимации (продолжительность, анимационная кривая и т.д.), которые были установлены в этом блоке?
Проект
Здесь анимация: оранжевый треугольник - это путь слоя формы. Черная контур - это рамка представления, в которой расположен слой с фигурой.
Посмотрите пример проекта на GitHub. (Этот проект предназначен для iOS 8 и требует запуска Xcode 6, извините.)
Update
Хосе Луис Пьедрахита указал мне на в этой статье Ника Локвуда, что предполагает следующий подход:
-
Переопределите метод просмотра
actionForLayer:forKey:
или слояactionForKey:
и проверьте, является ли ключ, переданный этому методу, тем, который вы хотите оживить (@"path"
). -
Если это так, вызов
super
в одном из "нормальных" анимационных свойств слоя (например,@"bounds"
) косвенно скажет вам, находитесь ли вы в блоке анимации. Если представление (делегат слоя) возвращает действительный объект (а неnil
илиNSNull
), мы. -
Задайте параметры (продолжительность, время и т.д.) для анимации пути из анимации, возвращенной из
[super actionForKey:]
, и верните анимацию пути.
Это действительно отлично работает под iOS 7.x. Однако в iOS 8 объект, возвращаемый из actionForLayer:forKey:
, не является стандартным (и документированным) CA...Animation
, а экземпляром частного класса _UIViewAdditiveAnimationAction
(подкласс NSObject
). Поскольку свойства этого класса не документированы, я не могу их легко использовать для создания анимации пути.
_Edit: Или это может просто работать в конце концов. Как упоминал Ник в своей статье, некоторые свойства типа backgroundColor
по-прежнему возвращают стандартный CA...Animation
на iOS 8. Мне придется попробовать его. `