iOS自動布局3-UIScrollView

iOS的視圖中结执,UIScrollView是比較常用的視圖骨坑。但是UIScrollView在自動布局中是一種特殊的視圖锻全。

不使用自動布局

假設(shè)需要實(shí)現(xiàn)一個(gè)簡單的需求:在一個(gè)UIScrollView中添加UILabel眯杏,標(biāo)簽中放置一個(gè)很長的字符串开瞭,要求可以根據(jù)字符串的長度滾動懒震。先來看一下不使用自動布局來繪制UIScrollView:

- (void)setupWithFrame{
    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width,self.view.bounds.size.height-100)];
    scrollView.backgroundColor = [UIColor yellowColor];
    scrollView.contentSize = CGSizeMake(self.view.bounds.size.width,700);
    [self.view addSubview:scrollView];
    
    UILabel *dataLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 700)];
    dataLabel.numberOfLines= 0;
    dataLabel.text = _longText;
    [scrollView addSubview:dataLabel];
}

實(shí)現(xiàn)的結(jié)果是這樣的:

scroll_frame_only

這樣實(shí)現(xiàn)的問題就在于,不知道UILabel占用的長度的時(shí)候嗤详,無法正確給出UIScrollView的contentSize个扰。如果contentSize中的長度設(shè)置得太短,就會跟上圖一樣顯示不完全(剩余的部分被截取為…)葱色。如果設(shè)置得太長就會有多余的空白區(qū)域递宅。

使用自動布局

那么如果希望使用自動布局,利用好UILabel的intrinsic content size苍狰。那應(yīng)該怎么寫呢办龄?

第一版的代碼如下:

- (void)setupWithAutoLayout1{
    UIScrollView *scrollView = [[UIScrollView alloc] init];
    scrollView.backgroundColor = [UIColor yellowColor];
    scrollView.delegate = self;
    [self.view addSubview:scrollView];
    [scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    
    UILabel *dataLabel = [[UILabel alloc] init];
    dataLabel.numberOfLines= 0;
    dataLabel.text = _longText;
    [scrollView addSubview:dataLabel];
    [dataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(scrollView).offset(16);
        make.top.equalTo(scrollView).offset(16+64);
        make.right.lessThanOrEqualTo(scrollView).offset(-16);
        make.bottom.equalTo(scrollView).offset(-16);
    }];
}

運(yùn)行代碼,得到的結(jié)果如圖:

scroll_wrong

淋昭?俐填??

什么情況呢翔忽?沒有任何內(nèi)容顯示在上面英融?查看Xcode的內(nèi)容調(diào)試面板盏檐。看到scrollView的約束中有個(gè)紫色的感嘆號圖標(biāo)驶悟,點(diǎn)擊以后顯示"Scrollable content size is ambiguous for UIScrollView"胡野,UIScrollView的contentSize是不確定的。

根據(jù)這個(gè)提示撩银,我嘗試給scrollView確定的scrollView给涕,于是第二個(gè)版本的代碼變成:

    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width,self.view.bounds.size.height-100)];
    scrollView.backgroundColor = [UIColor yellowColor];
    scrollView.contentSize = CGSizeMake(self.view.bounds.size.width,700);
    [self.view addSubview:scrollView];
    
    UILabel *dataLabel = [[UILabel alloc] init];
    dataLabel.numberOfLines= 0;
    dataLabel.text = _longText;
    [scrollView addSubview:dataLabel];
    [dataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(scrollView).offset(16);
        make.top.equalTo(scrollView).offset(16+64);
        make.right.lessThanOrEqualTo(scrollView).offset(-16);
        make.bottom.equalTo(scrollView).offset(-16);
    }];

用frame的方式初始化scrollView并指定contentSize,仍然用自動布局的方式繪制dataLabel额获。

運(yùn)行代碼够庙,發(fā)現(xiàn)結(jié)果是一樣的。dataLabel中的文字并沒有顯示出來抄邀,也仍然有"Scrollable content size is ambiguous for UIScrollView"的警告耘眨。

這是為什么呢?我們還是從第一個(gè)版本的代碼開始分析境肾,為什會系統(tǒng)會認(rèn)為scrollView的contentSize是不確定的呢剔难?

首先我們給scrollView添加了一個(gè)edges的約束。相當(dāng)于scrollView的left/right/top/bottom都和父視圖相等奥喻。這里相當(dāng)于確定了scrollView的frame了偶宫。

然后在scrollView中添加了一個(gè)dataLabel。

為dataLabel也設(shè)置了left/right/top/bottom四個(gè)約束环鲤,對于一般的視圖而言纯趋,有這四個(gè)試圖就意味著視圖位置的唯一性了。但是UIScrollView是一種特殊的視圖冷离,它除了具有普通視圖的frame屬性以外吵冒,還具有內(nèi)容區(qū)域。frame是UIScrollView中的可見部分西剥,而UIScrollView中的其它部分都包含在其內(nèi)容區(qū)域中痹栖。想象一下UITableView這種特殊的UIScrollView,第一行cell其實(shí)如果我們要設(shè)置布局約束的話瞭空,大概會像是這樣:

cell1.top = tableView.contentView.top;

注意UIScrollView并沒有contentView這個(gè)屬性揪阿,這里只是我們想象出來的視圖。

那么當(dāng)我們在UIScrollView中的子視圖中添加約束的時(shí)候咆畏,我們添加的約束是針對UIScrollView本身的可見區(qū)域呢图甜,還是其內(nèi)容區(qū)域呢?

以下是官方的解釋(https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html#//apple_ref/doc/uid/TP40010853-CH24-SW1):

  • Any constraints between the scroll view and objects outside the scroll view attach to the scroll view’s frame, just as with any other view.
  • For constraints between the scroll view and its content, the behavior varies depending on the attributes being constrained:
    • Constraints between the edges or margins of the scroll view and its content attach to the scroll view’s content area.
    • Constraints between the height, width, or centers attach to the scroll view’s frame.
  • You can also use constraints between the scroll view’s content and objects outside the scroll view to provide a fixed position for the scroll view’s content, making that content appear to float over the scroll view.

大概意思是:

對于UIScrollView和其它非子視圖的約束鳖眼,采用的方式和其它的視圖類似黑毅,也就是采用其可見區(qū)域的left,right钦讳,top矿瘦,bottom等枕面;

對于UIScrollView和它的子視圖的約束而言(上面的例子就是),left缚去,right潮秘,top,bottom采用的是UIScrollView的內(nèi)容區(qū)域易结,而width和height則任然是其可見區(qū)域的width和height枕荞。

你可以為UIScrollView中的子視圖和UIScrollView外的視圖添加一個(gè)固定位置的約束,這樣可以達(dá)到讓該子視圖浮動在UIScrollView上面的效果搞动。(想象一下UITableview的section header躏精,當(dāng)tableview 在滾動的時(shí)候,section header是固定在可見區(qū)域的頂部的)鹦肿。

所以說為什么系統(tǒng)會警告說UIScrollView沒有準(zhǔn)確的content size呢矗烛?因?yàn)槲覀冊赿ataLabel中添加的約束都是針對scrollView的內(nèi)容區(qū)域而言的÷崂#看以下這個(gè)約束:

make.right.lessThanOrEqualTo(scrollView).offset(-16);

這個(gè)時(shí)候scrollView的內(nèi)容區(qū)域的右邊界還不知道呢瞭吃,它可以是100,1000涣旨,10000歪架,或者是0,所以對于一個(gè)不定寬度的UILabel而言霹陡,自然沒辦法計(jì)算出其應(yīng)該占用的大小是多少和蚪。

那么應(yīng)該如何修正,才能達(dá)到需求想要的效果呢穆律?

第三版的代碼如下:

- (void)setupWithAutoLayout2{
    UIScrollView *scrollView = [[UIScrollView alloc] init];
    scrollView.backgroundColor = [UIColor yellowColor];
    scrollView.delegate = self;
    [self.view addSubview:scrollView];
    [scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    
    UILabel *dataLabel = [[UILabel alloc] init];
    dataLabel.numberOfLines= 0;
    dataLabel.text = _longText;
    [scrollView addSubview:dataLabel];
    [dataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(scrollView).offset(16);
        make.top.equalTo(scrollView).offset(16+64);
        make.right.lessThanOrEqualTo(self.view).offset(-16);
        make.right.equalTo(scrollView).offset(16);
        make.bottom.equalTo(scrollView).offset(-16);
    }];
}

很簡單,只要加多一個(gè)約束导俘,讓dataLabel的right屬性跟self.view的right屬性建立約束關(guān)系峦耘。這個(gè)時(shí)候由于dataLabel的right屬性同時(shí)還跟scrollView的內(nèi)容區(qū)域的right屬性有約束關(guān)系,就可以間接地計(jì)算出scrollView的內(nèi)容區(qū)域的right屬性旅薄。而有了確定的右邊界辅髓,dataLabel就可以根據(jù)font跟text計(jì)算出其字符串所占用的高度。因此dataLabel的bottom屬性得以確定少梁,又dataLabel的bottom屬性和scrollView的內(nèi)容區(qū)域的bottom屬性有約束關(guān)系洛口,因此確定了scrollView的內(nèi)容區(qū)域中的right屬性。對于UIScrollView而言凯沪,默認(rèn)的contentOffset是(0第焰,0)。也就是默認(rèn)的(靜止的時(shí)候)UIScrollView的內(nèi)容區(qū)域的top和left都跟其可見區(qū)域是一致的妨马。所以此時(shí)可以確定scrollView的top跟left挺举。即整個(gè)scrollView的content size已經(jīng)確定好了杀赢。就不會再出現(xiàn)警告。

運(yùn)行結(jié)果如圖:

scroll_right

那么第二版本中手動設(shè)置contentSize為什么不行呢湘纵?猜測是由于dataLabel設(shè)置約束的時(shí)候會改變scrollView的contentSize屬性脂崔。將第二版的代碼修改為:

- (void)setupWithFrameAndAutoLayout2{
    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width,self.view.bounds.size.height-100)];
    scrollView.backgroundColor = [UIColor yellowColor];
    scrollView.contentSize = CGSizeMake(self.view.bounds.size.width,1);
    [self.view addSubview:scrollView];
    
    UILabel *dataLabel = [[UILabel alloc] init];
    dataLabel.numberOfLines= 0;
    dataLabel.text = _longText;
    [scrollView addSubview:dataLabel];
    [dataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(scrollView).offset(16);
        make.top.equalTo(scrollView).offset(16+64);
        make.right.lessThanOrEqualTo(self.view).offset(-16);
        make.right.equalTo(scrollView).offset(16);
        make.bottom.equalTo(scrollView).offset(-16);
    }];
}

此時(shí)將content size的高度設(shè)置為1,但是其運(yùn)行結(jié)果是正確的(和第三版的代碼運(yùn)行結(jié)果一樣)梧喷。這個(gè)結(jié)果和我們的猜測一致砌左。

需要注意的是,現(xiàn)在我們的界面是非常簡單的铺敌,UIScrollView中只有一個(gè)UILabel汇歹,當(dāng)UIScrollView中的子視圖變得越來越多的時(shí)候,需要注意的地方就更多适刀。假如有視圖A,B,C,D...等是UIScrollView的直接子視圖秤朗,當(dāng)它們需要添加與UIScrollView的約束時(shí),都要考慮到約束是和UIScrollView的內(nèi)容區(qū)域關(guān)聯(lián)而不是其可見區(qū)域笔喉。要考慮什么時(shí)候要借助UIScrollView外部的視圖的屬性建立約束取视。

UIScrollView的自動布局技巧

那有沒有什么方法可以盡量讓布局的代碼顯得更加清晰,減少出錯(cuò)的幾率呢常挚?

注意上面一直都提到“UIScrollView的內(nèi)容區(qū)域”作谭,子視圖中的約束是和內(nèi)容區(qū)域相關(guān)聯(lián)的,但是實(shí)際上這個(gè)區(qū)域不在UIScrollView的屬性內(nèi)奄毡,那么是不是可以人為地給UIScrollView添加一個(gè)“內(nèi)容區(qū)域”的視圖呢折欠?所有本來直接在UIScrollView下面的視圖,都變成在額外添加的“內(nèi)容區(qū)域”視圖中吼过,那么所有的約束都是和該視圖關(guān)聯(lián)锐秦,就不用再去考慮UIScrollView的特殊的地方了。這樣做的話盗忱,只需要保證在添加“內(nèi)容區(qū)域”視圖的時(shí)候考慮一次與UIScrollView的約束關(guān)系就可以了酱床。

修改后的代碼如下:

- (void)setupViewsWithContentView{
    UIScrollView *scrollView = [[UIScrollView alloc] init];
    scrollView.backgroundColor = [UIColor yellowColor];
    scrollView.delegate = self;
    [self.view addSubview:scrollView];
    [scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    
    UIView *contentView = [[UIView alloc] init];
    
    [scrollView addSubview:contentView];
    [contentView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(scrollView);
        make.width.equalTo(scrollView);
    }];
    contentView.backgroundColor = [UIColor lightGrayColor];
    
    UILabel *dataLabel = [[UILabel alloc] init];
    [contentView addSubview:dataLabel];
    [dataLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(contentView).offset(16);
        make.left.equalTo(contentView).offset(16);
        make.right.lessThanOrEqualTo(contentView).offset(-16);
        make.bottom.equalTo(contentView.mas_bottom).offset(-16);
    }];
    dataLabel.numberOfLines = 0;
    dataLabel.text = _longText;
}

可以看到此時(shí)我們在添加dataLabel的時(shí)候就很方便了。只需要添加其與contentView的約束就可以了趟佃。

這里的關(guān)鍵點(diǎn)在于contentView與scrollView的約束的建立扇谣。首先讓contentView的邊界和scrollView的內(nèi)容區(qū)域的邊界相等。注意此時(shí)contentView的大小是不確定的闲昭,可以伸縮罐寨。那么根據(jù)我們的需求,我們只需要固定的寬度序矩,然后讓高度是可變的拙泽。那么我們添加寬度一個(gè)寬度的約束(根據(jù)官方文檔能耻,UIScrollView的width和height屬性是其可見區(qū)域的屬性)辈末。這樣contentView就只有高度是不確定的,那么我們通過dataLabel和scrollView的約束中計(jì)算出contentView的高度即可幔烛。

下面貼上官方文檔對于設(shè)置UIScrollView自動布局約束的技巧:

  1. Add the scroll view to the scene.

  2. Draw constraints to define the scroll view’s size and position, as normal.

  3. Add a view to the scroll view. Set the view’s Xcode specific label to Content View.

  4. Pin the content view’s top, bottom, leading, and trailing edges to the scroll view’s corresponding edges. The content view now defines the scroll view’s content area.

    REMEMBER The content view does not have a fixed size at this point. It can stretch and grow to fit any views and controls you place inside it.

  5. (Optional) To disable horizontal scrolling, set the content view’s width equal to the scroll view’s width. The content view now fills the scroll view horizontally.

  6. (Optional) To disable vertical scrolling, set the content view’s height equal to the scroll view’s height. The content view now fills the scroll view horizontally.

  7. Lay out the scroll view’s content inside the content view. Use constraints to position the content inside the content view as normal.

    IMPORTANT: Your layout must fully define the size of the content view (except where defined in steps 5 and 6). To set the height based on the intrinsic size of your content, you must have an unbroken chain of constraints and views stretching from the content view’s top edge to its bottom edge. Similarly, to set the width, you must have an unbroken chain of constraints and views from the content view’s leading edge to its trailing edge.If your content does not have an intrinsic content size, you must add the appropriate size constraints, either to the content view or to the content.When the content view is taller than the scroll view, the scroll view enables vertical scrolling. When the content view is wider than the scroll view, the scroll view enables horizontal scrolling. Otherwise, scrolling is disabled by default.

大意為:

  1. 添加一個(gè)scroll view

  2. 像普通視圖一樣為scroll view添加位置和大小的約束

  3. 在scroll view中添加一個(gè)子視圖(content view),給該視圖添加一個(gè)指定的標(biāo)簽(這個(gè)標(biāo)簽只是為了更好地顯示)

  4. 將content view的left囊蓝,right饿悬,top,bottom和scroll view的邊界建立相等約束聚霜。那么現(xiàn)在content view的邊界就確定了scroll view的內(nèi)容區(qū)域

    (注意此時(shí)content view還沒有固定的大小狡恬,它可以根據(jù)你在其中設(shè)置的視圖的伸縮大小)

  5. (可選)如果不需要水平滑動蝎宇,將content view的寬度設(shè)置為和scoll view的寬度相等弟劲。

  6. (可選)如果不需要垂直滑動,將content view的高度設(shè)置為和scroll view的高度相等姥芥。

  7. 在content view中添加子視圖兔乞,為子視圖和content view添加約束。

重要: 你的布局必須能夠決定content view的大辛固啤(除非在5和6中已經(jīng)設(shè)置過了)庸追。如果要基于你的內(nèi)容的固有尺寸來決定高度,那么在content view的top跟bottom之間必須有一條不間斷的約束鏈台囱。類似地淡溯,對于寬度,必須要在left和right間有不間斷的約束鏈簿训。如果你在content view中添加的內(nèi)容(子視圖)不具有固有尺寸咱娶,那么你要顯式地為content view或者其內(nèi)容確定好合適的尺寸。當(dāng)content view的高度大于scroll view的高度强品,那么scroll view支持垂直方向的滑動膘侮。當(dāng)content view的寬度大于scroll view的寬度,那么scoll view支持水平方向上的滑動的榛。否則琼了,默認(rèn)滑動是被禁止的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末困曙,一起剝皮案震驚了整個(gè)濱河市表伦,隨后出現(xiàn)的幾起案子谦去,更是在濱河造成了極大的恐慌慷丽,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳄哭,死亡現(xiàn)場離奇詭異要糊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)妆丘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門锄俄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來局劲,“玉大人,你說我怎么就攤上這事奶赠∮闾睿” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵毅戈,是天一觀的道長苹丸。 經(jīng)常有香客問我,道長苇经,這世上最難降的妖魔是什么赘理? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮扇单,結(jié)果婚禮上商模,老公的妹妹穿的比我還像新娘。我一直安慰自己蜘澜,他們只是感情好施流,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兼都,像睡著了一般嫂沉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扮碧,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天趟章,我揣著相機(jī)與錄音,去河邊找鬼慎王。 笑死蚓土,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赖淤。 我是一名探鬼主播蜀漆,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咱旱!你這毒婦竟也來了确丢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吐限,失蹤者是張志新(化名)和其女友劉穎鲜侥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诸典,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡描函,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舀寓。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胆数,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出互墓,到底是詐尸還是另有隱情必尼,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布篡撵,位于F島的核電站胰伍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏酸休。R本人自食惡果不足惜骂租,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斑司。 院中可真熱鬧渗饮,春花似錦、人聲如沸宿刮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僵缺。三九已至胡桃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間磕潮,已是汗流浹背翠胰。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留自脯,地道東北人之景。 一個(gè)月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像膏潮,于是被迫代替她去往敵國和親锻狗。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容