如何對使用了autolayout的UIView添加動畫

來源:互聯網
上載者:User

標籤:style   blog   http   color   io   os   使用   ar   for   

      當我們對一個UIView使用了autolayout自動布局之後,也就意味著我們放棄了傳統的

通過設定view的frame等方式手動的修改、確定這個view的位置、尺寸屬性。甚至從某種

程度上講,我們應該忘記view的frame屬性:它的確定不再取決於我(手動的直接修改),

而是通過我們在storyboard或者code中提供的約束條件(constraints),通過一個自動布

局引擎(蘋果為autolayout採用的是Cassowary布局引擎,參考文檔:點擊開啟連結),計

算出這個view的frame。因此我們可以認為使用了autolayout的view的frame屬性是一個唯讀

的屬性。在代碼裡認為的改動這個view的frame並不能對這個view的frame產生真正的效果(

事實也確實如此)。

    現在問題就來了,在以前我們經常通過對一個view的frame的修改產生view移動的動畫效

果,那麼在使用了autolayout的view世界中我們該如何?相同的效果呢?答案是,我們“將

計就計”,通過改變這個view上的某個約束constraint然後在uiview的animation block中觸發

layout來實現。

一、預期效果

        下面我們以一個簡單的例子來進行詳細的說明:

        如所示,整個介面都使用了autolayout,現在我們想實現這樣一個效果:當

我們點擊顯示生日的按鈕的時候,整個view向上滑動,同時向上推出一個日期選擇器

(date picker),類似於點擊textfield,彈出鍵盤後整個介面為了避免被遮住而向上移

動的效果。選取完成日期後點擊生日日期按鈕或者完成按鈕整個view向下縮回,同時

date picker向下滑出可視範圍。

二、實現細節

        首先來看一眼storyboard中view的層級結構:如所示,我們可以看到,

整個view的布局相當簡單,就兩級:根view和我們的date picker view,其中date 

picker view包含了一個完成按鈕和系統的date picker。這樣的話,要實現整個view和

date picker view同時上移的效果,我們只需要對根view和date picker view同時做動畫

即可。


        考慮如何?根view的動畫效果,這裡我們可以巧妙的通過修改根view的bounds屬性

來實現根view的上移效果。注意這裡我們需要明白view的bounds屬性和frame屬性的區別,

前者是相對於當前view的本地座標系而言的,而後者則是相對於當前view的父view的座標

系而言的。
        簡單的講,frame決定了一個view相對於父view的position和size資訊。而bounds則決

定了當前view展示的內容相對於本地座標系的位置。這裡我們將view自身的可視內容和

subviews可以看做一頁紙上的內容資訊,而view本身可以看成是一枚放於紙上的放大鏡,

放大鏡的大小不一定是和紙(content size)相同大小的。bounds屬性的作用就是確定這枚

放大鏡相對於紙的位置:一個bounds =(0, 200, 300, 300)就意味著我們要將這枚放大鏡向

紙的下方移動200個points,但放大鏡相對於父view的位置仍是保持不變的,這樣給我們的效

果就是這個view(顯示的內容)向上移動了200個points.


        改動bounds的origin屬性並不會改動這個view的frame,通過這種展示內容的移動給

我們產生一種view向上移動了的幻覺。如中,“哪個位置...”為成為我們放大鏡中看到

的第一行。
        根view上移動畫的效果解決了,下面我們再來看日期選擇器date picker,在

storyboard中對其增加的約束如下:定高207、trailing/leading/top相對於super 

view (根view)的位置。


        確定date picker view y軸方向上下移動的約束顯然是top約束,點開top約束,

可以看到該約束的詳細內容:

        一個約束可以描述為:firstItem.attributeA = secondItem.attributeB * multipler + 

constant。

結合我們可以得出date picker view的top約束為

datePickerView.Top = topLayoutGuide.bottom * 1 + 400

我們可以通過修改這裡的constant值來修改這個top約束以達到預期效果,事實上通過

修改而不是刪除舊的constraint再添加新的constraint也正是蘋果所推薦的,在

NSLayoutConstraint.h標頭檔中有如下說明:


        這樣,date picker view的上下移動就可以通過擷取並修改其top約束來實現。需要注意

的是在代碼中擷取datepicker view的top約束實際上是要在其父view的constraints數組中尋找,

這是因為每個view的constraints數組中儲存的實際上是layout 子view所需的約束的集合。

我們還要定義個輔助BOOL變數,已判斷date picker view是否以彈出:

<span style="font-size:18px;">@property (nonatomic, assign) BOOL hasShowPickerView;</span>

       接下來定義一個輔助函數,用於尋找date picker view的top約束並修改其constant屬性為

給定的值:

- (void)replacePickerContainerViewTopConstraintWithConstant:(CGFloat)constant{    for (NSLayoutConstraint *constraint in self.pickerContainerView.superview.constraints) {        if (constraint.firstItem == self.pickerContainerView && constraint.firstAttribute == NSLayoutAttributeTop) {            constraint.constant = constant;        }    }}


        代碼裡我們在picker container view (即文中的date picker view)的superview的

constraints屬性中尋找,如果發現firstItem和firstAttribute屬性分別是date picker view和

top,則該constraint即為目標約束,然後修改其constant屬性。
在view首次被載入的時候我們想確保date picker view 處於整個view的最底部即隱藏的狀

態,因而我們在viewcontroller的viewDidLoad方法中調用輔助方法修改一下date picker 

view的top約束:

<span style="font-size:18px;">[self replacePickerContainerViewTopConstraintWithConstant:self.view.frame.size.height];</span>

        在首次點擊birthday button的時候動畫修改根view的bounds和date picker view的top 

constraint,注意上移gap的計算。再次點擊birthday button的時候將根view的bounds恢複

到正常值,date picker view的top constraint也恢複到viewDidLoad中設定的值:

</pre><p class="p1"></p><pre name="code" class="objc"><span style="font-size:18px;">- (IBAction)didTapOnBirthdayButton:(id)sender{    self.hasShowPickerView = !self.hasShowPickerView;    if (self.hasShowPickerView) {        CGRect birthdayButtonFrame = self.birthdayButton.frame;        birthdayButtonFrame = [self.view convertRect:birthdayButtonFrame fromView:self.birthdayButton.superview];        CGFloat birthdayButtonYOffset = birthdayButtonFrame.origin.y + birthdayButtonFrame.size.height;        CGFloat gap = birthdayButtonYOffset - (self.view.frame.size.height - self.pickerContainerView.frame.size.height);        CGRect bounds = self.view.bounds;        if (gap > 0) {            bounds.origin.y = gap;        } else {            gap = 0;        }        [self replacePickerContainerViewTopConstraintWithConstant:birthdayButtonYOffset];        [UIView animateWithDuration:0.25 animations:^{            self.view.bounds = bounds;            [self.view layoutIfNeeded];        }];    } else {        [self replacePickerContainerViewTopConstraintWithConstant:self.view.frame.size.height];        CGRect bounds = self.view.bounds;        bounds.origin.y = 0;        [UIView animateWithDuration:0.25 animations:^{            self.view.bounds = bounds;            [self.view layoutIfNeeded];        }];    }}</span>


        上述代碼中的[self.view layoutIfNeed]去掉也是沒問題的。可能比較費解的是根view.

bounds.origin.y的上移gap的計算以及top constraint的constant值的計算,關鍵實在真正理

解view的frame和bounds的意義。

        至此程式達到了預期的效果。

三、小結

        在使用autolayout之前我們寫程式控制介面的構成就好比是開一輛手動擋的汽車,雖然

頻繁換擋(修改frame)很繁瑣,卻也很享受那種可以完全控制汽車檔位的自由感。使用了

autolayout之後則一下子升級為了自動擋汽車,切換檔位的活不再由我們直接操作,而只能

通過油門(constraints)的大小來間接的改變汽車的檔位。在自動擋汽車裡,我們必須要放

棄直接控制檔位的想法,那是不可能的了,我們必須要學會通過熟練掌握腳下的油門和刹車

來控制車速!在習慣了自動擋之後,相信大家也一樣能夠得心應手的做自己想做的事情。


如何對使用了autolayout的UIView添加動畫

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.