作者 | 歐陽大哥2013
MyLayout是一套功能全面的iOS開源UI界面布局框架。它囊括了前端所有流行的界面布局技術和解決方案,同時具有如下七大特點:
• 功能強大。它可以減少我們在開發UI界面時所花費的時間以及減少需要適配多種設備而所消耗的時間。實踐表明使用MyLayout進行界面布局時可以減少幾乎50%的工作量。
• 性能優越。MyLayout內部實現是基於frame計算來完成布局的,所以同等界面下性能是AutoLayout的5倍左右,因此複雜界面選擇MyLayout將是最佳實踐。
• 布局體系豐富。MyLayout提供了iOS、Android、HTML/CSS等前端中的所有流行布局實現。因此無論你之前工作在何種平臺上都可以選擇熟悉的布局類上手進行開發操作。MyLayout還支持從伺服器進行動態布局下發的能力。
• 系統結合緊密。MyLayout可以同時和AutoLayout技術進行結合使用,同時可以用在XIB和Storyboard中進行可視化布局,同時還支持SizeClass技術用於多設備適配處理。
• 多語言實現。MyLayout提供了OC語言版本的實現,同時也提供了Swift語言版本的實現:TangramKit。二者的語法和使用方式相似,您可以任意選擇一種語言進行代碼布局。
• 國際化支持。MyLayout支持LTR和RTL兩種方向的布局,其中的RTL模式可以用來支持希伯來語系的布局方式。
• 無版本限制。MyLayout並沒有作業系統版本上的使用限制,理論上它最低甚至可以支持到iOS5.0。
下面的表格列出的是MyLayout所提供的九大布局類所實現的功能以及和其它系統的對標能力:
布局類名功能介紹對標功能MyLinearLayout線性布局:提供視圖依次從上往下或者從左往右進行單行單列排列的能力iOS:UIStackView在這些眾多布局類中有些布局類提供了子視圖的有規律的布局排列,比如線性布局、流式布局、表格布局、浮動布局、路徑布局、彈性布局、柵格布局。有些布局類則提供了通過子視圖之間的約束限制來實現布局排列,比如浮動布局、相對布局、框架布局。有些布局類則需要通過多個層次嵌套來實現界面需求,比如線性布局、流式布局、表格布局、彈性布局。有些布局類則可以把界面需求拍平而只用單層排版就能實現所需功能,比如浮動布局、相對布局、柵格布局。有些布局類則可以實現一些特殊排列,比如路徑布局可以根據提供的數學函數來實現視圖根據特定路徑曲線來進行排列展示。有些布局類則可以提供從伺服器進行動態下發以及用JSON進行布局描述的能力,比如柵格布局。有些布局類則可以實現和HTML/CSS對標的能力,比如浮動布局和彈性布局。因此在實現界面需求時,我們可以靈活運用。在選擇布局時我將使用布局類的優先級列出來,供大家參考:
浮動布局->流式布局->線性布局->彈性布局->柵格布局->相對布局->框架布局->表格布局->路徑布局
您可以從如下地址下載這兩個版本的工程DEMO:
?OC語言版本MyLayout: https://github.com/youngsoft/MyLinearLayout.git
?Swift語言版本TangramKit: https://github.com/youngsoft/TangramKit.git
這次1.9.0版本的升級無論是新功能的添加、代碼的重構、性能的提升都做了大量的改進,新增和改進的功能主要有:
• 彈性布局flexbox的實現MyFlexLayout
• 最值約束
• 視圖尺寸和位置的壓縮
• 環繞和拉伸停靠的支持
• 拖放類MyLayoutDragger實現布局內視圖的拖放
• iOS13的黑白模式的適配支持
• 流式布局自定義行內對齊
• 流式布局和浮動布局對基線對齊的支持
• 重構和添加了對布局視圖進行布局時的動畫支持能力
• 完善和擴充對布局和視圖尺寸自適應設置支持
• 重構了流式布局和相對布局的實現,提升了所有布局的性能
• 修復了線上的BUG
下面是新版本的上述功能的詳細介紹:
1. 彈性布局MyFlexLayoutflexbox是目前Web前端比較流行的布局框架。它提供了一種在一個盒子內子視圖依次排列並可以進行換行排列和進行拉伸和壓縮的功能。目前也有很多將flexbox移植到native客戶端的解決方案。當然flexbox也有一定的缺陷:比如不支持重疊覆蓋、不支持相對間距、不支持行和列間距的統一設置、不支持不規則排列等等問題。
在以前的版本中流式布局MyFlowLayout就可以實現flexbox的大多數特性並且在此基礎上進行了更多複雜功能的擴展。因為其語法和設置方式和flexbox不兼容,因此對於flexbox的喜愛者來說是增加了學習和使用的成本。而這次的新版本則提供了一個新的布局類:彈性布局MyFlexLayout:
/*
* 彈性布局是為了兼容flexbox語法而建立了一個布局,它是從MyFlowLayout派生。在MyFlowLayout中也是支持類似flexbox的一些特性的
* 因為它的屬性和flexbox不兼容,所以提供一個新的類MyFlexLayout來完全支持flexbox.
*/
@interface MyFlexLayout:MyFlowLayout
/**
用於彈盒布局視圖自身的布局設置
*/
@property(nonatomic, strong, readonly) id myFlex;@end
從上面的類定義中可以看出這個布局類是從流式布局MyFlowLayout類派生,我們可以通過類中的myFlex屬性來進行彈性布局視圖的相關屬性設置。myFlex中提供了鏈式語法以及屬性設置語法兩種操作形式,您可以選擇喜歡的方式來操作和使用彈性布局。下面是屬性myFlex的接口MyFlexBox的詳細定義:
@protocol MyFlexBox <MyFlexItem>
@property(nonatomic, strong) id attrs;/**
設置或檢索伸縮盒對象的子元素在父容器中的位置。默認值:MyFlexDirection_Row
*/
-(id (^)(MyFlexDirection))flex_direction;/**
設置或檢索伸縮盒對象的子元素超出父容器時是否換行。默認值:MyFlexWrap_NoWrap
*/
-(id (^)(MyFlexWrap))flex_wrap;/**
同時設置檢索伸縮盒對象的子元素在父容器中的位置和伸縮盒對象的子元素超出父容器時是否換行。二者通過 | 運算進行組合
*/
-(id (^)(int))flex_flow;/**
設置或檢索彈性盒子元素在主軸(橫軸)方向上的對齊方式。可選值為:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Space_Between | MyFlexGravity_Space_Around 中的一個,默認值為MyFlexGravity_Flex_Start
*/
-(id (^)(MyFlexGravity))justify_content;/**
設置或檢索彈性盒子元素在側軸(縱軸)方向上的對齊方式。可選值為:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Baseline | MyFlexGravity_Stretch中的一個,默認值為MyFlexGravity_Flex_Start
*/
-(id (^)(MyFlexGravity))align_items;/**
設置或檢索彈性盒堆疊伸縮行的對齊方式。可選值為:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Between | MyFlexGravity_Around | MyFlexGravity_Stretch中的一個,默認值為MyFlexGravity_Stretch
*/
-(id (^)(MyFlexGravity))align_content;/**
指定主軸的子條目的數量。只有在flex_wrap設置為wrap時才有效。默認值是0表示會根據條目的尺寸自動進行換行。
*/
-(id (^)(NSInteger))item_size;/**
指定布局視圖中每頁的條目數量。這個值必須是item_size的倍數。
*/
-(id (^)(NSInteger))page_size;/**
指定布局會根據條目的尺寸自動排列,默認值是NO。
*/
-(id (^)(BOOL))auto_arrange;/**
設置彈性盒的內邊距
*/
-(id (^)(UIEdgeInsets))padding;/**
設置彈性盒內所有條目視圖之間的垂直間距
*/
-(id (^)(CGFloat))vert_space;/**
設置彈性盒內所有條目視圖之間的水平間距
*/
-(id (^)(CGFloat))horz_space;@end
而對於彈性盒視圖中的條目子視圖(item)來說則可以通過UIView的一個分類擴展提供的myFlex進行屬性設置:
@interface UIView(MyFlexLayout)
/**
用於彈盒視圖中的子視圖的布局設置。
*/
@property(nonatomic, strong, readonly) id myFlex;@end
條目視圖的myFlex屬性的實現接口:MyFlexItem的定義如下:
@protocol MyFlexItem
@property(nonatomic, strong, readonly) id attrs;
@property(nonatomic, weak, readonly) __kindof UIView *view;
/**
視圖的寬度設置,如果寬度設置為大於0小於1則表明是相對於父視圖寬度的比重值,如果是MyLayoutSize.wrap則表明寬度自適應,如果是MyLayoutSize.fill則表明寬度和父視圖相等,如果是MyLayoutSize.empty則表明不設置寬度值。其他的值就是一個固定寬度值。
*/
-(id (^)(CGFloat))width;
/**
視圖的寬度設置,percent表明佔用父視圖寬度的百分比值,inc表明在百分比值的基礎上的增量值。
*/
-(id (^)(CGFloat percent, CGFloat inc))width_percent;
/**
最小寬度限制設置
*/
-(id (^)(CGFloat))min_width;
/**
最大寬度限制設置
*/
-(id (^)(CGFloat))max_width;
/**
視圖的高度設置,如果高度設置為大於0小於1則表明是相對於父視圖高度的比重值,如果是MyLayoutSize.wrap則表明高度自適應,如果是MyLayoutSize.fill則表明高度和父視圖相等,如果是MyLayoutSize.empty則表明不設置高度值,其他的值就是一個固定高度值。
*/
-(id (^)(CGFloat))height;
/**
視圖的高度設置,percent表明佔用父視圖高度的百分比值,inc表明在百分比值的基礎上的增量值。
*/
-(id (^)(CGFloat percent, CGFloat inc))height_percent;
/**
最小高度限制設置
*/
-(id (^)(CGFloat))min_height;
/**
最大高度限制設置
*/
-(id (^)(CGFloat))max_height;
//視圖的外間距設置。
/**
視圖的頂部外間距設置
*/
-(id (^)(CGFloat))margin_top;
/**
視圖的底部外間距設置
*/
-(id (^)(CGFloat))margin_bottom;
/**
視圖的左邊外間距設置
*/
-(id (^)(CGFloat))margin_left;
/**
視圖的右邊外間距設置
*/
-(id (^)(CGFloat))margin_right;
/**
視圖的四周外間距設置
*/
-(id (^)(CGFloat))margin;
/**
視圖的可視設置
*/
-(id (^)(MyVisibility))visibility;
//添加到父視圖中
-(__kindof UIView* (^)(UIView*))addTo;
//添加子視圖
-(id (^)(UIView*))add;
/**
條目在彈盒中的排列順序,值越大越往後排。
*/
-(id (^)(NSInteger))order;
/**
設置或檢索彈性盒的擴展比率。默認值為0表示不擴展
*/
-(id (^)(CGFloat))flex_grow;
/**
設置或檢索彈性盒的收縮比率。默認值為1表示當條目尺寸超過彈性盒尺寸後會進行壓縮。值越大壓縮比越大
*/
-(id (^)(CGFloat))flex_shrink;
/**
設置或檢索彈性盒伸縮基準值。默認值為MyFlex_Auto表示由其他屬性決定,如果值為大於0小於1則表示相對值,其他為一個固定的尺寸值。
*/
-(id (^)(CGFloat))flex_basis;
/**
設置或檢索彈性盒子元素自身在側軸(縱軸)方向上的對齊方式。可選值為:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Baseline | MyFlexGravity_Stretch中的一個,默認值為MyFlex_Auto
*/
-(id (^)(MyFlexGravity))align_self;
@end
從上面的定義中可以看出因為其設置和使用方法都和flexbox規約幾乎保持一致,因此對於熟悉flexbox的人來說使用幾乎是零成本。比如我們用MyFlexLayout來實現下面這個界面:
代碼實現如下:
-(void)viewDidLoad{
[super viewDidLoad];
//用鏈式語法創建一個彈性布局,寬度和父視圖一致,高度自適應
MyFlexLayout *layout = MyFlexLayout.new.myFlex.flex_direction(MyFlexDirection_Row).flex_wrap(MyFlexWrap_Wrap).align_content(MyFlexGravity_Center).align_items(MyFlexGravity_Flex_End).vert_space(10).horz_space(10).padding(UIEdgeInsetsMake(10, 10, 10, 10)).marign_top(50).width(MyLayoutSize.fill).height(MyLayoutSize.wrap).addTo(self.view);
UILabel *itemA = UILabel.new.myFlex.width(MyLayoutSize.fill).height(30).addTo(layout);
UILabel *itemB = UILabel.new.myFlex.flex_grow(1).align_self(MyFlexGravity_Flex_Start).height(30).addTo(layout);
UILabel *itemC = UILabel.new.myFlex.flex_grow(1).height(40).addTo(layout);
UILabel *itemD = UILabel.new.myFlex.flex_grow(1).height(50).addTo(layout);
layout.backgroundColor = [UIColor grayColor];
itemA.text = @"A";
itemA.backgroundColor = [UIColor redColor];
itemB.text = @"B";
itemB.backgroundColor = [UIColor greenColor];
itemC.text = @"C";
itemC.backgroundColor = [UIColor blueColor];
itemD.text = @"D";
itemD.backgroundColor = [UIColor yellowColor];
}
除了使用鏈式語法進行布局和條目樣式設置外,還可以直接通過屬性賦值來進行樣式設置。您可以通過MyFlexBox中的attrs以及MyFlexItem中的attrs這兩個數據成員來以屬性值的形式進行布局的和條目的樣式設置。為了更好的演示MyFlexLayout的使用,我在MyLayout的Demo工程中建立了一個Flex布局(FlexLayout)。您可以在那裡看到彈性布局相關的所有操作。
2.最值約束?設想一個場景:某個視圖的寬度在豎屏下是屏幕寬度的一半,而在橫屏下則是屏幕高度的一半。換句話說就是視圖的寬度是屏幕寬度和高度中的最小值的一半。
?再設想一個場景:某個視圖的右邊位置希望跟另外兩個視圖中最靠右的那個位置對齊,換句話說就是視圖的右邊位置是另外兩個視圖右邊位置的最大值。
我們稱這種某個視圖的位置或者尺寸是一個位置集合或者尺寸集合中的最大值或者最小值的約束為最值約束。用表達式如下:
位置 = MAX(位置1,位置2,位置3,...) 或者 位置 = MIN(位置1,位置2,位置3,...)
尺寸 = MAX(尺寸1,尺寸2,尺寸3,...)或者 尺寸 = MIN(尺寸1,尺寸2,尺寸3,...)
MyLayout為了實現對位置最值的支持,在數組類NSArray上建立了一個擴展分類:
//位置最值擴展分類
@interface NSArray(MyLayoutMostPos)
//從數組中得到最小的位置值。要求數組的元素必須是MyLayoutPos或者NSNumber類型
@property(nonatomic, readonly) MyLayoutMostPos *myMinPos;
//從數組中得到最小的位置值。要求數組的元素必須是MyLayoutPos或者NSNumber類型
@property(nonatomic, readonly) MyLayoutMostPos *myMaxPos;
@end
我們可以通過數組中的myMinPos和myMaxPos兩個只讀屬性來分別獲取最小值和最大值的最值對象,獲取位置最值對象時要求數組中的元素只能是NSNumber以及MyLayoutPos類的實例對象,它表明最值是這些具體數字或者位置對象中的最大或者最小值。比如下面的代碼:
//A視圖的左邊位置是B視圖左邊位置,C視圖右邊位置,100這三個值中的最小的一個
A.leftPos.equalTo(@[B.leftPos, C.rightPos, @100].myMinPos);
//A視圖的垂直居中位置是B視圖頂部位置、100、C視圖底部位置這三個值中的最大一個。
A.centerYPos.equalTo(@[B.topPos, @100, C.bottomPos].myMaxPos);
//A視圖的左邊位置是B視圖左邊位置+20、C視圖右邊位置-20 這兩個位置中的最大一個。
A.leftPos.equalTo(@[B.leftPos.clone(20), C.rightPos.clone(20)].myMaxPos);
在上面的最後一個例子中我們看到使用了MyLayoutPos對象的clone方法,這個方法的作用是clone一個新的對象並帶上一定的偏移值。MyLayoutPos中的clone方法就是專門為最值約束使用的,主要為了解決那些獲取最值時希望在某個位置的偏移的場景。
目前只有相對布局下的子視圖才支持位置最值約束設置,其他布局下的子視圖不支持。同時在設置位置最值約束的時候,要求數組內的元素的位置約束計算必須要在當前視圖的位置約束計算之前完成,否則得到的結果將未可知。
尺寸最值MyLayout為了實現對尺寸最值的支持,在數組類NSArray上建立了一個擴展分類:
//尺寸最值擴展分類
@interface NSArray(MyLayoutMostSize)
//從數組中得到最小的尺寸值。要求數組的元素必須是MyLayoutSize或者NSNumber類型
@property(nonatomic, readonly) MyLayoutMostSize *myMinSize;
//從數組中得到最大的尺寸值。要求數組的元素必須是MyLayoutSize或者NSNumber類型
@property(nonatomic, readonly) MyLayoutMostSize *myMaxSize;
@end
我們可以通過數組中的myMinSize和myMaxSize兩個只讀屬性來分別獲取最小值和最大值的最值對象。獲取尺寸最值對象時要求數組中的元素只能是NSNumber以及MyLayoutSize類的實例對象,它表明最值是這些具體數字或者尺寸對象中的最大或者最小值。比如下面的例子:
//A視圖的寬度是B視圖的寬度,C視圖的高度,100這三個值中的最小的一個
A.widthSize.equalTo(@[B.widthSize, C.heightSize, @100].myMinSize);
//A視圖的高度是A視圖自身高度,B視圖高度的一半加20,100這三個值中的最大一個。
A.heightSize.equalTo(@[@(MyLayoutSize.wrap), B.heightSize.clone(20, 0.5), @100].myMaxSize);
在上面的最後一個例子中我們看到使用了MyLayoutSize對象的clone方法,這個方法的作用是clone一個新的尺寸對象並帶上一定的倍數和增量值。我們還可以用一個特殊的尺寸值MyLayoutSize.wrap在最值數組中,它表明自身的尺寸也參與最值比較中。
最值尺寸約束設置,可以應用在所有布局下的視圖中以及布局本身。但是在使用最值約束時,要求數組內的元素的尺寸約束計算必須要在當前視圖的尺寸約束計算之前完成,否則得到的結果將未可知。
3.視圖尺寸和位置的壓縮在一些場景中我們希望當所有子視圖的尺寸總和超過布局視圖的尺寸時為了能讓所有子視圖都得到完全的顯示而需要對子視圖的尺寸進行適當的壓縮,對於位置也是如此。這時候就需要應用到視圖尺寸和位置的壓縮技術了。舉例來說:假如一個橫向的水平線性布局的寬度是120,裡面的三個子視圖A,B,C的寬度和間距分別為:A左間距20,A寬度30, B左間距10,B寬度60, C左間距20,C寬度40。在不進行壓縮時界面顯示的效果如下:
為了實現壓縮的能力在MyLayoutSize和MyLayoutPos兩個類中分別提供了一個新的屬性shrink。這個屬性值的意義表明當位置和尺寸超過布局視圖時的壓縮比重值。值越大表明被壓縮的比重越大,值為0表明不會被壓縮。系統默認的壓縮比重值被設置為0。就以上面的例子來說假如我們分別設置視圖A,B,C的寬度和間距的壓縮比例值如下:
A.leftPos.equalTo(@20).shrink = 1;
A.widthSize.equalTo(@30).shrink = 1;
B.leftPos.equalTo(@10);
B.widthSize.equalTo(@50).shrink = 2;
C.leftPos.equalTo(@20).shrink = 1;
C.widthSize.equalTo(@40);
這樣在不壓縮的情況所有子視圖的間距和寬度總和為:20+30+10+50+20+40 = 170 ,減去布局視圖的寬度120後超出了50。而上述設置的壓縮比重值的總和為:1+1+2+1 = 5。因此最終的每個位置和尺寸被壓縮後的結果值分別為:
A的左間距 = 20 - 50 * (1/5.0) = 10
A的寬度 = 30 - 50 *(1/5.0) = 20
B的左間距 = 10 不會被壓縮
B的寬度 = 50 - 50 *(2/5.0) = 30
C的左間距 = 20 - 50 *(1/5.0) = 10
C的寬度 = 40 不會被壓縮
最終界面展示的效果如下:
目前只有線性布局、框架布局、流式布局、表格布局、彈性布局下的子視圖的寬度和尺寸才支持壓縮特性,其他布局中的子視圖不支持。而且壓縮的特性只有在所有子視圖的尺寸超出的時候才生效否則是不生效的。
需要注意的是彈性布局中的子視圖的壓縮特性一般不通過直接設置shrink屬性來實現,而是通過設置flex_shrink來實現。
視圖的壓縮屬性和視圖的weight屬性的區別是前者是用於視圖尺寸的壓縮,而後者則是用於視圖尺寸的拉伸。具體的weight屬性的使用請參考相關的文檔和DEMO。
4.環繞和拉伸停靠我們可以通過設置布局視圖的gravity屬性來設置布局內子視圖的整體停靠和對齊特性。在新版本中為了實現flexbox中的一些能力,特別增加了4個停靠屬性:
MyGravity_Horz_Around
MyGravity_Horz_Stretch
MyGravity_Vert_Around
MyGravity_Vert_Stretch
在以前的版本中如果我們希望拉伸子視圖之間的間距時可以通過MyGravity_Horz_Between或者MyGravity_Vert_Between來實現。拉伸間距時第一個以及最後一個子視圖離父布局視圖的間距將是0,而子視圖之間的間距將會平分剩餘的空間。而MyGravity_Horz_Around和MyGravity_Vert_Around則是第一個和最後一個子視圖離父布局視圖的間距是子視圖之間的間距的一半。下面的界面展示了Between和Around的區別:
尺寸的拉伸和環繞在以前的版本中如果我們希望填充拉伸所有子視圖之間的尺寸來佔滿布局視圖的尺寸時我們可以通過MyGravity_Horz_Fill或者MyGravity_Vert_Fill來實現。這兩個停靠屬性的功能會將布局視圖中的剩餘空間均勻的分配到所有子視圖(設置有尺寸自適應的布局視圖除外)的尺寸之上,而不管子視圖是否設置了尺寸約束與否,從而實現子視圖之間的尺寸拉伸效果。而MyGravity_Horz_Stretch以及MyGravity_Vert_Stretch則效果和填充是一樣的,只不過它只會拉伸那些沒有設置尺寸約束的子視圖以及設置了尺寸自適應的子視圖(設置了尺寸自適應的布局視圖除外)。下面的界面展示了Fill 和Stretch的區別:
目前只有線性布局、流式布局、浮動布局、框架布局、彈性布局中才具有整體停靠和對齊設置的效果,其他布局不支持。
5.布局中子視圖的拖放在一些應用中我們可以通過拖放功能來調整子視圖的位置或者進行一些其他處理。MyLayout以前的版本中實現了這麼一個DEMO。新版本中我們將DEMO中拖放的能力進行了抽象而形成了一個新的拖放類:MyLayoutDragger。在使用拖放類實現拖放功能時需要如下幾個步驟:
• 從布局視圖類中通過createLayoutDragger方法創建一個拖放類實例對象,並保存起來。
• 對添加到布局視圖中的子視圖分別添加如下事件:
[可以被拖放的子視圖 addTarget:self action:@selector(handleTouchDrag:withEvent:) forControlEvents:UIControlEventTouchDragInside]; //註冊拖動事件。
[可以被拖放的子視圖 addTarget:self action:@selector(handleTouchDrag:withEvent:) forControlEvents:UIControlEventTouchDragOutside]; //註冊外面拖動事件。
[可以被拖放的子視圖 addTarget:self action:@selector(handleTouchDown:withEvent:) forControlEvents:UIControlEventTouchDown]; //註冊按下事件
[可以被拖放的子視圖 addTarget:self action:@selector(handleTouchUp:withEvent:) forControlEvents:UIControlEventTouchUpInside]; //註冊抬起事件
[可以被拖放的子視圖 addTarget:self action:@selector(handleTouchUp:withEvent:) forControlEvents:UIControlEventTouchCancel]; //註冊終止事件
• 分別在對應的事件處理方法中,調用拖放器對象的相關方法:
- (IBAction)handleTouchDown:(id)sender withEvent:(UIEvent*)event {
//拖動子視圖開始處理。
[self.dragger dragView:sender withEvent:event];
}
- (IBAction)handleTouchUp:(id)sender withEvent:(UIEvent*)event {
//停止子視圖拖動處理。
[self.dragger dropView:sender withEvent:event];
}
- (IBAction)handleTouchDrag:(id)sender withEvent:(UIEvent*)event {
//子視圖拖動中處理。
[self.dragger dragginView:sender withEvent:event];
}
這樣就可以自動實現對子視圖的拖放功能了。我們還可以通過拖放器對象來進行一些特性化設置,比如可以設置拖放的動畫時長、可以設置哪些子視圖在拖放時不會移動、以及是否可以在拖放時實現懸停效果等等。具體的演示代碼請參考DEMO工程中的:FLLTest3ViewController
6.iOS13的黑白模式適配iOS13以後提供了黑白模式適配的能力。對於MyLayout來說因為具有對邊界線的支持的能力,邊界線內部實現是採用的CALayer來實現,而CALayer對顏色的輸入是CGColorRef對象,因此為了支持黑白模式適配也進行版本升級,以便讓邊界線也能實現黑白模式適配的能力。
7.流式布局的行內對齊控制在流式布局中我們可以通過設置gravity屬性和arrangedGravity屬性來設置布局內子視圖的整體停靠特性以及行內子視圖之間的對齊特性。然而在實際中我們可能希望某些行的停靠對齊屬性和其他行是不一樣的,也就是希望能夠定製每行的停靠對齊屬性。這樣通過行的停靠對齊屬性就可以不通過插入佔位視圖或者不需要進行多層嵌套來實現我們的界面需求。(如果用線性布局來實現多行多列則需要進行多個布局層次的嵌套處理)。就比如下面的這個界面:
為了支持行內對齊停靠自定義處理,流式布局提供了一個新的屬性:
/**
單獨為某一行定製的水平和垂直停靠對齊屬性,默認情況下布局視圖的gravity和arrangedGravity作用於所有行以及行內的停靠對齊。如果你想單獨定製某一行的停靠對齊方式時
可以通過設置這個block屬性。
lineGravity的入參分別是布局對象、當前行的索引(0開始)、當前行的條目視圖數量、是否是最後一行四個參數。
函數返回的是此行以及行內的停靠對齊方式,如果返回MyGravity_None則表示使用布局默認的gravity和arrangedGravity停靠對齊屬性。
*/
@property(nonatomic, copy) MyGravity (^lineGravity)(MyFlowLayout *layout, NSInteger lineIndex, NSInteger itemCount, BOOL isLastLine);
我們可以通過這個block的形式的屬性來進行行內停靠對齊的自定義處理。具體的行內對齊停靠的使用可以參考DEMO工程中的FLLTest4ViewController和FLLTest9ViewController
8.流式布局和浮動布局對基線對齊的支持新版本中對於垂直流式布局以及垂直浮動布局中的每一行子視圖之間新增加了對基線對齊的支持。你可以通過設置流式布局的arrangedGravity的值為MyGravity_Vert_Baseline。以及設置浮動布局的gravity的值為MyGravity_Vert_Baseline來實現行內的基線對齊。其中基線的標準視圖是行內的第一個文本視圖。這樣整個布局體系中水平線性布局、相對布局、垂直流式布局、垂直浮動布局、彈性布局都可以實現行內基線對齊的能力了。
9.布局動畫的支持和擴展動畫的適當使用會增強用戶的體驗效果。MyLayout中如果我們調整了子視圖的約束後希望有動畫效果,那麼可以調用布局視圖的方法:
/**
*設置布局時的動畫。並指定時間,選項,和完成時的處理,這個動畫只會在調用後的下次布局時執行一次。@param duration 指定動畫的時間間隔
*/
-(void)layoutAnimationWithDuration:(NSTimeInterval)duration;
-(void)layoutAnimationWithDuration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion;
上述的方法調用後系統會在下一個布局周期發生時自動執行動畫效果。在使用動畫方法時我們可以指定動畫的時長以及一些選項還有動畫完成後的回調處理。
10.完善和擴充視圖尺寸的自適應設置支持所謂尺寸自適應就是視圖的尺寸根據自身的內容和視圖內的子視圖的尺寸來動態確定自身的尺寸,從而形成所謂的包裹的效果。尺寸自適應的目的是為了讓視圖中的所有內容都得到完全的展示。
老版本的尺寸自適應設置視圖的自適應尺寸也算是一種特殊的尺寸。在老版本中如果我們想讓某個視圖的寬度自適應時可以通過設置wrapContentWidth 屬性為YES即可,而讓視圖的高度自適應時則可以通過設置wrapContentHeight屬性為YES即可。而要設置視圖的具體尺寸時則需要通過widthSize或者heightSize來實現。為了設置尺寸而分別使用兩個屬性來操作這是不合理的方式。因此新版本中不再建議使用wrapContentWidth和wrapContentHeight以及wrapContentSize來設置尺寸自適應了,而是建議使用新的設置方式。
新版本的尺寸自適應設置
新版本中將尺寸的自適應設置合併到了widthSize和heightSize中。因為自適應也是一種尺寸值,只不過是特殊值。下面的代碼是老版本和新版本的設置方法:
//老的方法
A.wrapContentWidth = YES;
//新的方法1
A.widthSize.equalTo(@(MyLayoutSize.wrap));
//新的方法2
A.myWidth = MyLayoutSize.wrap;
//老的方法:
B.wrapContentSize = YES;
//新的方法:
B.mySize = CGSizeMake(MyLayoutSize.wrap, MyLayoutSize.wrap);
//老的讀取和判斷的方法
if (A.wrapContentWidth) {}
//新的判斷和讀取的方法1
if (A.widthSize.isWrap){}
//新的判斷和讀取的方法2
if (A.myWidth == MyLayoutSize.wrap){}
在新版本中我們除了可以設置MyLayoutSize.wrap為尺寸自適應外,在MyLayoutSize類中還定義了另外兩個類屬性:MyLayoutSize.fill和MyLayoutSize.empty。它們也可以用來簡化尺寸的設置。
• MyLayoutSize.wrap:代表尺寸自適應
• MyLayoutSize.fill: 代表尺寸佔用父視圖的剩餘空間
• MyLayoutSize.empty: 代表清除尺寸約束
比如下面的代碼是等價的:
A.widthSize.equalTo(@(MyLayoutSize.wrap)) <==> A.myWidth = MyLayoutSize.wrap;
A.widthSize.equalTo(A.superview.widthSize) <==> A.myWidth = MyLayoutSize.fill;
A.widthSize.equalTo(nil) <==> A.myWidth = MyLayoutSize.empty;
由於語法篇幅MyLayout中的很多功能都沒有介紹到,如果您想進一步了解的話可以到github中下載對應的工程demo來進行詳細了解。
推薦閱讀• 如何對 iOS 啟動階段耗時進行分析• NNPopObjc:在 Objective-C 上進行面向協議的編程原理解析• Pecker:自動檢測項目中不用的代碼• Flutter 狀態管理指南篇 —— Provider• 5 個酷炫的 iOS 庫