Programming

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

procodes 2020. 6. 8. 21:30
반응형

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)

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


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

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

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

[self.view addSubview:self.view];

또는:

[self.myLabel addSubview:self.myLabel];

애니메이션을 사용하여 뷰 컨트롤러를 푸시 / 팝핑하는 것이 좋을 것 같아요.

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

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

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


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

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

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


@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'플래그를 지정하면 기본적으로 사용자가 vc1, vc2, vc3의 애니메이션 푸시를보고 vc3의 애니메이션 팝핑을 보게됩니다. 'acceptConflictingCommands'가 false이면 vc1이 완전히 푸시 될 때까지 모든 푸시 / 팝 요청이 삭제되므로 다른 3 개의 호출은 삭제됩니다.


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

https://github.com/antrix1989/ANNavigationController


나는이 문제를 많이 찾았습니다. 동시에 두 개 이상의 VC를 밀고있을 수 있습니다. 이는 푸시 애니메이션 문제를 유발합니다.이를 참조 할 수 있습니다 : 자기 자신을 하위보기로 추가 할 수 없음

전환 진행에 동시에 하나의 VC가 있는지 확인하십시오. 행운을 빕니다.


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

halfView.addSubview(halfView)

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

halfView.addSubview(favView)

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

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


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

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

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

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


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


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

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

참고 URL : https://stackoverflow.com/questions/19560198/ios-app-error-cant-add-self-as-subview

반응형