web-dev-qa-db-ja.com

PushViewControllerでiOSアプリがフリーズする

ナビゲーションコントローラーが断続的にプッシュでフリーズします。新しいビューコントローラをスタックに追加するようですが、アニメーションは発生しません。画面上にビューコントローラーを保持する他の2つのコンテナーもあり、ナビゲーションコントローラーがフリーズした後は、両方のコンテナーを問題なく操作できます。本当に興味深いのは、別のビューコントローラーをナビゲーションコントローラーのスタックにプッシュしようとすると、スタックの上に追加のビューコントローラー(ナビゲーションコントローラーをフリーズした最初にプッシュしたビューコントローラー)があることに気づきました。したがって、ホーム画面(VC-Homeと呼びます)で新しいビュー(VC-1)をプッシュしようとしてフリーズした場合、新しいビュー(VC-2)をプッシュしようとします。これは、プッシュの前に現在のスタックに表示されるものです。

_{ [VC-Home, VC-1] }_

そしてpushViewControllerが呼び出された後も同じままです。 VC-2はスタックに追加されません。

私が知ることができることから、ナビゲーションコントローラーは、アニメーションが始まる前に前のビューコントローラーを非アクティブにすることでアニメーションを開始しますが、アニメーションは発生せず、ナビゲーションコントローラーはフリーズされた状態のままです。

ストーリーボードからUIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController")を呼び出して新しいビューコントローラーを作成しているので、問題はないと思います。また、ナビゲーションバーのpushViewControllerをオーバーライドしていません。私のアプリのいくつかのユニークな点は、非常に高解像度の画像が重いことです(SDWebImageを使用してそれを管理します)、常に画面に3つのコンテナーを常に持っています(1つのナビゲーションコントローラー、1つの検索用ビューコントローラー、1つのインタラクティブなGutter /サイド/スライドアウトメニュー)。

CPU使用率は低く、メモリ使用率は正常です(フリーズが発生した場合、デバイス上で着実に約60〜70MB)。

これを引き起こしている可能性のあるアイデアや、実際の問題を発見するのに役立つデバッグのヒントはありますか?

更新

PushViewController()を使用してプッシュしているだけなので、UINavigationControllerに固有のコードはありません。これを呼び出すコードは次のとおりです。

_func didSelectItem(profile: SimpleProfile) {
     let vc = UIStoryboard.profileViewController()
     vc.profile = profile
     navigationController?.pushViewController(vc, animated: true)
}
_

私がプッシュしたViewControllerには、viewDidLoadに次のコードが含まれています。

_override func viewDidLoad() {
    super.viewDidLoad()
    button.roundView()

    if let type = profile?.profileType {
        //load multiple view controllers into a view pager based on type 
        let viewControllers = ProfileTypeTabAdapter.produceViewControllersBasedOnType(type)
        loadViewPagerViews(viewControllers)

        let topInset = headerView.bounds.height + tabScrollView.contentSize.height
        if let viewPager = viewPager {
            for view in viewPager.views {
                if let tempView = view as? PagingChildViewController {
                    tempView.profile = fullProfile
                    tempView.parentVCDelegate = self
                    tempView.topInset = topInset
                }
            }
        }
    }
}

func loadViewPagerViews(viewControllers: [UIViewController]) {
    viewPager?.views = viewControllers
    viewPager?.delegate = self

    //loading views into paging scroll view (using PureLayout to create constraints)
    let _ = subviews.map { $0.removeFromSuperview() }
    var i = 0
    for item in views {
        addSubview(item.view)
        item.view.autoSetDimensionsToSize(CGSize(width: tabWidth, height: tabHeight))
        if i == 0 {
            item.view.autoPinEdgeToSuperviewEdge(.Leading)
        } else if let previousView = views[i-1].view {
            item.view.autoPinEdge(.Leading, toEdge: .Trailing, ofView: previousView)
        }
        if i == views.count {
            item.view.autoPinEdgeToSuperviewEdge(.Trailing)
        }
        i += 1
    }

    contentSize = CGSize(width: Double(i)*Double(tabWidth), height: Double(tabHeight))
}
_

更新2

やっとフリーズしました。アプリがバックグラウンドにあったので、私はそれを元に戻し、フリーズしたときにスタック上のビューコントローラーを押してみました。アニメーションが行われていることに気づきました。ページの上部にスクロールビューがあり、コンテンツが10秒ごとに表示されます(アプリストアのトップバナーと考え​​てください)。このフリーズで、バナーがアニメーションの途中にあることに気づきました。

これが、10秒ごとに呼び出されるmy UIScrollViewのスクロール関数です。

_func moveToNextItem() {
    let pageWidth: CGFloat = CGRectGetWidth(frame)
    let maxWidth: CGFloat = pageWidth * CGFloat(max(images.count, profileImages.count))
    let contentOffset: CGFloat = self.contentOffset.x
    let slideToX = contentOffset + pageWidth

    //if this is the end of the line, stop the timer
    if contentOffset + pageWidth == maxWidth {
        timer?.invalidate()
        timer = nil
        return
    }

    scrollRectToVisible(CGRectMake(slideToX, 0, pageWidth, CGRectGetHeight(frame)), animated: true)
}
_

アニメーション/スクロールが行われているためにプッシュストップがあったことを覚えていませんが、間違っている可能性があります。

スタックも再確認しましたが、上記と同じ状況が[VC-Home、VC-1]がスタックで、VC-2がプッシュされていない場合にも当てはまります。私はVC-1の変数も調べて、すべてが読み込まれました(データ呼び出しと画像の読み込み)。

アップデート

これは秒ごとに見知らぬものになっています。 pushViewControllerをオーバーライドしたので、そこにブレークポイントを設定し、Alessandro Ornanoの応答に基づいてデバッグを行うことができます。ビューコントローラーのプッシュに失敗した後、アプリをバックグラウンドに送信し、ブレークポイントをpushViewController呼び出しに入れてアプリを元に戻すと、ブレークポイントがすぐに何度もヒットします。その後、すべてのヒットを通過し続けると、次のビューコントローラーが突然表示され、最後にプッシュしようとしたビューコントローラーが最後のビューコントローラーとしてスタックに置かれます。これは、私が見るものはまだ無効になっていることを意味し、本質的に以前と同じ立場にいます。

23
Maxwell

私たちは数週間前に同じ問題に直面しました。そして、私たちの問題では、それをleft-Edge pop gesture recogniser。以下の手順を使用して、この問題を再現できるかどうかを試して確認できます

  • その下にビューコントローラーがない場合(つまり、ルートビューコントローラーでは、VC-Homeコントローラ)
  • この後、UI要素をクリックしてみてください。

フリーズを再現できる場合は、ビューコントローラスタックにビューコントローラが1つしかないときにinteractivePopGestureRecognizerを無効にしてみてください。

詳細は this 質問を参照してください。以下は、参照しやすいようにリンクからのコードです。

- (void)navigationController:(UINavigationController *)navigationController
   didShowViewController:(UIViewController *)viewController
                animated:(BOOL)animate
{
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
{
        if (self.viewControllers.count > 1)
        {
            self.interactivePopGestureRecognizer.enabled = YES;
        }
        else
        {
            self.interactivePopGestureRecognizer.enabled = NO;
        }
    }
}
44
Penkey Suresh

すごい answer @Penkey Suresh!私の日を救った!これは、Swift 3のバージョンで、わずかに追加しただけで違いがありました。

    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {


    if (navigationController.viewControllers.count > 1)
    {
         self.navigationController?.interactivePopGestureRecognizer?.delegate = self
        navigationController.interactivePopGestureRecognizer?.isEnabled = true;
    }
    else
    {
         self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
        navigationController.interactivePopGestureRecognizer?.isEnabled = false;
    }
}

UINavigationControllerDelegateを追加してnavigationController?.delegate = selfを設定することを忘れないでください。もう1つの重要な部分は、interactivePopGestureRecognizerを自分自身またはnilに割り当てることです。

7
Tim Friedland

断続的にフリーズするメインスレッドSDWebImage

downloadImageWithURL:options:progress:completed:の完了したブロックからダウンロードしたイメージを使用していると想定すると、使用する前に、メインキューにディスパッチすることを確認してください。画像。

SDWebImageDownloaderを直接使用する場合、完了ブロック(前述のとおり)がバックグラウンドキューで呼び出されます。完了からメインキューでdispatch_asyncを使用して修正できます。

それ以外の場合は、SDWebImageManager downloadImageWithURL:options:progress:completed:(メインキューの完了ブロックを呼び出すメソッド)を使用できます。

問題が解決しない場合(「私のアプリのユニークな点は非常に画像が重いということです」と言っただけです) よくある問題 特に画像の更新を処理する問題を知っています。

チェックに次のニーススニペットコードも追加します。

import UIKit.UINavigationController

public typealias VoidBlock = (Void -> Void)

public extension UINavigationController  
{
    public func pushViewController(viewController: UIViewController, animated: Bool, completion: VoidBlock) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }
}

多分、pushViewControllerが終了したかどうか、すべてのviewControllersが期待どおりに終了したかどうかを理解するのに役立ちます。

IOS 9の周りのpureLayoutプロジェクトには issues がいくつかあるため、iOS 8.xおよびiPhone 6+でアプリを起動することも試みます。このテストに関するフィードバックを送信できますか?

Pushviewアクションの前の実際のscrollviewディメンションについても疑わしいことがあります。現在のビューを ビュー階層を調べる で分析できますか?

2

BaseNavigationViewControllerに以下のような不要なコードがないか確認してください:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

以下に、この問題を再現するサンプルコード(VCのプッシュでアプリをフリーズさせる)を示します。これらの手順で再現する必要があります。

  • その下にビューコントローラーがない場合(つまり、ルートビューコントローラー、VC-Homeコントローラー)、(左|右)エッジポップジェスチャーを使用してみてください。
  • この後、UI要素をクリックしてみてください(次のViewControllerに移動します)。

サンプルコード: https://github.com/aliunco​​Bamilo/TestNavigationPushBug

0
Aliunco

メインスレッドでお試しください

dispatch_async(dispatch_get_main_queue()){

UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController")
// your code

}
0
bourvill

Xcodeとシミュレーターを再起動し、シミュレーターのリストから別のデバイスを選択することで、私の問題は解決しました。

0
Okhan Okbay