Как изменить анимацию Push и Pop в приложении на основе навигации
У меня есть приложение на основе навигации, и я хочу изменить анимацию анимации push и pop. Как бы я это сделал?
Изменить 2018
Было много ответов на этот вопрос, и прошло уже довольно много времени, я переизбрал ответ на то, что я считаю наиболее актуальным сейчас. Если есть кто-то, кто думает иначе, пожалуйста, дайте мне знать в комментариях
24 ответа
Я сделал следующее, и это работает нормально.. и это просто и легко понять..
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[[self navigationController] popViewControllerAnimated:NO];
И то же самое для толчка..
Версия Swift 3.0:
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey: nil)
_ = self.navigationController?.popToRootViewController(animated: false)
Так мне всегда удавалось выполнить эту задачу.
Для толчка:
MainView *nextView=[[MainView alloc] init];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[nextView release];
Для поп:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.375];
[self.navigationController popViewControllerAnimated:NO];
[UIView commitAnimations];
Я все еще получаю много отзывов об этом, поэтому я собираюсь продолжить и обновлять его, чтобы использовать блоки анимации, что в любом случае является рекомендованным Apple способом создания анимации.
Для толчка:
MainView *nextView = [[MainView alloc] init];
[UIView animateWithDuration:0.75
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
}];
Для поп:
[UIView animateWithDuration:0.75
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
}];
[self.navigationController popViewControllerAnimated:NO];
Как изменить анимацию Push и Pop в приложении для навигации...
Для 2018 года "окончательный ответ!"
Преамбула Скажите, что вы новичок в разработке для iOS, возможно, из Android. Весьма запутанно, но Apple предоставляет два (2) перехода, которые можно легко использовать. Это: "кроссфейд" и "флип". Теперь, два самых бесполезных перехода во всех вычислениях - это кроссфейд и флип, которые никто не использует. Если вы хотите сделать самые обычные переходы, такие как "соскальзывание", вы должны выполнить ОГРОМНОЕ количество работы. Эта работа, объясняется в этом посте!
Во-первых, если вы хотите использовать одно из двух аниме, которые предоставляет Apple (кроссфейд, флипс), это просто - используйте решение @PeterDeWeese, описанное выше.
Во-вторых, есть старый быстрый и грязный быстрый переход CATransition. Это подробно объясняется здесь. Это действительно не работает, и не является реалистичным решением.
Иначе, как ни странно, вам придется приложить все усилия, чтобы сделать полный пользовательский переход.
Повторить:
даже если вы просто хотите получить самый простой, самый распространенный переход с переездом / с переездом, к лучшему или худшему, вам придется реализовать полный пользовательский переход.
Вот как это сделать...
1. Вам нужен кастом UIViewControllerAnimatedTransitioning
Тебе нужен собственный бул
popStyle
, (Это срабатывает, или выключается?)Вы должны включить
transitionDuration
(тривиальный) и главный вызов,animateTransition
На самом деле вам нужно будет написать две разные процедуры для внутри
animateTransition
, Один для толчка и один для поп-музыки. Вероятно, назовите ихanimatePush
а такжеanimatePop
, внутриanimateTransition
Просто ответь наpopStyle
на две процедурыВ приведенном ниже примере выполняется простое перемещение / удаление
В вашем
animatePush
а такжеanimatePop
Подпрограммы. Вы должны получить "от просмотра" и "для просмотра". (Как показано в примере.)и главное, что вы должны сделать, это, действительно,
addSubview
для нового просмотра "до".ты должен позвонить
completeTransition
в конце вашего аниме
Так..
class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning {
var popStyle: Bool = false
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.20
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if popStyle {
animatePop(using: transitionContext)
return
}
let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let f = transitionContext.finalFrame(for: tz)
let fOff = f.offsetBy(dx: f.width, dy: 55)
tz.view.frame = fOff
transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
tz.view.frame = f
}, completion: {_ in
transitionContext.completeTransition(true)
})
}
func animatePop(using transitionContext: UIViewControllerContextTransitioning) {
let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let f = transitionContext.initialFrame(for: fz)
let fOffPop = f.offsetBy(dx: f.width, dy: 55)
transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
fz.view.frame = fOffPop
}, completion: {_ in
transitionContext.completeTransition(true)
})
}
}
А потом...
2. Используйте его в вашем контроллере представления.
Обратите внимание, что вам нужно сделать это только в "первом" контроллере вида.
Тот, который вы попали на вершине, ничего не делать. Легко.
Итак, ваш класс...
class SomeScreen: UIViewController {
}
становится...
class FrontScreen: UIViewController,
UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
let simpleOver = SimpleOver()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
func navigationController(
_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationControllerOperation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
simpleOver.popStyle = (operation == .pop)
return simpleOver
}
}
Вот и все.
Толчок и поп точно так же, как обычно, без изменений. Толкать...
let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
.instantiateViewController(withIdentifier: "nextScreenStoryboardID")
as! NextScreen
navigationController?.pushViewController(n, animated: true)
и выложить его, если хотите, просто сделайте это на следующем экране:
class NextScreen: TotallyOrdinaryUIViewController {
@IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
navigationController?.popViewController(animated: true)
}
}
Это "так просто":O
Для толчка
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:ViewControllerYouWantToPush animated:NO];
для поп
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];
Помните, что в Swift, расширение определенно ваши друзья!
public extension UINavigationController {
/**
Pop current view controller to previous view controller.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func pop(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.popViewControllerAnimated(false)
}
/**
Push a new view controller on the view controllers's stack.
- parameter vc: view controller to push.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func push(viewController vc: UIViewController, transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.pushViewController(vc, animated: false)
}
private func addTransition(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = type
self.view.layer.addAnimation(transition, forKey: nil)
}
}
@Magnus ответ, только тогда для Swift (2.0)
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromTop
self.navigationController!.view.layer.addAnimation(transition, forKey: nil)
let writeView : WriteViewController = self.storyboard?.instantiateViewControllerWithIdentifier("WriteView") as! WriteViewController
self.navigationController?.pushViewController(writeView, animated: false)
Некоторые sidenotes:
Вы можете сделать это также с Segue, просто внедрите это в prepareForSegue
или же shouldPerformSegueWithIdentifier
, Тем не менее, это также сохранит анимацию по умолчанию. Чтобы это исправить, вам нужно перейти на раскадровку, нажать "Segue" и снять флажок "Animates". Но это ограничит ваше приложение для IOS 9.0 и выше (по крайней мере, когда я сделал это в Xcode 7).
При выполнении последующих двух строк следует заменить на:
self.navigationController?.popViewControllerAnimated(false)
Даже если я установил значение false, это как бы игнорирует это.
Использование личных звонков - плохая идея, так как Apple больше не одобряет приложения, которые делают это. Может быть, вы могли бы попробовать это:
//Init Animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.50];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:YES];
//Create ViewController
MyViewController *myVC = [[MyViewController alloc] initWith...];
[self.navigationController pushViewController:myVC animated:NO];
[myVC release];
//Start Animation
[UIView commitAnimations];
Это очень просто
self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
Так как это лучший результат в Google, я решил поделиться тем, что я считаю самым вменяемым способом; который должен использовать переходный API iOS 7+. Я реализовал это для iOS 10 с Swift 3.
Это довольно просто объединить это с тем, как UINavigationController
анимация между двумя контроллерами представления, если вы создаете подкласс UINavigationController
и вернуть экземпляр класса, который соответствует UIViewControllerAnimatedTransitioning
протокол.
Например вот мой UINavigationController
подкласс:
class NavigationController: UINavigationController {
init() {
super.init(nibName: nil, bundle: nil)
delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension NavigationController: UINavigationControllerDelegate {
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return NavigationControllerAnimation(operation: operation)
}
}
Вы можете видеть, что я установил UINavigationControllerDelegate
сам по себе, и в расширении на моем подклассе я реализую метод в UINavigationControllerDelegate
это позволяет вам возвращать пользовательский контроллер анимации (т.е. NavigationControllerAnimation
). Этот пользовательский контроллер анимации заменит вам стандартную анимацию.
Вы, наверное, удивляетесь, почему я передаю эту операцию NavigationControllerAnimation
экземпляр через его инициализатор. Я делаю это так, чтобы в NavigationControllerAnimation
Реализация UIViewControllerAnimatedTransitioning
Протокол Я знаю, что это за операция (т. е. "push" или "pop"). Это помогает узнать, какую анимацию я должен делать. В большинстве случаев вы хотите выполнить другую анимацию в зависимости от операции.
Остальное довольно стандартно. Реализуйте две необходимые функции в UIViewControllerAnimatedTransitioning
протокол и анимация, как вам нравится:
class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning {
let operation: UINavigationControllerOperation
init(operation: UINavigationControllerOperation) {
self.operation = operation
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
let containerView = transitionContext.containerView
if operation == .push {
// do your animation for push
} else if operation == .pop {
// do your animation for pop
}
}
}
Важно помнить, что для каждого отдельного типа операции (т. Е. "Push" или "pop") контроллеры представления "в" и "из" будут разными. Когда вы работаете в режиме push, контроллером для просмотра будет тот, который будет выдвинут. Когда вы работаете в режиме pop, контроллер для просмотра будет тем, на который выполняется переход, и контроллером from для представления будет тот, который выскочил.
Так же to
контроллер представления должен быть добавлен как подпредставление containerView
в контексте перехода.
Когда ваша анимация завершится, вы должны позвонить transitionContext.completeTransition(true)
, Если вы делаете интерактивный переход, вам придется динамически возвращать Bool
в completeTransition(didComplete: Bool)
в зависимости от того, завершен ли переход в конце анимации.
Наконец (необязательно чтение), вы можете захотеть посмотреть, как я сделал переход, над которым я работал. Этот код немного более хакерский, и я написал его довольно быстро, поэтому я бы не сказал, что это отличный анимационный код, но он все еще показывает, как выполнять анимационную часть.
Мой был действительно простой переход; Я хотел имитировать ту же анимацию, что обычно делает UINavigationController, но вместо анимации "следующая страница поверх", которую я это делал, я хотел реализовать анимацию 1:1 старого контроллера представления в то же время, что и новое представление контроллер появляется. Это приводит к тому, что два контроллера представления выглядят так, как будто они прикреплены друг к другу.
Для операции толчка, которая требует сначала настройки toViewController
на начало координат на оси X, добавив его в качестве подпредставления containerView
, анимируя его на экране, установив, что origin.x
в ноль. В то же время я оживляю fromViewController
открываю origin.x
с экрана:
toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0)
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations: {
toViewController.view.frame = containerView.bounds
fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0)
},
completion: { (finished) in
transitionContext.completeTransition(true)
})
Поп-операция в основном обратная. Добавить toViewController
как подпредставление containerView
и оживить fromViewController
вправо, как вы оживляете в toViewController
слева:
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations: {
fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0)
toViewController.view.frame = containerView.bounds
},
completion: { (finished) in
transitionContext.completeTransition(true)
})
Вот суть всего файла swift:
https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be
На основе jordanperry
обновлен ответ для swift 4
Для толчка UIViewController
let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController
UIView.animate(withDuration: 0.75, animations: {() -> Void in
UIView.setAnimationCurve(.easeInOut)
self.navigationController?.pushViewController(terms, animated: true)
UIView.setAnimationTransition(.flipFromRight, for: (self.navigationController?.view)!, cache: false)
})
Для поп
UIView.animate(withDuration: 0.75, animations: {() -> Void in
UIView.setAnimationCurve(.easeInOut)
UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false)
})
navigationController?.popViewController(animated: false)
@Лука Давандзо ответ в Swift 4.2
public extension UINavigationController {
/**
Pop current view controller to previous view controller.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func pop(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.popViewController(animated: false)
}
/**
Push a new view controller on the view controllers's stack.
- parameter vc: view controller to push.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func push(viewController vc: UIViewController, transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.pushViewController(vc, animated: false)
}
private func addTransition(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.type = type
self.view.layer.add(transition, forKey: nil)
}
}
Есть UINavigationControllerDelegate и UIViewControllerAnimatedTransitioning, где вы можете изменить анимацию для чего угодно.
Например, это вертикальная поп-анимация для VC:
@objc class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let containerView = transitionContext.containerView()
let bounds = UIScreen.mainScreen().bounds
containerView!.insertSubview(toViewController.view, belowSubview: fromViewController.view)
toViewController.view.alpha = 0.5
let finalFrameForVC = fromViewController.view.frame
UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
fromViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.height)
toViewController.view.alpha = 1.0
}, completion: {
finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
}
}
А потом
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .Pop {
return PopAnimator()
}
return nil;
}
Полезный учебник https://www.objc.io/issues/5-ios7/view-controller-transitions/
Теперь вы можете использовать UIView.transition
, Обратите внимание, что animated:false
, Это работает с любой опцией перехода, pop, push или заменой стека.
if let nav = self.navigationController
{
UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: {
_ = nav.popViewController(animated:false)
}, completion:nil)
}
Вот как я сделал то же самое в Swift:
Для толчка:
UIView.animateWithDuration(0.75, animations: { () -> Void in
UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
self.navigationController!.pushViewController(nextView, animated: false)
UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false)
})
Для поп:
На самом деле я сделал это немного иначе, чем некоторые из приведенных выше ответов, но, поскольку я новичок в разработке Swift, это может быть неправильно. Я переопределил viewWillDisappear:animated:
и добавил туда поп-код:
UIView.animateWithDuration(0.75, animations: { () -> Void in
UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
})
super.viewWillDisappear(animated)
Я недавно пытался сделать что-то подобное. Я решил, что мне не нравится скользящая анимация 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];
}
}
}
Вот и все. Вам понадобится дополнительный код, если вы хотите реализовать ротацию. Вам нужно будет установить размер кадра ваших видов, которые вы добавляете как подпредставления, прежде чем показывать их, в противном случае вы столкнетесь с проблемами ориентации по горизонтали, но в последний раз, когда вы видели предыдущий вид, это был портрет. Итак, затем вы добавляете его как вспомогательное представление и затемняете его, но он отображается как портрет, а затем, когда мы выдвигаемся без анимации, то же самое представление, но то, которое находится в стеке, теперь является альбомным. Все выглядит немного странно. Реализация ротации у всех немного отличается, поэтому я не включил здесь свой код.
Надеюсь, это поможет некоторым людям. Я искал что-то вроде этого и не мог ничего найти. Я не думаю, что это идеальный ответ, но на данный момент он работает очень хорошо для меня.
Используя ответ iJordan как источник вдохновения, почему бы просто не создать категорию на UINavigationController для использования во всем приложении вместо копирования / вставки этого кода анимации повсюду?
UINavigationController + animation.h
@interface UINavigationController (Animation)
- (void) pushViewControllerWithFlip:(UIViewController*) controller;
- (void) popViewControllerWithFlip;
@end
UINavigationController + Animation.m
@implementation UINavigationController (Animation)
- (void) pushViewControllerWithFlip:(UIViewController *) controller
{
[UIView animateWithDuration:0.50
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[self pushViewController:controller animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
}];
}
- (void) popViewControllerWithFlip
{
[UIView animateWithDuration:0.5
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
}];
[self popViewControllerAnimated:NO];
}
@end
Затем просто импортируйте файл UINavigationController+Animation.h и вызовите его как обычно:
[self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]];
[self.navigationController popViewControllerWithFlip];
Хотя все ответы здесь замечательные и большинство работают очень хорошо, есть немного более простой метод, который достигает того же эффекта...
Для толчка:
NextViewController *nextViewController = [[NextViewController alloc] init];
// Shift the view to take the status bar into account
CGRect frame = nextViewController.view.frame;
frame.origin.y -= 20;
frame.size.height += 20;
nextViewController.view.frame = frame;
[UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) {
[self.navigationController pushViewController:nextViewController animated:NO];
}];
Для поп:
int numViewControllers = self.navigationController.viewControllers.count;
UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view];
[UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
[self.navigationController popViewControllerAnimated:NO];
}];}
Взгляните на http://applidium.github.io/ADTransitionController/, замену UINavigationController с пользовательскими анимациями перехода (его API соответствует API UINavigationController), который мы создали в Applidium.
Вы можете использовать различные предопределенные анимации для таких действий, как Swipe, Fade, Cube, Carrousel, Zoom и так далее.
Я не знаю, как можно публично изменить анимацию перехода.
Если кнопка "назад" не нужна, вы должны использовать модальные контроллеры вида, чтобы иметь переходы "толчок снизу" / "перевернуть" / "исчезнуть" / (≥3.2)"скручивание страницы".
На частной стороне, метод -pushViewController:animated:
вызывает недокументированный метод -pushViewController:transition:forceImmediate:
Так, например, если вы хотите переворачивание слева направо, вы можете использовать
[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];
Однако, вы не можете изменить "поп" переход.
Посмотрите мой ответ на этот вопрос, чтобы узнать, как сделать это в гораздо меньшем количестве строк кода. Этот метод позволяет вам анимировать псевдо-"Push" нового контроллера представления любым удобным вам способом, и когда анимация завершена, он устанавливает Navigation Controller так же, как если бы вы использовали стандартный метод Push. Мой пример позволяет анимировать слайд слева или справа. Код повторяется здесь для удобства:
-(void) showVC:(UIViewController *) nextVC rightToLeft:(BOOL) rightToLeft {
[self addChildViewController:neighbor];
CGRect offscreenFrame = self.view.frame;
if(rightToLeft) {
offscreenFrame.origin.x = offscreenFrame.size.width * -1.0;
} else if(direction == MyClimbDirectionRight) {
offscreenFrame.origin.x = offscreenFrame.size.width;
}
[[neighbor view] setFrame:offscreenFrame];
[self.view addSubview:[neighbor view]];
[neighbor didMoveToParentViewController:self];
[UIView animateWithDuration:0.5 animations:^{
[[neighbor view] setFrame:self.view.frame];
} completion:^(BOOL finished){
[neighbor willMoveToParentViewController:nil];
[neighbor.view removeFromSuperview];
[neighbor removeFromParentViewController];
[[self navigationController] pushViewController:neighbor animated:NO];
NSMutableArray *newStack = [[[self navigationController] viewControllers] mutableCopy];
[newStack removeObjectAtIndex:1]; //self, just below top
[[self navigationController] setViewControllers:newStack];
}];
}
Я знаю, что эта ветка устарела, но я подумал, что вложу свои два цента. Вам не нужно создавать собственную анимацию, есть простой (возможно, хитрый) способ сделать это. Вместо использования push создайте новый контроллер навигации, сделайте новый контроллер представления корневым контроллером представления этого контроллера навигации, а затем представьте контроллер навигации из исходного контроллера навигации. Present легко настраивается с помощью множества стилей, и нет необходимости создавать собственную анимацию.
Например:
UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)
И вы можете изменить стиль презентации, как хотите.
Понимая, что это старый вопрос. Я все еще хотел бы опубликовать этот ответ, так как у меня были некоторые проблемы, появляющиеся несколько viewControllers
с предложенными ответами. Мое решение состоит в том, чтобы подкласс UINavigationController
и переопределить все методы pop и push.
FlippingNavigationController.h
@interface FlippingNavigationController : UINavigationController
@end
FlippingNavigationController.m:
#import "FlippingNavigationController.h"
#define FLIP_DURATION 0.5
@implementation FlippingNavigationController
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[UIView transitionWithView:self.view
duration:animated?FLIP_DURATION:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromRight
animations:^{ [super pushViewController:viewController
animated:NO]; }
completion:nil];
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
return [[self popToViewController:[self.viewControllers[self.viewControllers.count - 2]]
animated:animated] lastObject];
}
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
{
return [self popToViewController:[self.viewControllers firstObject]
animated:animated];
}
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
{
__block NSArray* viewControllers = nil;
[UIView transitionWithView:self.view
duration:animated?FLIP_DURATION:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ viewControllers = [super popToViewController:viewController animated:NO]; }
completion:nil];
return viewControllers;
}
@end
Просто используйте:
ViewController *viewController = [[ViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.navigationBarHidden = YES;
[self presentViewController:navController animated:YES completion: nil];
[viewController release];
[navController release];
Из примера приложения, проверьте этот вариант. https://github.com/mpospese/MPFoldTransition/
#pragma mark - UINavigationController(MPFoldTransition)
@implementation UINavigationController(MPFoldTransition)
//- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
- (void)pushViewController:(UIViewController *)viewController foldStyle:(MPFoldStyle)style
{
[MPFoldTransition transitionFromViewController:[self visibleViewController]
toViewController:viewController
duration:[MPFoldTransition defaultDuration]
style:style
completion:^(BOOL finished) {
[self pushViewController:viewController animated:NO];
}
];
}
- (UIViewController *)popViewControllerWithFoldStyle:(MPFoldStyle)style
{
UIViewController *toController = [[self viewControllers] objectAtIndex:[[self viewControllers] count] - 2];
[MPFoldTransition transitionFromViewController:[self visibleViewController]
toViewController:toController
duration:[MPFoldTransition defaultDuration]
style:style
completion:^(BOOL finished) {
[self popViewControllerAnimated:NO];
}
];
return toController;
}
Я нашел слегка рекурсивный способ сделать это, который работает для моих целей. У меня есть переменная экземпляра BOOL, которую я использую, чтобы заблокировать обычную анимацию всплывающих окон и заменить свое собственное неанимированное поп-сообщение. Переменная изначально установлена на NO. При нажатии кнопки "Назад" метод делегата устанавливает для него значение "YES" и отправляет новое не анимированное всплывающее сообщение на панель навигации, тем самым снова вызывая тот же метод делегата, на этот раз с переменной, установленной в "YES". Если для переменной задано значение YES, метод делегата устанавливает для него значение NO и возвращает YES, чтобы разрешить появление неанимированного всплывающего окна. После второго вызова делегата мы возвращаемся к первому, где возвращается NO, блокируя оригинальную анимацию! Это на самом деле не так грязно, как кажется. Мой метод shouldPopItem выглядит следующим образом:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
if ([[navigationBar items] indexOfObject:item] == 1)
{
[expandedStack restack];
}
if (!progPop)
{
progPop = YES;
[navBar popNavigationItemAnimated:NO];
return NO;
}
else
{
progPop = NO;
return YES;
}
}
Работает для меня.