Анімальна зміна контролерів перегляду, не використовуючи стек навігаційного контролера, підпогляди чи модальні контролери?


142

NavigationControllers мають стеки ViewController для управління та обмежені переходи анімації.

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

Якщо представити контролер модального перегляду, знову розміщуємо контролер перегляду поверх іншого, і хоча у нього немає проблем, що переносяться подіями, описаних вище, він насправді не 'замінює' контролер перегляду, він зберігає його.

Дошки для розгортання обмежені iOS 5 і майже ідеальні, але не можуть бути використані у всіх проектах.

Чи може хтось подати ПРИКЛАД ТВЕРДОГО КОДУ на спосіб зміни контролерів перегляду без вищезазначених обмежень і допускає анімовані переходи між ними?

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

Редагувати: користуватися Nav Controller нормально, але там повинні бути анімовані стилі переходу (не просто слайд-ефекти), показаний контролер перегляду потрібно повністю замінити (не складати). Якщо другий контролер подання повинен видалити інший контролер подання зі стека, він недостатньо інкапсульований.

Редагувати 2: iOS 4 має бути базовою ОС для цього питання, я мав би уточнити, що, згадуючи дошки для розповідей (вище).


1
Ви можете виконати власні анімаційні переходи за допомогою навігаційного контролера. Якщо це буде прийнятним, видаліть це обмеження із запитання, і я опублікую приклад коду.
Річард Брайтвелл

@ Richard, якщо він пропускає клопоти управління стеком і вміщує різні анімовані стилі переходу між контролерами перегляду, то використання контролера навігації прекрасно!
TigerCoding

ОК добре. Я нетерпляче і розмістив код. Спробувати. Працює для мене.
Річард Брайтвелл

@RichardBrightwell Ви тут говорили, що можна робити власні анімаційні переходи між контролерами перегляду за допомогою навігаційного контролера ... як? Чи можете ви розмістити приклад? Дякую.
Качка

Відповіді:


108

EDIT: Нова відповідь, яка працює в будь-якій орієнтації. Оригінальна відповідь працює лише тоді, коли інтерфейс знаходиться в портретній орієнтації. Це анімації переходу 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];
}

EDIT: Оригінальний відповідь нижче - працює лише для орієнтації на портати

Я зробив наступні припущення для цього прикладу:

  1. У rootViewControllerвашому вікні призначений контролер перегляду

  2. Коли ви переходите на новий вид, ви хочете замінити поточний вигляд Контролера на viewController, що володіє новим поданням. У будь-який час живий лише поточний вигляд Контролера (наприклад, виділено).

Код можна легко змінити, щоб він працював по-різному, ключовим моментом є анімований перехід і контролер єдиного перегляду. Переконайтеся, що ви не зберігаєте контролер перегляду в будь-якому місці поза його призначенням 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];
}

1
Так, дуже приємно! Це не тільки робить трюк, але це дуже простий і чистий приклад коду. Велике дякую!
TigerCoding

Чи не викликає він у вас проблем з ландшафтом? Також: чи це запускає методи willAppear та didAppear viewControllers?
Фелікс Ламуру

1
Я знаю, що з'являються дзвінки здійснюються, тому що я реєстрував їх. Я також не бачу, чому це вплине на зміни орієнтації. Чи можете ви пояснити, чому ви думаєте, що це було б?
TigerCoding

1
@XJones чудове рішення, працює досить добре в моєму додатку iPad, за винятком однієї проблеми, з якою я стикаюся, після першої ініціалізації transVC = [TransitionController ... initWithViewController: aUINavCtrlObj]; window.rootVC = transVC; вигляд у aUINavCtrlObj підкладений на 20 пікселів зверху (тобто знизу 20 пікселів поза межами екрана (UIWindow), межі в усіх орієнтаціях), але після того, як я перейду [transVC translationToViewController: anotherVC], прокладка відпала. Я спробував WantFullScreenLayout = НІ в loadView TransitionController, що це робиться, це додає чорну зону 20 пікселів просто під статусом Bar.
Абдуліам Рехманій

1
@AbduliamRehmanius: У мене була така ж проблема. Я виправив це, змінивши рядок 25 TransitionController.mна UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];, але я використовував це лише в самій новій версії iOS, тому перевіряйте ретельно.
Філ Кальвін

67

Ви можете використовувати нову систему стримування viewController від Apple. Для отримання більш поглибленої інформації дивіться відео сесії WWDC 2011 "Реалізація UIViewControllerрозваг".

Нове для iOS5, UIViewControllerContainment дозволяє мати батьківський переглядController та ряд дочірніх представниківControllers, які містяться в ньому. Так працює UISplitViewController. Виконуючи це, ви можете складати контролери подання даних у батьків, але для конкретного додатку ви просто використовуєте батьківський для управління переходом від одного видимого viewController до іншого. Це затверджений Apple спосіб робити речі, і анімація з одного дитячого перегляду Контролер безболісний. Крім того, ви можете використовувати всі різні UIViewAnimationOptionпереходи!

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

- (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];

тоді, коли вам потрібно змінити дочірній виглядController, ви викликаєте щось за рядками наступного в батьківському переглядіController:

-(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 для різних типів перегляду входівController, які не займають весь екран. Удачі


1
Чудовий приклад. Дякуємо, що знайшли час для публікації коду. З іншого боку, він обмежений лише iOS 5. Як зазначалося в запитанні: "[Дошки розказок] обмежені до iOS 5 і майже ідеальні, але не можуть бути використані у всіх проектах." Зважаючи на великий відсоток (близько 40%?) Клієнтів все ще використовують iOS 4, мета - забезпечити щось, що працює в iOS 4 і вище.
TigerCoding

Чи не слід телефонувати [self.currentViewController willMoveToParentViewController:nil];до переходу?
Фелікс Ламору

@FelixLam - за документами, що містяться в вмісті UIViewController, ви викликаєте willMoveToParentViewController лише у випадку, якщо ви перекриєте метод addChildViewController. У цьому прикладі я це називаю, але не переважаю.
timthetoolman

2
У демонстраційній програмі на WWDC я, мабуть, пам’ятаю, що вони називали її перед тим, як викликати початок переходу, тому що перехід не означає, що поточний VC переміститься до нуля. У випадку UITabBarController перехід не змінить жодного з батьківських джерел. Видалити з батьківського виклику didMoveToParentViewController: нуль, але немає ... дзвінка. ІМХО
Фелікс Ламуру

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

Оскільки я щойно стикався з цією точною проблемою і намагався змінити всі попередні відповіді з обмеженим успіхом, я опублікую, як я врешті вирішив її:

Як описано в цій публікації на користувальницьких сеґах , насправді дуже просто зробити власні сеги. Вони також дуже легко підключитися в Interface Builder, вони зберігають стосунки в ІБ, і їм не потрібна велика підтримка з боку контролерів подання джерела / місця призначення.

Посилання, пов’язане вище, надає код 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

Тепер натисніть клавішу управління, щоб налаштувати будь-який інший тип segue, а потім зробіть це користувацьким способом і введіть ім'я користувацького класу segue et et voilà!


Чи є шлях до цього програмно без розгортки?
zakdances

Наскільки я знаю, щоб використовувати 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's' на ' 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і , наприклад, з допомогою цих викликів методів.

Удачі, і я сподіваюся, що це допоможе!


Знову ж таки, ми повинні вдатися до додавання контролерів подання як підпогляди існуючих контролерів перегляду. Зрозуміло, що це робить існуючий контролер навігації, але це означає, що ми в основному повинні переписати його та всі його методи. Насправді нам слід уникати поширення 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];

Спробуйте цей код.


Цей код дає перехід від контролера перегляду до іншого контролера перегляду, який має контролер навігації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.