Я недавно пытался сделать что-то подобное. Я решил, что мне не нравится скользящая анимация UINavigationController, но я также не хотел делать анимации, которые UIView дает вам, как скручивание или что-то в этом роде. Я хотел сделать перекрестное затухание между видами, когда я нажимаю или щелкаю.
Проблема заключается в том, что представление буквально удаляет представление или накладывает его поверх текущего, поэтому исчезновение не работает. Решение, к которому я пришел, заключалось в том, чтобы взять новое представление и добавить его в качестве подпредставления к текущему виду сверху в стеке UIViewController. Я добавляю его с альфа 0, затем делаю кроссфейд. Когда последовательность анимации заканчивается, я помещаю представление в стек без его анимации. Затем я возвращаюсь к старому topView и очищаю вещи, которые я изменил.
Это немного сложнее, потому что у вас есть навигационные элементы, которые вы должны настроить, чтобы переход выглядел правильно. Кроме того, если вы делаете какое-либо вращение, вам нужно будет отрегулировать размеры кадра при добавлении видов в качестве подпредставлений, чтобы они правильно отображались на экране. Вот часть кода, который я использовал. Я подклассифицировал UINavigationController и переопределил методы push и pop.
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
UIViewController *currentViewController = [self.viewControllers lastObject];
//if we don't have a current controller, we just do a normal push
if(currentViewController == nil)
{
[super pushViewController:viewController animated:animated];
return;
}
//if no animation was requested, we can skip the cross fade
if(!animation)
{
[super pushViewController:viewController animated:NO];
return;
}
//start the cross fade. This is a tricky thing. We basically add the new view
//as a subview of the current view, and do a cross fade through alpha values.
//then we push the new view on the stack without animating it, so it seemlessly is there.
//Finally we remove the new view that was added as a subview to the current view.
viewController.view.alpha = 0.0;
//we need to hold onto this value, we'll be releasing it later
NSString *title = [currentViewController.title retain];
//add the view as a subview of the current view
[currentViewController.view addSubview:viewController.view];
[currentViewController.view bringSubviewToFront:viewController.view];
UIBarButtonItem *rButtonItem = currentViewController.navigationItem.rightBarButtonItem;
UIBarButtonItem *lButtonItem = currentViewController.navigationItem.leftBarButtonItem;
NSArray *array = nil;
//if we have a right bar button, we need to add it to the array, if not, we will crash when we try and assign it
//so leave it out of the array we are creating to pass as the context. I always have a left bar button, so I'm not checking to see if it is nil. Its a little sloppy, but you may want to be checking for the left BarButtonItem as well.
if(rButtonItem != nil)
array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,rButtonItem,nil];
else {
array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,nil];
}
//remove the right bar button for our transition
[currentViewController.navigationItem setRightBarButtonItem:nil animated:YES];
//remove the left bar button and create a backbarbutton looking item
//[currentViewController.navigationItem setLeftBarButtonItem:nil animated:NO];
//set the back button
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:title style:kButtonStyle target:self action:@selector(goBack)];
[currentViewController.navigationItem setLeftBarButtonItem:backButton animated:YES];
[viewController.navigationItem setLeftBarButtonItem:backButton animated:NO];
[backButton release];
[currentViewController setTitle:viewController.title];
[UIView beginAnimations:@"push view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePushDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[viewController.view setAlpha: 1.0];
[UIView commitAnimations];
}
-(void)animationForCrossFadePushDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
UIViewController *c = [(NSArray*)context objectAtIndex:0];
UIViewController *n = [(NSArray*)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:3];
UIBarButtonItem *r = nil;
//not all views have a right bar button, if we look for it and it isn't in the context,
//we'll crash out and not complete the method, but the program won't crash.
//So, we need to check if it is there and skip it if it isn't.
if([(NSArray *)context count] == 5)
r = [(NSArray *)context objectAtIndex:4];
//Take the new view away from being a subview of the current view so when we go back to it
//it won't be there anymore.
[[[c.view subviews] lastObject] removeFromSuperview];
[c setTitle:title];
[title release];
//set the search button
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//set the next button
if(r != nil)
[c.navigationItem setRightBarButtonItem:r animated:NO];
[super pushViewController:n animated:NO];
}
Как я уже упоминал в коде, у меня всегда есть элемент кнопки левой панели, поэтому я не проверяю, равен ли он нулю, прежде чем помещать его в массив, который я передаю в качестве контекста для делегата анимации. Если вы сделаете это, вы можете сделать эту проверку.
Проблема, которую я обнаружил, заключалась в том, что, если вы вообще аварийно завершите работу в методе делегата, это не приведет к аварийному завершению программы. Это просто останавливает делегата от завершения, но вы не получаете никакого предупреждения.
Так что, поскольку я выполнял очистку в той процедуре делегата, это вызывало странное визуальное поведение, так как он не заканчивал очистку.
Кнопка «Назад», которую я создаю, вызывает метод «goBack», и этот метод просто вызывает процедуру pop.
-(void)goBack
{
[self popViewControllerAnimated:YES];
}
Кроме того, вот моя популярная рутина.
-(UIViewController *)popViewControllerAnimated:(BOOL)animated
{
//get the count for the number of viewControllers on the stack
int viewCount = [[self viewControllers] count];
//get the top view controller on the stack
UIViewController *topViewController = [self.viewControllers objectAtIndex:viewCount - 1];
//get the next viewController after the top one (this will be the new top one)
UIViewController *newTopViewController = [self.viewControllers objectAtIndex:viewCount - 2];
//if no animation was requested, we can skip the cross fade
if(!animated)
{
[super popViewControllerAnimated:NO];
return topViewController;
}
//start of the cross fade pop. A bit tricky. We need to add the new top controller
//as a subview of the curent view controler with an alpha of 0. We then do a cross fade.
//After that we pop the view controller off the stack without animating it.
//Then the cleanup happens: if the view that was popped is not released, then we
//need to remove the subview we added and change some titles back.
newTopViewController.view.alpha = 0.0;
[topViewController.view addSubview:newTopViewController.view];
[topViewController.view bringSubviewToFront:newTopViewController.view];
NSString *title = [topViewController.title retain];
UIBarButtonItem *lButtonItem = topViewController.navigationItem.leftBarButtonItem;
UIBarButtonItem *rButtonItem = topViewController.navigationItem.rightBarButtonItem;
//set the new buttons on top of the current controller from the new top controller
if(newTopViewController.navigationItem.leftBarButtonItem != nil)
{
[topViewController.navigationItem setLeftBarButtonItem:newTopViewController.navigationItem.leftBarButtonItem animated:YES];
}
if(newTopViewController.navigationItem.rightBarButtonItem != nil)
{
[topViewController.navigationItem setRightBarButtonItem:newTopViewController.navigationItem.rightBarButtonItem animated:YES];
}
[topViewController setTitle:newTopViewController.title];
//[topViewController.navigationItem.leftBarButtonItem setTitle:newTopViewController.navigationItem.leftBarButtonItem.title];
NSArray *array = nil;
if(rButtonItem != nil)
array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,rButtonItem,nil];
else {
array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,nil];
}
[UIView beginAnimations:@"pop view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePopDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[newTopViewController.view setAlpha: 1.0];
[UIView commitAnimations];
return topViewController;
}
-(void)animationForCrossFadePopDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
UIViewController *c = [(NSArray *)context objectAtIndex:0];
//UIViewController *n = [(NSArray *)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:1];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *r = nil;
//Not all views have a right bar button. If we look for one that isn't there
// we'll crash out and not complete this method, but the program will continue.
//So we need to check if it is therea nd skip it if it isn't.
if([(NSArray *)context count] == 4)
r = [(NSArray *)context objectAtIndex:3];
//pop the current view from the stack without animation
[super popViewControllerAnimated:NO];
//if what was the current veiw controller is not nil, then lets correct the changes
//we made to it.
if(c != nil)
{
//remove the subview we added for the transition
[[c.view.subviews lastObject] removeFromSuperview];
//reset the title we changed
c.title = title;
[title release];
//replace the left bar button that we changed
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//if we were passed a right bar button item, replace that one as well
if(r != nil)
[c.navigationItem setRightBarButtonItem:r animated:NO];
else {
[c.navigationItem setRightBarButtonItem:nil animated:NO];
}
}
}
Вот и все. Вам понадобится дополнительный код, если вы хотите реализовать ротацию. Вам нужно будет установить размер кадра ваших представлений, которые вы добавляете как подпредставления, прежде чем показывать их, иначе вы столкнетесь с проблемами ориентации в альбомной ориентации, но в последний раз, когда вы видели предыдущий вид, это был портрет. Итак, затем вы добавляете его как вспомогательное представление и затемняете его, но оно отображается как портрет, а затем, когда мы выдвигаемся без анимации, то же самое представление, но то, которое находится в стеке, теперь является альбомным. Все выглядит немного странно. Реализация ротации у всех немного отличается, поэтому я не включил здесь свой код.
Надеюсь, это поможет некоторым людям. Я искал что-то вроде этого и не мог ничего найти. Я не думаю, что это идеальный ответ, но на данный момент он работает очень хорошо для меня.