At first I thought it wouldn't be too hard, but turns out there's a bit more to it than I thought.
At the moment my code's based around this: https://github.com/jwilling/JWFolders. However, it's not very clean, the biggest problem being that it takes a screenshot every time the folder opens (renderInContext: is pretty taxing on the CPU so turns out being very low fps). Aside from that this implementation doesn't look as clean and as polished as the Apple animation does.
Apple manages to pull off a pixel perfect animation that doesn't lag. I'd like to do the same! What is the best way to go about this/how would you do this - a cleanly coded, good looking and fluid animation? At the moment, my animation's lacking in all those areas, and I'm stumped about how I could do it better (like Apple does it).
-
I'm not begging for code, just a best method/how to. But if you want to go ahead and code it then there's no way I'd say no (that would definitely be bouty worthy)!!
I would separate the Springboard into several views, where each view is one row of icons.
If a folder icon is clicked, I would animate all following views to move down and up to make place for the folder content. at the same time I would add a subview that represents the folder content and animates with the same speed its size and position if necessary.
To support a background image that spans over the whole screen I would cut that into pieces and add this pieces as background image views to each row's view.
I wrote a quick and dirty sample code with the animation starting on any tap location, also available as complete project on github.
@interface ViewController ()
@property (nonatomic, strong) UIView *upperView;
@property (nonatomic, strong) UIView *lowerView;
@property (nonatomic, strong) UIImageView *upperImageView;
@property (nonatomic, strong) UIImageView *lowerImageView;
@property (nonatomic, strong) UIView *folderContentsView;
@property (nonatomic, assign) CGRect startRect;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage *bgImg = [UIImage imageNamed:@"bg.png"];
self.upperImageView = [[UIImageView alloc] initWithImage:bgImg];
self.lowerImageView = [[UIImageView alloc] initWithImage:bgImg];
[self.upperImageView setContentMode:UIViewContentModeTop];
[self.lowerImageView setContentMode:UIViewContentModeTop];
self.upperView = [[UIView alloc] initWithFrame:self.upperImageView.frame];
self.lowerView = [[UIView alloc] initWithFrame:self.lowerImageView.frame];
[self.upperView addSubview:_upperImageView];
[self.lowerView addSubview:_lowerImageView];
[self.view addSubview:_lowerView];
[self.view addSubview:_upperView];
UITapGestureRecognizer *upperTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(openOverlay:)];
UITapGestureRecognizer *lowerTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(openOverlay:)];
[self.upperView setUserInteractionEnabled:YES];
[self.upperView addGestureRecognizer:upperTapRecognizer];
[self.upperView setClipsToBounds:YES];
[self.lowerView setUserInteractionEnabled:YES];
[self.lowerView addGestureRecognizer:lowerTapRecognizer];
[self.lowerView setClipsToBounds:YES];
self.folderContentsView = [[UIView alloc] initWithFrame:CGRectZero];
self.folderContentsView.backgroundColor = [UIColor redColor];
UITapGestureRecognizer *closeTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(closeOverlay:)];
[self.folderContentsView addGestureRecognizer:closeTapRecognizer];
[self.view addSubview:self.folderContentsView];
[self.folderContentsView addSubview:[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bgFolder.png"]]];
[self.folderContentsView setClipsToBounds:YES];
self.startRect = [self.upperView frame];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
-(void)openOverlay:(UITapGestureRecognizer *) sender
{
[self.upperView setUserInteractionEnabled:NO];
[self.lowerView setUserInteractionEnabled:NO];
CGPoint location = [sender locationInView:sender.view];
self.folderContentsView.frame = CGRectMake(0, location.y,
_lowerView.frame.size.width, 0);
self.lowerView.frame = CGRectMake(0, location.y,
_lowerView.frame.size.width, _lowerView.frame.size.height);
self.upperView.frame = CGRectMake(0, 0,
_upperView.frame.size.width, location.y);
self.lowerImageView.frame = CGRectMake(_lowerImageView.frame.origin.x, -location.y,
_lowerImageView.frame.size.width, _lowerImageView.frame.size.height);
[UIView animateWithDuration:.5 animations:^{
self.folderContentsView.frame = CGRectMake(0, location.y,
_lowerView.frame.size.width, 200);
self.lowerView.frame = CGRectOffset(_lowerView.frame, 0, 200);
}];
}
-(void) closeOverlay:(UITapGestureRecognizer*) sender
{
[UIView animateWithDuration:.5 animations:^{
self.lowerView.frame = CGRectOffset(_lowerView.frame, 0, -200);
self.folderContentsView.frame = CGRectMake(0, self.folderContentsView.frame.origin.y,
self.folderContentsView.frame.size.width, 0);
} completion:^(BOOL finished) {
[self.upperView setUserInteractionEnabled:YES];
[self.lowerView setUserInteractionEnabled:YES];
self.upperView.frame = self.startRect;
}];
}
@end
two views a equipped with the same background in layers. If a tap is detected, both views are resized so that the upper view ends at the tap's y-coordinate, while the lower view starts there. the UIImageView added to the lowerView is moved in the opposite direction, so that the images stays in place.
Now the lowerView slides down while a contentView is added and resized at the same speed.
As you can see no image rendering or other tough action is required. just UIView animations.