两个视图交易布局动画(包括 Z 顺序)
Two views trade layouts animated (including Z Order)
我有两个视图,一个 UIImageView
显示用户选择的照片,另一个 MKMapKitView
显示用户选择的对象位置。休息时,视图排列如下:
当用户点击视图 2 时,我希望两者交换位置。反之亦然。两个视图都定位有约束。所以我可以做一个动画,我首先获取两组约束,然后清除它们,然后以翻转顺序应用它们,但 z 顺序不会切换。无论哪个视图处于 "small" 模式,都应该位于另一个视图之上。
我真的很希望能够为它制作动画,使交换显而易见。以某种方式让它看起来像旋转:
完成此任务最简单的方法是什么?我尝试了以下方法:
- (IBAction)swapPreview {
NSArray *photoConstraints = self.photoView.constraints;
NSArray *mapConstraints = self.mapView.constraints;
[self.photoView removeConstraints: photoConstraints];
[self.mapView removeConstraints: mapConstraints];
NSLog(@"photoConstraints %@", photoConstraints);
NSLog(@"mapConstraints %@", mapConstraints);
CGRect biggerFrame = CGRectUnion(self.photoView.frame, self.mapView.frame);
CGRect upperFrame = CGRectOffset(RectCenterScaled(biggerFrame, 0.5), 0, biggerFrame.size.height / -4);
CGRect lowerFrame = CGRectOffset(upperFrame, 0, upperFrame.size.height);
[UIView animateWithDuration: 0.4 delay: 0 options: UIViewAnimationOptionCurveLinear animations:^{
self.photoView.frame = upperFrame;
self.mapView.frame = lowerFrame;
} completion:^(BOOL finished) {
UIView *container = self.photoView.superview;
[self.photoView addConstraints: mapConstraints];
[self.mapView addConstraints: photoConstraints];
[container exchangeSubviewAtIndex: [container.subviews indexOfObject: self.photoView] withSubviewAtIndex:[container.subviews indexOfObject: self.mapView]];
[UIView animateWithDuration: 0.4 delay: 0 options: UIViewAnimationOptionCurveLinear animations:^{
[container updateConstraints];
} completion:^(BOOL finished) {
}];
}];
}
但是当我尝试交换约束时,这会在第二个动画块上中断。那么更好的方法是什么?我可以遍历视图层次结构中的所有约束,交换引用两个视图的任何 firstItem/secondItems。但是局限于较小视图的约束需要改变局部性。
我开始怀疑我是否不应该只创建一个有两个子类的自定义 UIView 子类并直接进行布局,而不是乱用约束。
UIView
提供这些:
- (void)bringSubviewToFront:(UIView *)view;
- (void)sendSubviewToBack:(UIView *)view;
控制视图的 Z 顺序。
在其中一个动画的完成块中使用。这样做的好时机是视图不重叠时。
我制作了测试项目,class 表现如你所愿:
@interface ViewController ()
//initial constants values
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view1Top;//0
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view2Top;//20
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view1Left;//0
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view2Left;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view1Height;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view2Height;//100
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view1Width;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view2Width;//50
@property (weak, nonatomic) IBOutlet UIView *view1;
@property (weak, nonatomic) IBOutlet UIView *view2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewWillAppear:(BOOL)animated{
//setting initial values for constraints related to view size
self.view1Width.constant = self.self.view.frame.size.width;
self.view1Height.constant = self.self.view.frame.size.height;
self.view2Left.constant = self.view.frame.size.width - 20 - self.view2Width.constant;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)animate:(id)sender {
CGFloat gap = 20.0;
CGFloat height = (self.view.frame.size.height - 3*gap)/2; //height of views in the middle of animation
CGFloat width = self.view.frame.size.width - 40; //width of views in the middle of animation
self.view1Top.constant = gap;
self.view1Height.constant = height;
self.view2Top.constant = height + 2*gap;
self.view2Height.constant = height;
self.view1Left.constant = self.view2Left.constant = gap;
self.view1Width.constant = self.view2Width.constant = width;
[UIView animateWithDuration:4.0 delay:0 options:0 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
[self.view exchangeSubviewAtIndex: [self.view.subviews indexOfObject: self.view1] withSubviewAtIndex:[self.view.subviews indexOfObject: self.view2]];
self.view2Top.constant = 0;
self.view2Left.constant = 0;
self.view2Height.constant = self.view.frame.size.height;
self.view2Width.constant = self.view.frame.size.width;
self.view1Top.constant = gap;
self.view1Width.constant = 50;
self.view1Height.constant = 100;
self.view1Left.constant = self.view.frame.size.width - self.view1Width.constant - gap;
[UIView animateWithDuration:4.0 animations:^{
[self.view layoutIfNeeded];
}];
}];
}
@end
view1是红色视图,view2是蓝色视图。
animate 是在我的案例中通过点击按钮启动的动作。
关键不在于删除约束,而只是动画常量属性。
我有两个视图,一个 UIImageView
显示用户选择的照片,另一个 MKMapKitView
显示用户选择的对象位置。休息时,视图排列如下:
当用户点击视图 2 时,我希望两者交换位置。反之亦然。两个视图都定位有约束。所以我可以做一个动画,我首先获取两组约束,然后清除它们,然后以翻转顺序应用它们,但 z 顺序不会切换。无论哪个视图处于 "small" 模式,都应该位于另一个视图之上。
我真的很希望能够为它制作动画,使交换显而易见。以某种方式让它看起来像旋转:
完成此任务最简单的方法是什么?我尝试了以下方法:
- (IBAction)swapPreview {
NSArray *photoConstraints = self.photoView.constraints;
NSArray *mapConstraints = self.mapView.constraints;
[self.photoView removeConstraints: photoConstraints];
[self.mapView removeConstraints: mapConstraints];
NSLog(@"photoConstraints %@", photoConstraints);
NSLog(@"mapConstraints %@", mapConstraints);
CGRect biggerFrame = CGRectUnion(self.photoView.frame, self.mapView.frame);
CGRect upperFrame = CGRectOffset(RectCenterScaled(biggerFrame, 0.5), 0, biggerFrame.size.height / -4);
CGRect lowerFrame = CGRectOffset(upperFrame, 0, upperFrame.size.height);
[UIView animateWithDuration: 0.4 delay: 0 options: UIViewAnimationOptionCurveLinear animations:^{
self.photoView.frame = upperFrame;
self.mapView.frame = lowerFrame;
} completion:^(BOOL finished) {
UIView *container = self.photoView.superview;
[self.photoView addConstraints: mapConstraints];
[self.mapView addConstraints: photoConstraints];
[container exchangeSubviewAtIndex: [container.subviews indexOfObject: self.photoView] withSubviewAtIndex:[container.subviews indexOfObject: self.mapView]];
[UIView animateWithDuration: 0.4 delay: 0 options: UIViewAnimationOptionCurveLinear animations:^{
[container updateConstraints];
} completion:^(BOOL finished) {
}];
}];
}
但是当我尝试交换约束时,这会在第二个动画块上中断。那么更好的方法是什么?我可以遍历视图层次结构中的所有约束,交换引用两个视图的任何 firstItem/secondItems。但是局限于较小视图的约束需要改变局部性。
我开始怀疑我是否不应该只创建一个有两个子类的自定义 UIView 子类并直接进行布局,而不是乱用约束。
UIView
提供这些:
- (void)bringSubviewToFront:(UIView *)view;
- (void)sendSubviewToBack:(UIView *)view;
控制视图的 Z 顺序。
在其中一个动画的完成块中使用。这样做的好时机是视图不重叠时。
我制作了测试项目,class 表现如你所愿:
@interface ViewController ()
//initial constants values
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view1Top;//0
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view2Top;//20
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view1Left;//0
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view2Left;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view1Height;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view2Height;//100
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view1Width;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *view2Width;//50
@property (weak, nonatomic) IBOutlet UIView *view1;
@property (weak, nonatomic) IBOutlet UIView *view2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewWillAppear:(BOOL)animated{
//setting initial values for constraints related to view size
self.view1Width.constant = self.self.view.frame.size.width;
self.view1Height.constant = self.self.view.frame.size.height;
self.view2Left.constant = self.view.frame.size.width - 20 - self.view2Width.constant;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)animate:(id)sender {
CGFloat gap = 20.0;
CGFloat height = (self.view.frame.size.height - 3*gap)/2; //height of views in the middle of animation
CGFloat width = self.view.frame.size.width - 40; //width of views in the middle of animation
self.view1Top.constant = gap;
self.view1Height.constant = height;
self.view2Top.constant = height + 2*gap;
self.view2Height.constant = height;
self.view1Left.constant = self.view2Left.constant = gap;
self.view1Width.constant = self.view2Width.constant = width;
[UIView animateWithDuration:4.0 delay:0 options:0 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
[self.view exchangeSubviewAtIndex: [self.view.subviews indexOfObject: self.view1] withSubviewAtIndex:[self.view.subviews indexOfObject: self.view2]];
self.view2Top.constant = 0;
self.view2Left.constant = 0;
self.view2Height.constant = self.view.frame.size.height;
self.view2Width.constant = self.view.frame.size.width;
self.view1Top.constant = gap;
self.view1Width.constant = 50;
self.view1Height.constant = 100;
self.view1Left.constant = self.view.frame.size.width - self.view1Width.constant - gap;
[UIView animateWithDuration:4.0 animations:^{
[self.view layoutIfNeeded];
}];
}];
}
@end
view1是红色视图,view2是蓝色视图。
animate 是在我的案例中通过点击按钮启动的动作。
关键不在于删除约束,而只是动画常量属性。