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

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

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입니다. 누구든지이 이상한 충돌을 경험합니까?

최신 정보:

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

두 번째 업데이트

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



답변

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

navigationController:didShowViewController:animated:

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


답변

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

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

우리의 솔루션은 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 : 버전을 사용할 필요가 없습니다. 사례)


답변

이 코드는 문제를 해결합니다 : 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;
}


답변

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

내 응용 프로그램에는 루트 컨트롤러가있는 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
    }
}

도움이 되었기를 바랍니다


답변

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

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

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


답변

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

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

#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


답변

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

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)

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