Ios 如何为子视图控制器设置topLayoutGuide位置

Ios 如何为子视图控制器设置topLayoutGuide位置,ios,objective-c,uiviewcontroller,ios7,autolayout,Ios,Objective C,Uiviewcontroller,Ios7,Autolayout,我正在实现一个定制容器,它与UINavigationController非常相似,只是它不包含整个控制器堆栈。它有一个UINavigationBar,它被约束在容器控制器的topLayoutGuide上,正好离顶部20px,这是正常的 当我添加一个子视图控制器并将其视图放入层次结构时,我希望它的topLayoutGuide显示在IB中,并用于布局子视图控制器视图的子视图,以显示在导航栏的底部。在相关文件中有一个关于要做什么的注释: 该属性的值就是长度的值 查询此属性时返回的对象的属性。这 值受视

我正在实现一个定制容器,它与UINavigationController非常相似,只是它不包含整个控制器堆栈。它有一个UINavigationBar,它被约束在容器控制器的topLayoutGuide上,正好离顶部20px,这是正常的

当我添加一个子视图控制器并将其视图放入层次结构时,我希望它的topLayoutGuide显示在IB中,并用于布局子视图控制器视图的子视图,以显示在导航栏的底部。在相关文件中有一个关于要做什么的注释:

该属性的值就是长度的值 查询此属性时返回的对象的属性。这 值受视图控制器或其封闭对象的约束 容器视图控制器(如导航栏或选项卡栏 控制器),如下所示:

  • 不在容器视图控制器内的视图控制器约束此属性以指示状态栏的底部(如果可见)
    或指示视图控制器视图的上边缘
  • 容器视图控制器中的视图控制器未设置此属性的值。相反,容器视图控制器 约束该值以指示:
    • 导航栏的底部(如果导航栏可见)
    • 状态栏的底部(如果只有状态栏可见)
    • 如果状态栏和导航栏都不可见,则视图控制器视图的上边缘
但我不太明白如何“约束它的值”,因为topLayoutGuide及其长度属性都是只读的

我尝试了以下代码来添加子视图控制器:

[self addChildViewController:gamePhaseController];
UIView *gamePhaseControllerView = gamePhaseController.view;
gamePhaseControllerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentContainer addSubview:gamePhaseControllerView];

NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[gamePhaseControllerView]-0-|"
                                                                         options:0
                                                                         metrics:nil
                                                                           views:NSDictionaryOfVariableBindings(gamePhaseControllerView)];

NSLayoutConstraint *topLayoutGuideConstraint = [NSLayoutConstraint constraintWithItem:gamePhaseController.topLayoutGuide
                                                                            attribute:NSLayoutAttributeTop
                                                                            relatedBy:NSLayoutRelationEqual
                                                                               toItem:self.navigationBar
                                                                            attribute:NSLayoutAttributeBottom
                                                                           multiplier:1 constant:0];
NSLayoutConstraint *bottomLayoutGuideConstraint = [NSLayoutConstraint constraintWithItem:gamePhaseController.bottomLayoutGuide
                                                                               attribute:NSLayoutAttributeBottom
                                                                               relatedBy:NSLayoutRelationEqual
                                                                                  toItem:self.bottomLayoutGuide
                                                                               attribute:NSLayoutAttributeTop
                                                                              multiplier:1 constant:0];
[self.view addConstraint:topLayoutGuideConstraint];
[self.view addConstraint:bottomLayoutGuideConstraint];
[self.contentContainer addConstraints:horizontalConstraints];
[gamePhaseController didMoveToParentViewController:self];

_contentController = gamePhaseController;
在IB中,我为gamePhaseController指定了“顶部栏下”和“底部栏下”。其中一个视图特别限制在顶部布局指南中,无论如何,在设备上,它似乎离容器导航栏底部20px


使用此行为实现自定义容器控制器的正确方法是什么?

在调试数小时后,据我所知,布局指南是只读的,并且是从用于基于约束的布局的私有类派生的。重写访问器没有任何作用(即使它们被调用),而且这一切都非常烦人。

我认为它们的意思是应该使用autolayout(即NSLayoutConstraint对象)约束布局指南,而不是手动设置length属性。length属性可用于选择不使用autolayout的类,但对于自定义容器视图控制器,您似乎没有此选择

我假设最佳做法是在容器视图控制器中将约束的优先级设置为“设置”length属性的值为
UILayoutPriorityRequired

我不确定您将绑定什么布局属性,可能是
nslayouttributeheight
nslayouttributebottom

(更新:现在作为cocoapod提供,请参阅)

一个可行的解决方案是删除苹果的布局约束,并添加自己的约束。我为此做了一个小分类

这是代码,但我建议使用cocoapod。它有单元测试,更可能是最新的

//
//  UIViewController+TTLayoutSupport.h
//
//  Created by Steffen on 17.09.14.
//

#import <UIKit/UIKit.h>

@interface UIViewController (TTLayoutSupport)

@property (assign, nonatomic) CGFloat tt_bottomLayoutGuideLength;

@property (assign, nonatomic) CGFloat tt_topLayoutGuideLength;

@end
//
//UIViewController+TTLayoutSupport.h
//
//斯蒂芬于2014年9月17日创作。
//
#进口
@界面UIViewController(TTLayoutSupport)
@属性(赋值,非原子)CGFloat tt_bottomLayoutGuideLength;
@属性(赋值,非原子)CGFloat tt_topLayoutGuideLength;
@结束
-

#导入“UIViewController+TTLayoutSupport.h”
#导入“TTLayoutSupportConstraint.h”
#进口
@接口UIViewController(TTLayoutSupportPrivate)
//为topLayoutGuide录制的apple'UILayoutSupportConstraint'对象
@属性(非原子,强)NSArray*tt_记录的布局支持约束;
//录制的apple'UILayoutSupportConstraint'对象,用于bottomLayoutGuide
@属性(非原子,强)NSArray*tt_记录的BottomLayoutSupportConstraints;
//已添加用于控制topLayoutGuide的自定义布局约束
@属性(非原子,强)TTLayoutSupportConstraint*tt_topConstraint;
//已添加用于控制bottomLayoutGuide的自定义布局约束
@属性(非原子,强)TTLayoutSupportConstraint*tt_bottomConstraint;
//这用于NSNotificationCenter取消订阅(我们无法覆盖类别中的dealloc)
@属性(非原子,强)id tt_观测器;
@结束
@实现UIViewController(TTLayoutSupport)
-(CGFloat)tt_顶部布局导轨长度
{
返回self.tt_topConstraint?self.tt_topConstraint.constant:self.topLayoutGuide.length;
}
-(无效)设置顶部布局导轨长度:(CGFloat)长度
{
[自我约束];
self.tt_topConstraint.constant=长度;
[自我测试更新设置:是];
}
-(CGFloat)tt_底部布局导轨长度
{
返回self.tt_bottomConstraint?self.tt_bottomConstraint.constant:self.bottomLayoutGuide.length;
}
-(无效)设置底部布局导轨长度:(CGFloat)长度
{
[self tt_(自我测试)确保进行自我测试;
self.tt_.constant=长度;
[自我测试更新设置:否];
}
-(无效)tt_保证了STOMTOPCONSTRAINT
{
if(self.tt_topConstraint){
//已经创建
返回;
}
//如果从未访问过视图,则录制不起作用
__未使用的UIView*视图=self.view;
//如果从未访问过topLayoutGuide,它可能还不存在
__未使用id topLayoutGuide=self.topLayoutGuide;
self.tt_recordedTopLayoutSupportConstraints=[self-findLayoutSupportConstraintsFor:self.topLayoutGuide];
NSAssert(self.tt_recordedTopLayoutSupportConstraints.count,@“未能记录topLayoutGuide约束。控制器的视图是否已添加到视图层次结构C中
#import "UIViewController+TTLayoutSupport.h"
#import "TTLayoutSupportConstraint.h"
#import <objc/runtime.h>

@interface UIViewController (TTLayoutSupportPrivate)

// recorded apple's `UILayoutSupportConstraint` objects for topLayoutGuide
@property (nonatomic, strong) NSArray *tt_recordedTopLayoutSupportConstraints;

// recorded apple's `UILayoutSupportConstraint` objects for bottomLayoutGuide
@property (nonatomic, strong) NSArray *tt_recordedBottomLayoutSupportConstraints;

// custom layout constraint that has been added to control the topLayoutGuide
@property (nonatomic, strong) TTLayoutSupportConstraint *tt_topConstraint;

// custom layout constraint that has been added to control the bottomLayoutGuide
@property (nonatomic, strong) TTLayoutSupportConstraint *tt_bottomConstraint;

// this is for NSNotificationCenter unsubscription (we can't override dealloc in a category)
@property (nonatomic, strong) id tt_observer;

@end

@implementation UIViewController (TTLayoutSupport)

- (CGFloat)tt_topLayoutGuideLength
{
    return self.tt_topConstraint ? self.tt_topConstraint.constant : self.topLayoutGuide.length;
}

- (void)setTt_topLayoutGuideLength:(CGFloat)length
{
    [self tt_ensureCustomTopConstraint];

    self.tt_topConstraint.constant = length;

    [self tt_updateInsets:YES];
}

- (CGFloat)tt_bottomLayoutGuideLength
{
    return self.tt_bottomConstraint ? self.tt_bottomConstraint.constant : self.bottomLayoutGuide.length;
}

- (void)setTt_bottomLayoutGuideLength:(CGFloat)length
{
    [self tt_ensureCustomBottomConstraint];

    self.tt_bottomConstraint.constant = length;

    [self tt_updateInsets:NO];
}

- (void)tt_ensureCustomTopConstraint
{
    if (self.tt_topConstraint) {
        // already created
        return;
    }

    // recording does not work if view has never been accessed
    __unused UIView *view = self.view;
    // if topLayoutGuide has never been accessed it may not exist yet
    __unused id<UILayoutSupport> topLayoutGuide = self.topLayoutGuide;

    self.tt_recordedTopLayoutSupportConstraints = [self findLayoutSupportConstraintsFor:self.topLayoutGuide];
    NSAssert(self.tt_recordedTopLayoutSupportConstraints.count, @"Failed to record topLayoutGuide constraints. Is the controller's view added to the view hierarchy?");
    [self.view removeConstraints:self.tt_recordedTopLayoutSupportConstraints];

    NSArray *constraints =
        [TTLayoutSupportConstraint layoutSupportConstraintsWithView:self.view
                                                     topLayoutGuide:self.topLayoutGuide];

    // todo: less hacky?
    self.tt_topConstraint = [constraints firstObject];

    [self.view addConstraints:constraints];

    // this fixes a problem with iOS7.1 (GH issue #2), where the contentInset
    // of a scrollView is overridden by the system after interface rotation
    // this should be safe to do on iOS8 too, even if the problem does not exist there.
    __weak typeof(self) weakSelf = self;
    self.tt_observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceOrientationDidChangeNotification
                                                                         object:nil
                                                                          queue:[NSOperationQueue mainQueue]
                                                                     usingBlock:^(NSNotification *note) {
        __strong typeof(self) self = weakSelf;
        [self tt_updateInsets:NO];
    }];
}

- (void)tt_ensureCustomBottomConstraint
{
    if (self.tt_bottomConstraint) {
        // already created
        return;
    }

    // recording does not work if view has never been accessed
    __unused UIView *view = self.view;
    // if bottomLayoutGuide has never been accessed it may not exist yet
    __unused id<UILayoutSupport> bottomLayoutGuide = self.bottomLayoutGuide;

    self.tt_recordedBottomLayoutSupportConstraints = [self findLayoutSupportConstraintsFor:self.bottomLayoutGuide];
    NSAssert(self.tt_recordedBottomLayoutSupportConstraints.count, @"Failed to record bottomLayoutGuide constraints. Is the controller's view added to the view hierarchy?");
    [self.view removeConstraints:self.tt_recordedBottomLayoutSupportConstraints];

    NSArray *constraints =
    [TTLayoutSupportConstraint layoutSupportConstraintsWithView:self.view
                                              bottomLayoutGuide:self.bottomLayoutGuide];

    // todo: less hacky?
    self.tt_bottomConstraint = [constraints firstObject];

    [self.view addConstraints:constraints];
}

- (NSArray *)findLayoutSupportConstraintsFor:(id<UILayoutSupport>)layoutGuide
{
    NSMutableArray *recordedLayoutConstraints = [[NSMutableArray alloc] init];

    for (NSLayoutConstraint *constraint in self.view.constraints) {
        // I think an equality check is the fastest check we can make here
        // member check is to distinguish accidentally created constraints from _UILayoutSupportConstraints
        if (constraint.firstItem == layoutGuide && ![constraint isMemberOfClass:[NSLayoutConstraint class]]) {
            [recordedLayoutConstraints addObject:constraint];
        }
    }

    return recordedLayoutConstraints;
}

- (void)tt_updateInsets:(BOOL)adjustsScrollPosition
{
    // don't update scroll view insets if developer didn't want it
    if (!self.automaticallyAdjustsScrollViewInsets) {
        return;
    }

    UIScrollView *scrollView;

    if ([self respondsToSelector:@selector(tableView)]) {
        scrollView = ((UITableViewController *)self).tableView;
    } else if ([self respondsToSelector:@selector(collectionView)]) {
        scrollView = ((UICollectionViewController *)self).collectionView;
    } else {
        scrollView = (UIScrollView *)self.view;
    }

    if ([scrollView isKindOfClass:[UIScrollView class]]) {
        CGPoint previousContentOffset = CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y + scrollView.contentInset.top);

        UIEdgeInsets insets = UIEdgeInsetsMake(self.tt_topLayoutGuideLength, 0, self.tt_bottomLayoutGuideLength, 0);
        scrollView.contentInset = insets;
        scrollView.scrollIndicatorInsets = insets;

        if (adjustsScrollPosition && previousContentOffset.y == 0) {
            scrollView.contentOffset = CGPointMake(previousContentOffset.x, -scrollView.contentInset.top);
        }
    }
}

@end

@implementation UIViewController (TTLayoutSupportPrivate)

- (NSLayoutConstraint *)tt_topConstraint
{
    return objc_getAssociatedObject(self, @selector(tt_topConstraint));
}

- (void)setTt_topConstraint:(NSLayoutConstraint *)constraint
{
    objc_setAssociatedObject(self, @selector(tt_topConstraint), constraint, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSLayoutConstraint *)tt_bottomConstraint
{
    return objc_getAssociatedObject(self, @selector(tt_bottomConstraint));
}

- (void)setTt_bottomConstraint:(NSLayoutConstraint *)constraint
{
    objc_setAssociatedObject(self, @selector(tt_bottomConstraint), constraint, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSArray *)tt_recordedTopLayoutSupportConstraints
{
    return objc_getAssociatedObject(self, @selector(tt_recordedTopLayoutSupportConstraints));
}

- (void)setTt_recordedTopLayoutSupportConstraints:(NSArray *)constraints
{
    objc_setAssociatedObject(self, @selector(tt_recordedTopLayoutSupportConstraints), constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSArray *)tt_recordedBottomLayoutSupportConstraints
{
    return objc_getAssociatedObject(self, @selector(tt_recordedBottomLayoutSupportConstraints));
}

- (void)setTt_recordedBottomLayoutSupportConstraints:(NSArray *)constraints
{
    objc_setAssociatedObject(self, @selector(tt_recordedBottomLayoutSupportConstraints), constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setTt_observer:(id)tt_observer
{
    objc_setAssociatedObject(self, @selector(tt_observer), tt_observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)tt_observer
{
    return objc_getAssociatedObject(self, @selector(tt_observer));
}
//
//  TTLayoutSupportConstraint.h
//
//  Created by Steffen on 17.09.14.
//

#import <UIKit/UIKit.h>

@interface TTLayoutSupportConstraint : NSLayoutConstraint

+ (NSArray *)layoutSupportConstraintsWithView:(UIView *)view topLayoutGuide:(id<UILayoutSupport>)topLayoutGuide;

+ (NSArray *)layoutSupportConstraintsWithView:(UIView *)view bottomLayoutGuide:(id<UILayoutSupport>)bottomLayoutGuide;

@end
//
//  TTLayoutSupportConstraint.m
// 
//  Created by Steffen on 17.09.14.
//

#import "TTLayoutSupportConstraint.h"

@implementation TTLayoutSupportConstraint

+ (NSArray *)layoutSupportConstraintsWithView:(UIView *)view topLayoutGuide:(id<UILayoutSupport>)topLayoutGuide
{
    return @[
             [TTLayoutSupportConstraint constraintWithItem:topLayoutGuide
                                                 attribute:NSLayoutAttributeHeight
                                                 relatedBy:NSLayoutRelationEqual
                                                    toItem:nil
                                                 attribute:NSLayoutAttributeNotAnAttribute
                                                multiplier:1.0
                                                  constant:0.0],
             [TTLayoutSupportConstraint constraintWithItem:topLayoutGuide
                                                 attribute:NSLayoutAttributeTop
                                                 relatedBy:NSLayoutRelationEqual
                                                    toItem:view
                                                 attribute:NSLayoutAttributeTop
                                                multiplier:1.0
                                                  constant:0.0],
             ];
}

+ (NSArray *)layoutSupportConstraintsWithView:(UIView *)view bottomLayoutGuide:(id<UILayoutSupport>)bottomLayoutGuide
{
    return @[
             [TTLayoutSupportConstraint constraintWithItem:bottomLayoutGuide
                                                 attribute:NSLayoutAttributeHeight
                                                 relatedBy:NSLayoutRelationEqual
                                                    toItem:nil
                                                 attribute:NSLayoutAttributeNotAnAttribute
                                                multiplier:1.0
                                                  constant:0.0],
             [TTLayoutSupportConstraint constraintWithItem:bottomLayoutGuide
                                                 attribute:NSLayoutAttributeBottom
                                                 relatedBy:NSLayoutRelationEqual
                                                    toItem:view
                                                 attribute:NSLayoutAttributeBottom
                                                multiplier:1.0
                                                  constant:0.0],
             ];
}

@end
- (void)viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    for (UIViewController * childViewController in self.childViewControllers) {

        // Pass the layouts to the child
        if ([childViewController isKindOfClass:[MyCustomViewController class]]) {
            [(MyCustomViewController *)childViewController parentTopLayoutGuideLength:self.topLayoutGuide.length parentBottomLayoutGuideLength:self.bottomLayoutGuide.length];
        }

    }

}