iOS 앱 오류-자체를 하위보기로 추가 할 수 없습니다


157

이 충돌 보고서를 받았지만 디버깅 방법을 모르겠습니다.

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

iOS 버전은 7.0.3입니다. 누구든지이 이상한 충돌을 경험합니까?

최신 정보:

내 코드 에서이 충돌의 원인을 알 수 없으므로 여기에 코드를 게시 할 수 없습니다. 죄송합니다.

두 번째 업데이트

아래 답변을 참조하십시오.


3
코드를 보여줄 수 있습니까?
David Gölzhäuser

43
죄송하지만 과잉 반응을 이해하지 못합니다. 문제에서 스택 오류가 분명합니다. 따라서 먼저 사용자가 요청한대로 더 많은 코드를 넣도록 할 수 있습니다 (질문 1 시간 만에 즉시 닫으십시오). 두 번째로 나는 대답이 명확하기 때문에 아무런 이유없이 공감대를 받았다. 문제는 "누군가이 이상한 충돌을 경험합니까?"입니다. 그리고 나는 그가 왜 이것을 얻었는지 말했다. 코드에 구체적으로 위치하지 않더라도.
Tancrede Chazallet

9
이 질문은 정답입니다. 이 상황에서 사용자는 정확한 오류 코드를 줄 수 없습니다. 그는 어떤 뷰 컨트롤러에서 어떤 문제가 발생했는지 알지 못하기 때문에
Ravindra Bagale

16
우리는 Crashlytics를 사용하며 "자신을 하위보기로 추가 할 수 없음"으로 앱을 다운 한 30 명 이상의 사용자를 보유하고 있습니다. 물론 하위보기로 추가하려는 코드는 없습니다. 역 추적에는 앱에 대한 참조가 전혀 없습니다.
Richie Hyatt

49
재개 투표; 폐쇄 된 사람들은 iOS 개발에 많은 도움이되지 않습니다. 이것은 iOS7에 의해 도입 된 일반적인 문제이므로 iOS6에서 훌륭했던 많은 앱을 죽입니다 (다른 회사의 여러 프로젝트에서 보았습니다). 이 질문이 Google에서 가장 인기있는 것은 유감이지만 근시안이 적은 사람들이이 질문을 닫았습니다.
Adam

답변:


51

최근에 디버깅 한 것과 비슷한 것을 기반으로 추측하고 있습니다 ... Animated : YES로 뷰 컨트롤러를 푸시 (또는 팝)하면 즉시 완료되지 않으며 애니메이션 전에 다른 푸시 또는 팝을 수행하면 나쁜 일이 발생합니다 완료합니다. 푸시 및 팝 작업을 일시적으로 Animated : NO (동 기적으로 완료되도록)로 변경하고 충돌이 제거되는지 확인하면 이러한 상황이 실제로 발생하는지 쉽게 테스트 할 수 있습니다. 이것이 실제로 문제이고 애니메이션을 다시 켜려면 올바른 전략은 UINavigationControllerDelegate 프로토콜을 구현하는 것입니다. 여기에는 애니메이션이 완료된 후 호출되는 다음 방법이 포함됩니다.

navigationController:didShowViewController:animated:

기본적으로 애니메이션이 완료되고 스택이 더 많은 변경을 준비 할 때까지 NavigationController 스택을 변경시킬 수있는 다른 작업이 발생하지 않도록하기 위해 필요에 따라 일부 코드를이 메소드로 이동하려고합니다.


iOS 4에 대한 되돌아 가기 우리의 앱 중 하나에서 비슷한 것을 보았습니다-IIRC, 애니메이션을 띄운 다음 애니메이션을 즉시 푸시하면 UI 코드가 심하게 엉망이됩니다. 두 번의 애니메이션 푸시 / 팝 작업을 다시 연속으로 수행하지 않도록 변경되었습니다. 물론 모든 기본 논리가 그 이후로 다시 작성되었지만 비슷한 버그가 아직 없다고 믿기는 어렵지 않습니다.
Hot Licks

나는 같은 문제가 있었다. 필자의 경우 이것은 앱이 [newViewController setLabelTitle:...]pushViewController를 호출 한 직후에 새로운보기 컨트롤러의 UI를 변경하는 명령을 실행했기 때문에 발생 Animated:YES.했으며 setLabelTitle 메소드를 newViewController의 viewDidLoad로 이동하는 것을 해결했습니다. 실마리를 주셔서 감사합니다.
jeprubio

도움이되어 다행입니다! 어떤 클래스가 될지 알면 새로운 ViewController로 코드를 이동하는 것도 옵션이라는 것이 좋습니다. 어쨌든 UINavigationControllerDelegate 프로토콜의 다양한 메소드를 잡는 것이 점점 더 유용하다는 것을 알았습니다. 그리고 iOS8 이벤트에서 다른 순서로 실행되는 것으로 나타났습니다. 다소 동기화 된 일부 항목은 이제 빠르게 반환되지만 백그라운드에서 비동기 적으로 수행되도록 일정을 설정하여 이와 같은 새로운 타이밍 버그를 많이 만듭니다. 고마워, 애플!
RobP

14

우리도이 문제를 해결하기 시작했고 같은 문제로 인해 문제가 발생했을 가능성이 높습니다.

우리의 경우에는 백엔드에서 데이터를 가져와야했기 때문에 사용자가 무언가를 탭한 다음 탐색 푸시가 발생하기 전에 약간의 지연이 발생했습니다. 사용자가 빠르게 탭핑하는 경우 동일한 뷰 컨트롤러에서 두 개의 탐색 푸시가 발생하여이 예외가 발생했을 수 있습니다.

우리의 솔루션은 UINavigationController의 카테고리로, 특정 시점에서 상단 vc가 동일하지 않으면 푸시 / 팝을 방지합니다.

.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

.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

지금까지 이것은 우리에게 문제를 해결 한 것으로 보입니다. 예:

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

기본적으로 규칙은 사용자와 관련이없는 지연 이 관련 탐색 컨트롤러에서 잠금을 잡고 푸시 / 팝 호출에 포함시키는 것입니다.

"lock"이라는 단어는 잠금 해제가 필요한 어떤 형태의 잠금이 발생하는 것을 막을 수 있기 때문에 약간 어색한 표현 일 수 있지만, "unlock"방법이 없기 때문에 괜찮을 것입니다.

참고로 "비 사용자 관련 지연"은 코드에서 발생하는 지연, 즉 비동기적인 것입니다. 애니메이션 방식으로 푸시 된 탐색 컨트롤러를 탭하는 사용자는 계산되지 않으며 navigationLock : 버전을 사용할 필요가 없습니다. 사례)


이 솔루션을 사용하고 있다고 말 했으므로 문제가 해결 되었습니까?
Mike D

지금까지는 그렇습니다. 문제가 재 포장되지 않았습니다. 답변을 업데이트하겠습니다.
Kalle

4
gist.github.com/mdewolfe/9369751 에 따라 수정 된 버전을 사용했습니다 . 문제가 해결 된 것 같습니다.
Mike D

2
@Kalle이 솔루션은 푸시 / 팝에서 작동합니다. 그러나 segue를 사용하면이 오류를 해결하는 방법은 무엇입니까?
Geek

@Kadle 이것을 구현하는 데 도움을 줄 수 있습니까? 룩 stackoverflow.com/q/23247713/1323014의 THX
Marckaraujo

12

이 코드는 문제를 해결합니다 : https://gist.github.com/nonamelive/9334458

개인 API를 사용하지만 App Store 안전하다는 것을 확인할 수 있습니다. (이 코드를 사용하는 내 앱 중 하나가 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;
}

이것은 지금까지 가장 좋은 해결책이었습니다. 다른 것들 중 일부는 여전히 무작위로 이중 푸시 문제를 얻거나 고정 된 탐색 컨트롤러를 얻습니다.
blueice

이 코드는 나를 위해 컴파일되지 않습니다. 빠진 것이 있습니까?
Maxime B

8

내 응용 프로그램 에서이 충돌에 대한 자세한 내용을 설명하고 응답으로 표시합니다.

내 응용 프로그램에는 루트 컨트롤러가있는 UINavigationController가 있으며 메모 개체 목록이 포함 된 UITableViewController입니다. note 객체는 html의 content 속성을 갖습니다. 메모를 선택하면 디테일 컨트롤러로 이동합니다.

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

디테일 컨트롤러

이 컨트롤러에는 UIWebView가 있으며 루트 컨트롤러에서 전달 된 메모 내용을 표시합니다.

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

이 컨트롤러는 webview 컨트롤의 위임입니다. 메모에 링크가 포함 된 경우 링크를 누르면 인앱 웹 브라우저로 이동합니다.

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

위의 충돌 보고서를 매일 받았습니다. 내 코드 에서이 충돌을 일으킨 곳을 모르겠습니다. 사용자의 도움으로 조사한 결과 마침내이 충돌을 해결할 수있었습니다. 이 html 컨텐츠로 인해 충돌이 발생합니다.

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

세부 컨트롤러의 viewDidLoad 메소드 에서이 HTML을 webview 컨트롤에로드 한 직후 위의 delegate 메소드가 request.URL과 함께 즉시 호출되었습니다 .URL은 iframe의 소스입니다 (google.com). 이 델리게이트 메소드는 viewDidLoad => crash 상태에서 pushViewController 메소드를 호출합니다!

navigationType을 확인 하여이 충돌을 수정했습니다.

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

도움이 되었기를 바랍니다


1
에서 호출 할 때 애니메이션없이 컨트롤러를 밀어 넣는 것이 좋지 않은 viewDidLoad가요?
Rivera

6

나는 똑같은 문제가 있었는데, 단순히 나를 위해 일한 것은 Animated : Yes를 Animated : No로 바꾸는 것이 었습니다.

문제가 애니메이션이 제 시간에 완료되지 않아서 발생한 것 같습니다.

이것이 누군가를 돕기를 바랍니다.


3

이 버그를 재현하려면 두 개의 뷰 컨트롤러를 동시에 밀어보십시오. 또는 똑같이 밀고 터지는 소리. 예:

여기에 이미지 설명을 입력하십시오 이러한 통화를 가로 채고 진행중인 동안 다른 푸시가 발생하지 않도록하여 카테고리를 만들었습니다. 코드를 프로젝트에 복사하기 만하면 메소드 스위 즐링을 사용하는 것이 좋습니다.

#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

이 솔루션의 한 가지 문제는 전화를 걸 popToRootViewController거나 popToViewController:이미 루트보기 컨트롤러 또는 viewController에있는 didShowViewController경우 호출되지 않고 호출되지 않고에 고정된다는 것입니다 viewTransitionInProgress.
divergio

1
다음 줄을 설명 할 수 있습니까 self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController; [self.interactivePopGestureRecognizer setEnabled:YES]; ? 인식기는 언제 비활성화 되었습니까? 그리고 대리인이 무엇인지 어떻게 알 수 있습니까? 그 줄을 넣으면 한 번 터지면 팝 제스처가 끊어집니다.
divergio

나는 이것을 구현하려고 시도했지만 잠시 후 @divergio가 언급 한 것으로 인해 탐색 컨트롤러를 잠급니다.
blueice

2

이 문제도 방금 경험했습니다. 내 코드를 보여 드리겠습니다.

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

이 줄로 인해 오류가 발생합니다.

firstView.addSubView(firstView)

하위보기에 자기를 추가 할 수 없습니다. 코드 줄을 다음과 같이 변경했습니다.

firstView.addSubView(secondView)

오류가 사라지고 두 가지 견해를 모두 볼 수있었습니다. 이것은 이것이 모범을 보려는 사람에게 도움이 될 것이라고 생각했습니다.


나는 또한이 접근법을 시도했지만 stacktrace는 다르며 실제로 충돌을 일으키는 코드 줄을 보여줍니다. 소스 문제가 질문과 다르다고 생각합니다.
Ben

1

"addSubview"에 대한 코드를 검색하십시오.

이 메소드를 호출 한 곳 중 하나에서이 메소드를 사용하여 자체 서브 뷰 배열에 뷰를 추가하려고했습니다.

예를 들면 다음과 같습니다.

[self.view addSubview:self.view];

또는:

[self.myLabel addSubview:self.myLabel];

귀하의 오류를 발견하게되어 기쁩니다. 이제 "자신을 하위보기로 추가 할 수 없습니다"라는 메시지가 표시됩니다. View2가 내비게이션 컨트롤러의 루트보기 컨트롤러 인 시점에서 View2를 푸시하여이를 유발했습니다. [View2.view addSubview:View2.view]따라서 자체를 하위보기로 추가했습니다.
Michal Shatz

1

애니메이션을 사용하여 뷰 컨트롤러를 푸시 / 팝핑하는 것은 완벽하게 좋으며 SDK는 우리를 위해 호출 대기열을 우아하게 처리해야한다고 생각합니다.

따라서 모든 솔루션이 후속 푸시를 무시하려고 시도하지 않으며 최종 탐색 스택이 코드의 의도가 아니기 때문에 버그로 간주 될 수 있습니다.

대신 푸시 호출 대기열을 구현했습니다.

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

푸시 및 팝 혼합 큐를 처리하지 않지만 대부분의 충돌을 해결하는 것이 좋습니다.

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


나는 매우 좋아 보이는 솔루션을 시도했지만 문제가 있습니다. 애니메이션 NO가있는 2 개의 뷰 컨트롤러를 차례로 밀면 첫 번째 컨트롤러가 매우 짧습니다. 이전에는 이런 일이 없었습니다. 내가 고치기 위해 무엇을 할 수 있는지 아는가?
Jan

이런 종류의 충돌을 일관되게 일으킬 수있는 프로젝트를 작성하려고합니다 (실제 프로젝트에는 이와 같은 충돌 보고서가 나타납니다). 탐색 컨트롤러, 루트 컨트롤러 및 4 개의 새로운보기 컨트롤러를 탐색 스택에 즉시 푸시 한 다음 마지막 컨트롤러에서 튀어 나오는 버튼으로 간단한 앱을 만들었습니다. 특별한 서브 클래 싱이나 아무것도 없으면 실제로 잘 작동하는 것 같습니다. 애플이 최근에이 문제를 해결 했습니까?
Cruinh

1

파티에 늦어서 미안해 최근에 둘 이상의 뷰 컨트롤러를 동시에 밀어서 탐색 표시 줄이 손상된 상태가되는이 문제가 발생했습니다. 이는 첫 번째 뷰 컨트롤러가 여전히 애니메이션을 실행하는 동안 다른 뷰 컨트롤러가 푸시되기 때문에 발생합니다. nonamelive 답변에서 힌트를 얻어 내 경우에 맞는 간단한 해결책을 찾았습니다. UINavigationControllerpushViewController 메소드 를 서브 클래 싱 하고 재정의하고 이전 뷰 컨트롤러 애니메이션이 아직 완료되었는지 확인하면됩니다. 클래스를 델리게이트로 UINavigationControllerDelegate설정하고 델리게이트를로 설정하여 애니메이션 완료를들을 수 있습니다 self.

나는 요점을 업로드 한 여기 일을 간단하게 할 수 있습니다.

스토리 보드에서이 새 클래스를 NavigationController로 설정하십시오.


지금까지 내가 작업했던 앱의 충돌을 수정 한 것 같습니다 ... 게다가 솔루션은 매우 간단하고 요점에 대해 명확합니다. 첫 번째 viewcontroller 애니메이션이 아직 완료되지 않았습니다. 같은 문제가있는 사람들은 이것을 확인해야합니다.
alasker

0

@RobP 훌륭한 힌트를 기반으로 이러한 문제를 방지하기 위해 UINavigationController 하위 클래스 를 만들었습니다 . 푸시 및 / 또는 팝핑을 처리하며 안전하게 실행할 수 있습니다.

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

'acceptConflictingCommands'플래그를 지정하면 true (기본값) vc1, vc2, vc3의 애니메이션 푸시가 표시되고 vc3의 애니메이션 팝핑이 표시됩니다. 'acceptConflictingCommands'가 false이면 vc1이 완전히 푸시 될 때까지 모든 푸시 / 팝 요청이 삭제되므로 다른 3 개의 호출은 삭제됩니다.


그 명령들이 실제로 상충 되는가? 위의 코드 (Swift에서)와 같은 코드를 사용 하여이 충돌이 발생하는 것을 확인하기 위해 빠른 새 프로젝트를 함께 던졌으며 실제로 각 푸시 및 팝을 모두 순차적으로 실행했습니다. 하나씩 차례로. 부수 지 마시오. 서브 클래스를 사용하지 않습니다. 애플의 일반 UINavigationController.
Cruinh

실제로 ObjC 및 iOS 7과 충돌했습니다. 여전히 발생하는지 확인할 수 없습니다. animated:true플래그로 명령을 실행하고 있습니까?
hris. to

예, animated : true 플래그를 사용하고있었습니다.
Cruinh

0

nonamelive의 솔루션은 훌륭합니다. 그러나 개인 API를 사용하지 않으려면 방법을 달성 UINavigationControllerDelegate하거나 애니메이션 YES을로 변경할 수 있습니다 NO. 다음은 샘플 코드입니다. 상속 할 수 있습니다. 도움이되기를 바랍니다 :)

https://github.com/antrix1989/ANNavigationController



0

때로는 실수로 자체보기에보기를 추가하려고했습니다.

halfView.addSubview(halfView)

이것을 하위보기로 변경하십시오.

halfView.addSubview(favView)

0

나는 또한이 문제에 직면했다. Firebase 로그 분석을 수행했을 때이 문제는 앱이 콜드 스타트 ​​된 경우에만 발생합니다. 이 충돌을 재현 할 수 있는 데모 를 작성 했습니다 .

.

또한 창의 루트 뷰 컨트롤러가 표시되면 여러 번의 푸시를 수행해도 동일한 문제가 다시 발생하지 않습니다. (AppDelegate.swift에서 testColdStartUp (rootNav)를 주석 처리하고 ViewController.swift에서 testColdStartUp () 주석을 주석 해제 할 수 있습니다)

추신 : 내 응용 프로그램 에서이 충돌 장면을 분석했습니다. 사용자가 푸시 알림을 클릭하여 앱을 콜드 스타트하면 앱이 여전히 시작 페이지에 있고 다른 푸시를 클릭하여 점프합니다. 현재 앱이 충돌로 나타날 수 있습니다. 내 현재 해결책은 푸시 또는 범용 링크 콜드 스타트를 캐시하여 앱 점프 페이지를 열고 rootviewcontroller가 표시 될 때까지 기다린 다음 실행을 지연시키는 것입니다.


-2

마지막 탐색 애니메이션을 완료하기 위해 지연 방법을 사용하여 탐색을 시도하십시오.

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


-2

자체적으로 하위보기로보기를 추가 할 수 없습니다.

뷰는 부모-자식 계층을 유지하므로 뷰를 자체적으로 하위 뷰로 추가하면 예외가 발생합니다.

클래스가 UIViewController 인 경우 뷰를 얻으려면 self.view를 사용하십시오.

클래스가 UIView 클래스 인 경우 뷰를 얻으려면 self를 사용하십시오.


-3

UiViewController 클래스가 될 경우 자체를 서브 뷰로 추가 할 수 없습니다. UiView 클래스가 될 경우 자체를 서브 뷰로 추가 할 수 있습니다.


-9

뷰에 하위 뷰를 추가하려면 다음과 같이 할 수 있습니다.

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

잘 했어, 그것은 좋은 코드 조각이다. 이제이 질문과 어떤 관계가 있으며 어떻게 해결해야하는지 말씀해 주시겠습니까?
Popeye

@Popeye 더 나은 아이디어가 있습니까?
David Gölzhäuser

아니요, 문제를 재현하기에 충분한 정보 / 코드를 제공하지 않았기 때문입니다. 따라서 이것이 답변 될 수있는 방법은 없습니다. 문제와 관련이없는 것을 수행하는 방법을 알려주는 것처럼 보입니다.
Popeye

2
문제를 해결하는 것이 훨씬 도움이되었다고 생각합니다. 알았다! 실제로 StackOverflow에서 누군가를 돕기 위해 David G에게 +1. 닫기 투표 수를 -1 할 수 있기를 바랍니다 !!! 이것은 여전히 ​​사용자에게 일어나고 있으며 우리가 알고있는 모든 iOS7의 버그 일 수 있습니다. 따라서 누군가가 문제의 코드를 게시 할 수 없다고해서 그 질문이 유효하지 않으며 다른 사용자가 볼 수있는 가치가있는 것은 아닙니다. 다른 사람들이 자신이 보는 이유에 대한 논리적 이유없이 동일한 문제를보고 있다는 것만 알더라도 말입니다. -rrh
Richie Hyatt
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.