拋出問題:為何在用到用到constraint的動畫時以下代碼無法實現動畫的功能 ,沒有動畫直接刷新UI跳到80
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [UIView animateWithDuration:2.0 animations:^{ self.blueViewH.constant = 80; }]; }
而我們直接使用frame的時候動畫是可以實現的
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [UIView animateWithDuration:2.0 animations:^{ CGRect size =self.blueView.frame; size.size.height = 80; self.blueView.frame = size;//注意 frame的更改會調用父控件的 layoutSubviews 更新UI }]; }
思考嘗試解決
沒有動畫效果 系統直接刷新渲染了
我們 手動強制更新刷新UI 放到動畫里面試一下:對NSLayoutConstraint的對象賦值之后調用layoutIfNeeded方法
注意:layoutIfNeeded方法只會刷新子控件,因此要使用必須通過它的父類
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { self.blueViewW.constant = 80; //frame/bound發生改變 (系統會對布局進行渲染 但是想做動畫的話 需要如下自己執行渲染) [UIView animateWithDuration:2.0 animations:^{ // 對布局進行渲染 [self.view layoutIfNeeded]; //layoutIfNeeded方法只會刷新子控件,因此要使用必須通過它的父類 }]; // //動畫的方式2(沒有上面的常用) // [UIView beginAnimations:nil context:nil]; // [UIView setAnimationRepeatCount:10]; // [self.view layoutIfNeeded]; // [UIView commitAnimations]; }
原理推測:
NSLayoutConstraint的本質就是在系統底層轉換為frame。立刻渲染 要想動畫需要把手動更新刷新UI 放到動畫里 這樣才能動畫 要不然就沒有動畫直接刷新UI跳到80
一個view的frame或bounds發生變化時,系統會設置一個flag給這個view,當下一個渲染時機到來時系統會重新按新的布局來渲染視圖;
因此我們調用layoutIfNeeded方法對"self.blueViewW.constant = 80;" 不等下一渲染時機了 只要有標記,就直接刷新 實現了動畫
實例應用如下(AutoLayout/Masonry):
AutoLayout
這是自定義了一個view 里面放里一個藍色的lineView
#import "CustomView.h" #import "Masonry.h" @interface CustomView () @property (weak, nonatomic) IBOutlet UILabel *label; @property (weak, nonatomic) IBOutlet UIView *lineView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *lineCenterConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *lineTopConstraint; @end @implementation CustomView + (instancetype)loadCustomView{ return [[[NSBundle mainBundle]loadNibNamed:@"CustomView" owner:nil options:nil]firstObject]; } //注意constraint是約束限制 constant是常數 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //1.先更改約束 (y = kx + b b是常數 (constant) 基於中心的0 負數是減 整數是加) self.lineCenterConstraint.constant = -self.frame.size.width/4; self.lineTopConstraint.constant = 70; //2.在動畫中更改布局 [UIView animateWithDuration:1.0 animations:^{ self.lineView.transform = CGAffineTransformRotate(self.lineView.transform, M_PI); [self layoutIfNeeded];//調用更改約束的view 的父視圖的layoutIfNeeded 不要掉自己self.lineView } completion:^(BOOL finished) { }]; // [UIView beginAnimations:nil context:nil]; // [UIView setAnimationRepeatCount:10]; // [self layoutIfNeeded];//調用更改約束的view 的父視圖的layoutIfNeeded 不要掉自己self.lineView // self.lineView.transform = CGAffineTransformRotate(self.lineView.transform, M_PI); // [UIView commitAnimations]; }
運行結果
動畫前
動畫后
第三方框架Masonry
常用方法
// 設置約束 初次設置約束使用 (NSArray *)mas_makeConstraints // 更改約束 更新mas_makeConstraints里面的約束使用 (NSArray *)mas_updateConstraints // 重新設置約束 先移除所有約束,再新增約束 (NSArray *)mas_remakeConstraints
常用屬性
自適應布局允許將寬度或高度設置為固定值.如果你想要給視圖一個最小或最大值,你可以這樣:
//width >= 200 && width <= 400 make.width.greaterThanOrEqualTo(@200); make.width.lessThanOrEqualTo(@400)
約束的優先級
.priority允許你指定一個精確的優先級,數值越大優先級越高.最高1000. .priorityHigh等價於 UILayoutPriorityDefaultHigh .優先級值為 750. .priorityMedium介於高優先級和低優先級之間,優先級值在 250~750之間. .priorityLow等價於 UILayoutPriorityDefaultLow , 優先級值為 250.
優先級可以在約束的尾部添加:
make.left.greaterThanOrEqualTo(label.mas_left).with.priorityLow(); make.top.equalTo(label.mas_top).with.priority(600);
center 中心
//使 make 的centerX和 centerY = button1 make.center.equalTo(button1) //使make的 centerX = superview.centerX - 5, centerY = superview.centerY + 10 make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
指定寬度為父視圖的 1/4.
make.width.equalTo(superview).multipliedBy(0.25);
- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. __weak typeof(self) weakSelf = self; _textField = [UITextField new]; _textField.backgroundColor = [UIColor redColor]; [self.view addSubview:_textField]; [_textField mas_makeConstraints:^(MASConstraintMaker *make) { //left,right,centerx,y 不能共存只能有其二 make.left.mas_equalTo(20); // make.right.mas_equalTo(-60); make.centerX.equalTo(weakSelf.view); make.height.mas_equalTo(40); make.bottom.mas_equalTo(0); }]; // 注冊鍵盤通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrameNotification:) name:UIKeyboardWillChangeFrameNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil]; } - (void)keyboardWillChangeFrameNotification:(NSNotification *)notification { // 獲取鍵盤基本信息(動畫時長與鍵盤高度) NSDictionary *userInfo = [notification userInfo]; CGRect rect = [userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; CGFloat keyboardHeight = CGRectGetHeight(rect); CGFloat keyboardDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; // 修改下邊距約束 [_textField mas_updateConstraints:^(MASConstraintMaker *make) { make.bottom.mas_equalTo(-keyboardHeight); }]; // 重新布局 [UIView animateWithDuration:keyboardDuration animations:^{ [self.view layoutIfNeeded]; }]; } - (void)keyboardWillHideNotification:(NSNotification *)notification { // 獲得鍵盤動畫時長 NSDictionary *userInfo = [notification userInfo]; CGFloat keyboardDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; // 修改為以前的約束(距下邊距0) [_textField mas_updateConstraints:^(MASConstraintMaker *make) { make.bottom.mas_equalTo(0); }]; // 重新布局 [UIView animateWithDuration:keyboardDuration animations:^{ [self.view layoutIfNeeded]; }]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; [self.view endEditing:YES]; }
以上是動畫 下面是用masony三控件等寬間距(xib 加約束的方式還沒有找到合適的)
假設有多個View,我們需要對其尺寸做批量設置
NSValue *sizeValue = [NSValue valueWithCGSize:CGSizeMake(100, 50)]; [@[view1,view2,view3] mas_makeConstraints:^(MASConstraintMaker *make) { make.size.equalTo(sizeValue); }];
用masony三控件等寬間距
方法一和方法二都在 NSArray+MASAdditions
中
方法一:
array 的 mas_distributeViewsAlongAxis withFixedSpacing
變化的是控件的長度或寬度 間距不變
定義一個存放三個控件的數組NSArray *array;
array = @[greenView,redView,blueView];
注意:
數組里面的元素不能小於1個,要不會報錯 views to distribute need to bigger than one
- (void)getHorizontalone { //方法一,array 的 mas_distributeViewsAlongAxis /** * 多個控件固定間隔的等間隔排列,變化的是控件的長度或者寬度值 * * @param axisType 軸線方向 * @param fixedSpacing 間隔大小 * @param leadSpacing 頭部間隔 * @param tailSpacing 尾部間隔 */ // MASAxisTypeHorizontal 水平 // MASAxisTypeVertical 垂直 [arrayList mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:20 leadSpacing:5 tailSpacing:5]; [arrayList mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(60); make.height.mas_equalTo(100); }];
方法二
array 的 mas_distributeViewsAlongAxis withFixedItemLength
控件size不變,變化的是間隙
// 豎直方向高度不變60 寬度左邊右邊20 間距變 - (void)getVertical { /** * 多個固定大小的控件的等間隔排列,變化的是間隔的空隙 * * @param axisType 軸線方向MASAxisTypeVertical 這時候withFixedItemLength是固定高度 * @param fixedItemLength 每個控件的固定長度或者寬度值 * @param leadSpacing 頭部間隔 * @param tailSpacing 尾部間隔 */ [arrayList mas_distributeViewsAlongAxis:MASAxisTypeVertical withFixedItemLength:60 leadSpacing:40 tailSpacing:10]; [arrayList mas_makeConstraints:^(MASConstraintMaker *make) { // make.top.mas_equalTo(100); // make.height.mas_equalTo(100); make.left.mas_equalTo(20); make.right.mas_equalTo(-20); }];
方法三 :直接設置multiplier
實現等間距
for (NSUInteger i = 0; i < 4; i++) { UIView *itemView = [self getItemViewWithIndex:i]; [_containerView addSubview:itemView]; [itemView mas_makeConstraints:^(MASConstraintMaker *make) { make.width.and.height.equalTo(@(ITEM_SIZE)); make.centerY.equalTo(_containerView.mas_centerY); make.centerX.equalTo(_containerView.mas_right).multipliedBy(((CGFloat)i + 1) / ((CGFloat)ITEM_COUNT + 1)); }]; }
方法四: 利用透明等寬度的SpaceView實現等間距
UIView *lastSpaceView = [UIView new]; lastSpaceView.backgroundColor = [UIColor greenColor]; [_containerView1 addSubview:lastSpaceView]; [lastSpaceView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.and.top.and.bottom.equalTo(_containerView1); }]; for (NSUInteger i = 0; i < ITEM_COUNT; i++) { UIView *itemView = [self getItemViewWithIndex:i]; [_containerView1 addSubview:itemView]; [itemView mas_makeConstraints:^(MASConstraintMaker *make) { make.height.and.width.equalTo(@(ITEM_SIZE)); make.left.equalTo(lastSpaceView.mas_right); make.centerY.equalTo(_containerView1.mas_centerY); }]; UIView *spaceView = [UIView new]; spaceView.backgroundColor = [UIColor greenColor]; [_containerView1 addSubview:spaceView]; [spaceView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(itemView.mas_right).with.priorityHigh(); // 降低優先級,防止寬度不夠出現約束沖突 make.top.and.bottom.equalTo(_containerView1); make.width.equalTo(lastSpaceView.mas_width); }]; lastSpaceView = spaceView; } [lastSpaceView mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_containerView1.mas_right); }];
項目中還沒有用到(一般這些方法都是重寫然后再調用super 方法 然后在寫一些其他的操作)
更新約束的方法 在view中
setNeedsUpdateConstraints:告知需要更新約束,但是不會立刻開始
updateConstraintsIfNeeded:告知立刻更新約束
updateConstraints:系統更新約束
在viewController 中
- (void)updateViewConstraints ; ViewController的View在更新視圖布局時,會先調用ViewController的updateViewConstraints 方法。 我們可以通過重寫這個方法去更新當前View的內部布局,而不用再繼承這個View去重寫-updateConstraints方法。我們在重寫這個方法時,務必要調用 super 或者 調用當前View的 -updateConstraints 方法。