web-dev-qa-db-ja.com

iOS 7:カスタムコンテナービューコントローラーとコンテンツインセット

ナビゲーションコントローラーにラップされたテーブルビューコントローラーがあります。ナビゲーションコントローラは、presentViewController:animated:completion:を介して表示されるときに、正しいコンテンツインセットをテーブルビューコントローラに自動的に適用するようです。 (これがどのように機能するかを誰かに説明できますか?)

ただし、組み合わせをカスタムコンテナービューコントローラーでラップし、代わりに表示すると、テーブルビューコンテンツの最上部がナビゲーションバーの後ろに隠れます。この構成で自動コンテンツインセットの動作を維持するためにできることはありますか?これが正しく機能するためには、コンテナービューコントローラーで何かを「パススルー」する必要がありますか?

IOS 5のサポートを続けたいので、手動または自動レイアウトでコンテンツインセットを調整する必要がないようにしたいと思います。

24
tajmahal

Johannes Fahrenkrugのヒントのおかげで、私は次のことを理解しました。automaticallyAdjustsScrollViewInsetsは、子のビューコントローラーのルートビューがUIScrollViewである場合にのみ、宣伝どおりに機能するようです。私にとって、これは次の配置が機能することを意味します:

コンテンツビューコントローラー内部ナビゲーションコントローラー内部カスタムコンテナービューコントローラー

これはしませんが:

コンテンツビューコントローラー内部カスタムコンテナービューコントローラー内部ナビゲーションコントローラー

ただし、2番目のオプションは、論理的な観点からはより賢明なようです。カスタムコンテナーのビューコントローラーを使用してビューをコンテンツの下部に添付するため、最初のオプションはおそらく機能します。ナビゲーションバーとコンテンツの間にビューを配置したい場合、そのようには機能しません。

4
tajmahal

同様の問題がありました。 iOS 7では、UIViewControllerautomaticallyAdjustsScrollViewInsets という新しいプロパティがあります。デフォルトでは、これはYESに設定されています。ビュー階層が、ナビゲーションまたはタブバーコントローラー内のスクロール/テーブルビューよりも複雑である場合、このプロパティは機能しないようでした。

私がやったことは、他のすべてのビューコントローラーが継承するベースビューコントローラークラスを作成したことです。次に、そのビューコントローラーがインセットの明示的な設定を処理します。

ヘッダー:

#import <UIKit/UIKit.h>

@interface SPWKBaseCollectionViewController : UICollectionViewController

@end

実装:

#import "SPWKBaseCollectionViewController.h"

@implementation SPWKBaseCollectionViewController


- (void)viewDidLoad
{
    [super viewDidLoad];

    [self updateContentInsetsForInterfaceOrientation:self.interfaceOrientation];
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [self updateContentInsetsForInterfaceOrientation:toInterfaceOrientation];

    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}

- (void)updateContentInsetsForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
        UIEdgeInsets insets;

        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
            insets = UIEdgeInsetsMake(64, 0, 56, 0);
        } else if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
            if (UIInterfaceOrientationIsPortrait(orientation)) {
                insets = UIEdgeInsetsMake(64, 0, 49, 0);
            } else {
                insets = UIEdgeInsetsMake(52, 0, 49, 0);
            }
        }

        self.collectionView.contentInset = insets;
        self.collectionView.scrollIndicatorInsets = insets;
    }
}

@end

これはWebビューでも機能します。

self.webView.scrollView.contentInset = insets;
self.webView.scrollView.scrollIndicatorInsets = insets;

これを行うためのよりエレガントで信頼できる方法がある場合は、私に知らせてください!ハードコードされたインセット値はかなり悪いにおいがしますが、iOS 5の互換性を維持したいときは、別の方法は実際には見ません。

12

誰かが同様の問題を抱えている場合の参考:automaticallyAdjustsScrollViewInsetsは、スクロールビュー(またはtableview/collectionview/webview)がビューコントローラーの階層の最初のビューである場合にのみ適用されるようです。

背景画像を作成するために、UIImageViewを階層の最初に追加することがよくあります。これを行う場合は、viewDidLayoutSubviewsでスクロールビューのEdgeインセットを手動で設定する必要があります。

- (void) viewDidLayoutSubviews {
    CGFloat top = self.topLayoutGuide.length;
    CGFloat bottom = self.bottomLayoutGuide.length;
    UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
    self.collectionView.contentInset = newInsets;

}
5
BFar

ドキュメントに記載されていないメソッドを呼び出す必要はありません。 setNeedsLayoutUINavigationControllerを呼び出すだけです。この他の回答を参照してください: https://stackoverflow.com/a/33344516/5488931

3
Dag Ågren

UINavigationControllerは、ドキュメント間の記載されていないメソッド_updateScrollViewFromViewController:toViewControllerをコントローラー間の遷移で呼び出し、コンテンツインセットを更新します。これが私の解決策です:

INavigationController + ContentInset.h

#import <UIKit/UIKit.h>
@interface UINavigationController (ContentInset)
- (void) updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to;
@end

INavigationController + ContentInset.m

#import "UINavigationController+ContentInset.h"
@interface UINavigationController()
- (void) _updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to;
@end

@implementation UINavigationController (ContentInset)
- (void) updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to {
    if ([UINavigationController instancesRespondToSelector:@selector(_updateScrollViewFromViewController:toViewController:)])
        [self _updateScrollViewFromViewController:from toViewController:to];
}
@end

カスタムコンテナービューコントローラーのどこかでupdateScrollViewFromViewController:toViewControllerを呼び出します。

[self addChildViewController:newContentViewController];
[self transitionFromViewController:previewsContentViewController
                  toViewController:newContentViewController
                          duration:0.5f
                           options:0
                        animations:^{
                            [self.navigationController updateScrollViewFromViewController:self toViewController:newContentViewController];

                    }
                    completion:^(BOOL finished) {
                        [newContentViewController didMoveToParentViewController:self];
                    }];
3
Shimanski Artem

Swiftソリューション

このソリューションは、SwiftのiOS 8以降を対象としています。

私のアプリのルートビューコントローラーはナビゲーションコントローラーです。最初、このナビゲーションコントローラーのスタックには1つのUIViewControllerがあり、これを親ビューコントローラーと呼びます。

ユーザーがナビゲーションバーボタンをタップすると、親ビューコントローラーは、包含APIを使用して、ビュー上の2つの子ビューコントローラーを切り替えます(マップされた結果とリストされた結果を切り替えます)。親View Controllerは、2つの子View Controllerへの参照を保持します。 2番目の子ビューコントローラは、ユーザーが初めてトグルボタンをタップするまで作成されません。 2番目の子ビューコントローラーはテーブルビューコントローラーであり、automaticallyAdjustsScrollViewInsetsプロパティの設定方法に関係なく、ナビゲーションバーに重なるという問題がありました。

これを修正するには、子テーブルビューコントローラーが作成された後、親ビューコントローラーのviewDidLayoutSubviewsメソッドでadjustChild:tableView:insetTop(以下に表示)を呼び出します。

子ビューコントローラーのテーブルビューと親ビューコントローラーのtopLayoutGuide.lengthは、次のようにadjustChild:tableView:insetTopメソッドに渡されます...

// called right after childViewController is created
adjustChild(childViewController.tableView, insetTop: topLayoutGuide.length)

AdjustChildメソッド...

private func adjustChild(tableView: UITableView, insetTop: CGFloat) {
    tableView.contentInset.top = insetTop
    tableView.scrollIndicatorInsets.top = insetTop

    // make sure the tableview is scrolled at least as far as insetTop
    if (tableView.contentOffset.y > -insetTop) {
        tableView.contentOffset.y = -insetTop
    }
}

ステートメントtableView.scrollIndicatorInsets.top = insetTopは、ナビゲーションバーのすぐ下から始まるように、テーブルビューの右側のスクロールインジケーターを調整します。これは微妙であり、気づくまで見落とされがちです。

viewDidLayoutSubviewsは次のようになります...

override func viewDidLayoutSubviews() {
    if let childViewController = childViewController {
        adjustChild(childViewController.tableView, insetTop: topLayoutGuide.length)
    }
}

上記のすべてのコードが親のビューコントローラーに表示されることに注意してください。

私は Christopher Pickslay's answer からの質問 "iOS 7のテーブルビューがコンテンツインセットの自動調整に失敗する"の助けを借りてこれを理解しました。

2
Mobile Dan