[iOS开发]autolayout自动布局
使用Objective-C風格的方法進行代碼autolayout布局
在iOS 6之后系統引入了相關的類來進行autolayout的代碼方式創建與布局設置。
 使用代碼進行 autolayout 布局首先要了解一個重要的類:NSLayoutConraint。NSLayoutConraint 類是進行代碼autolayout布局的核心類,其創建出具體的自動布局約束對象。使用Xcode 創建一個工程,在ViewController.m文件的viewDidLoad方法中添加如下代碼:
效果:
 
 上面代碼看起來非常復雜,然而用到的核心方法只有一個,即創建autolayout約束對象:
它是一個類方法:
+ (instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)cconstraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:方法參數有7個。第1個參數設置要約束的第一個視圖對象。第2個參數設置約束的第一個參數的約束屬性,具體 參數意義后面會介紹。第3個參數設置約束屬性間的關系,參數具體意義后面會介紹。第4個參數設置要約束的第二個視圖對象。第5個參參數設置第二個視圖對象的約束屬性。第6個參數設置約束的比例。第7個參數設置約束的值。
 其中,第2個參數和第5個參數都需要設文置為NSLayoutAttribute類型的枚舉,枚舉值為要約束控件的具體屬性,常用枚舉值及含義如下:
邊距和上面那些還是有一定差距的,我們看下效果:
NSLayoutConstraint *constraintX = [NSLayoutConstraint constraintWithItem:myView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeftMargin multiplier:1 constant:0];先看約束NSLayoutAttributeLeftMargin:
 
再看NSLayoutAttributeLeft:
 
可以看到Margin是有邊距的,這個后面再研究。上面的枚舉值中,在從左向右的布局結構中,NSLayoutAttributeLeft、NSLayoutAttributeRight和NSLayoutAttributeLeading、NSLayoutAttributeTrailing效果一致。上面所有枚舉值中,有一個比較特殊,即NSLayoutAttributeNotAnAttribute值,它沒有任何意義,只是作為某些情況下方法中的占位,例如:
NSLayoutConstraint *constraintH = [NSLayoutConstraint constraintwithItem:myView attribute:NSLayoutAttributeHeight relatedby:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:100];創建約束對象的方法中,第參數需要設置為LouRelation類型的枚舉,這個值決定了所約束屬性間的關系,枚舉值及含義如下:
typedef NS_ENUM(NSInteger, NSLayoutRelation) {NSLayoutRelationLessThanOrEqual = -1, //小于等于所約束的值NSLayoutRelationEqual = 0, //嚴格等于所約束的值NSLayoutRelationGreaterThanOrEqual = 1, //大于等于所約束的值 };創建約束對象方法的最后兩個參數決定了約束值,第6個參數設置約束的比例,第7個參數設置具體的約束值。例如,需要設置第1個控件的寬度是第2個控件寬度的2倍多100單位距離,可使用如下代碼:
NSLayoutConstraint *constraintW = [NSLayoutConstraint constraintWithItem:myView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:2 constant:100];multiplier與constant參數的計算方法遵守如下公式:
view1寬度 = view2寬度 * multiplier + constant運行工程,可以看到,無論橫屏模式還是豎屏模式,也無論屏幕尺寸如何,色塊控件始終出現在屏幕的正中央,寬度、高度均為 100 單位。
小提示:
使用格式化的字符進行autolayout布局對象創建
前面介紹使用代碼進行autolayout布局時,有一個致命的缺陷,一個十分簡單的布局結構卻要寫十分冗長的代碼。此外,還有一種十分神奇的創建autolayout的方法:
- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = [UIColor lightGrayColor];// Do any additional setup after loading the view.UIView * myView = [[UIView alloc]init];myView.translatesAutoresizingMaskIntoConstraints = NO;myView.backgroundColor = [UIColor redColor];//添加約束前,必須將子視圖添加在父視圖上[self.view addSubview:myView];NSArray *constraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[myView(100@1000)]" options:0 metrics:nil views:_NSDictionaryOfVariableBindings(@"myView", myView)];NSArray *constraintArray2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[myView(100)]" options:0 metrics:nil views:_NSDictionaryOfVariableBindings(@"myView", myView)];[self.view addConstraints:constraintArray];[self.view addConstraints:constraintArray2]; }代碼一下子簡潔不少,看一下運行效果:
 
 上面代碼中,使用constraintsWithVisualFormat:options:metrics:views:方法用于根據VFL格式化字符串創建一系列的NSLayoutConstrsint約束對象,這些創建出來的約束對象會以數組的形式返回。要理解這個方法,首先需要理解什么是VFL。
 乍一看,VFL確實十分令人費解,但是完全理解它之后就能感受到它的優美之處,它像極了中國古老的象形語言,通過半畫半文字的方式表達信息。上面的第一條語句實際是約束了 myView視圖左側距離父視圖 20個單位,寬度為100個單位,并且這條約束的優先級為1000。第二條語句約束了myView 視圖上側距離父視圖100個單位,myView視圖的高度為100。現在可以解釋一下 VFL 語言的語法含義了,最前面的H或者V代表約束的布局方向,H(horizontal)是為水平方向添加約束, V(vertical)是為豎直方向添加約束。“|”表示父視圖的邊緣。在H約束布局中,如果“”出現在字符串的左端,則代表父視圖的左邊界;如果“|”出現在字符串的右端,則代表父視圖的右邊界。在 V約束布局中,如果“|”出現在字符串的左邊,則代表父視圖的上邊界;如果“|”出現在字符串的右端,則代表父視圖的下邊界。“-x-”表示具體的約束距離,x既可為常量也可為變量,例如前面示例中的20就是常量,為變量的情況后面會有討論。[]內為要布局擺放的控件名稱,()內約束控件的尺寸。在H約束布局中,其含義是約束控件的寬度。在V約束布局中,其含義是約束控件的高度。@符號后面的值為設置此約束的優先級。
 理解了VFL語句的含義后,再來看通過VFL語句創建約束集合的方法:
constraintsWithVisualFormat:options:metrics:views:方法中第1個參數為創建約束的VFL字符串,第2個參數設置所約束控件的對齊模式,其需要設置為NSLayoutFormatOptions類型的枚舉。這個枚舉中常用枚舉值及含義如下:
typedef NS_OPTIONS(NSUInteger, NSLayoutFormatOptions) {//約束的控件左對齊NSLayoutFormatAlignAllLeft = (1 << NSLayoutAttributeLeft),//約束的控件右對齊NSLayoutFormatAlignAllRight = (1 << NSLayoutAttributeRight),//約束的控件上對齊NSLayoutFormatAlignAllTop = (1 << NSLayoutAttributeTop),//約束的控件下對齊NSLayoutFormatAlignAllBottom = (1 << NSLayoutAttributeBottom),//約束的控件前對齊NSLayoutFormatAlignAllLeading = (1 << NSLayoutAttributeLeading),//約束的控件后對齊NSLayoutFormatAlignAllTrailing = (1 << NSLayoutAttributeTrailing),//約束的控件X軸中心對齊NSLayoutFormatAlignAllCenterX = (1 << NSLayoutAttributeCenterX),//約束的控件Y軸中心對齊NSLayoutFormatAlignAllCenterY = (1 << NSLayoutAttributeCenterY),NSLayoutFormatAlignAllLastBaseline = (1 << NSLayoutAttributeLastBaseline),NSLayoutFormatAlignAllFirstBaseline API_AVAILABLE(macos(10.11), ios(8.0)) = (1 << NSLayoutAttributeFirstBaseline), #if TARGET_OS_IPHONE//約束的控件文字基線對齊NSLayoutFormatAlignAllBaseline NS_SWIFT_UNAVAILABLE("Use 'alignAllLastBaseline' instead") = NSLayoutFormatAlignAllLastBaseline, #elseNSLayoutFormatAlignAllBaseline = NSLayoutFormatAlignAllLastBaseline, #endifNSLayoutFormatAlignmentMask = 0xFFFF,/* choose only one of these three*/NSLayoutFormatDirectionLeadingToTrailing = 0 << 16, // defaultNSLayoutFormatDirectionLeftToRight = 1 << 16,NSLayoutFormatDirectionRightToLeft = 2 << 16,NSLayoutFormatDirectionMask = 0x3 << 16,#if TARGET_OS_IPHONE/* choose only one spacing format*///僅選擇一種間距格式NSLayoutFormatSpacingEdgeToEdge API_AVAILABLE(ios(11.0),tvos(11.0)) = 0 << 19, // default/* Valid only for vertical layouts. Between views with text content the valuewill be used to determine the distance from the last baseline of the view aboveto the first baseline of the view below. For views without text content the topor bottom edge will be used in lieu of the baseline position.The default spacing "]-[" will be determined from the line heights of the fontsinvolved in views with text content, when present.*//*僅對垂直布局有效。在具有文本內容的視圖之間該值將用于確定與上面視圖的最后一個基線的距離到下面視圖的第一個基線。對于沒有文本內容的視圖,頂部或下邊緣將用于代替基線位置。默認間距“]-[”將從字體的行高確定涉及具有文本內容的視圖(如果存在)。*/NSLayoutFormatSpacingBaselineToBaseline API_AVAILABLE(ios(11.0),tvos(11.0)) = 1 << 19,NSLayoutFormatSpacingMask API_AVAILABLE(ios(11.0),tvos(11.0)) = 0x1 << 19, #endif };上面創建約束的方法中第4個參數為變量映射字典,如果 VFL 字符串中需要使用到某些變量,則需要使用這個參數將變量映射到VFL字符串中,示例如下:
NSNumber *width = @100; NSNumber *left = @20; NSArray *constraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[myView(wid@1000)]" options:0 metrics:@{@"left" : left, @"wid" : width} views:_NSDictionaryOfVariableBindings(@"myView", myView)];上面示例代碼中創建了兩個NSNumber類型的變量量,在 metrics 參數中,使用字典鍵值對的模式將 VFL 中對應的字符串應設置變量對象。
 關于constraintsWithVisualFormat:options:metrics:views:方法中還有最后一個參數views,它對應的參數是一個約束對象映射字典。與變量映射的原理一樣,需需要將VFL 中使用到的具體控件名映射成視圖控件對象。_NSDictionaryOfVariableBindings()宏可以幫助開發者快捷地創建這個映射字典,開發者只需將使用到的視圖控件對象直接傳入。即可。需要注意的是,使用 _NSDictionaryOfVariableBindings()宏進行快捷映射字典創建時,視圖控件的名稱必須和VFL中的名稱完全一致。如果手寫這個映射字典,則上面的代碼和下面是等價的:
管理約束的幾個方法
除了已經介紹的添加約束的方法外,autolayout 框架中還提供了一些移除約束的方法。對于進行autolayout約束的視圖控件而言,其中所有可用的有關設置約束的方法如下:
//添加一個約束對象 - (void)addConstraint:(NSLayoutConstraint *)constraint API_AVAILABLE(ios(6.0)); // This method will be deprecated in a future release and should be avoided. Instead, set NSLayoutConstraint's active property to YES. //添加一組約束對象 - (void)addConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints API_AVAILABLE(ios(6.0)); // This method will be deprecated in a future release and should be avoided. Instead use +[NSLayoutConstraint activateConstraints:]. //移除一個約束對象 - (void)removeConstraint:(NSLayoutConstraint *)constraint API_AVAILABLE(ios(6.0)); // This method will be deprecated in a future release and should be avoided. Instead set NSLayoutConstraint's active property to NO. //移除一組約束對象 - (void)removeConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints API_AVAILABLE(ios(6.0));應用
@interface ViewController ()<UITextViewDelegate>@property (nonatomic, strong) UITextView *textView; @property (nonatomic, strong) NSArray *array1; @property (nonatomic, strong) NSArray *array2;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];_textView = [[UITextView alloc] init];_textView.layer.borderColor = [UIColor grayColor].CGColor;_textView.layer.borderWidth = 1;_textView.translatesAutoresizingMaskIntoConstraints = NO;_textView.delegate = self;[self.view addSubview:_textView];_array1 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-100-[textView]-100-|" options:0 metrics:nil views:_NSDictionaryOfVariableBindings(@"textView", _textView)];_array2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-150-[textView(30)]" options:0 metrics:nil views:_NSDictionaryOfVariableBindings(@"textView", _textView)];[self.view addConstraints:_array1];[self.view addConstraints:_array2]; }- (CGSize)getContentSize:(UITextView*)myTextView{return [myTextView sizeThatFits:CGSizeMake(myTextView.frame.size.width, FLT_MAX)]; }- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {if (_textView.contentSize.height != _textView.frame.size.height && _textView.contentSize.height < 100) {float height = textView.contentSize.height;[self.view removeConstraints:_array2];_array2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-150-[textView(height)]" options:0 metrics:@{@"height" : [NSNumber numberWithFloat:height]} views:_NSDictionaryOfVariableBindings(@"textView", _textView)];[self.view addConstraints:_array2];}[self.view updateConstraintsIfNeeded];return YES; }@end實現一個可以自適應多行輸入高度的UITextView。
 拋出疑問: 不知道為什么輸入字符時第二行只有一個字符時UITextView的contentSize不改變,或者刪除字符時第二行刪光后UITextView的contentSize也不改變。
總結
以上是生活随笔為你收集整理的[iOS开发]autolayout自动布局的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 部署好网站,同局域网中电脑无法访问的问题
- 下一篇: Android事件总线
