Ändern von Ansichtssteuerungen ohne Verwendung von Navigationssteuerungsstapel, Unteransichten oder modalen Steuerungen animieren?

142

NavigationController verfügen über zu verwaltende ViewController-Stapel und begrenzte Animationsübergänge.

Das Hinzufügen eines View-Controllers als Unteransicht zu einem vorhandenen View-Controller erfordert das Übergeben von Ereignissen an den Sub-View-Controller. Dies ist mühsam zu verwalten, mit wenig Ärger verbunden und fühlt sich bei der Implementierung im Allgemeinen wie ein schlechter Hack an (Apple rät ebenfalls davon ab Dies tun).

Durch erneutes Präsentieren eines modalen Ansichts-Controllers wird ein Ansichts-Controller über einen anderen gelegt, und obwohl die oben beschriebenen Probleme beim Übergeben von Ereignissen nicht auftreten, wird der Ansichts-Controller nicht wirklich "ausgetauscht", sondern gestapelt.

Storyboards sind auf iOS 5 beschränkt und nahezu ideal, können jedoch nicht in allen Projekten verwendet werden.

Kann jemand ein SOLID CODE-BEISPIEL für eine Möglichkeit zum Ändern von Ansichts-Controllern ohne die oben genannten Einschränkungen vorlegen und animierte Übergänge zwischen ihnen zulassen?

Ein genaues Beispiel, aber keine Animation: So verwenden Sie mehrere benutzerdefinierte iOS-Ansichtscontroller ohne Navigationscontroller

Bearbeiten: Die Verwendung des Nav-Controllers ist in Ordnung, es müssen jedoch animierte Übergangsstile vorhanden sein (nicht nur die Folieneffekte). Der angezeigte View-Controller muss vollständig ausgetauscht (nicht gestapelt) werden. Wenn der zweite Ansichtscontroller einen anderen Ansichtscontroller vom Stapel entfernen muss, ist er nicht ausreichend gekapselt.

Edit 2: iOS 4 sollte das Basisbetriebssystem für diese Frage sein, das hätte ich bei der Erwähnung von Storyboards (oben) klarstellen müssen.

TigerCoding
quelle
1
Sie können benutzerdefinierte Animationsübergänge mit einem Navigationscontroller durchführen. Wenn dies akzeptabel wäre, entfernen Sie diese Einschränkung bitte aus Ihrer Frage, und ich werde ein Codebeispiel veröffentlichen.
Richard Brightwell
@Richard Wenn die Verwaltung des Stapels übersprungen wird und verschiedene animierte Übergangsstile zwischen den Ansichts-Controllern berücksichtigt werden, ist die Verwendung des Navigations-Controllers in Ordnung!
TigerCoding
OK gut. Ich wurde ungeduldig und postete den Code. Versuche es. Funktioniert bei mir.
Richard Brightwell
@RichardBrightwell Sie sagten hier, dass man mit einem Navigations-Controller benutzerdefinierte Animationsübergänge zwischen Ansichts-Controllern durchführen könnte ... wie? Können Sie ein Beispiel posten? Vielen Dank.
Ente

Antworten:

108

BEARBEITEN: Neue Antwort, die in jeder Ausrichtung funktioniert. Die ursprüngliche Antwort funktioniert nur, wenn sich die Benutzeroberfläche im Hochformat befindet. Hierbei handelt es sich um b / c-Ansichtsübergangsanimationen, die eine Ansicht mit einer anderen Ansicht ersetzen. Die Ansichten müssen mindestens eine Ebene unter der ersten zum Fenster hinzugefügten Ansicht liegen (zwindow.rootViewController.view.anotherView.B.).

Ich habe eine einfache Containerklasse implementiert, die ich aufgerufen habe TransitionController. Sie finden es unter https://gist.github.com/1394947 .

Abgesehen davon bevorzuge ich die Implementierung in einer separaten Klasse b / c, die einfacher wiederzuverwenden ist. Wenn Sie das nicht möchten, können Sie dieselbe Logik einfach direkt in Ihrem App-Delegaten implementieren, ohne dass die TransitionControllerKlasse erforderlich ist . Die Logik, die Sie benötigen würden, wäre jedoch dieselbe.

Verwenden Sie es wie folgt:

In Ihrem App-Delegaten

// 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;
}

So wechseln Sie von einem beliebigen Ansichtscontroller zu einem neuen Ansichtscontroller

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

BEARBEITEN: Originalantwort unten - funktioniert nur zur Portait-Orientierung

Für dieses Beispiel habe ich folgende Annahmen getroffen:

  1. Sie haben einen View Controller als den rootViewControllerIhres Fensters zugewiesen

  2. Wenn Sie zu einer neuen Ansicht wechseln, möchten Sie den aktuellen viewController durch den viewController ersetzen, der die neue Ansicht besitzt. Zu jedem Zeitpunkt ist nur der aktuelle viewController aktiv (z. B. zugewiesen).

Der Code kann leicht geändert werden, um anders zu funktionieren. Der entscheidende Punkt ist der animierte Übergang und der Single View Controller. Stellen Sie sicher, dass Sie einen Ansichts-Controller nur außerhalb der Zuweisung behalten window.rootViewController.

Code zum Animieren des Übergangs im App-Delegaten

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

Beispiel für die Verwendung in einem View Controller

- (IBAction)flipToNextView
{
    AnotherViewController *anotherVC = [[AnotherVC alloc] init...];
    MyAppDelegate *appDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
    [appDelegate transitionToViewController:anotherVC
                             withTransition:UIViewAnimationOptionTransitionFlipFromRight];
}
XJones
quelle
1
Ja, sehr nett! Dies ist nicht nur der Trick, sondern auch ein sehr einfaches und sauberes Codebeispiel. Danke vielmals!
TigerCoding
Verursacht er nicht Probleme in der Landschaft für Sie? Außerdem: Löst dies die Methoden willAppear und didAppear der viewController aus?
Felix Lamouroux
1
Ich weiß, dass die angezeigten Anrufe ausgeführt werden, weil ich sie protokolliert habe. Ich verstehe auch nicht, warum dies Orientierungsänderungen beeinflussen würde. Können Sie erläutern, warum Sie glauben, dass dies der Fall ist?
TigerCoding
1
@XJones großartige Lösung, funktioniert in meiner iPad-App ziemlich gut, bis auf ein Problem, mit dem ich nach der ersten Initialisierung konfrontiert bin. TransVC = [TransitionController ... initWithViewController: aUINavCtrlObj]; window.rootVC = transVC; Die Ansicht in aUINavCtrlObj ist 20 Pixel von oben aufgefüllt (dh die unteren 20 Pixel sind in allen Ausrichtungen außerhalb des Bildschirmbereichs (UIWindow) begrenzt), aber nachdem ich [transVC TransitionToViewController: AnotherVC] ausgeführt habe, ist die Auffüllung weg. Ich habe in der LoadView von TransitionController versucht, wantFullScreenLayout = NO zu verwenden. Dadurch wird ein schwarzer Bereich von 20 Pixel direkt unter statusBar hinzugefügt.
Abduliam Rehmanius
1
@AbduliamRehmanius: Ich hatte das gleiche Problem. Ich habe es behoben, indem ich Zeile 25 von TransitionController.min UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];geändert habe, aber ich habe dies nur für die neueste Version von iOS verwendet. Testen Sie es daher sorgfältig.
Phil Calvin
67

Sie können das neue viewController-Containment-System von Apple verwenden. Weitere Informationen finden Sie im WWDC 2011-Sitzungsvideo "Implementing UIViewControllerContainment".

UIViewControllerContainment ist neu in iOS5 und ermöglicht es Ihnen, einen übergeordneten viewController und eine Reihe von untergeordneten viewControllern zu haben, die darin enthalten sind. So funktioniert der UISplitViewController. Auf diese Weise können Sie Ansichtscontroller in einem übergeordneten Element stapeln. Für Ihre spezielle Anwendung verwenden Sie jedoch nur das übergeordnete Element, um den Übergang von einem sichtbaren viewController zu einem anderen zu verwalten. Dies ist die von Apple genehmigte Methode, um Dinge zu tun, und das Animieren von einem untergeordneten ViewController ist schmerzlos. Außerdem können Sie die verschiedenen UIViewAnimationOptionÜbergänge nutzen!

Mit UIViewContainment müssen Sie sich auch keine Sorgen über die Unordnung bei der Verwaltung der untergeordneten viewController während Orientierungsereignissen machen, es sei denn, Sie möchten. Sie können einfach Folgendes verwenden, um sicherzustellen, dass Ihr parentViewController Rotationsereignisse an die untergeordneten viewController weiterleitet.

- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{
    return YES;
}

In der viewDidLoad-Methode Ihrer Eltern können Sie Folgendes oder ähnliches tun, um den ersten childViewController einzurichten:

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

Wenn Sie dann den untergeordneten viewController ändern müssen, rufen Sie im übergeordneten viewController Folgendes auf:

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

Ich habe hier ein vollständiges Beispielprojekt veröffentlicht: https://github.com/toolmanGitHub/stackedViewControllers . Dieses andere Projekt zeigt, wie UIViewControllerContainment für verschiedene Eingabeansichts-Controllertypen verwendet wird, die nicht den gesamten Bildschirm einnehmen. Viel Glück

timthetoolman
quelle
1
Ein gutes Beispiel. Vielen Dank, dass Sie sich die Zeit genommen haben, den Code zu veröffentlichen. Auf der anderen Seite ist es nur auf iOS 5 beschränkt. Wie in der Frage erwähnt: "[Storyboards] sind auf iOS 5 beschränkt und nahezu ideal, können jedoch nicht in allen Projekten verwendet werden." Angesichts der Tatsache, dass ein großer Prozentsatz (etwa 40%?) Der Kunden immer noch iOS 4 verwendet, besteht das Ziel darin, etwas bereitzustellen, das in iOS 4 und höher funktioniert.
TigerCoding
Sollten Sie nicht [self.currentViewController willMoveToParentViewController:nil];vor dem Übergang anrufen ?
Felix Lamouroux
@FelixLam - Gemäß den Dokumenten zum UIViewController-Containment rufen Sie willMoveToParentViewController nur auf, wenn Sie die addChildViewController-Methode überschreiben. In diesem Beispiel nenne ich es aber nicht überschreibend.
Timthetoolman
2
In der Demo auf der WWDC erinnere ich mich anscheinend daran, dass sie es vor dem Aufruf des Übergangs aufgerufen haben, da der Übergang nicht bedeutet, dass die aktuelle VC auf Null verschoben wird. Im Fall eines UITabBarControllers würde der Übergang das übergeordnete Element eines VC nicht ändern. Das Entfernen von übergeordneten Aufrufen ruft den didMoveToParentViewController auf: nil, aber es gibt keinen Willensaufruf. IMHO
Felix Lamouroux
7

OK, ich weiß, dass die Frage ohne Verwendung eines Navigationscontrollers lautet, aber kein Grund, dies nicht zu tun. OP reagierte nicht rechtzeitig auf Kommentare, damit ich schlafen gehen konnte. Wählt mich nicht ab. :) :)

So öffnen Sie den aktuellen Ansichts-Controller und wechseln mithilfe eines Navigations-Controllers zu einem neuen Ansichts-Controller:

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];
Richard Brightwell
quelle
Funktioniert dieser Stack nicht Controller?
TigerCoding
1
Ja, weil wir einen Navigationscontroller verwenden. Es geht jedoch um die Einschränkung, welche Art von Übergängen Sie ausführen können, was meiner Meinung nach der Kern Ihrer Frage war.
Richard Brightwell
Schließen, aber eines der großen Probleme ist, dass auf dem Stapel mehrere View-Controller verwaltet werden müssen. Ich bin auf der Suche nach einer Möglichkeit, View Controller vollständig zu ändern. =)
TigerCoding
Ah. Ich könnte auch so etwas haben ... gib mir eine Minute. Wenn nicht, werde ich diese Antwort löschen.
Richard Brightwell
Ok, ich habe zwei verschiedene Codebits zusammengeschustert. Ich denke, das wird tun, was Sie wollen.
Richard Brightwell
3

Da ich gerade auf dieses genaue Problem gestoßen bin und Variationen aller bereits vorhandenen Antworten auf begrenzten Erfolg ausprobiert habe, werde ich veröffentlichen, wie ich es letztendlich gelöst habe:

Wie in diesem Beitrag zu benutzerdefinierten Segmenten beschrieben , ist es eigentlich ganz einfach, benutzerdefinierte Segmente zu erstellen. Sie lassen sich auch sehr einfach in Interface Builder einbinden, halten Beziehungen in IB sichtbar und erfordern nicht viel Unterstützung durch die Quell- / Zielansichts-Controller des Segues.

Der oben verlinkte Beitrag enthält iOS 4-Code, um den aktuellen Controller für die Draufsicht auf dem Navigationscontroller-Stapel durch einen neuen zu ersetzen, der eine Animation zum Einschieben von oben verwendet.

In meinem Fall wollte ich, dass ein ähnlicher Ersatz- FlipFromLeftÜbergang stattfindet , jedoch mit einem Übergang. Ich brauchte auch nur Unterstützung für iOS 5+. Code:

Von RAFlipReplaceSegue.h:

#import <UIKit/UIKit.h>

@interface RAFlipReplaceSegue : UIStoryboardSegue
@end

Von 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

Ziehen Sie nun bei gedrückter Ctrl-Taste, um eine andere Art von Segue einzurichten, machen Sie es dann zu einem benutzerdefinierten Segue und geben Sie den Namen der benutzerdefinierten Segue-Klasse ein, et voilà!

Ryan Artecona
quelle
Gibt es einen Weg, dies programmgesteuert ohne Storyboard zu erreichen?
Zakdances
Soweit ich weiß, müssen Sie einen Segue definieren und ihm in einem Storyboard eine Kennung geben, um ihn zu verwenden. Mit der UIViewController- –performSegueWithIdentifier:sender:Methode können Sie einen Segue in Code aufrufen .
Ryan Artecona
1
Sie sollten viewDidXXX und viewWillXXX niemals direkt aufrufen.
Ben Sinclair
2

Ich hatte lange mit diesem Problem zu kämpfen, und eines meiner Probleme ist hier aufgeführt . Ich bin mir nicht sicher, ob Sie dieses Problem hatten. Aber hier ist, was ich empfehlen würde, wenn es mit iOS 4 funktionieren muss.

Erstellen Sie zunächst eine neue NavigationControllerKlasse. Hier erledigen wir die ganze Drecksarbeit - andere Klassen können Instanzmethoden wie pushViewController:und so "sauber" aufrufen . In Ihrem .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;

Das Array der untergeordneten Ansichtscontroller dient als Speicher für alle Ansichtscontroller in unserem Stapel. Wir würden den gesamten Rotations- und Größenänderungscode automatisch aus der NavigationControllerSicht von an weiterleiten currentController.

Nun zu unserer Implementierung:

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

Jetzt können Sie Ihre eigenen implementieren pushViewController:, popViewControllerund solche, diese Methodenaufrufe.

Viel Glück und ich hoffe das hilft!

aopsfan
quelle
Auch hier müssen wir View Controller als Unteransichten vorhandener View Controller hinzufügen. Zugegeben, das ist es, was der vorhandene Navigationscontroller tut, aber es bedeutet, dass wir ihn und alle seine Methoden grundsätzlich neu schreiben müssen. In der Realität sollten wir vermeiden, viewWillAppear und ähnliche Methoden verteilen zu müssen. Ich fange an zu denken, dass es keinen sauberen Weg gibt, dies zu tun. Ich danke Ihnen jedoch, dass Sie sich die Zeit und Mühe genommen haben!
TigerCoding
Ich denke, mit diesem System zum Hinzufügen und Entfernen von Ansichts-Controllern nach Bedarf verhindert diese Lösung, dass Sie diese Methoden weiterleiten müssen.
Aopsfan
Bist du sicher? Früher habe ich View Controller auf ähnliche Weise ausgetauscht und musste immer Nachrichten weiterleiten. Können Sie etwas anderes bestätigen?
TigerCoding
Nein, ich bin mir nicht sicher, aber ich würde davon ausgehen, dass, solange Sie die Ansichten der View Controller entfernen, sobald sie verschwinden, und sie so hinzufügen, wie sie erscheinen, dies automatisch ausgelöst werden viewWillAppearsollte viewDidAppear, und so weiter.
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];

Versuchen Sie diesen Code.


Dieser Code gibt den Übergang von einem Ansichtscontroller zu einem anderen Ansichtscontroller mit einem Navigationscontroller.

Deepak Kumar Sahu
quelle