iOS 子控件布局 決定父控件大小 AutoLayout && Frame 的坑

一般都是父控件布局子控件, 但是如何讓子控件布局父控件呢?
目的是 子控件大小改變, 父控件也改變!

很多情況下, 都是子控件改變了, 但是父控件的 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;

}

除了oneViewExerciseCellXib上, 其他的都是自定的控件
我的層級結(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的子控件 成為 oneviewtwoview的父控件. 然后設(shè)置 oneviewtwoview的約束和以前一樣
對于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的celllayoutSubview的方法.

我一直想去調(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ā)通知出來.


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容