一般都是父控件布局子控件, 但是如何让子控件布局父控件呢?
目的是 子控件大小改变, 父控件也改变!
很多情况下, 都是子控件改变了, 但是父控件的 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;
}
除了oneView
在ExerciseCell
Xib上, 其他的都是自定的控件
我的层级结构是
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
的子控件 成为 oneview
和twoview
的父控件. 然后设置 oneview
和twoview
的约束和以前一样
对于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的cell
的layoutSubview的方法
.
我一直想去调用,但是无果,况且调用也没有用, 因为整个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
我是变成了模型的一个属性层层传进去, 然后发通知出来.
网友评论