[〜#〜] 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を切り替えることです。
これらの子View Controllerにはすべて、ScrollView、TableView、またはCollection Viewのいずれかが含まれています。
最初のView Controllerが正常にロードされ、Navigation Barの下に正しく配置されていることがわかりました。ただし、新しいView Controllerに切り替えると、navbarは尊重されず、ビューはnav barの下に設定されます。
自動レイアウトとインターフェイスビルダーを使用しています。考えられるすべてを試しましたが、一貫した解決策を見つけることができません。
ユーザーがセグメント化されたコントロールをタップすると、最初の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;
}
}
}
カスタムコンテナView Controllerは、子View ControllerのcontentInset
プロパティを考慮して、既知のNavigation Barの高さに応じて2番目のView ControllerのautomaticallyAdjustsScrollViewInsets
を調整する必要があります。 (コンテナのtopLayoutGuide
プロパティにも関心がある場合があります。ビューの切り替え中および切り替え後に正しい値が返されることを確認してください。)
UIKitは、このロジックをどのように適用するかに関して著しく一貫性がありません(バグがあります)。階層内の複数のView Controllerに到達することで、この調整が自動的に実行されることもありますが、カスタムコンテナの切り替え後、多くの場合、自分で作業を行う必要があります。
これは、人々が何をするよりもずっと簡単なようです。
UINavigationControllerは、サブビューのレイアウト中にのみスクロールビューのインセットを設定します。 addChildViewController:
はレイアウトを引き起こしませんが、呼び出すと、navigationControllerでsetNeedsLayout
を呼び出すだけで済みます。カスタムタブのようなビューでビューを切り替えながら、次のことを行います。
[self addChildViewController:newcontroller];
[self.view insertSubview:newview atIndex:0];
[self.navigationController.view setNeedsLayout];
最後の行では、新しいView Controllerのコンテンツ用にスクロールビューのインセットが再計算されます。
誰かが同様の問題を抱えている場合の参考までに:この問題は、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;
}
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];
}];
}
設定edgesForExtendedLayout = []
私のchildcontrollersで私のために働いた。