MyLayout和TangramKit是一套基於frame之上的UI界面佈局庫的OC版本和Swift版本。目前最新版本升級爲MyLayout1.7.0和TangramKit1.4.0。git
👉OC1.7.0: github.com/youngsoft/M…github
👉Swift1.4.0: github.com/youngsoft/T…緩存
此次升級的主要目的是爲了和AutoLayout結合的更加緊密。bash
這不是一篇推廣文,而是介紹AutoLayout和MyLayout&TangramKit是如何實現視圖尺寸自適應的以及兩者是如何結合在一塊兒的。因此但願您耐着性子繼續往下看👇👇👇👇👇框架
AutoLayout中有兩種類型的尺寸自適應:一類是以UILabel和UITextView爲表明視圖的尺寸自適應,這類視圖中的寬度和高度有時候須要根據自身內容來肯定本身的寬度和高度。也就是說這類視圖有本身的固有內容尺寸(intrinsicContentSize)。在UIView類中提供了一個可供重載的方法:佈局
- (CGSize)intrinsicContentSize NS_AVAILABLE_IOS(6_0);
複製代碼
若是某類視圖有本身的固有內容尺寸則須要重載這個方法的實現。這個方法返回根據自身內容而計算出來的固有內容尺寸的size,若是沒有固有內容尺寸則方法返回一個特殊的默認值UIViewNoIntrinsicMetric(-1)。很明顯UIView類的返回值是默認值,而UILabel和UITextView這些類則重載了這個方法並返回了根據自身內容計算出來的尺寸。 當一個視圖有本身的固有內容尺寸時,就不須要再爲視圖設置寬度或者高度約束。這也就是爲何通常狀況下不對UILabel視圖設置寬度和高度約束時系統也能正常完成佈局。系統內部的實現中若是佈局引擎在佈局時發現某個視圖沒有設置高度或者寬度約束那麼就會去調用這個視圖的intrinsicContentSize方法,若是這個方法返回了正常的尺寸則視圖就按這個尺寸來進行渲染和展現,若是這個方法返回值的某個維度是UIViewNoIntrinsicMetric則代表某個維度也沒有固有內容尺寸從而實現約束缺失的現象。ui
另一類是一些容器視圖的高度或者寬度但願根據其中的子視圖來肯定。好比一些界面中有父視圖的尺寸由子視圖的尺寸來肯定的;還好比UIScrollView中爲了能實現滾動須要根據添加到裏面的子視圖來調整contentSize的尺寸;又好比某些UITableViewCell中的高度是動態的,其高度尺寸是由裏面的子視圖來肯定的。在這些類中並無重載intrinsicContentSize的實現,因此須要提供一種新的設置方法來實現這種尺寸自適應的能力。spa
對於一個容器父視圖來講,當要實現父視圖的尺寸依賴全部子視圖的尺寸來實現自適應時,要設置的約束依賴不是經過尺寸約束來實現而是經過位置約束來實現。假設有以下的佈局: code
咱們但願父容器視圖S的尺寸是自適應的,那麼就須要設置S視圖的右邊邊界等於子視圖B的右邊邊界,同時須要設置S視圖的底部邊界等於子視圖C的底部邊界。能夠看出來要實現父容器視圖S的尺寸自適應時不是經過設置寬度和高度的尺寸依賴來實現的而是經過設置讓父視圖的邊界依賴於某個子視圖的邊界來實現的。具體代碼展現以下:cdn
//這裏忽略了視圖的建立代碼。
//本文對AutoLayout進行約束設置都是用iOS9之後所提供的進行約束設置的簡易方法。
[A.leftAnchor constraintEqualToAnchor:S.leftAnchor constant:10].active = YES;
[A.topAnchor constraintEqualToAnchor:S.topAnchor constant:10].active = YES;
[A.widthAnchor constraintEqualToConstant:100].active = YES;
[A.heightAnchor constraintEqualToConstant:30].active = YES;
[B.leftAnchor constraintEqualToAnchor:S.leftAnchor constant:80].active = YES;
[B.topAnchor constraintEqualToAnchor:A.bottomAnchor constant:20].active = YES;
[B.widthAnchor constraintEqualToConstant:200].active = YES;
[B.heightAnchor constraintEqualToConstant:30].active = YES;
[C.leftAnchor constraintEqualToAnchor:S.leftAnchor constant:30].active = YES;
[C.topAnchor constraintEqualToAnchor:B.bottomAnchor constant:20].active = YES;
[C.widthAnchor constraintEqualToConstant:50].active = YES;
[C.heightAnchor constraintEqualToConstant:40].active = YES;
//假設S是添加在某個視圖控制器中。
[S.leftAnchor constraintEqualToAnchor:self.view.leftAnchor constant:20].active = YES;
[S.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:90].active = YES;
//右邊邊界依賴B子視圖的右邊,下邊邊界依賴C子視圖的下邊實現尺寸自適應
[S.rightAnchor constraintEqualToAnchor:B.rightAnchor constant:20].active = YES;
[S.bottomAnchor constraintEqualToAnchor:C.bottomAnchor constant:20].active = YES;
複製代碼
能夠看出這種實現的機制有必定的侷限性!那就是當添加或者刪除子視圖時以及調整了某個子視圖的位置和尺寸時就須要從新調整父視圖的自適應約束設置。
對於UIScrollView來講須要設置contentSize來實現滾動的能力。可是基於約束設置的佈局體系來講,由於不少約束都是經過依賴來實現的,所以要計算contentSize並非那麼的容易和簡單。爲此當UIScrollView要和AutoLayout進行結合使用並實現滾動能力的話就不能直接將全部子視圖都添加到UIScrollView中去, 而是須要中間創建一個容器視圖,首先將容器視圖添加到UIScrollView中去,而後再將全部子視圖添加到容器視圖中去。在設置約束依賴時將容器視圖的上下左右分別依賴UIScrollView視圖的上下左右邊界,若是須要上下滾動則將容器視圖中的最底部子視圖的底部邊界依賴容器視圖的底部邊界。若是不須要上下滾動則改成將容器視圖的高度等於UIScrollView視圖高度便可。 若是須要左右滾動則將容器視圖中的最右邊子視圖的右邊邊界依賴於容器視圖的右邊邊界。若是不須要水平滾動則改成將容器視圖的寬度等於UIScrollView視圖的寬度。經過這樣的設置後UIScrollView視圖的contentSize將獲得自動的計算。下面是具體的實例代碼:
//1.建立一個滾動視圖,並設置好約束,這個約束能夠是AutoLayout也能夠是frame的,這裏爲了簡單就用frame。
UIScrollView *scrollView = [UIScrollView new];
[self.view addSubview:scrollView];
scrollView.frame = CGRectMake(100, 100, 100, 100);
//2.建立一個容器視圖, 這個容器視圖放入滾動視圖中,保證滾動視圖只有一個容器子視圖。
UIView *containerView = [UIView new];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
containerView.backgroundColor = [UIColor orangeColor];
[scrollView addSubview:containerView];
//3.將全部的子視圖A,B,C都添加到容器視圖中。
UILabel *A = [UILabel new];
A.text = @"A";
A.translatesAutoresizingMaskIntoConstraints = NO;
A.backgroundColor = [UIColor redColor];
[containerView addSubview:A];
UILabel *B = [UILabel new];
B.text = @"B";
B.translatesAutoresizingMaskIntoConstraints = NO;
B.backgroundColor = [UIColor greenColor];
[containerView addSubview:B];
UILabel *C = [UILabel new];
C.text = @"C";
C.translatesAutoresizingMaskIntoConstraints = NO;
C.backgroundColor = [UIColor blueColor];
[containerView addSubview:C];
//4.分別設置容器視圖中子視圖的約束依賴。
[A.leftAnchor constraintEqualToAnchor:containerView.leftAnchor constant:10].active = YES;
[A.topAnchor constraintEqualToAnchor:containerView.topAnchor constant:10].active = YES;
[A.widthAnchor constraintEqualToConstant:100].active = YES;
[A.heightAnchor constraintEqualToConstant:30].active = YES;
[B.leftAnchor constraintEqualToAnchor:containerView.leftAnchor constant:80].active = YES;
[B.topAnchor constraintEqualToAnchor:A.bottomAnchor constant:20].active = YES;
[B.widthAnchor constraintEqualToConstant:200].active = YES;
[B.heightAnchor constraintEqualToConstant:30].active = YES;
[C.leftAnchor constraintEqualToAnchor:containerView.leftAnchor constant:30].active = YES;
[C.topAnchor constraintEqualToAnchor:B.bottomAnchor constant:20].active = YES;
[C.widthAnchor constraintEqualToConstant:50].active = YES;
[C.heightAnchor constraintEqualToConstant:40].active = YES;
//5.設置容器視圖的依賴約束,讓容器視圖的四個邊界分別等於滾動視圖的四個邊界,這裏必需要這樣設置。
[containerView.leftAnchor constraintEqualToAnchor:scrollView.leftAnchor].active = YES;
[containerView.topAnchor constraintEqualToAnchor:scrollView.topAnchor].active = YES;
[containerView.rightAnchor constraintEqualToAnchor:scrollView.rightAnchor].active = YES;
[containerView.bottomAnchor constraintEqualToAnchor:scrollView.bottomAnchor].active = YES;
//6.關鍵的一步,若是須要上下滾動則將容器視圖中的最底部子視圖這裏是C的底部邊界依賴於容器視圖的底部邊界。若是不須要上下滾動則不要這樣設置,而是改成將容器視圖的高度等於滾動視圖高度。
[C.bottomAnchor constraintEqualToAnchor:containerView.bottomAnchor].active = YES;
//[containerView.heightAnchor constraintEqualToAnchor:scrollView.heightAnchor].active = YES;
//7.關鍵的一步,若是須要左右滾動則將容器視圖中的最右部子視圖這裏是B的右邊邊界依賴於容器視圖的右邊邊界。若是不須要水平滾動則不要這樣設置,而是改成將容器視圖的寬度等於滾動視圖的寬度
[B.rightAnchor constraintEqualToAnchor:containerView.rightAnchor].active = YES;
//[containerView.widthAnchor constraintEqualToAnchor:scrollView.widthAnchor].active = YES;
複製代碼
若是是使用Storyboard來設置約束依賴的步驟和流程也是同樣的。上面的約束設置實現視圖滾動的機制也有必定的侷限性!那就是一旦在容器視圖中添加子視圖時就須要從新調整容器視圖的右邊界和下邊界的約束依賴。這就須要將舊的邊界約束依賴記住,並在設置新的邊界依賴前刪除舊的約束依賴。
UITableViewCell要實現高度自適應,須要在UITableViewDelegate中的方法:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
複製代碼
實現中針對某個cell返回一個特定高度值UITableViewAutomaticDimension
。而後在UITableViewCell的派生類的視圖代碼佈局處或者在-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
方法中進行特殊約束設置便可。在上面的第1節中有介紹如何將一個容器視圖的尺寸設置爲自適應,而通常狀況下在編寫UITableViewCell的佈局代碼時,都將全部的子視圖添加到contentView這個視圖中,所以要實現UITableViewCell的高度自適應時,只須要將contentView當作是一個容器視圖,而後按照第1節中介紹的佈局約束設置方法就能夠實現高度自適應了。
MyLayout&TangramKit中的一個重要的能力是支持佈局視圖尺寸自適應的自動計算,也就是說佈局視圖的寬度或者高度能夠根據子視圖的尺寸來自行肯定,而不須要去明確的依賴某個子視圖來實現這種能力。對於MyLayout來講能夠設置佈局視圖的wrapContentHeight或者wrapContentWidth爲YES來實現這種能力,而對於TangramKit來講能夠設置佈局視圖的tg_width, tg_height的值爲.wrap來實現這種能力。好比一個佈局父視圖S中有三個子視圖A,B,C。要求S的高度和寬度根據三個子視圖的高度和寬度自適應,那麼只須要將佈局視圖S的約束設置爲以下:
//OC版本
S.wrapContentSize = YES;
//Swift版本
S.tg_size(width:.wrap, height:.wrap)
複製代碼
在MyLayout&TangramKit中的定義出了特殊的佈局視圖這個概念。全部爲子視圖設置的約束都必須放入到一個佈局視圖中才有效。整個佈局框架提供了多種佈局視圖,每種佈局視圖中的子視圖都將按照特定的規則進行排列和佈局。當佈局視圖這個容器視圖要實現尺寸自適應時就很是簡單,它不須要依賴任何對子視圖的約束依賴,而只須要將佈局視圖的尺寸設置爲wrap便可。就以上面的圖片例子用MyLayout&TangramKit來實現來講,能夠將S視圖定義爲一個垂直線性佈局視圖,而將A,B,C三個子視圖添加到佈局視圖中便可。下面是具體實現的佈局部分的代碼:
------------------------------------------------
//OC版本,S是一個垂直線性佈局
A.myLeft = 10;
A.myTop = 10;
A.mySize = CGSizeMake(100,30);
B.myLeft = 80;
B.myTop = 20;
B.mySize = CGSizeMake(200, 30);
C.myLeft = 30;
C.myTop = 20;
C.mySize = CGSizeMake(50, 40);
//只須要將佈局視圖的相應屬性設置爲YES便可,不須要依賴於特定子視圖。
S.wrapContentSize = YES;
------------------------------------------------
//Swift版本,S是一個垂直線性佈局
A.tg_origin(x:10,y:10).and().tg_size(width:100,height:30)
B.tg_origin(x:80,y:20).and().tg_size(width:200,height:30)
C.tg_origin(x:30,y:20).and().tg_size(width:50,height:40)
//佈局視圖的尺寸根據子視圖自適應。
S.tg_size(width:.wrap, height:.wrap)
複製代碼
由於MyLayout&TangramKit中的尺寸自適應約束不須要明確依賴的某個子視圖,所以當佈局視圖中的子視圖有變化時系統會自動從新進行佈局視圖的尺寸計算,而不須要作任何調整,這是使用MyLayout&TangramKit的最大的一個優點!
MyLayout&TangramKit對於處理和UIScrollView進行結合時進行特殊處理,當將一個佈局視圖添加到滾動視圖時,佈局系統內部會負責處理滾動視圖的contentSize。要實現UIScrollView滾動時,只須要在一個滾動視圖內添加一個佈局視圖,而後將全部其餘子視圖都添加到這個佈局視圖中去,這個和上面的AutoLayout的處理方式是同樣的,最後將佈局視圖的尺寸自適應屬性設置爲YES就能夠了。具體實現的OC代碼以下:
//1.建立一個滾動視圖,並設置好約束,這個約束能夠是AutoLayout也能夠是frame的,這裏爲了簡單就用frame。
UIScrollView *scrollView = [UIScrollView new];
[self.view addSubview:scrollView];
scrollView.frame = CGRectMake(100, 100, 100, 100);
//2.建立一個容器視圖, 這個容器視圖放入滾動視圖中,保證滾動視圖只有一個容器子視圖。
MyLinearLayout *containerView = [MyLinearLayout new];
containerView.backgroundColor = [UIColor orangeColor];
//設置容器佈局視圖的尺寸自適應屬性爲YES。
containerView.wrapContentSize = YES;
[scrollView addSubview:containerView];
//3.將全部的子視圖A,B,C都添加到容器視圖中。
UILabel *A = [UILabel new];
A.text = @"A";
A.backgroundColor = [UIColor redColor];
[containerView addSubview:A];
UILabel *B = [UILabel new];
B.text = @"B";
B.backgroundColor = [UIColor greenColor];
[containerView addSubview:B];
UILabel *C = [UILabel new];
C.text = @"C";
C.backgroundColor = [UIColor blueColor];
[containerView addSubview:C];
//4.分別設置容器視圖中子視圖的約束依賴。
A.myLeft = 10;
A.myTop = 10;
A.mySize = CGSizeMake(100,30);
B.myLeft = 80;
B.myTop = 20;
B.mySize = CGSizeMake(200, 30);
C.myLeft = 30;
C.myTop = 20;
C.mySize = CGSizeMake(50, 40);
//5.而後就沒有而後了!用MyLayout不須要進行特殊的附加設置!!
複製代碼
由於MyLayout&TangramKit中的尺寸自適應約束不須要明確依賴某個子視圖,所以當佈局視圖中的子視圖有變化時系統會自動從新進行佈局視圖的尺寸計算,而當佈局視圖的尺寸變化時又會調整UIScrollView的contentSize。
UITableViewCell要實現高度自適應,須要在UITableViewDelegate中的方法:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
複製代碼
實現中針對某個cell返回一個特定高度值UITableViewAutomaticDimension。而後在UITableViewCell的派生類中創建一個根佈局視圖,這個根佈局視圖做爲子視圖添加到contentView中代碼以下:
//假設根佈局視圖是一個垂直線性佈局視圖。
self.rootLayout= [MyLinearLayout linearLayoutWithOrientation:MyOrientation_Vert];
self.rootLayout.cacheEstimatedRect = YES; //提供緩存加速計算
self.rootLayout.myHorzMargin = 0; //佈局寬度等於contentView的寬度
self.rootLayout.wrapContentHeight = YES; //佈局視圖高度自適應。
[self.contentView addSubview:self.rootLayout];
//這裏將全部子視圖都添加到rootLayout中,並設置約束。
複製代碼
而後在UITableViewCell的派生類中重載視圖的方法:
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority
{
//系統在對UITableVeiwCell進行佈局時會調用systemLayoutSizeFittingSize方法來獲得視圖的尺寸,咱們把計算cell尺寸的任務交由佈局視圖的sizeThatFits方法來完成。
return [self.rootLayout sizeThatFits:targetSize];
}
複製代碼
MyLayout&TangramKit的佈局體系是基於原生的frame的計算來實現佈局,而AutoLayout則再也不依賴frame而是依賴視圖之間的約束來是實現佈局。這裏只介紹將MyLayout&TangramKit的佈局視圖加入到AutoLayout佈局體系中去的一些方法。
由於佈局視圖也是一個視圖,都是從UIView派生。所以要將一個佈局視圖添加到採用AutoLayout約束的佈局體系時,就像爲普通視圖同樣給佈局視圖設置約束依賴便可。
由於MyLayout&TangramKit中的佈局視圖具備設置尺寸自適應的屬性,爲了實現跟AutoLayout結合,最新版本的庫的佈局視圖內部重載了intrinsicContentSize的方法。所以若是想使用佈局視圖的尺寸自適應功能,那麼在將佈局視圖的尺寸設置爲wrap後,就能夠像使用UILabel那樣不用去設置佈局視圖的寬度約束和高度約束了。好比有兩個兄弟視圖A,B。A視圖是一個MyLayout&TangramKit佈局視圖,其寬度等於父視圖S的寬度,而高度則根據佈局視圖裏面的子視圖的高度自適應,而B視圖則在A視圖的下方,而且寬度等於A視圖。那麼混合約束代碼的實現以下:
//這裏只演示OC代碼。
MyLinearLayout *A = [MyLinearLayout new];
A.translatesAutoresizingMaskIntoConstraints = NO;
//這裏設置A視圖的高度自適應。
A.wrapContentHeight = YES;
[S addSubView:A];
//往A佈局視圖裏面添加子視圖的代碼。。
UIView *B = [UIView new];
B.translatesAutoresizingMaskIntoConstraints = NO;
[S addSubView:B];
//A佈局視圖的約束設置,這裏不須要設置高度約束,由於使用了佈局視圖的高度自適應屬性。
[A.leftAnchor constraintEqualToAnchor:S.leftAnchor].active = YES;
[A.topAnchor constraintEqualToAnchor:S.topAnchor].active = YES;
[A.widthAnchor constraintEqualToAnchor:S.widthAnchor].active = YES;
//B視圖必須設置完整約束
[B.leftAnchor constraintEqualToAnchor:S.leftAnchor].active = YES;
[B.topAnchor constraintEqualToAnchor:A.bottomAnchor].active = YES;
[B.widthAnchor constraintEqualToAnchor:A.widthAnchor].active = YES;
[B.heightAnchor constraintEqualToConstant:30].active = YES;
複製代碼
在佈局庫MyLayout中有更加複雜和詳細的對佈局視圖如何和AutoLayout相互結合的代碼:AllTest12ViewController。您能夠在這個DEMO中看到如何實現父視圖的尺寸和兄弟視圖的尺寸和位置如何依賴尺寸自適應的佈局視圖的代碼。
若是你的全部視圖都不使用AutoLayout的話則能夠經過上面介紹的MyLayout&TangramKit來實現UITableViewCell的高度自適應的解決方案來實現。可是缺點就是要進行特定方法的重載。而這個問題在新版本中都已經獲得解決了!!由於佈局視圖重載intrinsicContentSize方法,所以當將某個佈局視圖做爲UITableViewCell的子視圖時若是想使用佈局視圖的尺寸自適應的能力,只須要將佈局視圖的尺寸設置爲wrap便可,而後將佈局視圖添加到其餘視圖中去,不須要再爲佈局視圖設置寬度和高度約束了,也再也不限制只能將佈局視圖添加到contentView中了,也再也不須要重載特定的方法了,就至關於將一個佈局視圖當作UILabel視圖來使用便可。具體的代碼能夠參考佈局庫MyLayout中的AllTest1TableViewCellForAutoLayout.m實現。
歡迎你們訪問歐陽大哥2013的github地址