上篇發出之後收集了一些反饋, 總結起來以下幾點:
- 沒有demo,程式碼沒有全部粘上來。
我認為這篇的內容不需要,不能我寫個弱引用的懶載入怎麼寫,還一定要把宣告weak屬性的程式碼放上來吧。 - 關於xib/strotyboard和純程式碼
我是兩種用過,實際工作中也基本都達到了熟練的程度,現在使用純程式碼也不是公司要求,自己覺得更好,其他的就不說了,這就相當於兩條路都行的通,任意一條走成老司機了另一條也沒問題,但是新人工作中最好純程式碼為主。 - 關於懶載入是否一定要
理解到位想怎麼搞怎麼搞,蘿蔔青菜各有所愛。
1 2 3 4 5 6 7 8 9 |
提綱:上篇說到第4條 1. 關於xib/storyboard 與 純程式碼的對比 2. 一條規範(又提了一點關於命名的) 3. UI工廠類 與 程式碼塊 4. 懶載入, View使用strong還是weak 5. 複雜介面要會分割槽,要會障眼法 6. masonry均布View,及其佈局時約束依賴關係 7. 關於螢幕適配的一點技巧 8. 迴圈引用(上篇文章有人對迴圈引用不理解,雖然是基礎,有人不理解還是說一下吧) |
5. 複雜介面要會分割槽,要會用障眼法
分割槽:什麼叫分割槽,其實就是封裝,幹啥其實都是一樣,UI網路邏輯思路有相同的地方,就包括收拾東西,為啥很多人喜歡把各種東西用各種盒子裝起來,假設現在要寫一個aView,上面是這樣的
這個要怎麼寫, 直接挨個建立直接往aView上加嗎, 這以後維護起來改點東西相信你死的心都會有的,一般這種元素有點多的都要適當的分一下區
這樣分割槽後,如圖所示,寫UI的時候就先依次單獨解決好上中下三部分,然後需要做的就是對上中下三部分的整體進行佈局,這一級佈局的時候就完全可以忽略他們內部的東西是什麼樣的,全部完成整體微調,該調裡面就裡面, 該調整體就整體
障眼法: 所謂障眼法就是投機取巧,當然可以有各種各樣的方法,把一些複雜功能簡化,不管用了什麼方法,最終看起來像是實現了就可以。
下面舉個例子, 這個例子是專案中的一個介面, 我簡化了一下抽出來, 這是一個消費記錄的介面, 有個tableview,每個cell如下所示,可以展開收起
看到這樣一個介面,首先不要考慮如何展開收起,就看一下展開的要怎麼寫,(演示Demo中的UI因為沒有使用網路資料,也為了演示方便,做了簡化,實際賬單消費下面還有一部分如何消費,可獲得什麼等等的區域),參照上一條,這種一個View裡元素較多的時候可以先分割槽如下:
先假設展開狀態已經寫好了,下面要考慮如何收起,觀察UI發現收起狀態的資訊是展開狀態中的主要資訊, 如圖元素其實表達的是同樣資訊
那麼難道要打破布局,將這幾個view找到重新佈局,其他的隱藏掉嗎?那再點選回到展開狀態怎麼辦,在重新佈局?想想就麻煩
所以,這時再搞一個summaryView,負責收起的資訊展示,這個View內部的時間桌號等控制元件,跟展開狀態的時間桌號雖然長的一樣,但是實際是兩個不同的UI物件。
所以完成之後,這個View裡會有如下幾大塊
- summaryView (收起的View)
- expendBgView(展開時的整體View)
- topView (這樣命名不好)
- midView
如此佈局,在點選了View要展開/收起的時候,只需要轉換summaryView和expendBgView的隱藏狀態,改變一下最外層View的底部約束即可
demo地址:https://github.com/CoderLXWang/LayoutViewDemo
6. masonry均布View,及其佈局時約束依賴關係
均布View: 等間距佈局 – 從0開始說一下masonry的使用
約束依賴關係:這個標題其實比較寬泛,也說不好,如何寫約束本身就是比較靈活的,每個人的寫法可能都不一樣,下面舉兩個例子大概說一下,
示例1:
這個很簡單, 左右間距都是30,第二三四行View的左右約束該怎麼寫,都寫下面的嗎?這樣寫如果要改這個30,就瞎了
1 2 |
make.left.equalTo(父檢視).offset(30); make.right.equalTo(父檢視).offset(-30); |
因為這裡的設計就是左右都要對其,所以下面都都依靠第一個佈局即可,第二行兩個不是左右都對其
1 |
make.left.right.equalTo(父檢視).offset(第一個View); |
程式碼少了一行是其次,主要是改的話只改一個,也可以透過程式碼看到這個地方的設計
注:這個示例很簡單,勿噴,主要說這種做法,複雜佈局也需要考慮到底依靠那個View佈局,具體體況多體會,簡單說就是要選取合適的依賴物件
示例2:
需求:
1.整體居中
2.寬度可變,看文字是否夠一行,最寬左右內邊距10
3.內部兩個View的centerY對其
4.最小高度為圖片高度,文字高度超度圖片,就以文字高度為準
直接上程式碼,只為說明約束,不要找別的毛病,具體自己看吧,這裡Label和ImageView一定要作為一個整體(即放到同一個父檢視中),內部因為圖片相對固定,左右尺寸都不變,要先佈局圖片才可以,否則Label沒有可以依賴的東西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
@interface ViewController () @property (nonatomic, strong) UIView *containerView; @end @implementation ViewController - (UIView *)containerView { if (!_containerView) { _containerView = [[UIView alloc] init]; _containerView.backgroundColor = [UIColor orangeColor]; UIImageView *imgView = [[UIImageView alloc] init]; imgView.image = [UIImage imageNamed:@"demo1.jpeg"]; [_containerView addSubview:imgView]; [imgView mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_containerView).offset(0); make.size.mas_equalTo(CGSizeMake(100, 100)); make.centerY.equalTo(_containerView); }]; UILabel *label = [[UILabel alloc] init]; label.numberOfLines = 0; label.text = @"這是阿三衝擊紅進口付出dsk紅進口付出ds口付出dsk紅進口付出dskjfhks口付kjfhks口付出dskj紅進口付出dskjfhks口付出dskjjfhks口付出dskjfhd付出dsk紅進口付出ds口付出dsk紅進口付出dskjfhks口付kjfhks口付出dskj紅進口付出dskjfhks口付出dskjjfhks口付出dskjfhdjfhdksjhfdk"; [_containerView addSubview:label]; [label mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(imgView.mas_left).offset(-20); make.left.equalTo(_containerView).offset(0); make.bottom.top.equalTo(_containerView).offset(0); make.height.mas_greaterThanOrEqualTo(100); }]; } return _containerView; } - (void)viewDidLoad { [super viewDidLoad]; [self.view addSubview:self.containerView]; [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.view).offset(200); make.centerX.equalTo(self.view); make.width.mas_lessThanOrEqualTo([UIScreen mainScreen].bounds.size.width-20); }]; } |
7. 關於螢幕適配的一點技巧
首先說一個螢幕適配到底是什麼, 工作中很多人,甚至產品都搞錯了
所謂螢幕適配,並不是大屏就要將UI變大,而是要顯示更多的內容。
再說一個關於按鈕的寫UI原則
按鈕設計的大沒啥可說,如果設計的按鈕很小,到程式設計師手裡一定要讓它看起來小,點起來大
KRATE :當然在這一基本原則下,有的時候大屏上的某些元素和小屏保持同樣大小會有一些難看,這時還是要分別對待,如果以5s螢幕尺寸為基準(也有用6的尺寸做基準的,都一樣,習慣問題),這裡一般會定義這樣一個巨集
1 |
#define KRATE (SCREEN_WIDTH/320.0) |
舉個例子
不做比例的適配,在6p上如圖
其實也沒啥問題,看起來也沒有很不協調的地方,但是注意看一下券左右兩條豎直虛線,會發現大屏上左面券命和右面列印的寬度會比較小,中間區域顯得過大,應該稍微勻一點給左右兩邊,兩邊看起來會不那麼擠,同時右側點選範圍也會相應放大,做法就是將左右約束的值*KRATE
1 2 3 4 5 6 7 8 9 10 11 12 |
[self.leftLine mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.bgView).offset(80*KRATE); make.top.equalTo(self.bgView).offset(15); make.bottom.equalTo(self.bgView).offset(-15); make.width.mas_equalTo(1); }]; [self.rightLine mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(self.bgView).offset(-40*KRATE); make.top.bottom.equalTo(self.bgView).offset(0); make.width.mas_equalTo(1); }]; |
乘上比例之後,效果如圖
為什麼會有這種問題產生, 其實也是由於個人的佈局習慣引起的,在佈局這個UI時,我是先將左右兩條虛線定位好,內部的東西根據虛線的位置確定,而虛線的位置就是一塊白色背景左右給定值佈局的,所以換到大屏會左右寬度不變,這種情況乘個KRATE就可以了。
麻煩有些人不要來噴啥你寫就不會有這個問題,按比例分割槽怎樣的,那是你的方法,你要是按比例分割槽不也要想到底給0.幾的比例嗎,要是豎直方向在scollView裡也有這個問題呢
KKRATE:因為KRATE是用不同螢幕的寬度算出的一個係數,假設某個寬度5s上寬度為10,KRATE後6上則為10375/320=11.7, 也就是螢幕大1號之後原本為10的寬度增大了1.7, 那麼這個寬度如果是40呢,40*374/320 = 46.9。
為了解決小寬度 ×KRATE基本沒效果或者寬度大 ×KRATE 又過分了的問題,又定義了這樣一個巨集,給KRATE在乘一個自己制定的係數,感覺沒效果就給KKRATE傳個大於1的係數,感覺過分了就KKRATE(0.95),這裡注意傳入的係數小於0.86就反而大屏UI更小了,這裡不想在裡面繼續寫個三目運算子判斷了,就這樣了
1 2 |
/** 在螢幕比例基礎上再次比例, 大於0.86, 否則反而變小 */ #define KKRATE(rate) (KRATE > 1 ? KRATE*rate : KRATE) |
拿一個介面舉個例子,如圖
這裡左右看起來窄窄的間距用的都是6dp,6dp如果直接*KRATE基本沒用,乘完也就加一個dp,效果基本就是如下,螢幕很大,間距很小氣,也許你會說小屏上也小氣,設計說了,你不懂,正好
如果將各處左右間距設定為
1 2 |
make.left.equalTo(ws.view).offset(6*KKRATE(1.8)); make.right.equalTo(ws.view).offset(-6*KKRATE(1.8)); |
效果如下
明顯大氣了許多。。。
8. 迴圈引用(上篇文章有人對迴圈引用不理解,雖然是基礎,有人不理解還是說一下吧)
這部分是計劃外的,因為上篇有不少同學問起這個東西,發現不少人對看似簡單的迴圈引用概念還是比較模糊,所以我就拿出來說一下,我會分別解釋一下常見的迴圈引用,以及代理,block中的迴圈引用問題,這裡只做理解解釋,沒有深入研究,大神直接略過吧。
先說一下記憶體管理,大家都知道記憶體管理在MRC下要手動寫retain,release等程式碼,操作一個物件的引用計數,以此控制物件持有及釋放,ARC下編譯器會自動新增retain/release等程式碼,ARC的一個基本規則就是,只要某個物件被任一strong指標指向,那麼它將不會被銷燬。如果物件沒有被任何strong指標指向,那麼就將被銷燬。
所以當前我們的程式碼基本都是ARC,當我們研究一個物件是否迴圈引用時,也就不考去考慮計數到底為幾,什麼時候retain,什麼時候release,我們只需要按照ARC的基本原則關心指向這個物件的strong指標。
下面就按這個基本原則解釋一下迴圈引用,觀察是否釋放在控制器列印dealloc方法即可
示例1:簡單粗暴無邏輯演示
有一個控制器SampleRetainCycleController *retainVC
,retainVC.view
上面有個SampleRetainCycleView *testView
,testView
有個強引用指標,指向retainVC
,看起來貌似迴圈成一個圈了,這就是迴圈引用嗎?貌似怪怪的,因為少了一個引用
實際上少了一個引用關係,沒有考慮retainVC是那裡來的,retainVC被建立之後載入nav導航棧裡是被navController強引用的,這是我們就可以按ARC的原則分析了,就是看線,找實線,這裡我們關心的是控制器會不會被正常釋放,那我們就看控制器有幾根實線,這時就會發現有兩根,pop出去的時候,上面的那條nav的線斷了,但是還有一條View的線,所以控制器就不會被釋放
示例2:代理為什麼用weak宣告
如圖就是代理為什麼用弱引用,如果用強引用就變成示例1的情況了
示例3:一般使用block為什麼注意迴圈引用,使用weakSelf
先說為什麼block一定要用copy,既然會迴圈引用,那麼就像代理一樣,使用弱引用的指標不行嗎?
詳細看這篇文章吧 Block為什麼使用copy修飾,
更詳細可以看這篇談Objective-C block的實現
總結起來就是為了使其存放在堆中,如果不copy一下,block是存放在棧中的,出了建立它的作用域,就可能被釋放掉,但是用了copy,對這個block就是強引用,所以需要注意迴圈引用,使用weakSelf。
那什麼是weakSelf,block預設對內部引用的外部變數是強引用,所以如果直接使用了self,則相當於block有一條實線(強指標)指向self,則self又有兩條實線了
示例4:什麼樣的block不會造成迴圈引用
最常見的就是系統的一些block與masonry,系統的比如:
1 2 3 |
[UIView animateWithDuration: animations:^{ }]; |
首先是self(假設是當前的控制器)並沒有copy(強引用)這個block,其次這還是個類方法,類方法裡不能對屬性進行復制,即也不能強引用這個block,所以直接用self(即block對self強引用)也不會形成迴圈引用
再比如masonry
1 2 3 |
[self.containerView mas_makeConstraints:^(MASConstraintMaker *make) { }]; |
看一下mas_makeConstraints是怎麼寫的
1 2 3 4 5 6 |
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; block(constraintMaker); return [constraintMaker install]; } |
只有第三行執行了一下這個block,並沒有任何引用的程式碼(即類似self.xxblock = block),所以根據上面的幾篇文章,這種block是存放在棧上的,出了作用域(即這個方法)就會被釋放掉,既然block都被釋放掉了,自然不會迴圈引用。
demo地址:https://github.com/CoderLXWang/RetainCycleDemo
上篇地址:關於如何寫UI及螢幕適配的一些技巧(上)
碼字不易,共同進步,歡迎提意見。