一般都是父控件布局子控件, 但是如何讓子控件布局父控件呢?
目的是 子控件大小改變, 父控件也改變!
很多情況下, 都是子控件改變了, 但是父控件的 layoutSubviews不調(diào)用, 這里就是解決這個問題的, 強(qiáng)制父控件調(diào)用layoutSubviews
這里針對的是自定義的view
三個好方法 例文中用到了前面兩個
[self.superview setNeedsLayout];
[self setNeedsUpdateConstraints];
[self.superview setNeedsUpdateConstraints];
1,對于用傳統(tǒng)的 frame 來布局的方式
在layoutSubviews 用 [self.superview setNeedsLayout];
使父類也調(diào)用layoutSubviews, 一層一層往上傳
并且要把布局的關(guān)鍵代碼 寫到layoutSubviews
stemLabel.frame是在界面上可以手動改變的
這是在子控件(stemLabel)的代碼
- (void)layoutSubviews
{
[super layoutSubviews];
[self.superview setNeedsLayout];
}
這是在父控件(stemView)的代碼
- (void)layoutSubviews
{
[super layoutSubviews];
// 布局關(guān)鍵代碼
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]; 使父類也調(diào)用layoutSubviews, 一層一層往上傳 并且要把布局的關(guān)鍵代碼 寫到layoutSubviews
對于自定義的view這里面有一個方法- (void)updateConstraints 可以重寫
對于我,目前沒有用到這個方法 因為我的布局代碼要么寫到[[view alloc] init]后面, 要么把關(guān)鍵布局代碼寫到 layoutSubviews
這是在子控件(ExerciseStemView)的代碼
- (void)updateConstraints
{
[super updateConstraints];
}
- (void)layoutSubviews
{
[super layoutSubviews];
// 布局關(guān)鍵代碼
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, CGRectGetMaxY(self.stemView.frame)+20);
// 這里不能像上面那樣調(diào)用 [self.superview setNeedsLayout]; 會發(fā)生死循環(huán)
// 可能原因是 這個控件是 frame 布局, 而他的父控件是 AutoLayout 布局的 所以要調(diào)用 [self setNeedsUpdateConstraints];
[self setNeedsUpdateConstraints];
}
在這個ExerciseStemView 的父控件ExerciseCell上, 用的是AutoLayout
這是在父控件(ExerciseCell)的代碼
- (void)updateConstraints
{
[super updateConstraints];
}
- (void)layoutSubviews
{
[super layoutSubviews];
// 布局關(guān)鍵代碼
self.stemViewHeight.constant = self.stemViewModel.cellHeight;
}
除了oneView 在ExerciseCellXib上, 其他的都是自定的控件
我的層級結(jié)構(gòu)是
ExerciseCell ---xib包含----->oneView---addSubview----->ExerciseStemView ---addSubview----->stemView---addSubview----->stemLabel
這樣做的原因是 是因為stemLabel 一直會改變大小, 所以這樣一層一層傳出去.
這里面既有 xib AutoLayout布局 也有 代碼frame布局 所以坑很多. 記錄一下.
至于什么是布局的關(guān)鍵代碼, 就是能影響其他其他控件的布局的代碼.
例如 子控件改變,父控件要改變,然后父控件的左右控件要改變 等等
PS
這里還有一個方法, 讓這個自定義的控件 強(qiáng)制調(diào)用它所在的控制器 的 viewDidLayoutSubviews 來重新布局控件
有的時候會用到
自定義的控件的 內(nèi)部代碼如下
- (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);
// 強(qiáng)制viewController 調(diào)用 viewDidLayoutSubviews
[[self viewController] viewDidLayoutSubviews];
}
/**
* 返回當(dāng)前視圖的控制器
*/
- (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,更新 繼續(xù)填坑
由于我以前的ExerciseCell 這個cell 的尺寸是整個屏幕,固定大小, 里面有個Scollview, 所以, 里面控件改變,不用改變最外層的 ExerciseCell的尺寸, 只要改變 Scollview的contentsize 就行了其實如果約束加好, Scollview的contentsize不用代碼改變,只要更新下就行了
但是, 當(dāng)我把這個ExerciseStemView加到一個tableview的cell 的時候,就出現(xiàn)了問題.
問題 1
table加載的時候, cell 高度不正確
由于cell的高度是動態(tài)的,所以要根據(jù)ExerciseStemView 的高度而決定 當(dāng)然 cell里面還有其他控件
我開始是在tableviewcell的xib上添加了一個oneView 一個twoView
設(shè)置約束,
oneView距離tableviecell的 contenview 上 左 右 各20, 高度為100 并拖拽到tableviewcell 的.m文件中 為 oneViewHeight
twoView距離tableviecell的 contenview 下 左 右 各20, 高度為50 并拖拽到tableviewcell 的.m文件中為 twoViewHeight
oneView底部距離twoView 頂部 20
很簡單的布局
然后在oneView添加 ExerciseStemView,并在layoutsubview中設(shè)置oneViewHeight = ExerciseStemView的高度(其實是一個模型的cellheight)
但是運行的時候約束會報錯, 不過不會崩潰
但是cell的高度其實是不正常的. 出現(xiàn)了ExerciseStemView比cell還高.
對于這個問題,我用了上面1 和2的所有方法, 都無效.
后來,我想到增加一個中間層. 為 tempView, 成為 cell的 contenview的子控件 成為 oneview 和twoview的父控件. 然后設(shè)置 oneview 和twoview的約束和以前一樣
對于tempView的約束, 只增加3個 下 左 右 距離父控件為0, 下面不設(shè)置, 這樣不會報錯
然后 在layoutsubview的時候, 起始我是想把tempView的高度直接賦值給 cell的 高度,后來發(fā)現(xiàn)不行, 因為tempView的高度雖然在界面顯示后是正常的, 但是在layout中并不正確, 而是 在你的xib的起始的大小. 所以
我就把所有的子控件都加到了一起
像這樣
self.cellViewModel.cellHeight = self.oneViewHeight.constant + self.twoViewHeight.constant + 1 + 60;
其實這個當(dāng)初加的中間層也就沒有意義了,完全沒用到
這樣以后 , 在控制器的 tableview的heightForRowAtIndexPath代理方法中 ,根據(jù)indexPath獲取對應(yīng)模型的 .cellHeight即可
這樣 tableview加載以后,高度就動態(tài)的高度了.
問題 2
當(dāng)點擊ExerciseStemView的按鈕的時候,需要增加填充一段數(shù)據(jù),這個時候 ExerciseStemView的高度改變了,但是 cell的高度并沒有改變,
layoutSubview的方法也只是調(diào)用到 ExerciseStemView就截止了,不會調(diào)用父類tableview的cell的layoutSubview的方法.
我一直想去調(diào)用,但是無果,況且調(diào)用也沒有用, 因為整個tableview 還要刷新,不然影響其他cell的顯示.
我的錯誤嘗試
1, 在ExerciseStemView調(diào)用 layoutSubview的方法的時候 發(fā)通知通知控制器, 讓[self.tableView reloadData];
發(fā)現(xiàn) 會發(fā)通知很多次, 并且界面顯示卡頓,空白,飛走,等各種奇怪現(xiàn)象.
2, 我試圖接收到通知只執(zhí)行一次, 但是依然各種問題
3, 我在ExerciseStemView點擊方法中 發(fā)通知通知控制器, 讓[self.tableView reloadData]; 但是這個時候子控件還沒有刷新完成, 無法獲取 ExerciseStemView的真正高度,失敗
4, 后來我想, 不如直接更新模型,然后再[self.tableView reloadData]; 這樣就會顯示對了
當(dāng)然前提是需要把點擊事件后,增加的填充一段數(shù)據(jù)傳出去, 這就用到了通知, 直接同第三步一樣發(fā)通知, 不同的是包裝一個字典把數(shù)據(jù)傳到控制器,當(dāng)然也應(yīng)該保護(hù)cell的indexpath, 然后拼接到控制器的 cell的模型數(shù)組中的對應(yīng)的元素上
然后調(diào)用[self.tableView reloadData];
這個時候就能顯示正常了.
indexpath 我是變成了模型的一個屬性層層傳進(jìn)去, 然后發(fā)通知出來.