modal View denetleyicileri - nasıl görüntülenir ve kapatılır


82

Birden çok görüntü denetleyicisini gösterme ve reddetme sorununu nasıl çözeceğime dair son bir haftadır kafamı kırıyorum. Örnek bir proje oluşturdum ve kodu doğrudan projeden yapıştırdım. Karşılık gelen .xib dosyalarıyla 3 görünüm denetleyicim var. MainViewController, VC1 ve VC2. Ana görüntü denetleyicisinde iki düğmem var.

- (IBAction)VC1Pressed:(UIButton *)sender
{
    VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil];
    [vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc1 animated:YES completion:nil];
}

Bu, VC1'i sorun olmadan açar. VC1'de, VC2'yi açarken aynı zamanda VC1'i kapatması gereken başka bir düğmem var.

- (IBAction)buttonPressedFromVC1:(UIButton *)sender
{
    VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil];
    [vc2 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc2 animated:YES completion:nil];
    [self dismissViewControllerAnimated:YES completion:nil];
} // This shows a warning: Attempt to dismiss from view controller <VC1: 0x715e460> while a presentation or dismiss is in progress!


- (IBAction)buttonPressedFromVC2:(UIButton *)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
} // This is going back to VC1. 

Ana görüntü denetleyicisine geri dönmesini istiyorum, aynı zamanda VC1'in bellekten tamamen kaldırılmış olması gerekirdi. VC1 yalnızca ana denetleyicideki VC1 düğmesine tıkladığımda görünmelidir.

Ana görünüm denetleyicisindeki diğer düğme de VC2'yi doğrudan VC1'i atlayarak görüntüleyebilmelidir ve VC2'de bir düğme tıklandığında ana denetleyiciye geri dönmelidir. Uzun süre çalışan kod, döngüler veya herhangi bir zamanlayıcı yoktur. Denetleyicileri görüntülemek için çıplak kemik çağrıları.

Yanıtlar:


189

Bu hat:

[self dismissViewControllerAnimated:YES completion:nil];

kendisine bir mesaj göndermiyor, aslında sunum yapan VC'sine bir mesaj gönderiyor ve işten çıkarmayı yapmasını istiyor. Bir VC sunduğunuzda, sunum yapan VC ile sunulan VC arasında bir ilişki oluşturursunuz. Bu nedenle, sunum sırasında mevcut VC'yi yok etmemelisiniz (sunulan VC, bu kapatma mesajını geri gönderemez…). Bunu gerçekten hesaba katmadığınız için uygulamayı şaşkın bir durumda bırakıyorsunuz. Cevabıma bakın Bu yöntemin daha açık bir şekilde yazılmasını önerdiğim Sunulan bir Görüntü Denetleyicisini Kapatma :

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

Sizin durumunuzda, tüm kontrollerin yapıldığından emin olmanız gerekir mainVC . Doğru mesajı ViewController1'den MainViewController'a geri göndermek için bir temsilci kullanmanız gerekir, böylece mainVC, VC1'i kapatabilir ve sonra VC2'yi sunabilir.

In VC2 VC1 @interface yukarıdaki .h dosyasında bir protokol ekleyin:

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end

ve @interface bölümünde aynı dosyada daha aşağıya, temsilci işaretçisini tutmak için bir özellik bildirin:

@property (nonatomic,weak) id <ViewController1Protocol> delegate;

VC1 .m dosyasında, reddetme düğmesi yöntemi temsilci yöntemini çağırmalıdır

- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
    [self.delegate dissmissAndPresentVC2]
}

Şimdi mainVC'de, VC1'i oluştururken onu VC1'in temsilcisi olarak ayarlayın:

- (IBAction)present1:(id)sender {
    ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
    vc.delegate = self;
    [self present:vc];
}

ve temsilci yöntemini uygulayın:

- (void)dismissAndPresent2 {
    [self dismissViewControllerAnimated:NO completion:^{
        [self present2:nil];
    }];
}

present2:VC2Pressed:Düğme IBAction yönteminizle aynı yöntem olabilir . VC2'nin VC1 tamamen kapatılıncaya kadar sunulmamasını sağlamak için tamamlama bloğundan çağrıldığını unutmayın.

Şimdi VC1-> VCMain-> VC2'den hareket ediyorsunuz, bu nedenle muhtemelen geçişlerden yalnızca birinin animasyonlu olmasını isteyeceksiniz.

Güncelleme

Yorumlarınızda, görünüşte basit olan bir şeyi başarmak için gereken karmaşıklığa şaşkınlık ifade ediyorsunuz. Sizi temin ederim, bu yetkilendirme modeli Objective-C ve Cocoa'nın çoğu için çok merkezi ve bu örnek alabileceğiniz en basit şeyle ilgili, gerçekten rahat olmak için çaba göstermelisiniz.

Apple'ın View Controller Programming Guide'da şunları söyleyecekler :

Sunulan Bir Görünüm Denetleyicisini Kapatma

Sunulan bir görüntü denetleyicisini reddetme zamanı geldiğinde, tercih edilen yaklaşım, sunan görünüm denetleyicisinin onu reddetmesine izin vermektir. Diğer bir deyişle, mümkün olduğunda, görüş denetleyicisini sunan aynı görüş denetleyicisi, onu reddetme sorumluluğunu da almalıdır. Sunulan görünüm denetleyicisine sunulan görünüm denetleyicisinin atılması gerektiğini bildirmek için birkaç teknik olmasına rağmen, tercih edilen teknik yetkilendirmedir. Daha fazla bilgi için bkz. "Diğer Denetleyicilerle İletişim Kurmak için Temsilciliğin Kullanılması."

Eğer gerçekten neyi başarmak istediğinizi ve bunu nasıl yapacağınızı gerçekten düşünürseniz, bir NavigationController kullanmak istemediğinizde, MainViewController'ınıza tüm işi yapması için mesaj göndermenin tek mantıklı çıkış yolu olduğunu fark edeceksiniz. Eğer varsa yapmak bir NavController kullanmak, aslında sen bile değil açıkça eğer iş yapmak için navController için, 'delege' vardır. Orada olması gereken bazı görüntülü konferans navigasyon neler merkezi izler nesne ve ihtiyacınız bazı ne yaparsanız onunla iletişim yöntemini.

Pratikte Apple'ın tavsiyesi biraz aşırıdır ... normal durumlarda, özel bir temsilci ve yöntem oluşturmanıza gerek yoktur, güvenebilirsiniz [self presentingViewController] dismissViewControllerAnimated:- sizinki gibi durumlarda, işten çıkarılmanızın uzaktan kumanda üzerinde başka etkileri olmasını istersiniz. dikkat etmeniz gereken nesneler.

İşte tüm delege güçlüğü olmadan çalışmayı hayal edebileceğiniz bir şey ...

- (IBAction)dismiss:(id)sender {
    [[self presentingViewController] dismissViewControllerAnimated:YES 
                                                        completion:^{
        [self.presentingViewController performSelector:@selector(presentVC2:) 
                                            withObject:nil];
    }];

}

Sunum denetleyicisinden bizi görevden almasını rica ettikten sonra, VC2'yi çağırmak için presentingViewController'da bir yöntemi çağıran bir tamamlama bloğumuz var. Temsilciye gerek yok. (Blokların büyük bir satış noktası, bu koşullarda delege ihtiyacını azaltmalarıdır). Ancak bu durumda yolumuza çıkan birkaç şey var ...

  • VC1 sen yok biliyorum mainVC yöntemini uygulayan present2- Zor debug hata veya çöker ile sona erebilir. Delegeler bundan kaçınmanıza yardımcı olur.
  • VC1 bir kez reddedildiğinde, tamamlama bloğunu yürütmek için gerçekten ortalıkta değil ... yoksa değil mi? Self.presentingViewController artık bir şey ifade ediyor mu? Bilmiyorsunuz (ben de bilmiyorum) ... bir delege ile, bu belirsizliğe sahip değilsiniz.
  • Bu yöntemi çalıştırmaya çalıştığımda, hiçbir uyarı veya hata olmadan kilitleniyor.

Bu yüzden lütfen ... yetkilendirmeyi öğrenmek için zaman ayırın!

güncelleme2

Yorumunuzda, bunu VC2'nin kapat düğmesi işleyicisinde kullanarak çalışmasını sağlamayı başardınız:

 [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; 

Bu kesinlikle çok daha basit, ancak size bir takım sorunlar bırakıyor.

Sıkı kaplin
ViewController yapınızı birlikte kabloluyorsunuz. Örneğin, mainVC'den önce yeni bir viewController eklerseniz, gerekli davranışınız bozulur (öncekine gidersiniz). VC1'de ayrıca # VC2'yi içe aktarmanız gerekiyor. Bu nedenle, OOP / MVC hedeflerini bozan oldukça fazla karşılıklı bağımlılığınız var.

Temsilcileri kullanarak, ne VC1 ne de VC2'nin mainVC veya öncülleri hakkında hiçbir şey bilmesine gerek kalmaz, bu nedenle her şeyi gevşek bağlı ve modüler tutuyoruz.

Bellek
VC1 gitmedi, hala ona iki işaretçi tutuyorsunuz:

  • mainVC'nin presentedViewControllermülkü
  • VC2'nin presentingViewControllerözelliği

Bunu günlüğe kaydederek ve ayrıca bunu VC2'den yaparak test edebilirsiniz.

[self dismissViewControllerAnimated:YES completion:nil]; 

Hala çalışıyor, yine de sizi VC1'e geri götürüyor.

Bu bana bir hafıza sızıntısı gibi görünüyor.

Bunun ipucu, burada aldığınız uyarıda:

[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
 // Attempt to dismiss from view controller <VC1: 0x715e460>
 // while a presentation or dismiss is in progress!

Aşağı mantık kırılırsa, sunu VC görevden çalıştığınız olarak hangi VC2 takdim VC olduğunu. İkinci mesaj gerçekten uygulanmıyor - belki bazı şeyler oluyor, ancak yine de kurtulduğunuzu düşündüğünüz bir nesneye iki işaretçi kalıyorsunuz. ( düzenle - Bunu kontrol ettim ve o kadar da kötü değil, mainVC'ye döndüğünüzde her iki nesne de kayboluyor )

Bu oldukça uzun soluklu bir söylem - lütfen delegeleri kullanın. İşe yarayacaksa, burada kalıbın başka bir kısa açıklamasını yaptım:
Bir denetçiden bir denetleyiciyi geçirmek her zaman kötü bir uygulama mıdır?

güncelleme 3
Temsilcilerden gerçekten kaçınmak istiyorsanız, bu en iyi çıkış yolu olabilir:

VC1'de:

[self presentViewController:VC2
                   animated:YES
                 completion:nil];

Ama hiçbir şeyi göz ardı etmeyin ... bizim de belirlediğimiz gibi, aslında zaten olmuyor.

VC2'de:

[self.presentingViewController.presentingViewController 
    dismissViewControllerAnimated:YES
                       completion:nil];

Biz (biliyorum) biz VC1 çıkarmadık, biz geri ulaşabilir aracılığıyla VC1 için MainVC. MainVC, VC1'i kapatır. VC1 gittiği için, VC2'nin onunla birlikte geldiği sunuldu, böylece MainVC'ye temiz bir durumda geri döndünüz.

VC1'in VC2 hakkında bilmesi gerektiğinden ve VC2'nin buna MainVC-> VC1 aracılığıyla ulaşıldığını bilmesi gerektiğinden, hala oldukça bağlantılı, ancak biraz açık yetkilendirme olmadan elde edeceğiniz en iyisi bu.


1
karmaşık görünüyor. İzlemeye ve noktaya kopyalamaya çalıştım ama ortada kayboldum. Bunu başarmanın başka bir yolu var mı? Ayrıca uygulama temsilcisine, ana denetleyicinin kök görünüm denetleyicisi olarak ayarlandığını da eklemek istedim. Navigasyon denetleyicilerini kullanmak istemiyorum ama bunun neden bu kadar karmaşık olması gerektiğini merak ediyorum. Özetlemek gerekirse, uygulama başladığında, 2 düğmeli bir ana görünüm denetleyicisi gösteriyorum. İlk düğmeye tıklamak VC1'i yükler. VC1'de bir düğme vardır ve tıklandığında VC2'yi hiçbir hata veya uyarı olmadan yüklemeli ve aynı zamanda VC1'i bellekten çıkarmalıdır.
Hema

VC2'de bir düğme var ve tıkladığında VC2 bellekten çıkarılmalı ve kontrol ana denetleyiciye geri dönmeli ve VC1'e gitmemelidir.
Hema

@Hema, seni çok iyi ihtiyaçlarınızı anlaşılmış ve bu sizi temin var olan bunu yapmanın doğru yolu. Cevabımı biraz daha bilgi ile güncelledim, umarım yardımcı olur. Yaklaşımımı denediyseniz ve takılıp kaldıysanız, lütfen tam olarak neyin işe yaramadığını gösteren yeni bir soru sorun, böylece yardımcı olabiliriz. Ayrıca netlik açısından bu soruya tekrar bağlantı verebilirsiniz.
dökümhane

Merhaba O: Görüşünüz için teşekkürler. Ayrıca başka bir başlıktan (orijinal konu) bahsediyorum ve orada bahsedilen önerilerden bir pasaj gönderdim. Bu sorunu çözmek için tüm uzman yanıtlarını deniyorum. URL burada: stackoverflow.com/questions/14840318/…
Hema

1
@Honey - Belki de öyledir, ancak ifade 'hayali' sözde kod parçasına retorik bir yanıttı. Vurgulamak istediğim nokta, döngü tuzaklarını korumakla ilgili değil, sorgulayıcıyı yetkilendirmenin neden değerli bir tasarım modeli olduğu konusunda eğitmektir (bu da tesadüfen bu sorunu önler). Bence buradaki yanıltıcı tartışma bu - soru modsal VC'lerle ilgili, ancak cevabın değeri çoğunlukla, soruyu kullanarak delege modelini açıklamasında ve katalizör olarak OP'nin belirgin hayal kırıklıklarında yatıyor. İlginiz (ve düzenlemeleriniz) için teşekkürler !!
döküm

12

Örnek Swift , yukarıda Döküm açıklamasını ve Apple'ın belgelerine hayal:

  1. Oturtmakta Apple'ın belgelerine ve temsilci tasarım kalıbı kullanarak presentViewController versiyonu (bazı hataları düzeltme) Yukarıdaki Döküm açıklamasıyla:

ViewController.swift

import UIKit

protocol ViewControllerProtocol {
    func dismissViewController1AndPresentViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        vc1.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
        self.presentViewController(vc1, animated: true, completion: nil)
    }

    func dismissViewController1AndPresentViewController2() {
        self.dismissViewControllerAnimated(false, completion: { () -> Void in
            let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
            self.presentViewController(vc2, animated: true, completion: nil)
        })
    }

}

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.dismissViewController1AndPresentViewController2()
    }

}

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}
  1. Yukarıdaki dökümhanenin açıklamasına dayanarak (bazı hataların düzeltilmesi), delege tasarım modelini kullanan pushViewController sürümü:

ViewController.swift

import UIKit

protocol ViewControllerProtocol {
    func popViewController1AndPushViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        self.navigationController?.pushViewController(vc1, animated: true)
    }

    func popViewController1AndPushViewController2() {
        self.navigationController?.popViewControllerAnimated(false)
        let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
        self.navigationController?.pushViewController(vc2, animated: true)
    }

}

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.popViewController1AndPushViewController2()
    }

}

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}

Örnek ViewControllersınıfınızda mainVC değil mi?
Honey

10

Sanırım iOS kalıcı görünüm denetleyicileriyle ilgili bazı temel kavramları yanlış anladınız. VC1'i kapattığınızda, VC1 tarafından sunulan tüm görünüm denetleyicileri de atılır. Apple, modal görünüm denetleyicilerinin yığılmış bir şekilde akması için tasarlanmıştır - sizin durumunuzda VC2, VC1 tarafından sunulur. VC2'yi VC1'den sunduğunuz anda VC1'i reddediyorsunuz, bu yüzden tam bir karmaşa. İstediğinizi elde etmek için, buttonPressedFromVC1, mainVC'nin VC2'yi VC1 kendisini kapattıktan hemen sonra sunması gerekir. Ve bunun delegeler olmadan başarılabileceğini düşünüyorum. Çizgiler boyunca bir şey:

UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
 ^{
    [presentingVC presentViewController:vc2 animated:YES completion:nil];
 }];

Self.presentingViewController'ın başka bir değişkende saklandığına dikkat edin, çünkü vc1 kendisini kapattıktan sonra ona herhangi bir gönderme yapmamalısınız.


1
çok basit! Başkalarının en üstteki gönderide durmak yerine cevabınızı aşağı kaydırmasını diliyorum.
Ryan Loggerythm

OP kodunda, bittikten sonra neden [self dismiss...]olmaz ? Eşzamansız bir şey olmuyor [self present...]
Honey

1
@Honey aslında, presentViewController'ı çağırırken eşzamansız bir şeyler oluyor - bu yüzden bir tamamlama işleyicisi var. Ancak bunu kullansanız bile, sunum görünümü denetleyicisini bir şey sunduktan sonra çıkarırsanız, sunduğu her şey de reddedilir. OP aslında mevcut olanı görevden böylece aslında başka bir sunum gelen viewcontroller sunmak istediğinde Yani
Radu Simionescu

Ancak bunu kullansanız bile, sunum görünümü denetleyicisini bir şey sunduktan sonra kapatırsanız, sunduğu her şey de reddedilir ... Aha, yani derleyici temelde "yaptığınız şey aptalca. Az önce önceki işleminizi geri aldınız. kod satırı (VC1 olarak kendimi ve sunduğum her şeyi reddedeceğim). Yapma "değil mi?
Honey

Derleyici bunun hakkında hiçbir şey "söylemeyecek" ve aynı zamanda bunu çalıştırırken
çökmemek

5

Radu Simionescu - harika iş! ve aşağıda Swift sevenler için çözümünüz:

@IBAction func showSecondControlerAndCloseCurrentOne(sender: UIButton) {
    let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("ConrollerStoryboardID") as UIViewControllerClass // change it as You need it
    var presentingVC = self.presentingViewController
    self.dismissViewControllerAnimated(false, completion: { () -> Void   in
        presentingVC!.presentViewController(secondViewController, animated: true, completion: nil)
    })
}

bu bir bakıma gerçekten çalıştığı için beni hayal kırıklığına uğratıyor .. Bloğun neden "self.presentingViewController" ı yakalamadığını ve güçlü bir referansa ihtiyaç duyulduğunu anlamıyorum, yani "var presentingVC" .. her neyse, bu çalışıyor. thx
emdog4

1

Bunu istedim:

MapVC tam ekran bir Haritadır.

Bir düğmeye bastığımda, haritanın üzerinde PopupVC (tam ekranda değil) açılıyor.

PopupVC'de bir düğmeye bastığımda, MapVC'ye dönüyor ve ardından viewDidAppear'ı çalıştırmak istiyorum.

Bunu ben yaptım:

MapVC.m: düğme eyleminde, programlı olarak bir segment ve delege ayarla

- (void) buttonMapAction{
   PopupVC *popvc = [self.storyboard instantiateViewControllerWithIdentifier:@"popup"];
   popvc.delegate = self;
   [self presentViewController:popvc animated:YES completion:nil];
}

- (void)dismissAndPresentMap {
  [self dismissViewControllerAnimated:NO completion:^{
    NSLog(@"dismissAndPresentMap");
    //When returns of the other view I call viewDidAppear but you can call to other functions
    [self viewDidAppear:YES];
  }];
}

PopupVC.h: @interface öncesinde, protokolü ekleyin

@protocol PopupVCProtocol <NSObject>
- (void)dismissAndPresentMap;
@end

@interface'den sonra yeni bir özellik

@property (nonatomic,weak) id <PopupVCProtocol> delegate;

PopupVC.m:

- (void) buttonPopupAction{
  //jump to dismissAndPresentMap on Map view
  [self.delegate dismissAndPresentMap];
}

1

Sunum sırasında UINavigationController kullanarak sorunu çözdüm. MainVC'de, VC1 gösterilirken

let vc1 = VC1()
let navigationVC = UINavigationController(rootViewController: vc1)
self.present(navigationVC, animated: true, completion: nil)

VC1'de, VC2'yi göstermek ve aynı anda VC1'i kapatmak istediğimde (sadece bir animasyon), bir itme animasyonuna sahip olabilirim.

let vc2 = VC2()
self.navigationController?.setViewControllers([vc2], animated: true)

Ve VC2'de, görünüm denetleyicisini kapattığınızda, her zamanki gibi kullanabiliriz:

self.dismiss(animated: true, completion: nil)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.