美文网首页iOS备忘录
iOS 子控件布局 决定父控件大小 AutoLayout &am

iOS 子控件布局 决定父控件大小 AutoLayout &am

作者: okerivy | 来源:发表于2017-07-12 21:57 被阅读690次
    一般都是父控件布局子控件, 但是如何让子控件布局父控件呢?
    目的是 子控件大小改变, 父控件也改变!

    很多情况下, 都是子控件改变了, 但是父控件的 layoutSubviews不调用, 这里就是解决这个问题的, 强制父控件调用layoutSubviews

    这里针对的是自定义的view
    三个好方法 例文中用到了前面两个
    [self.superview setNeedsLayout];
    [self setNeedsUpdateConstraints];
    [self.superview setNeedsUpdateConstraints];

    1,对于用传统的 frame 来布局的方式

    layoutSubviews[self.superview setNeedsLayout];
    使父类也调用layoutSubviews, 一层一层往上传
    并且要把布局的关键代码 写到layoutSubviews

    stemLabel.frame是在界面上可以手动改变的

    这是在子控件(stemLabel)的代码

    - (void)layoutSubviews
    {
        [super layoutSubviews];
        [self.superview setNeedsLayout];
    }
    

    这是在父控件(stemView)的代码

    - (void)layoutSubviews
    {
        [super layoutSubviews];
         // 布局关键代码
        self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, CGRectGetMaxY(self.stemLabel.frame)+20);
    
        [self.superview setNeedsLayout];
    }
    
    

    2,对于用 AutoLayout 来布局的方式

    layoutSubviews[self setNeedsUpdateConstraints]; 使父类也调用layoutSubviews, 一层一层往上传 并且要把布局的关键代码 写到layoutSubviews

    对于自定义的view这里面有一个方法- (void)updateConstraints 可以重写

    对于我,目前没有用到这个方法 因为我的布局代码要么写到[[view alloc] init]后面, 要么把关键布局代码写到 layoutSubviews

    这是在子控件(ExerciseStemView)的代码

    - (void)updateConstraints
    {
        [super updateConstraints];
    }
    
    - (void)layoutSubviews
    {
        [super layoutSubviews];
        // 布局关键代码
        self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, CGRectGetMaxY(self.stemView.frame)+20);
    
        // 这里不能像上面那样调用 [self.superview setNeedsLayout]; 会发生死循环
        // 可能原因是 这个控件是 frame 布局, 而他的父控件是 AutoLayout 布局的 所以要调用  [self setNeedsUpdateConstraints];
        [self setNeedsUpdateConstraints];
    }
    
    

    在这个ExerciseStemView 的父控件ExerciseCell上, 用的是AutoLayout
    这是在父控件(ExerciseCell)的代码

    - (void)updateConstraints
    {
        [super updateConstraints];
    }
    
    - (void)layoutSubviews
    {
        [super layoutSubviews];
        // 布局关键代码
        self.stemViewHeight.constant = self.stemViewModel.cellHeight;
    
    }
    
    

    除了oneViewExerciseCellXib上, 其他的都是自定的控件
    我的层级结构是

    ExerciseCell ---xib包含----->oneView---addSubview----->ExerciseStemView ---addSubview----->stemView---addSubview----->stemLabel

    这样做的原因是 是因为stemLabel 一直会改变大小, 所以这样一层一层传出去.
    这里面既有 xib AutoLayout布局 也有 代码frame布局 所以坑很多. 记录一下.

    至于什么是布局的关键代码, 就是能影响其他其他控件的布局的代码.
    例如 子控件改变,父控件要改变,然后父控件的左右控件要改变 等等


    PS

    这里还有一个方法, 让这个自定义的控件 强制调用它所在的控制器viewDidLayoutSubviews 来重新布局控件
    有的时候会用到

    自定义的控件的 内部代码如下

    - (void)layoutSubviews
    {
        [super layoutSubviews];
        self.stemLabel.width = self.frame.size.width;
        
        self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, CGRectGetMaxY(self.stemLabel.frame)+20);
    
        // 强制viewController 调用 viewDidLayoutSubviews
        [[self viewController] viewDidLayoutSubviews];
    }
    
    
    /**
     *  返回当前视图的控制器
     */
    - (UIViewController *)viewController {
        for (UIView* next = [self superview]; next; next = next.superview) {
            UIResponder *nextResponder = [next nextResponder];
            if ([nextResponder isKindOfClass:[UIViewController class]]) {
                return (UIViewController *)nextResponder;
            }
        }
        return nil;
    }
    
    

    3,更新 继续填坑

    由于我以前的ExerciseCell 这个cell 的尺寸是整个屏幕,固定大小, 里面有个Scollview, 所以, 里面控件改变,不用改变最外层的 ExerciseCell的尺寸, 只要改变 Scollview的contentsize 就行了其实如果约束加好, Scollview的contentsize不用代码改变,只要更新下就行了
    但是, 当我把这个ExerciseStemView加到一个tableview的cell 的时候,就出现了问题.

    问题 1

    table加载的时候, cell 高度不正确

    由于cell的高度是动态的,所以要根据ExerciseStemView 的高度而决定 当然 cell里面还有其他控件

    我开始是在tableviewcell的xib上添加了一个oneView 一个twoView

    设置约束,
    oneView距离tableviecell的 contenview 上 左 右 各20, 高度为100 并拖拽到tableviewcell 的.m文件中 为 oneViewHeight
    twoView距离tableviecell的 contenview 下 左 右 各20, 高度为50 并拖拽到tableviewcell 的.m文件中为 twoViewHeight
    oneView底部距离twoView 顶部 20

    很简单的布局

    然后在oneView添加 ExerciseStemView,并在layoutsubview中设置oneViewHeight = ExerciseStemView的高度(其实是一个模型的cellheight)
    但是运行的时候约束会报错, 不过不会崩溃

    但是cell的高度其实是不正常的. 出现了ExerciseStemView比cell还高.
    对于这个问题,我用了上面1 和2的所有方法, 都无效.

    后来,我想到增加一个中间层. 为 tempView, 成为 cell的 contenview的子控件 成为 oneviewtwoview的父控件. 然后设置 oneviewtwoview的约束和以前一样
    对于tempView的约束, 只增加3个 下 左 右 距离父控件为0, 下面不设置, 这样不会报错

    然后 在layoutsubview的时候, 起始我是想把tempView的高度直接赋值给 cell的 高度,后来发现不行, 因为tempView的高度虽然在界面显示后是正常的, 但是在layout中并不正确, 而是 在你的xib的起始的大小. 所以
    我就把所有的子控件都加到了一起
    像这样

    self.cellViewModel.cellHeight = self.oneViewHeight.constant + self.twoViewHeight.constant + 1 + 60;
    

    其实这个当初加的中间层也就没有意义了,完全没用到

    这样以后 , 在控制器的 tableview的heightForRowAtIndexPath代理方法中 ,根据indexPath获取对应模型的 .cellHeight即可

    这样 tableview加载以后,高度就动态的高度了.

    问题 2

    当点击ExerciseStemView的按钮的时候,需要增加填充一段数据,这个时候 ExerciseStemView的高度改变了,但是 cell的高度并没有改变,

    layoutSubview的方法也只是调用到 ExerciseStemView就截止了,不会调用父类tableview的celllayoutSubview的方法.

    我一直想去调用,但是无果,况且调用也没有用, 因为整个tableview 还要刷新,不然影响其他cell的显示.

    我的错误尝试
    1, 在ExerciseStemView调用 layoutSubview的方法的时候 发通知通知控制器, 让[self.tableView reloadData];
    发现 会发通知很多次, 并且界面显示卡顿,空白,飞走,等各种奇怪现象.

    2, 我试图接收到通知只执行一次, 但是依然各种问题

    3, 我在ExerciseStemView点击方法中 发通知通知控制器, 让[self.tableView reloadData]; 但是这个时候子控件还没有刷新完成, 无法获取 ExerciseStemView的真正高度,失败

    4, 后来我想, 不如直接更新模型,然后再[self.tableView reloadData]; 这样就会显示对了

    当然前提是需要把点击事件后,增加的填充一段数据传出去, 这就用到了通知, 直接同第三步一样发通知, 不同的是包装一个字典把数据传到控制器,当然也应该保护cell的indexpath, 然后拼接到控制器的 cell的模型数组中的对应的元素上
    然后调用[self.tableView reloadData];

    这个时候就能显示正常了.

    indexpath 我是变成了模型的一个属性层层传进去, 然后发通知出来.


    相关文章

      网友评论

        本文标题:iOS 子控件布局 决定父控件大小 AutoLayout &am

        本文链接:https://www.haomeiwen.com/subject/yflihxtx.html