Анимация изменения контроллеров представления без использования стека контроллера навигации, подпредставлений или модальных контроллеров?

142

NavigationController имеет стеки ViewController для управления и ограниченные переходы анимации.

Добавление контроллера представления в качестве вспомогательного представления к существующему контроллеру представления требует передачи событий в контроллер вспомогательного просмотра, что является трудной задачей для управления, загружается с небольшим раздражением и в целом выглядит как неудачный взлом при реализации (Apple также рекомендует против делая это).

Представление модального контроллера представления снова помещает контроллер представления поверх другого, и, хотя у него нет проблем передачи событий, описанных выше, он на самом деле не «меняет» контроллер представления, он укладывает его в стек.

Раскадровки ограничены iOS 5 и почти идеальны, но их нельзя использовать во всех проектах.

Может ли кто-нибудь представить ПРИМЕР СОЛИДНОГО КОДА о способе изменения контроллеров представления без вышеуказанных ограничений и допускает анимированные переходы между ними?

Близкий пример, но без анимации: как использовать несколько пользовательских контроллеров представления iOS без контроллера навигации

Редактировать: использование Nav Controller хорошо, но должны быть анимированные стили перехода (а не просто эффекты слайдов), отображаемый контроллер представления должен быть полностью заменен (не сложен). Если второй контроллер представления должен удалить другой контроллер представления из стека, то он недостаточно инкапсулирован.

Редактировать 2: iOS 4 должна быть базовой ОС для этого вопроса, я должен был уточнить это при упоминании раскадровки (выше).

TigerCoding
источник
1
Вы можете делать собственные переходы анимации с помощью контроллера навигации. Если это приемлемо, удалите это ограничение из своего вопроса, и я опубликую пример кода.
Ричард Брайтвелл
@Richard, если он пропускает хлопоты по управлению стеком и приспосабливает различные анимированные стили перехода между контроллерами представления, тогда использование контроллера навигации в порядке!
TigerCoding
Хорошо. Я потерял терпение и разместил код. Попробуйте. Работает для меня.
Ричард Брайтвелл
@RichardBrightwell, вы сказали здесь, что можно сделать пользовательские анимационные переходы между контроллерами представления, используя контроллер навигации ... как? Можете ли вы опубликовать пример? Спасибо.
Утка

Ответы:

108

РЕДАКТИРОВАТЬ: Новый ответ, который работает в любой ориентации. Оригинальный ответ работает только тогда, когда интерфейс находится в портретной ориентации. Это анимация перехода вида b / c, которая заменяет вид с другим видом, который должен иметь место, если вид, по меньшей мере, на уровень ниже первого вида, добавленного в окно (напримерwindow.rootViewController.view.anotherView).

Я реализовал простой контейнерный класс, который я назвал TransitionController. Вы можете найти его на https://gist.github.com/1394947 .

Кроме того, я предпочитаю реализацию в отдельном классе, потому что ее проще использовать повторно. Если вы этого не хотите, вы можете просто реализовать ту же логику непосредственно в делегате приложения, устраняя необходимость в TransitionControllerклассе. Однако логика, которая вам нужна, будет такой же.

Используйте это следующим образом:

В вашем приложении делегат

// add a property for the TransitionController

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    MyViewController *vc = [[MyViewContoller alloc] init...];
    self.transitionController = [[TransitionController alloc] initWithViewController:vc];
    self.window.rootViewController = self.transitionController;
    [self.window makeKeyAndVisible];
    return YES;
}

Для перехода на новый контроллер представления из любого контроллера представления

- (IBAction)flipToView
{
    anotherViewController *vc = [[AnotherViewController alloc] init...];
    MyAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
    [appDelegate.transitionController transitionToViewController:vc withOptions:UIViewAnimationOptionTransitionFlipFromRight];
}

РЕДАКТИРОВАТЬ: оригинальный ответ ниже - работает только для ориентации портрета

Я сделал следующие предположения для этого примера:

  1. У вас есть контроллер вида, назначенный в качестве rootViewControllerвашего окна

  2. Когда вы переключаетесь на новое представление, вы хотите заменить текущий viewController на viewController, которому принадлежит новое представление. В любое время активен только текущий viewController (например, alloc'ed).

Код может быть легко изменен для работы по-другому, ключевой момент - анимированный переход и контроллер единого представления. Убедитесь, что вы не сохраняете контроллер представления нигде, кроме того, чтобы назначить его window.rootViewController.

Код для анимации перехода в приложении делегат

- (void)transitionToViewController:(UIViewController *)viewController
                    withTransition:(UIViewAnimationOptions)transition
{
    [UIView transitionFromView:self.window.rootViewController.view
                        toView:viewController.view
                      duration:0.65f
                       options:transition
                    completion:^(BOOL finished){
                        self.window.rootViewController = viewController;
                    }];
}

Пример использования в контроллере вида

- (IBAction)flipToNextView
{
    AnotherViewController *anotherVC = [[AnotherVC alloc] init...];
    MyAppDelegate *appDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
    [appDelegate transitionToViewController:anotherVC
                             withTransition:UIViewAnimationOptionTransitionFlipFromRight];
}
XJones
источник
1
Да, очень мило! Мало того, что это делает трюк, но это очень простой и чистый пример кода. Большое спасибо!
TigerCoding
Разве его не вызывает проблемы в ландшафте для вас? Также: это вызывает методы willAppear и didAppear для viewControllers?
Феликс Ламуру
1
Я знаю, что появляются вызовы, потому что я их записал. Я также не понимаю, почему это повлияет на изменение ориентации. Можете ли вы объяснить, почему вы думаете, что это будет?
TigerCoding
1
@XJones отличное решение, прекрасно работает в моем приложении для iPad, за исключением одной проблемы, с которой я сталкиваюсь, после первой инициализации transVC = [TransitionController ... initWithViewController: aUINavCtrlObj]; window.rootVC = transVC; представление в aUINavCtrlObj дополняется на 20 пикселей сверху (т.е. нижние 20 пикселей выходят за пределы экрана (UIWindow) во всех ориентациях), но после того, как я сделаю [transVC transitionToViewController: anotherVC], заполнение пропало. Я попытался wantFullScreenLayout = NO в loadView TransitionController, он добавляет черную область размером 20 пикселов прямо под statusBar.
Абдулиам Рехманиус
1
@AbduliamRehmanius: у меня была такая же проблема. Я исправил это, изменив строку 25 TransitionController.mна UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];, но я использовал это только на самой новой версии iOS, поэтому протестируйте внимательно.
Фил Кэлвин
67

Вы можете использовать новую систему сдерживания viewController от Apple. Для получения более подробной информации ознакомьтесь с видео сеанса WWDC 2011 «Внедрение UIViewControllerсдерживания».

Новое в iOS5, UIViewControllerContainment позволяет вам иметь родительский viewController и несколько дочерних viewController, которые содержатся в нем. Вот как работает UISplitViewController. Делая это, вы можете сложить контроллеры представления в родительском, но для вашего конкретного приложения вы просто используете родительский для управления переходом от одного видимого viewController к другому. Это одобренный Apple способ ведения дел, и анимация от одного дочернего viewController безболезненна. Кроме того, вы можете использовать все различные UIViewAnimationOptionпереходы!

Кроме того, с UIViewContainment, вам не нужно беспокоиться, если вы не хотите, о беспорядке управления дочерними viewControllers во время событий ориентации. Вы можете просто использовать следующее, чтобы убедиться, что ваш parentViewController перенаправляет события вращения в дочерние viewControllers.

- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{
    return YES;
}

Вы можете сделать следующее или подобное в методе viewDidLoad вашего родителя, чтобы настроить первый childViewController:

[self addChildViewController:self.currentViewController];
[self.view addSubview:self.currentViewController.view];
[self.currentViewController didMoveToParentViewController:self];
[self.currentViewController.swapViewControllerButton setTitle:@"Swap" forState:UIControlStateNormal];

затем, когда вам нужно изменить дочерний viewController, вы вызываете что-то вроде следующего в родительском viewController:

-(void)swapViewControllers:(childViewController *)addChildViewController:aNewViewController{
     [self addChildViewController:aNewViewController];
     __weak __block ViewController *weakSelf=self;
     [self transitionFromViewController:self.currentViewController
                       toViewController:aNewViewController
                               duration:1.0
                                options:UIViewAnimationOptionTransitionCurlUp
                             animations:nil
                             completion:^(BOOL finished) {
                                   [aNewViewController didMoveToParentViewController:weakSelf];

                                   [weakSelf.currentViewController willMoveToParentViewController:nil];
                                   [weakSelf.currentViewController removeFromParentViewController];

                                   weakSelf.currentViewController=[aNewViewController autorelease];
                             }];
 }

Я разместил полный пример проекта здесь: https://github.com/toolmanGitHub/stackedViewControllers . Этот другой проект показывает, как использовать UIViewControllerContainment на некоторых входных типах viewController, которые не занимают весь экран. Удачи

timthetoolman
источник
1
Отличный пример. Спасибо, что нашли время опубликовать код. С другой стороны, он ограничен только iOS 5. Как упоминалось в вопросе: «[Раскадровки] ограничены iOS 5 и почти идеальны, но не могут использоваться во всех проектах». Учитывая, что большой процент (около 40%?) Клиентов по-прежнему используют iOS 4, цель состоит в том, чтобы предоставить то, что работает в iOS 4 и более поздних версиях.
TigerCoding
Вы не должны звонить [self.currentViewController willMoveToParentViewController:nil];до перехода?
Феликс Ламуру
@FelixLam - согласно документам по содержанию UIViewController вы вызываете willMoveToParentViewController, только если вы переопределяете метод addChildViewController. В этом примере я называю это, но не переопределяю.
timthetoolman
2
В демоверсии WWDC я, кажется, помню, что они вызывали его перед вызовом, начиная переход, потому что переход не подразумевает, что currentVC переместится в ноль. В случае UITabBarController переход не изменит родителя vc. Удаление из родительского объекта вызывает didMoveToParentViewController: nil, но нет вызова воли .... ИМХО
Феликс Ламуру
7

Хорошо, я знаю, что вопрос говорит без использования навигационного контроллера, но нет причин не делать этого. ОП не отвечала на комментарии вовремя, чтобы я пошла спать. Не голосуй за меня. :)

Вот как вывести текущий контроллер представления и перейти к новому контроллеру представления с помощью контроллера навигации:

UINavigationController *myNavigationController = self.navigationController;
[[self retain] autorelease];

[myNavigationController popViewControllerAnimated:NO];

PreferencesViewController *controller = [[PreferencesViewController alloc] initWithNibName:nil bundle:nil];

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.65];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:myNavigationController.view cache:YES];
[myNavigationController pushViewController:controller animated:NO];
[UIView commitAnimations];

[controller release];
Ричард Брайтвелл
источник
Разве это не контроллеры стека?
TigerCoding
1
Да, потому что мы используем навигационный контроллер. Тем не менее, это обходит ограничение на то, какие переходы вы можете выполнять, что, как я думал, было основой вашего вопроса.
Ричард Брайтвелл
Близко, но одна из больших проблем в том, что в стеке есть несколько контроллеров представления. Я ищу способ полностью изменить представление контроллеров. =)
TigerCoding
Ах. У меня тоже может быть что-то подобное ... дай мне минуту. Если нет, я удалю этот ответ.
Ричард Брайтвелл
Хорошо, я собрал два разных куска кода. Я думаю, что это будет делать то, что вы хотите.
Ричард Брайтвелл
3

Поскольку я только что натолкнулся на эту точную проблему и попробовал варианты всех ранее существовавших ответов на ограниченный успех, я опубликую, как в конце концов решил ее:

Как описано в этом посте на пользовательских сегментах , на самом деле очень легко создавать собственные сегменты. Их также очень легко подключить в Интерфейсном Разработчике, они поддерживают отношения в IB видимыми и не требуют особой поддержки со стороны контроллеров представления исходного / конечного объекта.

В сообщении, приведенном выше, приведен код iOS 4, позволяющий заменить текущий контроллер вида сверху в стеке navigationController новым, использующим анимацию скольжения сверху.

В моем случае я хотел, чтобы произошла похожая замена, но с FlipFromLeftпереходом. Мне также нужна была только поддержка iOS 5+. Код:

Из RAFlipReplaceSegue.h:

#import <UIKit/UIKit.h>

@interface RAFlipReplaceSegue : UIStoryboardSegue
@end

Из RAFlipReplaceSegue.m:

#import "RAFlipReplaceSegue.h"

@implementation RAFlipReplaceSegue

-(void) perform
{
    UIViewController *destVC = self.destinationViewController;
    UIViewController *sourceVC = self.sourceViewController;
    [destVC viewWillAppear:YES];

    destVC.view.frame = sourceVC.view.frame;

    [UIView transitionFromView:sourceVC.view
                        toView:destVC.view
                      duration:0.7
                       options:UIViewAnimationOptionTransitionFlipFromLeft
                    completion:^(BOOL finished)
                    {
                        [destVC viewDidAppear:YES];

                        UINavigationController *nav = sourceVC.navigationController;
                        [nav popViewControllerAnimated:NO];
                        [nav pushViewController:destVC animated:NO];
                    }
     ];
}

@end

Теперь, удерживая нажатой клавишу Control, настройте любой другой вид перехода, затем сделайте его пользовательским и введите имя пользовательского класса segue, et voilà!

Райан Артекона
источник
Есть ли способ это программно без раскадровки?
Закданс
Насколько я знаю, чтобы использовать segue, вы должны определить его и дать ему идентификатор в раскадровке. Вы можете вызвать переход в коде с помощью –performSegueWithIdentifier:sender:метода UIViewController .
Райан Артекона
1
Вы никогда не должны вызывать viewDidXXX и viewWillXXX напрямую.
Бен Синклер
2

Я долго боролся с этим, и один из моих вопросов указан здесь , я не уверен, что у вас была эта проблема. Но вот что я бы порекомендовал, если он должен работать с iOS 4.

Во-первых, создайте новый NavigationControllerкласс. Вот где мы выполним всю грязную работу - другие классы смогут «чисто» вызывать методы экземпляра типа « pushViewController:такой-то». В вашем .h:

@interface NavigationController : UIViewController {
    NSMutableArray *childViewControllers;
    UIViewController *currentViewController;
}

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL))completion;
- (void)addChildViewController:(UIViewController *)childController;
- (void)removeChildViewController:(UIViewController *)childController;

Массив дочерних контроллеров представления будет служить хранилищем для всех контроллеров представления в нашем стеке. Мы автоматически перенаправим весь код поворота и изменения размера из NavigationControllerпредставления в currentController.

Теперь в нашей реализации:

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL))completion
{
    currentViewController = [toViewController retain];
    // Put any auto- and manual-resizing handling code here

    [UIView animateWithDuration:duration animations:animations completion:completion];

    [fromViewController.view removeFromSuperview];
}

- (void)addChildViewController:(UIViewController *)childController {
    [childViewControllers addObject:childController];
}

- (void)removeChildViewController:(UIViewController *)childController {
    [childViewControllers removeObject:childController];
}

Теперь вы можете реализовать свой собственный pushViewController:, popViewControllerи тому подобное, используя эти вызовы методов.

Удачи, и я надеюсь, что это поможет!

aopsfan
источник
Опять же, мы должны прибегнуть к добавлению контроллеров представления в качестве вложенных представлений существующих контроллеров представления. Конечно, это то, что делает существующий Navigation Controller, но это означает, что мы в основном должны переписать его и все его методы. В действительности, мы должны избегать распространения viewWillAppear и подобных методов. Я начинаю думать, что нет чистого способа сделать это. Тем не менее, я благодарю вас за потраченное время и усилия!
TigerCoding
Я думаю, что с этой системой добавления и удаления контроллеров представления по мере необходимости это решение предотвращает необходимость перенаправления этих методов.
Aopsfan
Ты уверен? Раньше я заменял контроллеры представления подобным образом, и мне всегда приходилось пересылать сообщения. Вы можете подтвердить иначе?
TigerCoding
Нет, я не уверен, но я предположил бы , что, до тех пор , как вы удалите вид на контроллерах View , как они исчезают, и добавить их , так как они появляются, то должны автоматически триггером viewWillAppear, viewDidAppearи тому подобное.
Aopsfan
-1
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UINavigationController *viewController = (UINavigationController *)[storyboard instantiateViewControllerWithIdentifier:@"storyBoardIdentifier"];
viewController.modalTransitionStyle = UIModalTransitionStylePartialCurl;
[self presentViewController:viewController animated:YES completion:nil];

Попробуйте этот код.


Этот код дает Переход от контроллера представления к другому контроллеру представления, который имеет контроллер навигации.

Дипак Кумар Саху
источник