iOS - 布局NSLayoutConstraint動畫的實現


 

拋出問題:為何在用到用到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);
}];

 

 

 

方法二

arraymas_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 方法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM