161

このクラッシュ レポートを受け取りましたが、デバッグ方法がわかりません。

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 です。この奇妙なクラッシュを経験した人はいますか?

アップデート:

コードのどこでこのクラッシュが発生したのかわからないため、ここにコードを投稿できません。申し訳ありません。

2回目の更新

以下の回答を参照してください。

4

19 に答える 19

53

最近デバッグしたものに似たものに基づいて推測しています... Animated:YES を使用してビュー コントローラーをプッシュ (またはポップ) すると、すぐには完了せず、アニメーションの前に別のプッシュまたはポップを行うと悪いことが起こります完了します。Push 操作と Pop 操作を一時的に Animated:NO に変更して (同期的に完了するように)、これが実際に当てはまるかどうかを簡単にテストし、それによってクラッシュが解消されるかどうかを確認できます。これが実際に問題であり、アニメーションをオンに戻したい場合、正しい戦略は UINavigationControllerDelegate プロトコルを実装することです。これには、アニメーションの完了後に呼び出される次のメソッドが含まれます。

navigationController:didShowViewController:animated:

基本的に、必要に応じてこのメソッドにいくつかのコードを移動して、アニメーションが終了し、スタックがさらに変更できるようになるまで、NavigationController スタックに変更を引き起こす可能性のある他のアクションが発生しないようにします。

于 2014-01-20T05:26:33.870 に答える
14

この問題も発生し始めましたが、同じ問題が原因である可能性が非常に高くなりました.

私たちの場合、場合によってはバックエンドからデータを取得する必要がありました。これは、ユーザーが何かをタップしてから、ナビゲーション プッシュが発生するまでにわずかな遅延が発生することを意味していました。ユーザーがすばやくタップしている場合、同じビュー コントローラーから 2 回のナビゲーション プッシュが発生し、この例外がトリガーされる可能性があります。

私たちのソリューションは 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];
}];

基本的に、ルールは次のとおりです。ユーザーに関係のない遅延が発生する前に、関連するナビゲーションコントローラーからロックを取得し、プッシュ/ポップの呼び出しに含めます。

「ロック」という言葉は、ロックを解除する必要のある何らかの形のロックが発生していることをほのめかしている可能性があるため、少し言葉遣いが悪いかもしれませんが、「ロック解除」方法はどこにもないので、おそらく問題ありません.

(ちなみに、「ユーザーに関係のない遅延」とは、コードが引き起こしている遅延、つまり非同期のものです。アニメーションで押されたナビゲーション コントローラーをタップしたユーザーはカウントされず、navigationLock: バージョンを実行する必要はありません。ケース。)

于 2014-01-31T16:42:23.897 に答える
12

このコードは問題を解決します: https://gist.github.com/nonamelive/9334458

プライベート API を使用していますが、App Store で安全であることを確認できます。(このコードを使用した私のアプリの 1 つが 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;
}
于 2014-04-01T17:10:43.297 に答える
8

アプリでこのクラッシュの詳細を説明し、これを回答済みとしてマークします。

私のアプリには、ノート オブジェクトのリストを含む UITableViewController であるルート コントローラーを持つ UINavigationController があります。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 コントロールのデリゲートです。メモにリンクが含まれている場合は、リンクをタップすると、アプリ内の Web ブラウザーに移動します。

- (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はiframeのソース(google.com)です。このデリゲート メソッドは、viewDidLoad => クラッシュ中に pushViewController メソッドを呼び出します。

navigationType を確認して、このクラッシュを修正しました。

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

お役に立てれば

于 2014-02-19T08:55:46.133 に答える
6

私は同じ問題を抱えていました.Animated:YesをAnimated:Noに変更するだけでうまくいきました。

この問題は、アニメーションが時間内に完了しなかったことが原因のようです。

これが誰かに役立つことを願っています。

于 2014-05-16T16:04:59.457 に答える
3

この問題も経験しました。私のコードをお見せしましょう:

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)

エラーがなくなり、両方のビューを見ることができました。これは、例を見たい人なら誰でも役立つと思いました。

于 2018-02-24T21:55:51.800 に答える
2

ビューを独自のビューに誤って追加しようとする場合があります。

halfView.addSubview(halfView)

これをサブビューに変更します。

halfView.addSubview(favView)
于 2019-01-23T06:42:49.950 に答える
1

パーティーに遅れてすみません。最近、複数のView Controllerを同時に押すと、ナビゲーションバーが破損した状態になるというこの問題が発生しました。これは、最初のビュー コントローラーがまだアニメーションしている間に、他のビュー コントローラーがプッシュされるために発生します。nonamelive の回答からヒントを得て、私の場合に機能する簡単な解決策を思いつきました。UINavigationControllerpushViewController メソッドをサブクラス化してオーバーライドし、以前のビュー コントローラーのアニメーションがまだ終了しているかどうかを確認するだけです。UINavigationControllerDelegateクラスを のデリゲートにして、デリゲートをに設定することで、アニメーションの完了を聞くことができますself

物事を簡単にするために、ここに要旨をアップロードしました。

ストーリーボードでこの新しいクラスを NavigationController として設定してください。

于 2015-02-28T05:37:21.387 に答える
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

于 2014-08-15T03:18:33.007 に答える
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 つの呼び出しは破棄されます。

于 2015-04-22T14:22:12.740 に答える
0

nonamelive のソリューションは素晴らしいです。ただし、プライベート API を使用したくない場合は、メソッドを実現できUINavigationControllerDelegateます。または、アニメーションYESNO. これはコードのサンプルです。継承できます。お役に立てば幸いです:)

https://github.com/antrix1989/ANNavigationController

于 2015-11-09T03:03:30.503 に答える
-2

ビューをサブビューとして追加することはできません。

ビューは親子階層を維持するため、ビューをサブビュー自体として追加すると、例外が発生します。

クラスが UIViewController の場合、そのビューを取得するには、self.view を使用します。

クラスが UIView クラスの場合、そのビューを取得するには、self を使用します。

于 2014-01-31T05:14:55.973 に答える
-2

最後のナビゲーション アニメーションを完了するために、delay メソッドを使用してナビゲーションを試してください。

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

于 2013-12-31T08:56:39.210 に答える
-3

UiViewController クラスになる場合、サブビューとして self を追加することはできません。UiView クラスになる場合は、サブビューとして self を追加できます。

于 2013-12-30T10:25:09.053 に答える