web-dev-qa-db-ja.com

カスタムコンテナビューコントローラに埋め込まれている場合、コンテンツはナビゲーションバーの下に表示されます。

[〜#〜] update [〜#〜]

Timの回答に基づいて、カスタムコンテナーの一部であるscrollview(またはサブクラス)を持つ各View Controllerに以下を実装しました。

- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (parent) {
        CGFloat top = parent.topLayoutGuide.length;
        CGFloat bottom = parent.bottomLayoutGuide.length;

        // this is the most important part here, because the first view controller added 
        // never had the layout issue, it was always the second. if we applied these
        // Edge insets to the first view controller, then it would lay out incorrectly.
        // first detect if it's laid out correctly with the following condition, and if
        // not, manually make the adjustments since it seems like UIKit is failing to do so
        if (self.collectionView.contentInset.top != top) {
            UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
            self.collectionView.contentInset = newInsets;
            self.collectionView.scrollIndicatorInsets = newInsets;
        }
    }

    [super didMoveToParentViewController:parent];
}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

SegmentedPageViewControllerというカスタムコンテナビューコントローラがあります。これをUINavigationController's rootViewControllerとして設定します。

SegmentedPageViewControllerの目的は、NavControllerのtitleViewとして設定されたUISegmentedControlを許可して、異なる子View Controllerを切り替えることです。

enter image description here

これらの子View Controllerにはすべて、ScrollView、TableView、またはCollection Viewのいずれかが含まれています。

最初のView Controllerが正常にロードされ、Navigation Barの下に正しく配置されていることがわかりました。ただし、新しいView Controllerに切り替えると、navbarは尊重されず、ビューはnav barの下に設定されます。

enter image description here

自動レイアウトとインターフェイスビルダーを使用しています。考えられるすべてを試しましたが、一貫した解決策を見つけることができません。

ユーザーがセグメント化されたコントロールをタップすると、最初のView Controllerを設定し、別のView Controllerに切り替えるメインコードブロックを次に示します。

- (void)switchFromViewController:(UIViewController *)oldVC toViewController:(UIViewController *)newVC
{
    if (newVC == oldVC) return;

    // Check the newVC is non-nil otherwise expect a crash: NSInvalidArgumentException
    if (newVC) {

        // Set the new view controller frame (in this case to be the size of the available screen bounds)
        // Calulate any other frame animations here (e.g. for the oldVC)
        newVC.view.frame = self.view.bounds;

        // Check the oldVC is non-nil otherwise expect a crash: NSInvalidArgumentException
        if (oldVC) {
            // **** THIS RUNS WHEN A NEW VC IS SET ****
            // DIFFERENT FROM FIRST VC IN THAT WE TRANSITION INSTEAD OF JUST SETTING


            // Start both the view controller transitions
            [oldVC willMoveToParentViewController:nil];
            [self addChildViewController:newVC];

            // Swap the view controllers
            // No frame animations in this code but these would go in the animations block
            [self transitionFromViewController:oldVC
                              toViewController:newVC
                                      duration:0.25
                                       options:UIViewAnimationOptionLayoutSubviews
                                    animations:^{}
                                    completion:^(BOOL finished) {
                                        // Finish both the view controller transitions
                                        [oldVC removeFromParentViewController];
                                        [newVC didMoveToParentViewController:self];
                                        // Store a reference to the current controller
                                        self.currentViewController = newVC;
                                    }];
        } else {

            // **** THIS RUNS WHEN THE FIRST VC IS SET ****
            // JUST STANDARD VIEW CONTROLLER CONTAINMENT

            // Otherwise we are adding a view controller for the first time
            // Start the view controller transition
            [self addChildViewController:newVC];

            // Add the new view controller view to the view hierarchy
            [self.view addSubview:newVC.view];

            // End the view controller transition
            [newVC didMoveToParentViewController:self];

            // Store a reference to the current controller
            self.currentViewController = newVC;
        }
    }

}
54
djibouti33

カスタムコンテナView Controllerは、子View ControllerのcontentInsetプロパティを考慮して、既知のNavigation Barの高さに応じて2番目のView ControllerのautomaticallyAdjustsScrollViewInsetsを調整する必要があります。 (コンテナのtopLayoutGuideプロパティにも関心がある場合があります。ビューの切り替え中および切り替え後に正しい値が返されることを確認してください。)

UIKitは、このロジックをどのように適用するかに関して著しく一貫性がありません(バグがあります)。階層内の複数のView Controllerに到達することで、この調整が自動的に実行されることもありますが、カスタムコンテナの切り替え後、多くの場合、自分で作業を行う必要があります。

26
Tim

これは、人々が何をするよりもずっと簡単なようです。

UINavigationControllerは、サブビューのレイアウト中にのみスクロールビューのインセットを設定します。 addChildViewController:はレイアウトを引き起こしませんが、呼び出すと、navigationControllerでsetNeedsLayoutを呼び出すだけで済みます。カスタムタブのようなビューでビューを切り替えながら、次のことを行います。

[self addChildViewController:newcontroller];
[self.view insertSubview:newview atIndex:0];
[self.navigationController.view setNeedsLayout];

最後の行では、新しいView Controllerのコンテンツ用にスクロールビューのインセットが再計算されます。

22
Dag Ågren

誰かが同様の問題を抱えている場合の参考までに:この問題は、View Controllerが組み込まれていない場合でも発生する可能性があります。 automaticallyAdjustsScrollViewInsetsは、ScrollView(またはtableview/collectionview/webview)がView Controllerの階層の最初のビューである場合にのみ適用されるようです。

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

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

}
9
BFar

UINavigationControllerの文書化されていないメソッドを使用して、より良いソリューションを見つけました。

#import <UIKit/UIKit.h>

@interface UINavigationController (ContentInset)


- (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller;

@end
#import "UINavigationController+ContentInset.h"

@interface UINavigationController()


- (void)_computeAndApplyScrollContentInsetDeltaForViewController:(id)arg1; 

@end


@implementation UINavigationController (ContentInset)

- (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller
{
    if ([UINavigationController instancesRespondToSelector:@selector(_computeAndApplyScrollContentInsetDeltaForViewController:)])
        [self _computeAndApplyScrollContentInsetDeltaForViewController:controller];
}

@end

その後、このようにします

- (void) cycleFromViewController: (UIViewController*) oldC
                toViewController: (UIViewController*) newC
{
    [oldC willMoveToParentViewController:nil];                        
    [self addChildViewController:newC];
  
    [self transitionFromViewController: oldC toViewController: newC   
                              duration: 0.25 options:0
                            animations:^{
                                newC.view.frame = oldC.view.frame;                                    
                                [self.navigationController computeAndApplyScrollContentInsetDeltaForViewController:newC];
                            }
                            completion:^(BOOL finished) {
                                [oldC removeFromParentViewController];                  
                                [newC didMoveToParentViewController:self];
                            }];
}
0
liaofc

設定edgesForExtendedLayout = []私のchildcontrollersで私のために働いた。

0
coyer