Kesalahan aplikasi iOS - Tidak dapat menambahkan diri sebagai subview

157

Saya menerima laporan kerusakan ini, tetapi saya tidak tahu bagaimana cara debug itu.

Fatal Exception NSInvalidArgumentException
Can't add self as subview
0 ...    CoreFoundation  __exceptionPreprocess + 130
1    libobjc.A.dylib     objc_exception_throw + 38
2    CoreFoundation  -[NSException initWithCoder:]
3    UIKit   -[UIView(Internal) _addSubview:positioned:relativeTo:] + 110
4    UIKit   -[UIView(Hierarchy) addSubview:] + 30
5    UIKit   __53-[_UINavigationParallaxTransition animateTransition:]_block_invoke + 1196
6    UIKit   +[UIView(Animation) performWithoutAnimation:] + 72
7    UIKit   -[_UINavigationParallaxTransition animateTransition:] + 732
8    UIKit   -[UINavigationController _startCustomTransition:] + 2616
9    UIKit   -[UINavigationController _startDeferredTransitionIfNeeded:] + 418
10   UIKit   -[UINavigationController __viewWillLayoutSubviews] + 44
11   UIKit   -[UILayoutContainerView layoutSubviews] + 184
12   UIKit   -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 346
13   QuartzCore  -[CALayer layoutSublayers] + 142
14   QuartzCore  CA::Layer::layout_if_needed(CA::Transaction*) + 350
15   QuartzCore  CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 16
16   QuartzCore  CA::Context::commit_transaction(CA::Transaction*) + 228
17   QuartzCore  CA::Transaction::commit() + 314
18   QuartzCore  CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 56

Versi iOS adalah 7.0.3. Adakah yang mengalami kecelakaan aneh ini?

MEMPERBARUI:

Saya tidak tahu di mana dalam kode saya menyebabkan kerusakan ini, jadi saya tidak dapat memposting kode di sini, maaf.

UPDATE kedua

Lihat jawabannya di bawah.

Arnol
sumber
3
Bisakah Anda menunjukkan kepada kami kode Anda?
David Gölzhäuser
43
Maaf tapi saya tidak mengerti reaksi Anda yang berlebihan. Kesalahan tumpukan jelas pada masalah. Jadi pertama-tama, Anda dapat membiarkan pengguna memasukkan lebih banyak kode seperti yang diminta kepadanya (hanya 1 jam pertanyaan yang diajukan dan Anda meminta untuk segera menutupnya). Kedua saya menerima downvote tanpa alasan karena jawaban saya jelas. Pertanyaannya adalah "Adakah yang mengalami kecelakaan aneh ini?". Dan saya katakan mengapa dia mendapatkan ini. Bahkan jika itu tidak secara spesifik terletak dalam kodenya.
Tancrede Chazallet
9
Pertanyaan ini benar. pengguna tidak dapat memberikan kode kesalahan yang tepat dalam situasi ini. karena dia tidak tahu di mana pandangan controller sesuatu yang salah
Ravindra Bagale
16
Kami menggunakan Crashlytics dan memiliki lebih dari 30 pengguna yang telah merusak aplikasi kami dengan "Tidak dapat menambahkan diri sebagai subview" tentu saja kami tidak memiliki kode yang mencoba menambahkan dirinya sebagai subview. Dari backtrace tidak ada referensi ke aplikasi kami sama sekali.
Richie Hyatt
49
Voting untuk dibuka kembali; orang-orang yang menutupnya tidak melakukan banyak dev iOS, karena ini adalah masalah umum yang diperkenalkan oleh iOS7 dan membunuh banyak aplikasi yang baik-baik saja di iOS6 (saya telah melihatnya di beberapa proyek dari perusahaan yang berbeda). Sangat disayangkan bahwa pertanyaan ini adalah hit teratas di Google, tetapi beberapa orang berpandangan pendek menutupnya.
Adam

Jawaban:

51

Saya berspekulasi berdasarkan pada sesuatu yang serupa yang saya debug baru-baru ini ... jika Anda mendorong (atau pop) view controller dengan Animated: YES tidak langsung selesai, dan hal-hal buruk terjadi jika Anda melakukan push atau pop lain sebelum animasi selesai. Anda dapat dengan mudah menguji apakah ini memang benar terjadi dengan mengubah sementara operasi Push dan Pop Anda menjadi Animated: NO (sehingga semuanya selesai secara serempak) dan melihat apakah itu menghilangkan crash. Jika ini memang masalah Anda dan Anda ingin menghidupkan kembali animasi, maka strategi yang benar adalah menerapkan protokol UINavigationControllerDelegate. Ini termasuk metode berikut, yang dipanggil setelah animasi selesai:

navigationController:didShowViewController:animated:

Pada dasarnya Anda ingin memindahkan beberapa kode yang diperlukan ke dalam metode ini untuk memastikan bahwa tidak ada tindakan lain yang dapat menyebabkan perubahan pada tumpukan NavigationController akan terjadi sampai animasi selesai dan tumpukan siap untuk lebih banyak perubahan.

RobP
sumber
Kembali ke masa lalu tentang iOS 4-sesuatu yang saya lihat serupa di salah satu aplikasi kami - IIRC, jika Anda muncul animasi dan kemudian segera mendorong animasi kode UI akan menjadi sangat buruk. Akhirnya hanya berubah untuk tidak pernah melakukan dua operasi push / pop animasi kembali ke belakang. Tentu saja, semua logika yang mendasarinya telah ditulis ulang sejak saat itu, tetapi tidak sulit untuk percaya bahwa bug yang sama masih belum ada.
Hot Licks
Saya memiliki masalah yang sama. Dalam kasus saya ini terjadi karena aplikasi menjalankan instruksi yang mengubah ui view controller [newViewController setLabelTitle:...]baru setelah memanggil pushViewController dengan Animated:YES.Dan saya memecahkan masalah dengan memindahkan metode setLabelTitle ke viewDidLoad pada newViewController. Terima kasih telah memberi saya petunjuk.
jeprubio
Senang itu membantu! Poin bagus bahwa memindahkan kode ke ViewController baru juga merupakan opsi jika Anda tahu kelas mana yang akan dipilih. Semakin banyak saya merasa berguna untuk menangkap berbagai metode protokol UINavigationControllerDelegate, dalam hal apa pun. Dan saya telah menemukan bahwa dalam peristiwa iOS8 menjalankan pesanan yang berbeda, dan beberapa hal yang dulunya lebih atau kurang sinkron sekarang kembali dengan cepat tetapi jadwalkan hal-hal yang harus dilakukan di latar belakang secara tidak sinkron, membuat banyak bug waktu baru seperti ini. Terima kasih, Apple!
RobP
14

Kami mulai mendapatkan masalah ini juga, dan kemungkinan besar kemungkinan kami disebabkan oleh masalah yang sama.

Dalam kasus kami, kami harus menarik data dari ujung belakang dalam beberapa kasus, yang berarti pengguna mungkin mengetuk sesuatu dan kemudian akan ada sedikit keterlambatan sebelum dorongan nav terjadi. Jika pengguna dengan cepat mengetuk sekitar, mereka mungkin berakhir dengan dua dorongan nav dari pengontrol tampilan yang sama, yang memicu pengecualian ini.

Solusi kami adalah kategori pada UINavigationController yang mencegah push / pops kecuali vc atas adalah sama dari titik waktu tertentu.

file .h:

@interface UINavigationController (SafePushing)

- (id)navigationLock; ///< Obtain "lock" for pushing onto the navigation controller

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock; ///< Uses a horizontal slide transition. Has no effect if the view controller is already in the stack. Has no effect if navigationLock is not the current lock.
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock; ///< Pops view controllers until the one specified is on top. Returns the popped controllers. Has no effect if navigationLock is not the current lock.
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated navigationLock:(id)navigationLock; ///< Pops until there's only a single view controller left on the stack. Returns the popped controllers. Has no effect if navigationLock is not the current lock.

@end

file .m:

@implementation UINavigationController (SafePushing)

- (id)navigationLock
{
    return self.topViewController;
}

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock
{
    if (!navigationLock || self.topViewController == navigationLock) 
        [self pushViewController:viewController animated:animated];
}

- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated navigationLock:(id)navigationLock
{
    if (!navigationLock || self.topViewController == navigationLock)
        return [self popToRootViewControllerAnimated:animated];
    return @[];
}

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock
{
    if (!navigationLock || self.topViewController == navigationLock)
        return [self popToViewController:viewController animated:animated];
    return @[];
}

@end

Sejauh ini tampaknya ini telah menyelesaikan masalah bagi kami. Contoh:

id lock = _dataViewController.navigationController.navigationLock;
[[MyApi sharedClient] getUserProfile:_user.id success:^(MyUser *user) {
    ProfileViewController *pvc = [[ProfileViewController alloc] initWithUser:user];
    [_dataViewController.navigationController pushViewController:pvc animated:YES navigationLock:lock];
}];

Pada dasarnya, aturannya adalah: sebelum ada penundaan terkait pengguna ambil kunci dari nav controller yang relevan, dan sertakan dalam panggilan untuk push / pop.

Kata "kunci" mungkin kata-kata yang sedikit buruk karena mungkin menyindir ada beberapa bentuk kunci yang perlu dibuka, tetapi karena tidak ada metode "membuka" di mana pun, mungkin tidak apa-apa.

(Sebagai sidenote, "keterlambatan yang tidak terkait dengan pengguna" adalah keterlambatan apa pun yang disebabkan oleh kode, yaitu apa pun yang tidak sinkron. Pengguna yang mengetuk kontroler nav yang didorong secara animasi tidak masuk hitungan dan tidak perlu melakukan navigasiLock: versi untuk itu kasus.)

Kalle
sumber
Karena Anda mengatakan Anda mencoba solusi ini, apakah sudah memecahkan masalah untuk Anda?
Mike D
Sejauh ini ya. Masalahnya belum muncul kembali. Saya akan memperbarui jawabannya.
Kalle
4
Saya menggunakan versi modifikasi berdasarkan milik Anda: gist.github.com/mdewolfe/9369751 . Sepertinya sudah diperbaiki.
Mike D
2
@Kalle Solusi ini berfungsi untuk push / pop. Tapi bagaimana cara mengatasi kesalahan ini jika saya menggunakan segue?
Geek
@ Kadle Bisakah Anda membantu saya menerapkan ini? Lihat stackoverflow.com/q/23247713/1323014 THX
Marckaraujo
12

Kode ini menyelesaikan masalah: https://gist.github.com/nonamelive/9334458

Ini menggunakan API pribadi, tetapi saya dapat mengonfirmasi bahwa itu adalah App Store aman. (Salah satu aplikasi saya yang menggunakan kode ini disetujui oleh App Store.)

@interface UINavigationController (DMNavigationController)

- (void)didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;

@end

@interface DMNavigationController ()

@property (nonatomic, assign) BOOL shouldIgnorePushingViewControllers;

@end

@implementation DMNavigationViewController

#pragma mark - Push

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if (!self.shouldIgnorePushingViewControllers)
    {
        [super pushViewController:viewController animated:animated];
    }

    self.shouldIgnorePushingViewControllers = YES;
}

#pragma mark - Private API

// This is confirmed to be App Store safe.
// If you feel uncomfortable to use Private API, you could also use the delegate method navigationController:didShowViewController:animated:.
- (void)didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [super didShowViewController:viewController animated:animated];
    self.shouldIgnorePushingViewControllers = NO;
}
tidak menarik
sumber
Ini telah menjadi solusi terbaik sejauh ini, dengan beberapa yang lain saya masih akan secara acak mendapatkan masalah dorong ganda atau saya akan mendapatkan pengontrol navigasi beku.
blueice
Kode ini tidak dapat dikompilasi untuk saya, apakah ada yang hilang?
Maxime B
8

Saya akan menjelaskan lebih detail tentang kerusakan ini di aplikasi saya dan menandainya sebagai jawaban.

Aplikasi saya memiliki UINavigationController dengan pengendali root adalah UITableViewController yang berisi daftar objek catatan. Objek note memiliki properti konten dalam html. Pilih catatan akan pergi ke pengontrol detail.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    //get note object
    DetailViewController *controller = [[DetailViewController alloc] initWithNote:note];
    [self.navigationController pushViewController:controller animated:YES];
}

Pengontrol detail

Pengontrol ini memiliki UIWebView, menampilkan konten catatan yang diteruskan dari pengontrol root.

- (void)viewDidLoad
{
    ...
    [_webView loadHTMLString:note.content baseURL:nil];
    ...
}

Pengontrol ini adalah delegasi dari kontrol tampilan web. Jika catatan berisi tautan, ketuk tautan akan masuk ke peramban web dalam aplikasi.

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    WebBrowserViewController *browserController = [[WebBrowserViewController alloc] init];
    browserController.startupURL = request.URL;
    [self.navigationController pushViewController:webViewController animated:YES];
    return NO;
}

Saya menerima laporan kerusakan di atas setiap hari. Saya tidak tahu di mana dalam kode saya menyebabkan kerusakan ini. Setelah beberapa penyelidikan dengan bantuan pengguna, saya akhirnya dapat memperbaiki kerusakan ini. Konten html ini akan menyebabkan crash:

...
<iframe src="http://google.com"></iframe>
...

Dalam metode viewDidLoad dari pengontrol detail, saya memuat html ini ke kontrol tampilan web, tepat setelah itu, metode delegasi di atas dipanggil segera dengan request.URL adalah sumber iframe (google.com). Metode delegasi ini memanggil metode pushViewController saat dalam viewDidLoad => crash!

Saya memperbaiki kerusakan ini dengan memeriksa jenis navigasi:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    if (navigationType != UIWebViewNavigationTypeOther)
    {
        //go to web browser controller
    }
}

Semoga ini membantu

Arnol
sumber
1
Bukankah itu pilihan yang baik untuk mendorong controller tanpa animasi saat dipanggil dari viewDidLoad?
Rivera
6

Saya memiliki masalah yang sama, yang hanya berhasil bagi saya adalah mengubah Animated: Yes to Animated: Tidak.

Sepertinya masalah itu karena animasi tidak selesai tepat waktu.

Semoga ini bisa membantu seseorang.

Lion789
sumber
3

Untuk mereproduksi bug ini, cobalah mendorong dua pengontrol tampilan secara bersamaan. Atau mendorong dan meletus pada saat yang sama. Contoh:

masukkan deskripsi gambar di sini Saya telah membuat kategori yang mencegat panggilan ini dan membuatnya aman dengan memastikan bahwa tidak ada dorongan lain yang terjadi saat satu sedang berlangsung. Cukup salin kode ke proyek Anda dan karena metode swizzling Anda akan baik untuk pergi.

#import "UINavigationController+Consistent.h"
#import <objc/runtime.h>
/// This char is used to add storage for the isPushingViewController property.
static char const * const ObjectTagKey = "ObjectTag";

@interface UINavigationController ()
@property (readwrite,getter = isViewTransitionInProgress) BOOL viewTransitionInProgress;

@end

@implementation UINavigationController (Consistent)

- (void)setViewTransitionInProgress:(BOOL)property {
    NSNumber *number = [NSNumber numberWithBool:property];
    objc_setAssociatedObject(self, ObjectTagKey, number , OBJC_ASSOCIATION_RETAIN);
}


- (BOOL)isViewTransitionInProgress {
    NSNumber *number = objc_getAssociatedObject(self, ObjectTagKey);

    return [number boolValue];
}


#pragma mark - Intercept Pop, Push, PopToRootVC
/// @name Intercept Pop, Push, PopToRootVC

- (NSArray *)safePopToRootViewControllerAnimated:(BOOL)animated {
    if (self.viewTransitionInProgress) return nil;
    if (animated) {
        self.viewTransitionInProgress = YES;
    }
    //-- This is not a recursion, due to method swizzling the call below calls the original  method.
    return [self safePopToRootViewControllerAnimated:animated];

}


- (NSArray *)safePopToViewController:(UIViewController *)viewController animated:(BOOL)animated {
    if (self.viewTransitionInProgress) return nil;
    if (animated) {
        self.viewTransitionInProgress = YES;
    }
    //-- This is not a recursion, due to method swizzling the call below calls the original  method.
    return [self safePopToViewController:viewController animated:animated];
}


- (UIViewController *)safePopViewControllerAnimated:(BOOL)animated {
    if (self.viewTransitionInProgress) return nil;
    if (animated) {
        self.viewTransitionInProgress = YES;
    }
    //-- This is not a recursion, due to method swizzling the call below calls the original  method.
    return [self safePopViewControllerAnimated:animated];
}



- (void)safePushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    self.delegate = self;
    //-- If we are already pushing a view controller, we dont push another one.
    if (self.isViewTransitionInProgress == NO) {
        //-- This is not a recursion, due to method swizzling the call below calls the original  method.
        [self safePushViewController:viewController animated:animated];
        if (animated) {
            self.viewTransitionInProgress = YES;
        }
    }
}


// This is confirmed to be App Store safe.
// If you feel uncomfortable to use Private API, you could also use the delegate method navigationController:didShowViewController:animated:.
- (void)safeDidShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    //-- This is not a recursion. Due to method swizzling this is calling the original method.
    [self safeDidShowViewController:viewController animated:animated];
    self.viewTransitionInProgress = NO;
}


// If the user doesnt complete the swipe-to-go-back gesture, we need to intercept it and set the flag to NO again.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    id<UIViewControllerTransitionCoordinator> tc = navigationController.topViewController.transitionCoordinator;
    [tc notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        self.viewTransitionInProgress = NO;
        //--Reenable swipe back gesture.
        self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController;
        [self.interactivePopGestureRecognizer setEnabled:YES];
    }];
    //-- Method swizzling wont work in the case of a delegate so:
    //-- forward this method to the original delegate if there is one different than ourselves.
    if (navigationController.delegate != self) {
        [navigationController.delegate navigationController:navigationController
                                     willShowViewController:viewController
                                                   animated:animated];
    }
}


+ (void)load {
    //-- Exchange the original implementation with our custom one.
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(pushViewController:animated:)), class_getInstanceMethod(self, @selector(safePushViewController:animated:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(didShowViewController:animated:)), class_getInstanceMethod(self, @selector(safeDidShowViewController:animated:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(popViewControllerAnimated:)), class_getInstanceMethod(self, @selector(safePopViewControllerAnimated:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(popToRootViewControllerAnimated:)), class_getInstanceMethod(self, @selector(safePopToRootViewControllerAnimated:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(popToViewController:animated:)), class_getInstanceMethod(self, @selector(safePopToViewController:animated:)));
}

@end
dan
sumber
Salah satu masalah dengan solusi ini adalah bahwa jika Anda menelepon popToRootViewControlleratau popToViewController:ketika Anda sudah di controller tampilan root atau pada viewController akan muncul, maka didShowViewControllertidak akan dipanggil dan itu akan terjebak viewTransitionInProgress.
divergio
1
Bisakah Anda menjelaskan baris-baris ini: self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController; [self.interactivePopGestureRecognizer setEnabled:YES]; Kapan pengenal dinonaktifkan? Dan bagaimana Anda tahu apa yang seharusnya menjadi delegasi? Dengan garis-garis itu, bagi saya itu merusak gerakan pop setelah muncul sekali.
divergio
Saya mencoba menerapkan ini dan setelah beberapa waktu mengunci pengontrol navigasi, mungkin karena apa yang @divergio sebutkan.
blueice
2

Baru saja mengalami masalah ini juga. Biarkan saya tunjukkan kode saya:

override func viewDidLoad() { 
  super.viewDidLoad()

  //First, I create a UIView
  let firstFrame = CGRect(x: 50, y: 70, height: 200, width: 200)
  let firstView = UIView(frame: firstFrame)
  firstView.addBackgroundColor = UIColor.yellow
  view.addSubview(firstView) 

  //Now, I want to add a subview inside firstView
  let secondFrame = CGRect(x: 20, y:50, height: 15, width: 35)
  let secondView = UIView(frame: secondFrame)
  secondView.addBackgroundColor = UIColor.green
  firstView.addSubView(firstView)
 }

Kesalahan muncul karena baris ini:

firstView.addSubView(firstView)

Anda tidak dapat menambahkan diri ke subview. Saya mengubah baris kode menjadi:

firstView.addSubView(secondView)

Kesalahan hilang dan saya bisa melihat kedua pandangan. Hanya berpikir ini akan membantu siapa saja yang ingin melihat contoh.

halapgos1
sumber
Saya juga mencoba pendekatan ini tetapi stacktrace akan berbeda dan benar-benar menunjukkan baris kode Anda yang menyebabkan crash. Saya percaya masalah sumber berbeda dari pertanyaan.
Ben
1

Cari kode Anda untuk "addSubview".

Di salah satu tempat yang Anda panggil metode ini, Anda mencoba menambahkan tampilan ke array sub-view sendiri menggunakan metode ini.

Sebagai contoh:

[self.view addSubview:self.view];

Atau:

[self.myLabel addSubview:self.myLabel];
Michal Shatz
sumber
Senang mendengar Anda menemukan kesalahan Anda, dan sekarang saya mengerti persis mengapa Anda menerima "Tidak dapat menambahkan diri sebagai subview". Pada titik di mana View2 Anda adalah pengontrol tampilan root pengontrol navigasi Anda, Anda mendorong View2 yang menyebabkan ini: [View2.view addSubview:View2.view]dengan demikian, menambahkan diri sebagai subview.
Michal Shatz
1

Saya pikir bahwa mendorong / memunculkan view controller dengan animasi di setiap titik harus baik-baik saja dan SDK harus dengan anggun menangani antrian panggilan untuk kita.

Oleh karena itu tidak dan semua solusi mencoba untuk mengabaikan dorongan berikutnya, yang dapat dianggap sebagai bug karena tumpukan navigasi akhir bukan apa yang dimaksud kode.

Saya malah menerapkan antrian panggilan push:

// SafeNavigationController.h

@interface SafeNavigationController : UINavigationController
@end

 

// SafeNavigationController.m

#define timeToWaitBetweenAnimations 0.5

@interface SafeNavigationController ()

@property (nonatomic, strong) NSMutableArray * controllersQueue;
@property (nonatomic)         BOOL animateLastQueuedController;
@property (nonatomic)         BOOL pushScheduled;
@property (nonatomic, strong) NSDate * lastAnimatedPushDate;

@end

@implementation SafeNavigationController

- (void)awakeFromNib
{
    [super awakeFromNib];

    self.controllersQueue = [NSMutableArray array];
}

- (void)pushViewController:(UIViewController *)viewController
                  animated:(BOOL)animated
{
    [self.controllersQueue addObject:viewController];
    self.animateLastQueuedController = animated;

    if (self.pushScheduled)
        return;

    // Wait for push animation to finish
    NSTimeInterval timeToWait = self.lastAnimatedPushDate ? timeToWaitBetweenAnimations + [self.lastAnimatedPushDate timeIntervalSinceNow] : 0.0;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((timeToWait > 0.0 ? timeToWait : 0.0) * NSEC_PER_SEC)),
                   dispatch_get_main_queue(), ^
                   {
                       [self pushQueuedControllers];

                       self.lastAnimatedPushDate = self.animateLastQueuedController ? [NSDate date] : nil;
                       self.pushScheduled = NO;
                   });
    self.pushScheduled = YES;
}

- (void)pushQueuedControllers
{
    for (NSInteger index = 0; index < (NSInteger)self.controllersQueue.count - 1; index++)
    {
        [super pushViewController:self.controllersQueue[index]
                         animated:NO];
    }
    [super pushViewController:self.controllersQueue.lastObject
                     animated:self.animateLastQueuedController];

    [self.controllersQueue removeAllObjects];
}

@end

Itu tidak menangani antrian campuran push dan pops, tetapi itu adalah awal yang baik untuk memperbaiki sebagian besar crash kami.

Intisari: https://gist.github.com/rivera-ernesto/0bc628be1e24ff5704ae

Rivera
sumber
Saya sudah mencoba solusi Anda yang tampaknya sangat bagus, tetapi saya mengalami masalah. Saat mendorong 2 pengontrol tampilan dengan animasi TIDAK satu demi satu, saya secara singkat melihat yang pertama. Ini tidak terjadi sebelumnya. Adakah yang bisa saya lakukan untuk memperbaikinya?
Jan
Saya mencoba membangun sebuah proyek yang secara konsisten dapat menyebabkan crash semacam ini (proyek saya yang sebenarnya mendapat laporan kerusakan seperti ini). Saya melakukan aplikasi sederhana dengan pengontrol navigasi, pengontrol root, dan sebuah tombol yang segera mendorong 4 pengontrol tampilan baru ke nav stack, dan kemudian memunculkan yang terakhir. Tanpa subclass khusus atau apa pun itu tampaknya berfungsi dengan baik. Apakah Apple memperbaikinya baru-baru ini?
Cruinh
1

Maaf terlambat datang ke pesta. Saya baru-baru ini mengalami masalah ketika bilah navigasi saya rusak karena mendorong lebih dari satu pengontrol tampilan sekaligus. Ini terjadi karena pengontrol tampilan lain didorong saat pengontrol tampilan pertama masih hidup. Mengambil petunjuk dari jawaban yang tidak ramah saya datang dengan solusi sederhana saya yang berfungsi dalam kasus saya. Anda hanya perlu UINavigationControllermensubclass dan mengganti metode pushViewController dan memeriksa apakah animasi pengontrol tampilan sebelumnya sudah selesai. Anda dapat mendengarkan penyelesaian animasi dengan membuat kelas Anda menjadi delegasi UINavigationControllerDelegatedan mengatur delegasi self.

Saya telah mengunggah inti di sini untuk mempermudah.

Pastikan Anda mengatur kelas baru ini sebagai NavigationController di storyboard Anda.

nikhil.thakkar
sumber
Sejauh ini tampaknya telah memperbaiki crash pada aplikasi yang saya kerjakan ... ditambah, solusinya cukup mudah dan jelas tentang intinya: animasi viewcontroller pertama belum lengkap. Orang yang memiliki masalah yang sama harus memeriksanya.
alasker
0

Berdasarkan @RobP petunjuk besar saya membuat subclass UINavigationController untuk mencegah masalah seperti itu. Ini menangani mendorong dan / atau bermunculan dan Anda dapat dengan aman menjalankan:

[self.navigationController pushViewController:vc1 animated:YES];
[self.navigationController pushViewController:vc2 animated:YES];
[self.navigationController pushViewController:vc3 animated:YES];
[self.navigationController popViewControllerAnimated:YES];

Jika flag 'acceptConflictingCommands' benar (secara default) pengguna akan melihat animasi mendorong vc1, vc2, vc3 dan kemudian akan melihat animasi popping vc3. Jika 'acceptConflictingCommands' salah, semua permintaan push / pop akan dibuang sampai vc1 sepenuhnya didorong - maka 3 panggilan lainnya akan dibuang.

hris.to
sumber
apakah perintah-perintah itu sebenarnya saling bertentangan? Saya baru saja menyusun proyek baru yang cepat untuk melihat kerusakan ini terjadi, menggunakan kode seperti yang Anda miliki di atas (tetapi dalam Swift), dan itu benar-benar mengeksekusi setiap push, dan pop, semua dalam urutan. satu demi satu. Tidak ada crash Tanpa menggunakan subclass apa pun. hanya apel UINavigationController biasa.
Cruinh
Itu memang crash dengan ObjC dan iOS 7. Saya tidak dapat mengkonfirmasi apakah itu masih terjadi sekarang Apakah Anda yakin Anda menjalankan perintah dengan animated:truebendera?
hris.to
Ya saya menggunakan animasi: true flag.
Cruinh
0

solusi nonamelive mengagumkan. Tetapi jika Anda tidak ingin menggunakan api pribadi, Anda dapat mencapai UINavigationControllerDelegatemetode ini. Atau Anda dapat mengubah animasi YESmenjadi NO. Ini adalah contoh kode, Anda dapat mewarisinya. Semoga bermanfaat :)

https://github.com/antrix1989/ANNavigationController

NSKevin
sumber
0

Terkadang Anda secara keliru mencoba menambahkan tampilan ke tampilan sendiri.

halfView.addSubview(halfView)

ubah ini ke sub tampilan Anda.

halfView.addSubview(favView)
Vinoth Vino
sumber
0

Saya juga mengalami masalah ini. Ketika saya melakukan analisis log Firebase, saya menemukan bahwa masalah ini hanya terjadi ketika aplikasi dingin dimulai. Jadi saya menulis demo yang dapat mereproduksi crash ini.

.

Saya juga menemukan bahwa ketika viewcontroller root jendela ditampilkan, melakukan beberapa dorongan tidak akan menyebabkan masalah yang sama lagi. (Anda dapat mengomentari testColdStartUp (rootNav) di AppDelegate.swift, dan batalkan komentar pada testColdStartUp () komentar di ViewController.swift)

ps: Saya menganalisis tempat kecelakaan ini di aplikasi saya. Ketika pengguna mengklik notifikasi push untuk memulai aplikasi dengan dingin, aplikasi tersebut masih di halaman Luncurkan dan mengklik push lain untuk melompat. Pada saat ini, aplikasi mungkin muncul Kecelakaan. My current Solusinya adalah dengan men-cache push atau tautan Universal mulai dingin untuk membuka halaman lompatan App, menunggu rootviewcontroller untuk ditampilkan, dan kemudian menunda eksekusi.

Jader Yang
sumber
-2

coba navigasi Anda menggunakan metode penundaan, untuk menyelesaikan animasi navigasi terakhir,

[self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>]

Naeem Paracha
sumber
-2

Tampilan tidak dapat ditambahkan sebagai subview di dalamnya.

Tampilan mempertahankan hierarki orangtua-anak jadi jika Anda menambahkan tampilan sebagai subview dalam dirinya sendiri akan melalui pengecualian.

jika suatu kelas adalah UIViewController maka untuk mendapatkan pandangannya Anda menggunakan self.view.

jika suatu kelas adalah Kelas UIView maka untuk mendapatkan pandangannya Anda menggunakan sendiri.

Mradul Kumar
sumber
-3

Anda tidak dapat menambahkan diri sebagai subview jika akan menjadi Kelas UiViewController. Anda dapat menambahkan diri sebagai subview jika akan menjadi Kelas UiView.

pengguna1533983
sumber
-9

Jika Anda ingin menambahkan Subview ke Tampilan, Anda dapat melakukannya seperti ini;

UIView *mainview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)]; //Creats the mainview
    UIView *subview = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; //Creates the subview, you can use any kind of Views (UIImageView, UIWebView, UIView…)

    [mainview addSubview:subview]; //Adds subview to mainview
David Gölzhäuser
sumber
Bagus, itu sepotong kode yang bagus. Sekarang bisakah Anda memberi tahu saya apa kaitannya dengan pertanyaan ini dan bagaimana cara menyelesaikannya?
Popeye
@ Popeye Apakah Anda punya ide yang lebih baik?
David Gölzhäuser
Tidak, karena mereka belum memberikan informasi / kode yang cukup untuk mereplikasi masalah. Jadi tidak mungkin ini bisa dijawab, sepertinya Anda memberi tahu mereka cara melakukan sesuatu yang tidak ada hubungannya dengan masalah mereka.
Popeye
2
Saya kira itu jauh lebih bermanfaat bagi mereka untuk hanya menutup masalah. Mengerti! +1 untuk David G karena benar-benar mencoba membantu seseorang di StackOverflow. Saya berharap saya bisa -1 Vote Tutup Anda !!! Ini masih terjadi untuk pengguna dan mungkin ada bug di iOS7 untuk semua yang kita tahu. Jadi hanya karena seseorang tidak dapat memposting kode yang menyinggung tidak berarti pertanyaannya tidak valid dan berharga untuk dilihat oleh pengguna lain. Bahkan jika itu hanya untuk melihat bahwa orang lain melihat masalah yang sama tanpa alasan logis mengapa mereka melihatnya. -rrh
Richie Hyatt