Soru modal Görünüm denetleyicileri - nasıl görüntülenir ve işten çıkarılır


Son bir hafta boyunca, birden çok görüntü denetleyicisini gösterme ve çıkarma ile sorunu çözme konusunda kafamı kırıyorum. Örnek bir proje oluşturdum ve kodu doğrudan projeden yapıştırdım. Ilgili .xib dosyalarına sahip 3 görüntü denetleyicim var. MainViewController, VC1 ve VC2. Ana görünüm kontrol cihazında iki düğme 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 hiçbir sorun olmadan açar. VC1'de, VC2'yi açarken aynı zamanda VC1'i devre dışı bırakacak başka bir düğme 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önmek istiyorum, aynı zamanda VC1 iyi bellek için bellekten kaldırılmış olmalıdır. VC1 sadece ana denetleyicideki VC1 düğmesine tıkladığımda görünmelidir.

Ana görüntü denetleyicisindeki diğer düğme, VC2'yi VC1'i doğrudan bypass etmeli ve VC2'ye bir tuşa basıldığında ana denetleyiciye geri dönmelidir. Uzun koşu kodu, döngü veya zamanlayıcı yok. Kontrolörleri görüntülemek için sadece çıplak kemik çağırır.


76
2018-02-16 06:06


Menşei




Cevaplar:


Bu hat:

[self dismissViewControllerAnimated:YES completion:nil];

Kendisine bir mesaj yollamıyor, aslında sunum yapan VC'ye bir mesaj gönderiyor ve işten çıkarmayı istiyor. Bir VC sunduğunuzda, sunum yapan VC ile sunulmuş olanı arasında bir ilişki yaratırsınız. Öyleyse, sunarken VC'yi yok etmemelisiniz (sunulan VC bu mesajı geri gönderemez…). Bunu gerçekten dikkate almadığınız için uygulamayı karışık bir durumda bırakıyorsunuz. Cevabımı gör Sunulan Bir Görünüm Kontrolcüsünü Reddetme Bu yöntemi önerdiğim daha açık bir şekilde yazılmıştır:

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

Durumunuzda, tüm kontrollerin yapıldığından emin olmalısınız. mainVC. Doğru bir iletiyi ViewController1'den MainViewController'a göndermek için bir temsilci kullanmalısınız, böylece mainVC VC1'i kapatabilir ve ardından VC2'yi sunabilir.

İçinde VC2 VC1, @interface'inizin üstünde .h dosyanızda bir protokol ekleyin:

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end

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

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

VC1 .m dosyasında işten çıkarma düğmesi yöntemi temsilci yöntemini çağırmalıdır.

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

Şimdi mainVC'de, VC1 oluştururken VC1 delege 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: seninle aynı yöntem olabilir VC2Pressed: düğme IBAction yöntemi. VC1'in tamamen reddedilene kadar VC2'nin sunulmadığından emin olmak için tamamlama bloğundan çağrıldığını unutmayın.

Şimdi VC1-> VCMain-> VC2'den hareket ediyorsunuz, bu yüzden muhtemelen geçişlerin yalnızca bir tanesi animasyonlu olacak.

güncelleştirme 

Yorumlarınızda, görünüşte basit bir şey elde etmek için gereken karmaşıklıkta sürpriz ifade ediyorsunuz. Sizi temin ederim ki, bu delegasyon modeli Objective-C ve Kakao'nun çoğuna göre çok merkezi ve bu örnek alabildiğiniz en basit şey hakkında, bununla rahat olmanız için çaba göstermeniz gerektiğidir.

Apple’da Denetleyici Programlama Kılavuzunu Görüntüle onlar sahip bunu söylemek:

Sunulan Bir Görünüm Kontrolcüsünü Reddetme

Sunulan bir görünüm denetleyicisini reddetme zamanı geldiğinde, tercih edilen yaklaşım, sunum yapan denetleyicinin onu reddetmesine izin vermektir. Diğer bir deyişle, mümkün olduğunda, denetleyiciyi sunan aynı görüntü denetleyicisinin de onu reddetme sorumluluğu alması gerekir. Sunum yapan kontrolörün sunulan görüş kontrolcüsünün reddedilmesi gerektiğini bildiren birkaç teknik olmasına rağmen, tercih edilen teknik delegasyondur. Daha fazla bilgi için, bkz. “Diğer Denetleyicilerle İletişim Kurmak için Yetkilendirmeyi Kullanma”.

Gerçekten neyi başarmak istediğinizi düşünürseniz ve bunun nasıl ilerlediğini düşünüyorsanız, MainViewController'ınızın tüm işleri yapması için mesajlaşmanızın, bir NavigationController kullanmak istememenizden kaynaklanan tek mantıklı yol olduğunu anlayacaksınız. Eğer sen yap Bir NavController kullanın, gerçekte, tüm işi yapmak için navController'a açık bir şekilde de olsa "delegasyon yapıyorsunuz". Burada ... olmalı bazı VC navigasyonunuzda neler olup bittiğine dair merkezi bir iz bırakan nesne ve ihtiyacınız olan bazı Ne yaparsanız yapın, onunla iletişim kurma yöntemi.

Uygulamada Apple'ın tavsiyesi biraz aşırı ... normal durumlarda, özel bir delege ve yöntem yapmanıza gerek yok, güvenebilirsiniz [self presentingViewController] dismissViewControllerAnimated: - Seninki gibi, senin gözaltına almanı istediğin uzak nesneler üzerinde başka etkilere sahip olmanı istiyorum.

İşte yapabileceğin bir şey hayal etmek tüm delege güçlükleri olmadan çalışmak ...

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

}

Sunum denetleyicisinin bizi reddetmesini istedikten sonra, VC2'yi çağırmak için presentViewController'da bir yöntemi çağıran bir tamamlama bloğumuz var. Hiçbir delege gerekli değil. (Blokların büyük bir satış noktası, bu şartlarda delegelerin ihtiyacını azaltmalarıdır). Ancak bu durumda bir kaç şey var ...

  • VC1'de değilsiniz bilmek bu mainVC yöntemi uygular present2 - Hata ayıklama hataları veya çökmeler ile sonuçlanabilir. Delegeler bundan kaçınmanıza yardımcı olur.
  • VC1 görevden alındığında, tamamlama bloğunu yürütmek gerçekten değil ... ya da değil mi? Self.presentingViewController artık bir şey ifade ediyor mu? Bilmiyorsun (bende) ... bir delege ile, bu belirsizliğe sahip değilsin.
  • Bu yöntemi çalıştırmayı denediğimde, yalnızca uyarı veya hata olmadan askıda kalıyor.

Öyleyse lütfen ... delegasyonu öğrenmek için zaman ayırın!

Update2

Yorumunuzda, bunu VC2'nin kapatma düğmesi işleyicisinde kullanarak çalışmayı başardınız:

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

Bu kesinlikle çok daha basit, ancak sizi bir dizi meseleye bırakıyor.

Sıkı bağlama
ViewController yapınızı birbirine bağlıyorsunuz. Örneğin, mainVC'den önce yeni bir viewController ekleyecekseniz, gerekli davranışınız kesilir (öncekine doğru gezinirsiniz). VC1'de ayrıca VC2'yi #import etmek zorunda kaldınız. Bu nedenle, OOP / MVC hedeflerini kıran birçok bağımlılığa sahipsiniz.

Delegeleri kullanarak, ne VC1 ne de VC2'nin mainVC veya onun öncülleri hakkında bir şey bilmesi gerekmiyor, bu yüzden her şeyi gevşek bir şekilde birleştirilmiş ve modüler halde tutuyoruz.

Bellek
VC1 gitmedi, hala iki tane işaretçiniz var:

  • mainVC en presentedViewController özellik
  • VC2 en presentingViewController özellik

Bunu, giriş yaparak ve ayrıca bunu VC2'den yaparak test edebilirsiniz.

[self dismissViewControllerAnimated:YES completion:nil]; 

Hala çalışıyor, hala VC1'e geri dönüyor.

Bu bana bir bellek sızıntısı gibi geliyor.

Bunun ipucu, buraya geldiğiniz uyarıdır:

[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!

Var olan VC'yi reddetmeye çalışırken mantık bozuluyor olan VC2 sunulan VC'dir. İkinci mesaj gerçekten idam edilmez - belki de bazı şeyler olur, ama hala kurtulduğunu düşündüğünüz bir nesneye iki gösterici ile devam edersiniz. (edit - Bunu kontrol ettim ve çok da kötü değil, mainVC'ye geri döndüğünüzde her iki nesne de kayboluyor)

Oldukça uzun soluklu bir söyleme biçimi - lütfen delegeleri kullanın. Eğer yardımcı olursa, buradaki desenin kısa bir açıklamasını burada yaptım:
Bir denetleyiciye bir denetleyiciden geçmek her zaman kötü bir uygulamadır?

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

VC1'de:

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

Fakat yapamaz Bir şeyi reddediyoruz ... ... belirlediğimiz gibi, gerçekten de gerçekleşmiyor.

VC2'de:

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

Biz (bildiğimiz gibi) VC1'i reddetmedik, geri dönebiliriz vasitasiyla VC1 için MainVC. MainVC, VC1'i reddetti. VC1 gitmiş olduğu için, VC2 bununla birlikte sunulur, bu yüzden MainVC'de temiz bir durumdasınız.

VC1'in VC2'yi bilmesi gerektiğine ve VC2'nin MainVC-> VC1'e ulaştığını bilmesi gerektiğine dair hâlâ büyük ölçüde eşleşiyor, ancak açık bir delegasyon olmaksızın alacağınız en iyisi.


184
2018-02-16 12:49



karmaşık görünüyor. Noktayı takip etmeye ve kopyalamaya çalıştım ama ortada kayboldum. Bunu gerçekleştirmenin başka bir yolu var mı? Uygulama delegesinde bunu eklemek istedim, ana denetleyici kök görüntü denetleyicisi olarak ayarlandı. Navigasyon kontrol cihazlarını 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 tuşa tıklamak VC1'i yükler. VC1'de bir düğme vardır ve tıklatıldığında VC2'yi hata veya uyarı olmadan yüklerken aynı zamanda VC1'i bellekten de çıkarır. - Hema
VC2'de bir düğmem var ve tıklatıldığında VC2 bellekten çıkarılmalı ve kontrol ana denetleyiciye geri dönmeli ve VC1'e gitmemelidir. - Hema
@Hema, gereksinimlerinizi tam olarak anladım ve sizi temin ederim olduğu Bunu yapmanın doğru yolu. Cevabımı biraz daha fazla bilgi ile güncelledim, umarım yardımcı olur. Yaklaşımımı denediyseniz ve sıkışmışsanız, lütfen neyin işe yaramadığını gösteren yeni bir soru sorun. Netlik için bu soruya da geri dönebilirsiniz. - foundry
Merhaba oldu: senin içgörü için teşekkürler. Ayrıca başka bir iş parçacığı (orijinal iş parçacığı) üzerinde konuşuyorum ve orada belirtilen önerilerden bir pasaj gönderdim. Bu sorunu çözmek için tüm uzman cevapları deniyorum. URL burada: stackoverflow.com/questions/14840318/... - Hema
@Honey - Belki de, ama bu ifade 'hayali' bir sözde kodun bir parçasına retorik bir cevaptı. Yapmak istediğim nokta, döngüleri tuzağa düşürmek değil, soruşturmayı, delegasyonun neden değerli bir tasarım deseni olduğunu (ki bu da söz konusu olayı yanlışlıkla önleyen) olduğu konusunda eğitmek. Bence bu yanıltıcı çekişmedir - soru modal VC'lerle ilgilidir, fakat cevabın değeri, çoğunlukla delegenin kalıbının açıklanmasında, soruyu kullanarak ve OP'nin katalizör olarak belirsiz hayal kırıklıklarında yatmaktadır. İlginiz için teşekkürler (ve düzenlemeler)! - foundry


İOS model denetleyicileri hakkında bazı temel kavramları yanlış anladınız. VC1'i reddettiğinizde, VC1 tarafından sunulan tüm görüntü denetleyicileri de reddedilir. Apple, modal görüntü denetleyicileri için yığılmış bir şekilde akması için tasarlanmıştır - VC2'nizde VC1 tarafından sunulur. VC1'i VC1'den VC1'den çıkardığınız anda işten atıyorsunuz, bu yüzden bir toplam karmaşa. İstediğinizi elde etmek için, buttonPressedFromVC1, VC1'in VC2'yi derhal sonlandırmasından hemen sonra VC2'ye sahip olmalıdır. Ve bunun delegeler olmadan gerçekleştirilebileceğ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şkenin içinde saklandığını unutmayın, çünkü vc1'in kendisini reddetmesinden sonra, herhangi bir başvuruda bulunmamanız gerekir.


9
2018-02-25 14:59



çok basit! Diğerlerinin en iyi yayında durmak yerine cevabınıza kaydırmasını isterdim. - Ryan Loggerythm
OP'nin kodunda neden olmasın [self dismiss...] olmak sonra  [self present...] bitti? Asenkron bir şey olmuyor - Honey
@Honey aslında, presentViewController çağırırken asenkron bir şey oluyor - bu yüzden bir tamamlama işleyicisi var. Ancak bunu kullanırken bile, sunumu yapan denetleyiciyi bir şey sunduktan sonra reddederseniz, sunduğu her şey de reddedilir. Bu yüzden OP aslında gerçek bir görüntüleyiciyi başka bir sunucudan sunmak istiyor, böylece mevcut olanı kapatabilir. - Radu Simionescu
Ancak bunu kullanırken bile, sunumu yapan denetleyiciyi bir şey sunduktan sonra reddederseniz, sunduğu her şey de reddedilir.... Aha, yani derleyici temelde "ne yapıyorsun aptal" diyorsun. Sadece önceki kod satırını geri aldın (VC1 olarak kendimi ve ben ne sunarım diye reddediyorum.) Yapma " sağ? - Honey
Derleyici bu konuda bir şey "söyleyemez", ve bunu yürütürken çökmemesi de olabilir, sadece programcının beklemeyeceği şekilde davranır. - Radu Simionescu


Örnekte hızlıdökümanın açıklamasını ve Apple'ın belgelerini resmeden:

  1. Dayanarak Apple'ın belgeleri ve dökümhanenin Yukarıdaki açıklama (bazı hataları düzeltme), presentViewController temsilci tasarım desenini kullanarak sürüm:

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. Dökümün yukarıdaki açıklamalarına dayanarak (bazı hataları düzelterek), temsilci tasarım desenini kullanarak 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 {

}

8
2017-12-12 01:21



örneğinizde ViewController sınıf mainVC doğru mu? - Honey


Radu Simionescu - harika bir çalışma! ve Swift severler için çözümünüzün altında:

@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)
    })
}

4
2018-04-16 10:14



Bu bir şekilde gerçekten işe yaramazsa beni hayal kırıklığına uğratıyor .. Bloğun neden "self.presentingViewController" ı yakalamadığını ve güçlü bir referansa ihtiyaç duyduğunu anlamıyorum, yani "var sunanVC" .. her neyse, bu işe yarar. Teşekkürler - emdog4


Ben bunu istedim:

MapVC tam ekranda bir Haritadır.

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

PopupVC'de bir düğmeye bastığımda, MapVC'ye döner ve sonra viewDidAppear'ı yürütmek istiyorum.

Bunu ben yaptım:

MapVC.m: düğme eyleminde, programatik olarak bir segue ve temsilci belirleme

- (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'den önce 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];
}

0
2017-08-31 11:47





Sunumu yaparken UINavigationController kullanarak sorunu çözdüm. MainVC'de VC1'i sunarken

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

VC1'de, VC2'yi göstermek ve aynı zamanda VC1'i kapatmak istediğimde (sadece bir animasyon),

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

Ve VC2'de, görünüm denetleyicisini kapatırken, her zamanki gibi şunları kullanabiliriz:

self.dismiss(animated: true, completion: nil)

0
2017-11-02 17:21