Menghidupkan perubahan pengontrol tampilan tanpa menggunakan tumpukan pengontrol navigasi, subview, atau pengontrol modal?

142

NavigationControllers memiliki tumpukan ViewController untuk dikelola, dan transisi animasi terbatas.

Menambahkan pengontrol tampilan sebagai sub-tampilan ke pengontrol tampilan yang ada mengharuskan melewati acara ke pengontrol tampilan-bawah, yang merupakan rasa sakit untuk dikelola, dimuat dengan sedikit gangguan dan secara umum terasa seperti hack yang buruk saat menerapkan (Apple juga merekomendasikan melakukan ini).

Menyajikan pengontrol tampilan modal lagi menempatkan pengontrol tampilan di atas yang lain, dan sementara itu tidak memiliki masalah acara yang dijelaskan di atas, itu tidak benar-benar 'menukar' pengendali tampilan, ia menumpuknya.

Storyboard terbatas untuk iOS 5, dan hampir ideal, tetapi tidak dapat digunakan di semua proyek.

Dapatkah seseorang menampilkan CONTOH KODE SOLID dalam cara mengubah pengontrol tampilan tanpa batasan di atas dan memungkinkan transisi animasi di antara mereka?

Contoh dekat, tetapi tidak ada animasi: Cara menggunakan beberapa pengontrol tampilan khusus iOS tanpa pengontrol navigasi

Sunting: Penggunaan Nav Controller baik-baik saja, tetapi perlu ada gaya transisi animasi (bukan hanya efek slide) pengendali tampilan yang ditampilkan harus ditukar sepenuhnya (tidak ditumpuk). Jika pengontrol tampilan kedua harus menghapus pengontrol tampilan lain dari tumpukan, maka itu tidak cukup dienkapsulasi.

Sunting 2: iOS 4 harus menjadi OS dasar untuk pertanyaan ini, saya harus menjelaskan bahwa ketika menyebutkan storyboard (di atas).

TigerCoding
sumber
1
Anda dapat melakukan transisi animasi khusus dengan pengontrol navigasi. Jika ini dapat diterima, harap hapus batasan itu dari pertanyaan Anda dan saya akan memposting contoh kode.
Richard Brightwell
@Richard jika melewatkan kerumitan dalam mengelola tumpukan dan mengakomodasi gaya transisi animasi yang berbeda antara pengontrol tampilan maka penggunaan pengontrol navigasi baik-baik saja!
TigerCoding
Oke bagus. Saya menjadi tidak sabar dan memposting kode. Cobalah. Bekerja untukku.
Richard Brightwell
@ RichardBrightwell Anda mengatakan di sini bahwa orang dapat melakukan transisi animasi khusus antara pengontrol tampilan menggunakan pengontrol navigasi ... bagaimana? Bisakah Anda memposting contoh? Terima kasih.
Bebek

Jawaban:

108

EDIT: Jawaban baru yang berfungsi dalam orientasi apa pun. Jawaban asli hanya berfungsi ketika antarmuka dalam orientasi potret. Ini adalah animasi transisi tampilan b / c yang menggantikan tampilan dengan tampilan berbeda harus terjadi dengan tampilan setidaknya tingkat di bawah tampilan pertama yang ditambahkan ke jendela (miswindow.rootViewController.view.anotherView.).

Saya telah mengimplementasikan kelas kontainer sederhana yang saya panggil TransitionController. Anda dapat menemukannya di https://gist.github.com/1394947 .

Sebagai tambahan, saya lebih suka implementasi di kelas yang terpisah b / c lebih mudah untuk digunakan kembali. Jika Anda tidak menginginkannya, Anda bisa menerapkan logika yang sama secara langsung di delegasi aplikasi Anda untuk menghilangkan kebutuhan TransitionControllerkelas. Namun, logika yang Anda butuhkan akan sama.

Gunakan sebagai berikut:

Di delegasi aplikasi Anda

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

Untuk beralih ke pengontrol tampilan baru dari pengontrol tampilan apa pun

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

Sunting: Jawaban Asli di bawah - hanya berfungsi untuk orientasi portait

Saya membuat asumsi berikut untuk contoh ini:

  1. Anda memiliki pengontrol tampilan yang ditetapkan sebagai rootViewController jendela Anda

  2. Saat Anda beralih ke tampilan baru, Anda ingin mengganti viewController saat ini dengan viewController yang memiliki tampilan baru. Kapan saja, hanya viewController saat ini yang hidup (mis.

Kode dapat dengan mudah dimodifikasi untuk bekerja secara berbeda, kuncinya adalah transisi animasi dan pengontrol tampilan tunggal. Pastikan Anda tidak mempertahankan pengontrol tampilan di mana pun di luar penetapan ituwindow.rootViewController .

Kode untuk menghidupkan transisi dalam delegasi aplikasi

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

Contoh digunakan dalam pengontrol tampilan

- (IBAction)flipToNextView
{
    AnotherViewController *anotherVC = [[AnotherVC alloc] init...];
    MyAppDelegate *appDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
    [appDelegate transitionToViewController:anotherVC
                             withTransition:UIViewAnimationOptionTransitionFlipFromRight];
}
XJones
sumber
1
Ya sangat bagus! Tidak hanya melakukan trik, tetapi ini adalah contoh kode yang sangat sederhana dan bersih. Terimakasih banyak!
TigerCoding
Tidakkah ia menyebabkan masalah dalam lansekap untuk Anda? Juga: apakah ini memicu metode willAppear dan didAppear dari viewControllers?
Felix Lamouroux
1
Saya tahu panggilan yang muncul sedang dilakukan karena saya mencatatnya. Saya juga tidak melihat mengapa ini akan mempengaruhi perubahan orientasi. Bisakah Anda menguraikan mengapa Anda berpikir demikian?
TigerCoding
1
@XJones solusi hebat, bekerja cukup baik di aplikasi iPad saya kecuali untuk satu masalah yang saya hadapi, setelah inisialisasi pertama transVC = [TransitionController ... initWithViewController: aUINavCtrlObj]; window.rootVC = transVC; tampilan di aUINavCtrlObj adalah empuk 20px dari atas (yaitu bawah 20 px berada di luar layar (UIWindow) batas dalam semua orientasi), tetapi setelah saya melakukan [transVC transitionToViewController: anotherVC] padding hilang. Saya mencoba wantFullScreenLayout = TIDAK di LoadView TransitionController, yang dilakukannya adalah menambahkan area hitam 20 px tepat di bawah statusBar.
Abduliam Rehmanius
1
@AbduliamRehmanius: Saya punya masalah yang sama. Saya memperbaikinya dengan mengubah baris 25 TransitionController.mmenjadi UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];, tetapi saya hanya menggunakan ini pada versi terbaru iOS, jadi uji dengan cermat.
Phil Calvin
67

Anda dapat menggunakan sistem penahanan viewController baru Apple. Untuk informasi lebih lanjut, lihat video sesi WWDC 2011 "Implementing UIViewControllerContainment".

Baru di iOS5, UIViewControllerContainment memungkinkan Anda memiliki viewController induk dan sejumlah viewController anak yang terkandung di dalamnya. Ini adalah cara kerja UISplitViewController. Dengan melakukan ini, Anda dapat menumpuk view controller di dalam induk, tetapi untuk aplikasi khusus Anda, Anda hanya menggunakan induk untuk mengelola transisi dari satu viewController yang terlihat ke yang lain. Ini adalah cara yang disetujui Apple untuk melakukan sesuatu dan menjiwai dari satu viewController anak tidak menyakitkan. Plus Anda bisa menggunakan semua berbagai UIViewAnimationOptiontransisi yang berbeda !

Juga, dengan UIViewContainment, Anda tidak perlu khawatir, kecuali jika Anda mau, tentang kekacauan mengelola viewControllers anak selama acara orientasi. Anda bisa menggunakan yang berikut ini untuk memastikan parentViewView Anda meneruskan kejadian rotasi ke viewControllers anak.

- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{
    return YES;
}

Anda dapat melakukan hal berikut atau serupa dalam metode viewDidLoad orang tua Anda untuk mengatur childViewController pertama:

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

kemudian ketika Anda perlu mengubah child viewController, Anda memanggil sesuatu di sepanjang baris berikut dalam induk 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];
                             }];
 }

Saya memposting proyek contoh lengkap di sini: https://github.com/toolmanGitHub/stackedViewControllers . Ini proyek lain menunjukkan bagaimana menggunakan UIViewControllerkendali pada beberapa berbagai jenis masukan viewController yang tidak mengambil seluruh layar. Semoga berhasil

timthetoolman
sumber
1
Sebuah contoh yang bagus. Terima kasih telah meluangkan waktu untuk memposting kode. Di sisi lain, ini terbatas hanya untuk iOS 5. Seperti yang disebutkan dalam pertanyaan: "[Storyboard] terbatas untuk iOS 5, dan hampir ideal, tetapi tidak dapat digunakan di semua proyek." Mengingat sebagian besar (sekitar 40%?) Pelanggan masih menggunakan iOS 4, tujuannya adalah untuk menyediakan sesuatu yang berfungsi di iOS 4 dan lebih besar.
TigerCoding
Jika Anda tidak menelepon [self.currentViewController willMoveToParentViewController:nil];sebelum transisi?
Felix Lamouroux
@FelixLam - sesuai dokumen pada penahanan UIViewController, Anda memanggil willMoveToParentViewController hanya jika Anda mengganti metode addChildViewController. Dalam contoh ini saya menyebutnya tetapi tidak mengesampingkan.
timthetoolman
2
Dalam demo di WWDC saya ingat bahwa mereka memanggilnya sebelum memanggil memulai transisi, karena transisi tidak menyiratkan bahwa currentVC akan bergerak ke nol. Dalam kasus UITabBarController, transisi tidak akan mengubah induk vc. Remove from parent memanggil didMoveToParentViewController: nil, tetapi tidak ada ... panggilan. IMHO
Felix Lamouroux
7

OK, saya tahu pertanyaannya mengatakan tanpa menggunakan pengontrol navigasi, tetapi tidak ada alasan untuk tidak melakukannya. OP tidak menanggapi komentar pada waktunya untuk saya tidur. Jangan memilih saya. :)

Berikut cara memunculkan pengontrol tampilan saat ini dan beralih ke pengontrol tampilan baru menggunakan pengontrol navigasi:

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
sumber
Bukankah ini stack view controller?
TigerCoding
1
Ya, karena kami menggunakan pengontrol navigasi. Namun, itu mengatasi keterbatasan pada jenis transisi yang dapat Anda lakukan, yang saya pikir adalah inti dari pertanyaan Anda.
Richard Brightwell
Tutup, tetapi salah satu masalah besar adalah ada beberapa pengontrol tampilan untuk dikelola di stack. Saya mencari cara untuk mengubah pengontrol tampilan sepenuhnya. =)
TigerCoding
Ah. Saya mungkin punya sesuatu seperti itu juga ... beri saya waktu sebentar. Jika tidak, saya akan menghapus jawaban ini.
Richard Brightwell
Oke, saya membuat dua bit kode yang berbeda. Saya pikir ini akan melakukan apa yang Anda inginkan.
Richard Brightwell
3

Karena saya baru saja menemukan masalah yang tepat ini, dan mencoba variasi pada semua jawaban yang sudah ada sebelumnya untuk kesuksesan terbatas, saya akan memposting bagaimana saya akhirnya menyelesaikannya:

Seperti dijelaskan dalam posting ini pada segmen khusus , sebenarnya sangat mudah untuk membuat segmen khusus. Mereka juga sangat mudah dihubungkan di Interface Builder, mereka membuat hubungan di IB tetap terlihat, dan mereka tidak memerlukan banyak dukungan oleh pengontrol tampilan sumber / tujuan.

Posting yang ditautkan di atas menyediakan kode iOS 4 untuk menggantikan pengontrol tampilan atas saat ini pada tumpukan navigationController dengan yang baru menggunakan animasi slide-in-dari-atas.

Dalam kasus saya, saya ingin penggantian segmen yang sama terjadi, tetapi dengan FlipFromLefttransisi. Saya juga hanya membutuhkan dukungan untuk iOS 5+. Kode:

Dari RAFlipReplaceSegue.h:

#import <UIKit/UIKit.h>

@interface RAFlipReplaceSegue : UIStoryboardSegue
@end

Dari 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

Sekarang, kontrol-seret untuk menyiapkan segala jenis segue lainnya, lalu buat Segue khusus, dan ketikkan nama kelas segue khusus, lalu pilih!

Ryan Artecona
sumber
Apakah ada cara untuk melakukan hal ini secara terprogram tanpa storyboard?
zakdances
Sejauh yang saya tahu, untuk menggunakan segue Anda harus mendefinisikannya dan memberinya pengenal di storyboard. Anda dapat memanggil segue dalam kode dengan –performSegueWithIdentifier:sender:metode UIViewController's .
Ryan Artecona
1
Anda seharusnya tidak pernah memanggil viewDidXXX dan viewWillXXX secara langsung.
Ben Sinclair
2

Saya berjuang dengan yang ini untuk waktu yang lama, dan salah satu masalah saya tercantum di sini , saya tidak yakin jika Anda memiliki masalah itu. Tapi inilah yang akan saya rekomendasikan jika harus bekerja dengan iOS 4.

Pertama, buat NavigationControllerkelas baru . Di sinilah kita akan melakukan semua pekerjaan kotor - kelas lain akan dapat "bersih" memanggil metode instance seperti pushViewController:dan semacamnya. Di Anda .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;

Array pengontrol tampilan anak akan berfungsi sebagai toko untuk semua pengontrol tampilan di tumpukan kami. Kami akan meneruskan semua rotasi dan mengubah ukuran kode secara otomatis dari NavigationControllertampilan ke currentController.

Sekarang, dalam implementasi kami:

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

Sekarang Anda dapat menerapkan kebiasaan Anda sendiri pushViewController:, popViewControllerdan semacamnya, menggunakan pemanggilan metode ini.

Semoga berhasil, dan saya harap ini membantu!

aopsfan
sumber
Sekali lagi kita harus menggunakan menambahkan pengontrol tampilan sebagai sub-view dari pengontrol tampilan yang ada. Memang, inilah yang dilakukan oleh Pengontrol Navigasi yang ada, tetapi itu berarti kita pada dasarnya harus menulis ulang dan semua metode itu. Pada kenyataannya, kita harus menghindari keharusan mendistribusikan viewWillAppear dan metode serupa. Saya mulai berpikir tidak ada cara yang bersih untuk melakukan ini. Namun, saya berterima kasih karena telah meluangkan waktu dan upaya!
TigerCoding
Saya pikir, dengan sistem ini menambahkan dan menghapus pengendali tampilan sesuai kebutuhan, solusi ini mencegah Anda dari harus meneruskan metode tersebut.
aopsfan
Apakah kamu yakin Saya dulu menukar view controller dengan cara yang sama sebelumnya dan saya selalu harus meneruskan pesan. Bisakah Anda mengonfirmasi sebaliknya?
TigerCoding
Tidak, saya tidak yakin, tapi saya akan berasumsi bahwa, selama Anda menghapus pandangan pengendali tampilan karena mereka menghilang, dan menambahkan mereka sebagai mereka muncul, yang seharusnya secara otomatis memicu viewWillAppear, viewDidAppear, dan semacamnya.
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];

Coba Kode Ini.


Kode ini memberikan Transisi dari pengontrol tampilan ke pengontrol tampilan lain yang memiliki pengontrol navigasi.

Deepak Kumar Sahu
sumber