Lỗi ứng dụng iOS - Không thể thêm bản thân dưới dạng xem phụ


157

Tôi đã nhận được báo cáo sự cố này, nhưng tôi không biết cách gỡ lỗi.

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

Phiên bản iOS là 7.0.3. Bất cứ ai cũng trải qua vụ tai nạn kỳ lạ này?

CẬP NHẬT:

Tôi không biết nơi mã của tôi gây ra sự cố này, vì vậy tôi không thể đăng mã ở đây, xin lỗi.

CẬP NHẬT thứ hai

Xem câu trả lời dưới đây.


3
Bạn có thể cho chúng tôi xem mã của bạn?
David Gölzhäuser

43
Xin lỗi nhưng tôi không hiểu phản ứng của bạn. Các lỗi stack là rõ ràng về vấn đề. Vì vậy, trước tiên, bạn có thể cho phép người dùng đặt thêm mã khi được hỏi với anh ta (chỉ 1h câu hỏi được hỏi và bạn yêu cầu đóng nó ngay lập tức). Thứ hai tôi đã nhận được một downvote không có lý do vì câu trả lời của tôi là rõ ràng. Câu hỏi là "Bất cứ ai cũng trải qua vụ tai nạn kỳ lạ này?". Và tôi đã nói tại sao anh ta có được điều này. Ngay cả khi nó không nằm trong mã của nó.
Tancrede Chazallet

9
Câu hỏi này là một chính xác. người dùng không thể đưa ra mã chính xác của lỗi trong tình huống này. bởi vì anh ta không biết trong đó trình điều khiển xem có gì đó không ổn
Ravindra Bagale

16
Chúng tôi sử dụng Crashlytics và có hơn 30 người dùng đã đánh sập ứng dụng của chúng tôi với "Không thể thêm bản thân dưới dạng xem xét", tất nhiên chúng tôi không có mã cố gắng tự thêm vào dưới dạng một cuộc phỏng vấn. Từ backtrace không có tài liệu tham khảo cho ứng dụng của chúng tôi cả.
Richie Hyatt

49
Bỏ phiếu để mở lại; Rõ ràng, những người đóng nó không làm được nhiều nhà phát triển iOS, vì đây là vấn đề phổ biến do iOS7 giới thiệu và giết chết cả đống ứng dụng tốt trên iOS6 (Tôi đã thấy nó trên nhiều dự án từ các công ty khác nhau). Thật đáng tiếc khi câu hỏi này là một câu hỏi hàng đầu trên Google, nhưng một vài người thiển cận đã đóng nó.
Adam

Câu trả lời:


51

Tôi đang suy đoán dựa trên một cái gì đó tương tự mà tôi đã gỡ lỗi gần đây ... nếu bạn đẩy (hoặc bật) trình điều khiển chế độ xem bằng Hoạt hình: CÓ, nó không hoàn thành ngay lập tức và điều tồi tệ sẽ xảy ra nếu bạn thực hiện một lần đẩy hoặc bật khác trước hoạt hình hoàn thành Bạn có thể dễ dàng kiểm tra xem đây có phải là trường hợp thực sự hay không bằng cách tạm thời thay đổi các hoạt động Push và Pop của bạn thành Animated: NO (để chúng hoàn thành đồng bộ) và xem liệu điều đó có giúp loại bỏ sự cố hay không. Nếu đây thực sự là vấn đề của bạn và bạn muốn bật hoạt hình trở lại, thì chiến lược chính xác là thực hiện giao thức UINavestionControllDelegate. Điều này bao gồm phương thức sau, được gọi sau khi hoạt ảnh hoàn tất:

navigationController:didShowViewController:animated:

Về cơ bản, bạn muốn di chuyển một số mã khi cần vào phương thức này để đảm bảo rằng không có hành động nào khác có thể gây ra thay đổi đối với ngăn xếp NavigationContoder sẽ xảy ra cho đến khi hoạt ảnh kết thúc và ngăn xếp sẵn sàng cho nhiều thay đổi hơn.


Quay trở lại với iOS 4 - một cái gì đó tôi đã thấy một cái gì đó tương tự trong một trong các ứng dụng của chúng tôi - IIRC, nếu bạn bật hoạt hình và sau đó ngay lập tức đẩy mã hoạt hình, mã UI sẽ bị hỏng. Cuối cùng chỉ thay đổi để không bao giờ thực hiện hai thao tác đẩy / bật hoạt hình trở lại. Tất nhiên, tất cả logic cơ bản đã được viết lại từ đó, nhưng không khó để tin rằng một lỗi tương tự không còn tồn tại.
Hot Licks

Tôi gặp vấn đề tương tự. Trong trường hợp của tôi, điều này đã xảy ra bởi vì ứng dụng đã thực thi một lệnh thay đổi ui của trình điều khiển khung nhìn mới [newViewController setLabelTitle:...]ngay sau khi gọi PushViewContoder với Animated:YES.Và tôi đã giải quyết việc chuyển phương thức setLabelTitle sang viewDidLoad trên newViewControll. Cảm ơn đã cho tôi manh mối.
jeprubio

Vui vì đã giúp! Điểm hay là việc di chuyển mã sang ViewContoder mới cũng là một tùy chọn nếu bạn biết nó sẽ thuộc lớp nào. Càng ngày tôi càng thấy hữu ích khi nắm bắt các phương thức khác nhau của giao thức UINavestionControllDelegate, trong mọi trường hợp. Và tôi đã thấy rằng trong các sự kiện iOS8 thực hiện theo các thứ tự khác nhau và một số thứ trước đây ít nhiều đồng bộ trở lại nhanh chóng nhưng lên lịch mọi thứ để thực hiện trên nền không đồng bộ, tạo ra nhiều lỗi thời gian mới như thế này. Cảm ơn, Apple!
RobP

14

Chúng tôi cũng bắt đầu gặp phải vấn đề này và rất có khả năng chúng tôi bị gây ra bởi cùng một vấn đề.

Trong trường hợp của chúng tôi, chúng tôi đã phải lấy dữ liệu từ mặt sau trong một số trường hợp, điều đó có nghĩa là người dùng có thể chạm vào một cái gì đó và sau đó sẽ có một chút chậm trễ trước khi điều hướng đẩy xảy ra. Nếu một người dùng nhanh chóng chạm vào xung quanh, họ có thể kết thúc bằng hai lần điều hướng từ cùng một bộ điều khiển chế độ xem, điều này đã kích hoạt chính trường hợp ngoại lệ này.

Giải pháp của chúng tôi là một danh mục trên UINavestionControll, ngăn chặn các lần đẩy / bật trừ khi vc trên cùng là cùng một điểm từ một thời điểm nhất định.

tập tin .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

tập tin .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

Cho đến nay điều này dường như đã giải quyết vấn đề cho chúng tôi. Thí dụ:

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

Về cơ bản, quy tắc là: trước khi bất kỳ sự chậm trễ không liên quan đến người dùng nào lấy khóa từ bộ điều khiển điều hướng có liên quan và đưa nó vào lệnh gọi để đẩy / bật.

Từ "khóa" có thể là từ ngữ hơi kém vì nó có thể ám chỉ có một số dạng khóa xảy ra cần mở khóa, nhưng vì không có phương pháp "mở khóa" ở bất cứ đâu, nên có lẽ không sao.

. các trường hợp.)


Vì bạn nói rằng bạn đã thử giải pháp này, nó đã giải quyết vấn đề cho bạn chưa?
Mike D

Cho đến nay, vâng. Vấn đề chưa xuất hiện trở lại. Tôi sẽ cập nhật câu trả lời.
Kalle

4
Tôi đã sử dụng một phiên bản sửa đổi dựa trên của bạn: gist.github.com/mdewolfe/9369751 . Có vẻ như nó đã sửa nó.
Mike D

2
@Kalle Giải pháp này hoạt động cho đẩy / pop. Nhưng làm thế nào để giải quyết lỗi này nếu tôi sử dụng segue?
Geek

@Kadle Bạn có thể giúp tôi thực hiện điều này? Nhìn stackoverflow.com/q/23247713/1323014 THX
Marckaraujo

12

Mã này giải quyết vấn đề: https://gist.github.com/nonamelive/9334458

Nó sử dụng API riêng, nhưng tôi có thể xác nhận rằng đó là App Store an toàn. (Một trong những ứng dụng của tôi sử dụng mã này đã được App Store chấp thuận.)

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

Đây là giải pháp tốt nhất cho đến nay, với một số người khác tôi vẫn sẽ ngẫu nhiên gặp phải sự cố đẩy kép hoặc tôi sẽ nhận được một bộ điều khiển điều hướng đóng băng.
blueice

Mã này không biên dịch cho tôi, có thiếu gì không?
Maxime B

8

Tôi sẽ mô tả chi tiết hơn về sự cố này trong ứng dụng của tôi và đánh dấu điều này là đã trả lời.

Ứng dụng của tôi có UINavestionContaptor với bộ điều khiển gốc là UITableViewControll chứa danh sách các đối tượng ghi chú. Đối tượng ghi chú có thuộc tính nội dung trong html. Chọn một ghi chú sẽ đi đến bộ điều khiển chi tiết.

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

Bộ điều khiển chi tiết

Bộ điều khiển này có UIWebView, hiển thị nội dung ghi chú được truyền từ bộ điều khiển gốc.

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

Bộ điều khiển này là đại biểu của điều khiển webview. Nếu ghi chú chứa các liên kết, hãy nhấn vào một liên kết sẽ chuyển đến trình duyệt web trong ứng dụng.

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

Tôi nhận được báo cáo sự cố ở trên hàng ngày. Tôi không biết nơi mã của tôi gây ra vụ tai nạn này. Sau một số điều tra với sự giúp đỡ của người dùng, cuối cùng tôi đã có thể khắc phục sự cố này. Nội dung html này sẽ gây ra sự cố:

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

Trong phương thức viewDidLoad của bộ điều khiển chi tiết, tôi đã tải html này vào điều khiển webview, ngay sau đó, phương thức ủy nhiệm ở trên được gọi ngay lập tức với request.URL là nguồn của iframe (google.com). Phương thức ủy nhiệm này gọi phương thức PushViewControll khi đang trong viewDidLoad => crash!

Tôi đã khắc phục sự cố này bằng cách kiểm tra navigationType:

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

Hi vọng điêu nay co ich


1
Sẽ không phải là một lựa chọn tốt để đẩy bộ điều khiển mà không có hình ảnh động khi được gọi từ viewDidLoad?
Rivera

6

Tôi có cùng một vấn đề, điều đơn giản làm việc cho tôi là thay đổi Hoạt hình: Có thành Hoạt hình: Không.

Có vẻ như vấn đề là do hoạt hình không hoàn thành kịp thời gian.

Hy vọng điều này sẽ giúp được ai đó.


3

Để tái tạo lỗi này, hãy thử đẩy hai bộ điều khiển xem cùng một lúc. Hoặc đẩy và bật cùng một lúc. Thí dụ:

nhập mô tả hình ảnh ở đây Tôi đã tạo một danh mục chặn các cuộc gọi này và làm cho chúng an toàn bằng cách đảm bảo rằng không có sự thúc đẩy nào khác đang diễn ra trong khi một cuộc gọi đang diễn ra. Chỉ cần sao chép mã vào dự án của bạn và do phương pháp thay đổi, bạn sẽ thấy ổn.

#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

Một vấn đề với giải pháp này là nếu bạn gọi popToRootViewControllerhoặc popToViewController:khi bạn đã ở trên trình điều khiển xem gốc hoặc trên viewContoder được bật lên, thì didShowViewControllersẽ không được gọi và nó sẽ bị kẹt viewTransitionInProgress.
chuyển hướng

1
Bạn có thể giải thích những dòng này: self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController; [self.interactivePopGestureRecognizer setEnabled:YES]; Khi nào bộ nhận dạng bị vô hiệu hóa? Và làm thế nào để bạn biết những gì các đại biểu nên được? Với những dòng đó, đối với tôi, nó phá vỡ cử chỉ pop sau khi bật một lần.
chuyển hướng

Tôi đã thử thực hiện điều này và sau một lúc nó khóa bộ điều khiển điều hướng, có lẽ là do những gì @divergio đã đề cập.
blueice

2

Chỉ cần trải nghiệm vấn đề này là tốt. Hãy để tôi chỉ cho bạn mã của tôi:

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

Lỗi xuất hiện do dòng này:

firstView.addSubView(firstView)

Bạn không thể thêm bản thân để xem. Tôi đã thay đổi dòng mã thành:

firstView.addSubView(secondView)

Lỗi đã biến mất và tôi đã có thể nhìn thấy cả hai chế độ xem. Chỉ cần nghĩ rằng điều này sẽ giúp bất cứ ai muốn xem một ví dụ.


Tôi cũng đã thử cách tiếp cận này nhưng stacktrace sẽ khác và thực sự hiển thị dòng mã của bạn gây ra sự cố. Tôi tin rằng vấn đề nguồn khác với câu hỏi.
Ben

1

Tìm kiếm mã của bạn cho "addSubview".

Tại một trong những nơi bạn đã gọi phương thức này, bạn đã cố gắng thêm một khung nhìn vào mảng khung nhìn phụ của chính nó bằng phương thức này.

Ví dụ:

[self.view addSubview:self.view];

Hoặc là:

[self.myLabel addSubview:self.myLabel];

Rất vui khi biết bạn đã tìm thấy lỗi của mình và bây giờ tôi hiểu chính xác lý do tại sao bạn nhận được "Không thể thêm bản thân dưới dạng xem xét". Tại một điểm mà View2 của bạn là bộ điều khiển xem gốc của bộ điều khiển điều hướng của bạn, bạn đã đẩy View2, điều này gây ra điều này: [View2.view addSubview:View2.view]do đó, tự thêm vào dưới dạng xem phụ.
Michal Shatz

1

Tôi nghĩ rằng việc đẩy / bật các bộ điều khiển xem với hình ảnh động tại bất kỳ thời điểm nào cũng hoàn toàn tốt và SDK nên xử lý một cách lịch sự hàng đợi các cuộc gọi cho chúng tôi.

Do đó, nó không và tất cả các giải pháp cố gắng bỏ qua các lần đẩy tiếp theo, có thể được coi là một lỗi do ngăn xếp điều hướng cuối cùng không phải là những gì mã dự định.

Tôi đã thực hiện một hàng đợi cuộc gọi thay thế:

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

Nó không xử lý hàng đợi hỗn hợp của đẩy và bật nhưng đó là một khởi đầu tốt để khắc phục hầu hết các sự cố của chúng tôi.

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


Tôi đã thử giải pháp của bạn có vẻ rất tốt, nhưng tôi gặp vấn đề. Khi đẩy 2 bộ điều khiển xem với hoạt hình KHÔNG có cái nào khác, tôi rất nhanh thấy cái đầu tiên. Điều này đã không xảy ra trước đây. Bất cứ ý tưởng những gì tôi có thể làm để sửa chữa nó?
1

Tôi đang cố gắng xây dựng một dự án có thể liên tục gây ra sự cố này (dự án thực sự của tôi nhận được các báo cáo sự cố như thế này). Tôi đã thực hiện một ứng dụng đơn giản với bộ điều khiển điều hướng, bộ điều khiển gốc và một nút ngay lập tức đẩy 4 bộ điều khiển chế độ xem mới lên ngăn xếp điều hướng, và sau đó bật ra cái cuối cùng. Không có bất kỳ phân lớp đặc biệt hoặc bất cứ điều gì nó thực sự hoạt động tốt. Apple đã sửa lỗi này gần đây?
Cruinh

1

Xin lỗi vì đến trễ bữa tiệc. Gần đây tôi đã gặp sự cố này trong đó thanh điều hướng của tôi rơi vào trạng thái bị hỏng do đẩy nhiều hơn một bộ điều khiển chế độ xem cùng một lúc. Điều này xảy ra vì bộ điều khiển khung nhìn khác được đẩy trong khi bộ điều khiển khung nhìn thứ nhất vẫn đang hoạt hình. Lấy gợi ý từ câu trả lời không có ý nghĩa, tôi đã đưa ra giải pháp đơn giản phù hợp với trường hợp của mình. Bạn chỉ cần phân lớp UINavigationControllervà ghi đè phương thức PushViewContoder và kiểm tra xem hoạt ảnh của bộ điều khiển xem trước đã kết thúc chưa. Bạn có thể nghe hoàn thành hoạt hình bằng cách biến lớp của bạn thành đại biểu UINavigationControllerDelegatevà đặt đại biểu self.

Tôi đã tải lên một ý chính ở đây để làm cho mọi thứ đơn giản.

Chỉ cần đảm bảo rằng bạn đặt lớp mới này làm Bộ điều hướng trong bảng phân cảnh của mình.


Cho đến nay, nó dường như đã khắc phục các sự cố trên ứng dụng tôi đang làm việc ... thêm vào đó, giải pháp khá đơn giản và rõ ràng về điểm: hoạt hình điều khiển chế độ xem đầu tiên chưa hoàn tất. Những người có cùng một vấn đề nên kiểm tra này.
alasker

0

Dựa trên @RobP gợi ý tuyệt vời, tôi đã tạo lớp con UINavestionControll để ngăn chặn những vấn đề như vậy. Nó xử lý đẩy và / hoặc popping và bạn có thể thực hiện một cách an toàn:

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

Nếu 'acceptConflictingCommands' đánh dấu thì đúng (theo mặc định) người dùng sẽ thấy hoạt hình đẩy của vc1, vc2, vc3 và sau đó sẽ thấy hoạt hình xuất hiện của vc3. Nếu 'acceptConflictingCommands' là sai, tất cả các yêu cầu đẩy / pop sẽ bị loại bỏ cho đến khi vc1 được đẩy hoàn toàn - do đó 3 cuộc gọi khác sẽ bị loại bỏ.


những lệnh đó có thực sự mâu thuẫn không? Tôi vừa cùng nhau thực hiện một dự án mới để thấy sự cố này xảy ra, sử dụng mã như bạn có ở trên (nhưng trong Swift), và nó thực sự đã thực hiện từng cú đẩy và pop, tất cả theo trình tự. cái này sau cái kia. Không có sự cố. Không sử dụng bất kỳ lớp con. chỉ cần UINavestionControll thông thường của apple.
Cruinh

Nó thực sự đã bị sập với ObjC và iOS 7. Tôi không thể xác nhận liệu nó có còn xảy ra hay không. Bạn có chắc chắn rằng bạn đang thực hiện các lệnh với animated:truecờ?
hris.to 27/08/2015

Có tôi đã sử dụng hoạt hình: cờ thật.
Cruinh

0

giải pháp của nonamelive là tuyệt vời. Nhưng nếu bạn không muốn sử dụng api riêng tư, bạn có thể đạt được phương thức UINavigationControllerDelegate. Hoặc bạn có thể thay đổi hoạt hình YESthành NO. Đây là một mẫu mã, bạn có thể kế thừa nó. Hy vọng nó hữu ích :)

https://github.com/antrix1989/ANNavestionControll


0

Tôi đã tìm kiếm vấn đề này rất nhiều, nó có thể đẩy hai hoặc nhiều VC cùng một lúc, điều này gây ra vấn đề hoạt hình đẩy, bạn có thể tham khảo điều này: Không thể tự thêm vào khi xem xét 解决

chỉ cần đảm bảo rằng có một VC trong quá trình chuyển đổi cùng một lúc, chúc may mắn.


0

Đôi khi bạn đã cố gắng thêm một chế độ xem vào chế độ xem của chính nó.

halfView.addSubview(halfView)

thay đổi điều này để xem phụ của bạn.

halfView.addSubview(favView)

0

Tôi cũng gặp phải vấn đề này. Khi tôi thực hiện phân tích nhật ký Firebase, tôi thấy rằng vấn đề này chỉ xảy ra khi ứng dụng bắt đầu lạnh. Vì vậy, tôi đã viết một bản demo có thể tái tạo vụ tai nạn này.

.

Tôi cũng thấy rằng khi trình điều khiển xem gốc của cửa sổ được hiển thị, thực hiện nhiều lần đẩy sẽ không gây ra vấn đề tương tự nữa. (Bạn có thể nhận xét testColdStartUp (rootNav) trong AppDelegate.swift và bỏ ghi chú testColdStartUp () trong ViewContoder.swift)

ps: Tôi đã phân tích hiện trường vụ tai nạn này trong ứng dụng của tôi. Khi người dùng nhấp vào thông báo đẩy để khởi động lạnh ứng dụng, ứng dụng vẫn ở trên trang Khởi chạy và nhấp vào một lần đẩy khác để nhảy. Tại thời điểm này, ứng dụng có thể xuất hiện sự cố. Hiện tại của tôi Giải pháp là lưu trữ bộ đệm đẩy hoặc liên kết phổ quát để bắt đầu mở trang nhảy ứng dụng, đợi trình điều khiển rootview hiển thị và sau đó trì hoãn thực thi.


-2

thử điều hướng của bạn bằng phương pháp trì hoãn, để hoàn thành hoạt hình điều hướng cuối cùng,

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


-2

Một khung nhìn không thể được thêm vào dưới dạng một khung nhìn phụ.

Các khung nhìn duy trì một hệ thống phân cấp cha-con vì vậy nếu bạn thêm một khung nhìn dưới dạng một khung nhìn phụ thì nó sẽ thông qua ngoại lệ.

nếu một lớp là UIViewControll thì để có được khung nhìn của nó, bạn sử dụng self.view.

nếu một lớp là UIView Class thì để có được cái nhìn của nó, bạn hãy tự sử dụng.


-3

bạn không thể thêm bản thân dưới dạng subview nếu nó sẽ là Lớp UiViewControll. bạn có thể thêm bản thân dưới dạng xem phụ nếu nó sẽ là Lớp UiView.


-9

Nếu bạn muốn thêm một Subview vào View, bạn có thể làm như thế này;

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

Tốt lắm, đó là một đoạn mã hay. Bây giờ bạn có thể cho tôi biết những gì nó phải làm với câu hỏi này và làm thế nào nó giải quyết nó?
Popeye

@Popeye Bạn có ý tưởng nào hay hơn không?
David Gölzhäuser

Không, vì họ chưa cung cấp đủ thông tin / mã để sao chép vấn đề. Vì vậy, không có cách nào điều này có thể được trả lời, nó giống như việc bạn nói với họ cách làm điều gì đó không liên quan đến vấn đề của họ.
Popeye

2
Tôi đoán nó sẽ hữu ích hơn nhiều cho họ khi chỉ đóng vấn đề. Hiểu rồi! +1 cho David G vì đã thực sự cố gắng giúp đỡ ai đó trên StackOverflow. Tôi ước tôi có thể -1 Bình chọn Đóng của bạn !!! Điều này vẫn đang xảy ra với người dùng và nó có thể là một lỗi trong iOS7 cho tất cả những gì chúng ta biết. Vì vậy, chỉ vì ai đó không thể đăng mã vi phạm không có nghĩa là câu hỏi không hợp lệ và có giá trị cho người dùng khác xem. Ngay cả khi chỉ để thấy rằng những người khác đang nhìn thấy vấn đề tương tự mà không có bất kỳ lý do hợp lý nào tại sao họ lại nhìn thấy nó. -rrh
Richie Hyatt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.