原文:Animating Autolayout Constraints
作者:@kharrison
譯者:CocoaChina--起個名字好難(CC論壇ID)
記於二零一五年晚春:距上次發文已有五天,看了眾同僚的反饋,我把這段代碼重新搞了搞,以此來避免在運行時添加或刪除約束。取代這些像JAVA一樣笨重的方法的是我開始動態的改變黃色和藍色視圖約束的優先級。超級簡單,超級高效。
在沒有autoLayout之前,如果你想移動一個view,frame就會派出他兩個小弟origin或者size來對你大喊@"你動我下試試!"。當然,frame、bounds和center都是UIView的屬性,所以當你真的動了他們,UIView是不會作罷甘休的。所以你知道為啥用戶能看到一系列的動畫了吧,UIView想削你呢。
如果你開始用autoLayout了,很快你就發現你不用親手去處理frame(或者bounds或者center)了。你可以叫約束去搞他。這篇文章就是通過一個簡單的栗子,來告訴你怎麼用約束搞點事情出來。額,確切的說是搞出一個簡單的動畫效果。
挑戰
為了簡單明了,我們只用兩個視圖。一個黃色視圖(以下簡稱黃圖)和一個藍色視圖(以下簡稱藍圖)。在"普通"模式下,我們只看到黃圖。在"五彩缤紛"模式下,藍色和黃色視圖都能看到。
視圖…
"喂!為什麼都只看到黃圖啊!"
"明明只有兩種顏色叫五彩缤紛什麼的真的好嗎!"
"你倒是留個種子啊!"
啊,不好意思,光注意黃圖了。在"五彩缤紛"模式下,我們看到黃圖和藍圖兩個。這倆視圖應該填滿整個屏幕,除了設備的邊邊以及switch占據的框框。下面這個gif就是我們想去實現的效果。
藍圖應該在右邊滑出,然後黃圖相應地充斥整個屏幕…
設置基本約束
一開始,我在IB上拖出視圖,拉上約束。這個時候倆視圖都是可見的。
黃圖有五個約束:左邊相對父視圖間隔,右邊相對藍圖間隔,上邊相對switch間隔,下邊相對父視圖間隔,以及和藍圖寬度相等約束。
藍圖和黃圖的約束差不多,除了藍圖是右邊相對父視圖間隔。
非必需約束優先級
在只有黃圖可見的時候(真是不錯),我們需要加另一個約束,也就是它右側相對父視圖的間隔約束。如果在上面我加上這個約束,那麼他就和那個"右側相對藍圖約束"沖突了,因為他倆同時有優先級1000。為了避免沖突以及移動藍圖,我們可以改變一下黃藍圖間隔的那個約束的優先級。
必需約束優先級是這個UILayoutPriorityRequired(1000),你不能在運行時改變一個必需約束的優先級。優先級比UILayoutPriorityRequired小的,就是一個可選或者非必需的約束,類似這種,只要你別把優先級設置為UILayoutPriorityRequired,你就可以改。
所以首先,我們把藍圖右側相對父視圖約束的優先級搞低一點,搞到750.
然後我們在給黃圖加一個它右側相對父視圖的約束(就像上面提到的),優先級也搞到750.
把約束拖出來!
為了在運行時改變藍圖右側約束我們得先把這個約束拖到代碼中。你也可以像這樣拖任意的約束出來。(就像把控件關聯到代碼中一樣,選中約束,按Ctrl拖)
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *blueViewConstraint;
為了確保我們把藍圖推出屏幕,我們也得調整黃圖和藍圖中間的間隔約束,所以我們把這個約束也整到代碼中。
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *viewSpacingContraint;
更新約束
現在可以很容易的寫一個方法來根據模式開關設置藍圖約束想要的優先級。
- (void)updateConstraintsForMode { if (self.modeSwitch.isOn) { self.viewSpacingContraint.constant = 8.0; self.blueViewConstraint.priority = UILayoutPriorityDefaultHigh+1; } else { self.viewSpacingContraint.constant = self.view.frame.size.width; self.blueViewConstraint.priority = UILayoutPriorityDefaultHigh-1; } }
我們在storyboard中把黃圖右側相對父視圖的約束也設定了優先級UILayoutPriorityDefaultHigh(750)。為了使藍圖可見,我們需要把藍圖的右側約束優先級設定的比750高一些,而隱藏起藍圖時我們得把它設定的低一些。
請注意!看黑板!我們要給黃藍圖的間隔設定一個大點的值(我這裡用的屏幕寬度)以確保藍圖推出右側邊界。
我們在視圖第一次加載時也應該配置下約束。厚此薄彼可不好。
- (void)viewDidLoad { // ... [self updateConstraintsForMode]; }
動起來!
現在萬事俱備只欠東風了,我們現在只需要輕輕的撥動一下模式開關,咦?輕輕的,咦?啊不好意思,忘記把開關的事件代碼寫上了- -,蘋果的Auto Layout Guide描述了autoLayout搞動畫的基本方法,推薦的代碼如下:
[containerView layoutIfNeeded]; [UIView animateWithDuration:1.0 animations:^{ // Make all constraint changes here [containerView layoutIfNeeded]; }];
這兩個對layoutIfNeeded的調用強迫將要執行的操作完成,然後在動畫塊裡捕獲frame的改變。
(譯者補充:想起知乎裡一個回答 你在公司項目裡面看到過哪些操蛋的代碼?)
if (m_doc->isModified() == true) { for (int i = 0; i < 100; i++) { save(); //Save the document for 100 times to ensure it has been saved successfully. } }
在我們的栗子中用上面的方法,就是這樣式的:
- (IBAction)enableMode:(UISwitch *)sender { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setBool:sender.isOn forKey:modeUserDefaultKey]; [defaults synchronize]; [self.view layoutIfNeeded]; [UIView animateWithDuration:1.0 animations:^{ [self updateConstraintsForMode]; [self.view layoutIfNeeded]; }]; }
總結
代碼在這,點我點我。