presentViewController: animasi: Tampilan YES tidak akan muncul sampai pengguna mengetuk lagi

93

Saya mendapatkan beberapa perilaku aneh dengan presentViewController:animated:completion. Apa yang saya buat pada dasarnya adalah permainan menebak.

Saya memiliki UIViewController(frequencyViewController) yang berisi UITableView(frequencyTableView). Ketika pengguna mengetuk baris di questionTableView yang berisi jawaban yang benar, tampilan (correctViewController) harus dibuat dan tampilannya harus meluncur ke atas dari bawah layar, sebagai tampilan modal. Ini memberi tahu pengguna bahwa mereka memiliki jawaban yang benar dan menyetel ulang frequencyViewController di belakangnya untuk pertanyaan berikutnya. correctViewController diberhentikan dengan menekan tombol untuk menampilkan pertanyaan berikutnya.

Ini semua bekerja dengan benar setiap saat, dan tampilan correctViewController muncul secara instan selama presentViewController:animated:completionitu animated:NO.

Jika saya set animated:YES, correctViewController diinisialisasi dan melakukan panggilan ke viewDidLoad. Namun viewWillAppear,, viewDidAppeardan blok penyelesaian dari presentViewController:animated:completiontidak dipanggil. Aplikasi hanya ada di sana dan masih menampilkan frequencyViewController sampai saya membuat ketukan kedua. Sekarang, viewWillAppear, viewDidAppear dan blok penyelesaian dipanggil.

Saya menyelidiki lebih lanjut, dan bukan hanya ketukan lain yang akan menyebabkannya berlanjut. Tampaknya jika saya memiringkan atau mengguncang iPhone saya, ini juga dapat menyebabkannya memicu viewWillLoad dll. Ini seperti menunggu masukan pengguna lain sebelum maju. Ini terjadi pada iPhone asli dan di simulator, yang saya buktikan dengan mengirimkan perintah goyang ke simulator.

Saya benar-benar bingung apa yang harus saya lakukan tentang ini ... Saya sangat menghargai bantuan yang dapat diberikan siapa pun.

Terima kasih

Ini kode saya. Ini sangat sederhana ...

Ini adalah kode di questionViewController yang bertindak sebagai delegasi ke questionTableView

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    if (indexPath.row != [self.frequencyModel currentFrequencyIndex])
    {
        // If guess was wrong, then mark the selection as incorrect
        NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath];
        [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]];            
    }
    else
    {
        // If guess was correct, show correct view
        NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        self.correctViewController = [[HFBCorrectViewController alloc] init];
        self.correctViewController.delegate = self;
        [self presentViewController:self.correctViewController animated:YES completion:^(void){
            NSLog(@"Completed Presenting correctViewController");
            [self setUpViewForNextQuestion];
        }];
    }
}

Ini adalah keseluruhan dari correctViewController

@implementation HFBCorrectViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self)
    {
        // Custom initialization
        NSLog(@"[HFBCorrectViewController initWithNibName:bundle:]");
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    NSLog(@"[HFBCorrectViewController viewDidLoad]");
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"[HFBCorrectViewController viewDidAppear]");
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)close:(id)sender
{
    NSLog(@"[HFBCorrectViewController close:sender:]");
    [self.delegate didDismissCorrectViewController];
}


@end

Edit:

Saya menemukan pertanyaan ini sebelumnya: UITableView dan presentViewController membutuhkan 2 klik untuk ditampilkan

Dan jika saya mengubah didSelectRowkode saya menjadi ini, itu bekerja sangat lama dengan animasi ... Tapi itu berantakan dan tidak masuk akal mengapa itu tidak berhasil di tempat pertama. Jadi saya tidak menganggap itu sebagai jawaban ...

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    if (indexPath.row != [self.frequencyModel currentFrequencyIndex])
    {
        // If guess was wrong, then mark the selection as incorrect
        NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath];
        [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]];
        // [cell setAccessoryType:(UITableViewCellAccessoryType)]

    }
    else
    {
        // If guess was correct, show correct view
        NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);

        ////////////////////////////
        // BELOW HERE ARE THE CHANGES
        [self performSelector:@selector(showCorrectViewController:) withObject:nil afterDelay:0];
    }
}

-(void)showCorrectViewController:(id)sender
{
    self.correctViewController = [[HFBCorrectViewController alloc] init];
    self.correctViewController.delegate = self;
    self.correctViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
    [self presentViewController:self.correctViewController animated:YES completion:^(void){
        NSLog(@"Completed Presenting correctViewController");
        [self setUpViewForNextQuestion];
    }];
}
HalfNormalled
sumber
1
Saya memiliki masalah yang sama. Saya memeriksa dengan NSLogs bahwa saya tidak memanggil metode apa pun yang membutuhkan waktu lama untuk dieksekusi. Tampaknya hanya animasi yang presentViewController:seharusnya memicu dimulai dengan penundaan besar. Ini tampaknya menjadi bug di iOS 7 dan juga dibahas di Forum Pengembang Apple.
Theo
Saya memiliki masalah yang sama. Sepertinya bug iOS7 - tidak terjadi di iOS6. Juga, dalam kasus saya, ini hanya terjadi pertama kali setelah saya membuka pengontrol tampilan presentasi. @ Theo, dapatkah Anda memberikan tautan ke Apple Dev Forums?
AXE

Jawaban:

158

Saya mengalami masalah yang sama hari ini. Saya menggali topik dan tampaknya itu terkait dengan runloop utama yang tertidur.

Sebenarnya ini adalah bug yang sangat halus, karena jika Anda memiliki sedikit animasi umpan balik, pengatur waktu, dll. Dalam kode Anda, masalah ini tidak akan muncul karena runloop akan tetap hidup oleh sumber-sumber ini. Saya telah menemukan masalah ini dengan menggunakan UITableViewCellyang telah selectionStyledisetel ke UITableViewCellSelectionStyleNone, sehingga tidak ada animasi pemilihan yang memicu runloop setelah penangan pemilihan baris dijalankan.

Untuk memperbaikinya (hingga Apple melakukan sesuatu), Anda dapat memicu runloop utama dengan beberapa cara:

Solusi yang paling tidak mengganggu adalah memanggil CFRunLoopWakeUp:

[self presentViewController:vc animated:YES completion:nil];
CFRunLoopWakeUp(CFRunLoopGetCurrent());

Atau Anda dapat memasukkan blok kosong ke antrean utama:

[self presentViewController:vc animated:YES completion:nil];
dispatch_async(dispatch_get_main_queue(), ^{});

Ini lucu, tetapi jika Anda menggoyangkan perangkat, itu juga akan memicu putaran utama (itu harus memproses peristiwa gerakan). Hal yang sama dengan keran, tetapi itu termasuk dalam pertanyaan asli :) Selain itu, jika sistem memperbarui bilah status (misalnya pembaruan jam, kekuatan sinyal WiFi berubah, dll.), Itu juga akan mengaktifkan loop utama dan menampilkan tampilan pengontrol.

Bagi siapa pun yang tertarik, saya menulis proyek demonstrasi minimal dari masalah ini untuk memverifikasi hipotesis runloop: https://github.com/tzahola/present-bug

Saya juga telah melaporkan bug tersebut ke Apple.

Tamás Zahola
sumber
Terima kasih Tamás. Itu info yang bagus. Ini menjelaskan mengapa terkadang saya perlu mengetuk untuk memulai putaran lari, atau terkadang hanya menunggu dan itu akan berhasil. Saya telah menandai ini sebagai solusi karena sepertinya tidak ada hal lain yang dapat kami lakukan sampai bug diperbaiki.
HalfNormalled
Apel menggali lubang yang besar, saya jatuh ke dalamnya dan merasa sangat terluka.
OpenThread
7
OMG, ini sangat lucu! BTW, bug ini masih ada.
igrrik
1
Saya memiliki masalah yang sama persis benar-benar menjengkelkan, untungnya ini memperbaikinya.
Tandai
1
Dikonfirmasi bahwa masalah ini masih terjadi di iOS 13
Eric Horacek
69

Lihat ini: https://devforums.apple.com/thread/201431 Jika Anda tidak ingin membaca semuanya - solusi untuk beberapa orang (termasuk saya) adalah membuatpresentViewController panggilan secara eksplisit di utas utama:

Swift 4.2:

DispatchQueue.main.async { 
    self.present(myVC, animated: true, completion: nil)
}

Objective-C:

dispatch_async(dispatch_get_main_queue(), ^{
    [self presentViewController:myVC animated:YES completion:nil];
});

Mungkin iOS7 mengacaukan utasnya didSelectRowAtIndexPath.

KAPAK
sumber
wow - ini berhasil untuk saya. Saya juga melihat penundaan. Saya tidak mengerti mengapa ini perlu. Terima kasih telah memposting ini.
drudru
4
Mengajukan radar dengan Apple tentang masalah ini: rdar: // 19563577 openradar.appspot.com/19563577
mluisbrown
1
Saya menggunakan iOS 8 dan ini tidak memperbaikinya untuk saya - selalu melaporkan berada di utas utama, tetapi saya masih melihat masalah yang dijelaskan.
Robert
7

Saya melewati itu di Swift 3.0 dengan menggunakan kode berikut:

DispatchQueue.main.async { 
    self.present(UIViewController(), animated: true, completion: nil)
}
Menandai
sumber
1
itu menyelesaikan masalah. Saya mengalami masalah yang sama karena saya menelepon presentmelalui panggilan balik penutupan.
Jeremy Piednoel
2

Memanggil [viewController view]pengontrol tampilan yang disajikan melakukan trik untuk saya.

JaganY
sumber
Ini bekerja untuk saya di iOS 8. Saya pikir ini lebih baik daripada dispatch_async.
Robert
0

Saya akan penasaran untuk melihat apa yang [self setUpViewForNextQuestion];dilakukannya.

Anda dapat mencoba menelepon [self.correctViewController.view setNeedsDisplay];di akhir blok penyelesaian Anda di presentViewController.

Nick
sumber
Saat ini, setUpViewForNextQuestion hanya memanggil reloadData pada tampilan tabel (untuk mengubah semua warna latar belakang kembali menjadi putih jika ada yang disetel menjadi merah oleh tebakan yang salah). Tetapi apakah perubahan apa pun di sana akan membuat perbedaan? Masalahnya adalah correctViewController tidak muncul hingga penggunaan diketuk lagi, jadi blok penyelesaian ini tidak akan dipanggil hingga setelah ketukan kedua itu.
HalfNormalled
Saya memang mencoba saran Anda, tetapi tidak ada bedanya. Seperti yang saya katakan, tampilan tidak muncul, jadi blok penyelesaian tidak dipanggil sampai setelah ketukan kedua, atau gerakan goyang.
HalfNormalled
hmm. sebagai permulaan, saya akan menggunakan breakpoint untuk mengonfirmasi bahwa kode presentasi Anda tidak dipanggil sampai ketukan ke-2. (dibandingkan dipanggil pada ketukan pertama, tetapi tidak ditampilkan di layar sampai beberapa waktu kemudian). jika yang terakhir benar, coba paksa presentasi Anda terjadi di utas utama. Saat Anda menggunakan "performSelectorWithDelay: 0" yang memaksa aplikasi menunggu hingga iterasi berikutnya dari run loop sebelum dijalankan. Bisa jadi terkait threading.
Nick
Terima kasih, Nick. Bagaimana cara memeriksa menggunakan breakpoint? Saat ini saya menggunakan NSLog untuk memeriksa kapan setiap metode dipanggil. Inilah yang saya miliki sekarang: Ketuk 1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] Sekarang tidak ada yang terjadi hingga: Ketuk 218:05:00.515 Frequency[641:60b] [HFBCorrectViewController viewDidAppear] 18:05:00.516 Frequency[641:60b] Completed Presenting correctViewController 18:05:00.517 Frequency[641:60b] [HFBFrequencyViewController setUpViewForNextQuestion]
HalfNormalled
Maaf, seharusnya lebih eksplisit. Saya mengerti bagaimana menggunakan breakpoint. Saya mendapatkan cerita yang sama dengan NSLog. Saya meletakkan breakpoint pada viewDidLoad dan viewDidAppear di correctViewController. Itu rusak di viewDidLoad, lalu ketika saya melanjutkan, itu duduk dan menunggu ketukan lagi, lalu istirahat di viewDidAppear. Kadang-kadang tampaknya tidak memerlukan ketukan dan itu berdasarkan waktu, pada saat saya melangkah melalui hal-hal melihat apa lagi yang dipanggil, itu berputar untuk memanggil viewDidLoad.
HalfNormalled
0

Saya telah menulis ekstensi (kategori) dengan metode swizzling untuk UIViewController yang memecahkan masalah. Terima kasih kepada AX dan NSHipster untuk petunjuk implementasi ( swift / objektif-c ).

Cepat

extension UIViewController {

 override public class func initialize() {
    struct DispatchToken {
      static var token: dispatch_once_t = 0
    }
    if self != UIViewController.self {
      return
    }
    dispatch_once(&DispatchToken.token) {
      let originalSelector = Selector("presentViewController:animated:completion:")
      let swizzledSelector = Selector("wrappedPresentViewController:animated:completion:")

      let originalMethod = class_getInstanceMethod(self, originalSelector)
      let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

      let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

      if didAddMethod {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
      }
      else {
        method_exchangeImplementations(originalMethod, swizzledMethod)
      }
    }
  }

  func wrappedPresentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
    dispatch_async(dispatch_get_main_queue()) {
      self.wrappedPresentViewController(viewControllerToPresent, animated: flag, completion: completion)
    }
  }
}  

Objective-C

#import <objc/runtime.h>

@implementation UIViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(presentViewController:animated:completion:);
        SEL swizzledSelector = @selector(wrappedPresentViewController:animated:completion:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)wrappedPresentViewController:(UIViewController *)viewControllerToPresent 
                             animated:(BOOL)flag 
                           completion:(void (^ __nullable)(void))completion {
    dispatch_async(dispatch_get_main_queue(),^{
        [self wrappedPresentViewController:viewControllerToPresent
                                  animated:flag 
                                completion:completion];
    });

}

@end
Gladkov_Art
sumber
0

Periksa apakah sel Anda di papan cerita memiliki Seleksi = tidak ada

Jika demikian, ubah menjadi biru atau abu-abu dan itu akan berfungsi

Mkey
sumber
0

XCode Vesion: 9.4.1, Swift 4.1

Dalam kasus saya ini terjadi, ketika saya mengetuk sel dan berpindah ke tampilan lain. Saya men-debug lebih dalam dan tampaknya itu terjadi di dalam viewDidAppearkarena berisi kode berikut

if let indexPath = tableView.indexPathForSelectedRow {
   tableView.deselectRow(at: indexPath, animated: true)
}

kemudian saya menambahkan segmen kode di atas di dalam prepare(for segue: UIStoryboardSegue, sender: Any?)dan bekerja dengan sempurna.

Dalam pengalaman saya, solusi saya adalah, Jika kita berharap untuk melakukan perubahan baru (mis. Memuat ulang tabel, batal memilih sel yang dipilih, dll.) Untuk tampilan tabel ketika kembali lagi dari tampilan kedua, kemudian gunakan delegasi daripada viewDidAppeardan gunakan di atastableView.deselectRow kode di segmen sebelum memindahkan pengontrol tampilan kedua

Sachintha Udara
sumber