關於AutoLayout和代碼修改約束

作者:Love@YR
鏈接:http://blog.csdn.net/jingqiu880905/article/details/52047030
請尊重原創,謝謝!

demo下載
先普及下AutoLayout的基礎知識:

  • 在storyboard中,一個view controller的右邊欄的File inspector中默認是勾選了Use Auto Layout和Use Size Classes。如果你不想在storyboard裏設置約束,可以勾掉,一旦勾掉Use Auto Layout,Use Size Classes也會被去掉,不再能選擇Any,Compact,Regular視圖大小,無法預覽每個設備下controller的UI

    不勾掉的話,點擊右上邊導航的show assistant editor, 會出現關聯controller文件,選擇Preview出現預覽視圖,然後左下角有個加號,可以選擇你想看的設備大小的預覽。

    一般情況下我們選擇以下視圖來繪製UI
    iPhone4S,iPhone5/5s,iPhone6
    豎屏:(w:Compact h:Regular)
    橫屏:(w:Compact h:Compact)
    iPhone6 Plus
    豎屏:(w:Compact h:Regular)
    橫屏:(w:Regular h:Compact)
    或者說
    iphone豎屏:寬:緊湊 高:正常
    iphone橫屏:寬:任意 高:緊湊
    iPad豎屏:(w:Regular h:Regular)
    ipad橫屏:(w:Regular h:Regular)

  • 在storyboard上設置約束和代碼寫約束
    這裏我們有個例子如圖:
    這裏寫圖片描述

self.view上有4個控件
第一個titleLabel:trailing:0 leading:0 top:40
第二個label:centerX=其superView.centerX,離上方的titleLabel相距0
imageView:centerX=其superView.centerX, centerY=其superView.centerY,寬240 高164
第三個bottomLabel:離上方的imageView相距5,centerX=其superView.centerX

如果用代碼寫約束的話,就把此viewController的Use Auto Layout勾掉

#pragma mark - 代碼添加約束
-(void)addConstraintForAll{
    //如果不加上下面4句的話,就會呈現storyboard上顯現的位置,加上之後約束才生效
 self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;   self.label.translatesAutoresizingMaskIntoConstraints = NO;   self.imgView.translatesAutoresizingMaskIntoConstraints = NO;  self.bottomLabel.translatesAutoresizingMaskIntoConstraints = NO;

    NSLayoutConstraint *titleTop = [NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:40];
    NSLayoutConstraint *titleLeft = [NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
    NSLayoutConstraint *titleRight = [NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1 constant:0];

    NSLayoutConstraint *labelCenterX = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
    NSLayoutConstraint *labelTop = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.titleLabel attribute:NSLayoutAttributeBottom multiplier:1 constant:0];

     NSLayoutConstraint *imageCenterX = [NSLayoutConstraint constraintWithItem:self.imgView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
     NSLayoutConstraint *imageCenterY = [NSLayoutConstraint constraintWithItem:self.imgView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
    NSLayoutConstraint *imgWidth = [NSLayoutConstraint constraintWithItem:self.imgView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:240];
      NSLayoutConstraint *imgHeight = [NSLayoutConstraint constraintWithItem:self.imgView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:164];

    NSLayoutConstraint *bottomlabelTop = [NSLayoutConstraint constraintWithItem:self.bottomLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.imgView attribute:NSLayoutAttributeBottom multiplier:1 constant:5];
    NSLayoutConstraint *bottomlabelCenterX = [NSLayoutConstraint constraintWithItem:self.bottomLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
    [self.imgView addConstraints:@[imgWidth,imgHeight]];
    [self.view addConstraints:@[titleTop,titleLeft,titleRight,labelCenterX,labelTop,imageCenterX,imageCenterY,bottomlabelTop,bottomlabelCenterX]];


}

代碼看着很長,其實很簡單。就是你要設置的item的某個attribute 參照的toItem的某個attribute的值爲constant的某個Relation

比如self.bottomLabel的NSLayoutAttributeTop 與self.imgView的NSLayoutAttributeBottom相距NSLayoutRelationEqual 5

如果要設置某個控件的高寬度爲一個死值,則參照爲nil,參照的attribute爲NSLayoutAttributeNotAnAttribute

multiplier爲縮放的比例。比如你可以設置寬度爲superview寬的一半,這邊multiplier就可以設置爲0.5

  • 爲什麼有的約束不是被加在自身裏了?
    看上面的代碼我們知道,除了圖片的寬,高,其他的約束全都被加到self.view裏了。從上面的圖裏我們也可以看出來。那就有疑問了,比如圖片和bottomLabel相距爲5,按理說這個約束應該爲圖片或者bottomLabel裏,爲什麼跑到self.view裏了呢?
    參考:http://www.jianshu.com/p/93016a1ceabf
    中添加約束的規則。
    比如cview有兩個子view a和b,a和b分別有一個子view1和2。
    a和b之間的關係約束會被添加到其共同父view c上
    1和2之間的關係約束會被添加到最近共同父view c上
    b和c之間的關係約束會被添加到層次較高的父view c上

    下面我們來看下怎麼在代碼中動態修改約束:

  • 直接創建約束對象的對象關聯,修改約束常量值
    也就是說從storyboard裏把約束像控件一樣拉到.h裏創建一個IBOutlet NSLayoutConstraint *someConstraint
    這樣的話可以直接 self.someConstraint.constant=40;去賦值,而其他都是隻讀的,不能修改

NSLayoutConstraint.h
@property (readonly, assign) id firstItem;
@property (readonly) NSLayoutAttribute firstAttribute;
@property (readonly) NSLayoutRelation relation;
@property (nullable, readonly, assign) id secondItem;
@property (readonly) NSLayoutAttribute secondAttribute;
@property (readonly) CGFloat multiplier;

@property CGFloat constant;
  • 代碼中拿到某個約束,刪除再添加新約束
    比如這裏我把bottomLabel與圖片相距5,但是我想在某個時刻改成bottomLabel與titleLabel相距0

-(void)modifyConstraint{
    NSArray* constrains = self.view.constraints;
    for (NSLayoutConstraint* constraint in constrains) {
                    if (constraint.firstAttribute == NSLayoutAttributeTop&&constraint.firstItem == self.bottomLabel) {
                        //修改的約束會立即生效,加上下面這句變化的過程就顯得比較自然
                        [UIView animateWithDuration:3 animations:^
                         {                             //constraint.constant = 80;
                              [self.view removeConstraint:constraint];
                               [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.bottomLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.titleLabel  attribute:NSLayoutAttributeBottom multiplier:1 constant:0]];
 [self.view layoutIfNeeded];
         }];
      }
    }
}

上面代碼是在self.view的約束裏去找而不是圖片或者bottomLabel裏,原因上面已經分析了,不知道的話這裏是個大坑。

在viewDidLoad裏

 [self performSelector:@selector(modifyConstraint) withObject:nil afterDelay:0.1];

這裏爲什麼一定要延遲執行呢。因爲雖然在viewDidLoad中修改的約束的代碼塊運行了,但是運行完之後又被storyboard自己的配置給覆蓋了,所以 你看到的還是你之前設置的約束!
這也是個坑,沒有延時我一直在constrains這個數組裏沒找到imageview和bottomlabel那個關係,延時了之後constrains這個數組纔有。

最後我們來看下怎麼再把ipad的約束加進去:

首先,target-general- deployment info那邊device爲universal才能保證ipad運行的不是大號版的iphone

然後storyboard改成regular regular之後發現有些控件變灰了,控件的attributes inspector那裏installed勾上就會由灰色變成正常的,這時控件就可以被挪動和設置約束了

這時會有一些約束也是灰的,代表無效約束,不要管它,不要改它,也不要刪它!(它在之前設置的iphone版是正確的,刪了之後iphone版的就不能正確顯示了)

如果你在之前iphone版設置某個控件的寬高,pad版想改,直接改。
如果你在之前iphone版設置某個控件的centerX, pad版想改,直接改。
然後你也可以添加約束,比如這裏我把label和bottomLabel設置成橫排,分別靠屏幕左右各30,離titleLabel 100。

設置完了選擇pad運行,ok~
總之,你可以更改約束值,可以添加新的約束,但不要刪除對應其他設備大小版本的約束!

關於如何給scrollview設置約束:
按照正常設置約束的步驟給scrollview設置了其相對於父view的trailing leading top bottom之後,會出現錯誤警告,說沒有設置scrollview的寬和高。
這是因爲上述設置只是定住了scrollview的frame,而沒有給其contentView設置個size。
如何給contentView設置size呢?
contentView肯定是左上角的point在frame的(0,0)處,其中加了一個子view之後,子view設置相對於scrollview的trailing leading top bottom,代表了此子view在scrollview中距離content邊緣的位置,一旦確定了其大小,則可以確定這個contentView的大小。
如圖:
這裏寫圖片描述

這裏我們在self.view上加一個scrollview
scrollview設置上下左右分別距離self.view爲0 即佔滿view
然後添加藍色子view 讓藍色子view距離scrollview的contentView左右上下分別爲20 300 20 300
然後藍色子view的高度和寬度設置爲scrollview的父view的寬高分別減去40
這樣即確定了contentView的大小(20+self.view寬-40+300,20+self.view高-40+300)

當設置藍色子view的高度寬度不用高度寬度約束而用距離self.view上下左右去做的時候,發現這個子view是無法隨着scrollview一起滾動的(因self.view固定,其相對於self.view又是固定的),只有設置其與scrollview(contentView)的上下左右間距,其纔會滾動。

總結下來如何給scrollview添加約束:
首先找個子view設置其與scrollview內容視圖的間距
然後設置這個子view的大小爲一個定值

發佈了56 篇原創文章 · 獲贊 28 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章