1. 程式人生 > >iOS 利用autolayout自定義行高仿寫朋友圈介面,OC與Swift版本

iOS 利用autolayout自定義行高仿寫朋友圈介面,OC與Swift版本

在此,本文章將會提到並用到的知識

1、tableview預設行高與自動佈局autolayout綜合使用,
2、autoLayout佈局在tableviewCell裡的約束細節。
3、關於MVC,tableview的解耦與封裝。

1、tableview的朋友圈佈局

微信朋友圈截圖,接下來我們將要去實現它,分別用OC跟Swift
那麼,先實現第一步,tableview的佈局。裡面的結構不多說了,各個結構部分,頭像,使用者名稱稱,釋出內容,以及圖片,還有點贊評論區域。關於釋出內容是一個label,這也是利用自動佈局不計算高度的關鍵所在,無論label裡多少內容,通過預設cell高度的方法,使其高度不需要開發者計算而是利用autolayout系統計算的方法獲得。
對於圖片的展現,用collectionview的方法,那麼如何根據圖片有無圖片數目來判斷collectionview的高度,我的方法是控制collectionview的高度約束線。如果有更好的方法請指導我。對於底部評論區域我用的是一個tableview,通過評論點贊按鈕控制其顯示,對於tabeview的高度,我也是用控制約束線的方法,不過應該也是有更加優秀的方法,其cell的高度則是通過使用autolayout和tableview的預設高度來設定。
通過分析朋友圈主要的檢視結構,我們主要應用到三點。1、通過autoLayout自動佈局以及tableviewcell的預設高度來系統計算cell的行高,2、通過約束線控制檢視的高度。主要的還是在控制cell的行高上。接下里我們會分層次一步一步的進行分析。

2、autoLayout與tableview預設行高結合控制cell的行高

我所要是實現的就是,通過結合autoLayout跟設定tableview預設行高來實現自適應cell的高度,我會通過控制cell裡兩個檢視的高度來做到不同cell有不同高度。
簡單效果圖
看一下約束線描述
約束線描述
簡單程式碼,主要是為了實現效果,
- #pragma mark - UITableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 6;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

demoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"demoTableViewCell" forIndexPath:indexPath];
   cell.greenViewHeight.constant = [_viewHeight[indexPath.row] intValue];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;

}

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 200;
}

通過觀察程式碼,我通過控制greenViewHeigh的值,來確保每個cell的高度不一樣,然後做了一個cell預設高度的代理,利用autolayout來實現cell高度的計算,很簡介。

兩個佈局坑

1、tableview屬性中的Row Height必須設定為44,哪怕是45系統也不會計算出來的。如圖
tableview RowHeight必須設定為44
2、關於約束衝突的問題,你可能會遇到,也可能不會遇到,即便是遇到了也不會影響佈局,但是理清思路很重要,autolayout計算cell是怎麼計算cell的高度的,它跟程式碼控制的關係是怎樣的。我接下來會高度大家。而對於這種情況,一般只需控制約束進行的優先順序就好。如圖:
約束線優先順序

我是通過減小上下兩個view之間約束來保證不會有約束衝突。那對於用autoLayout自動算行高跟控制view約束線大小,很顯然,當我們在程式碼控制完成之後,autoLayout 才會計算cell的行高,此時,會跟據Prioorty的優先順序進行計算,當約束線多,特別是在特殊的cell裡時,因為我們是需要通過內容充實cell的高度的,也就有可能出現約束衝突的情況,即便是你約束的沒有誤差,所以我們做約束的優先順序,這樣的話就不會有約束衝突的情況了。

明白了這個的實現方式以及注意事項,對於朋友圈的檢視佈局就顯得有思路了。

理解了這種在tableview裡利用autoLayout佈局來確定行高的方式,以及注意事項,在佈局的時候強調一點是,高度的約束一定要充滿整個cell,也就是在高度方面跟tableview裡的cell保和。接下來我們就是向tableview里加載label,collectionView,評論的tablview等一系列的元素。先用OC的方法來實現:

佈局圖例

tableviewCell中的約束線

我用顏色塊區分各個區域,明顯的看出各個部分。值得注意的是,我是秉承內容驅動cell高度的佈局,在佈局上能夠確保cell的高度確定,否則會出現cell顯示不全內容的情況。

佈局基本完成,接下來就是考慮檢視的實現邏輯。關鍵點主要在圖片的顯示,以及評論區域的顯示和各自的高度的方面,這個是需要我們依據內容來進行高度的判斷的。所以,要求有一個很好的資料結構來支援。對於實現,上面部分以及提及了,根據要顯示的內容通過改約束線的高度更改各種區域的高度,從而來通過autoLayout的方式來驅動cell的高度。

主要是通過獲取的資料來搞定collectionView的高度跟評論區域tableview的高度。在此,我貼出根據image的數目來判斷collectionView高度的方法。
if(_collectionNum == 0) {
//沒有圖片,collectionView的高度約束線為0,
_contentCollectionHeight.constant = 0
}else {
if (_collectionNum == 3) {
//1-3 個圖片的情況,
_contentCollectionHeight.constant = ([UIScreen mainScreen].bounds.size.width - 90 - 8) / 3 ;
}else if(_collectionNum == 6) {
_contentCollectionHeight.constant = ([UIScreen mainScreen].bounds.size.width - 90 - 8) / 3 * 2 + 4;
}else if(_collectionNum == 9){
_contentCollectionHeight.constant = ([UIScreen mainScreen].bounds.size.width - 90 - 8) / 3 * 3 + 8;
}else {
_contentCollectionHeight.constant = ([UIScreen mainScreen].bounds.size.width - 90 - 8) / 3 * 1.5;
}
[_contentCollection reloadData];
}

而對應的,我們在確認collectionView的代理中,cell的長跟寬我是這樣做的。
//設定每個 UICollectionView 的大小
- (CGSize)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
//由圖片個數確定UICollectionView的大小
if (_collectionNum == 1) {
//如果只有一個圖片,則確定圖片的大小
return CGSizeMake((self.contentCollection.frame.size.width - 8 )/ 3 * 2.2,(self.contentCollection.frame.size.width - 8 )/ 3 * 1.5);
}else{
return CGSizeMake((self.contentCollection.frame.size.width - 8 )/ 3,(self.contentCollection.frame.size.width - 8)/ 3);
}
}

frame佈局的坑

關於MVC,swift與oc程式碼比較

我們從程式碼中獲取自定佈局中控制元件frame的width跟height,如果是在建立檢視的時候,我們獲取到frame的width跟height是1000,而在載入完成後我們獲取的是正確的frame,所以在載入的時候如果需要獲取空間的frame注意不要通過空間frame的形式。

其實對於oc的語法已經講過了,在這裡說一些swift的語法。swift語法跟oc的語法是不相同的,但是思路是類似的甚至說是相同的,我們在oc裡的大部分邏輯已經實現思路,在swift裡還是可以用到的,但是我還是會進一步的把主要程式碼貼出來,一起優化探究。

在用到tableview時,我們會想到MVC模式,我們也用swift裡的MVC實現吧。

關於V,上文的stroboard已經給出來了,跟在oc裡是一樣的,M,model,簡單的列出程式碼,不全,只是為了體現思路

import UIKit
class autoTableModel: NSObject {
var name:NSString!
var content:NSString!
}

那麼我們對比oc裡的model

#import <Foundation/Foundation.h>
@interface tableObjModel : NSObject
@property (strong, nonatomic) NSString*userName;
@property (strong, nonatomic) NSString
*userContent;
@end

單從程式碼簡潔上,swift的確實比oc好多了

我們下面來在controller裡面的內容,特別是tableview的代理。

var contentLabelArray = ["我終於醒來了,我的青春不在了。我不該哭泣,我該鼓掌。請你也為我鼓掌。","不爭,是一種慈悲 不辯","境明,千里皆明 心美,一切皆美 情深,永珍皆深","每晚和繁星相對,我把它們認得很熟了"]
var username = ["開家小店","尤克里裡","莫子","西詩"];
var tableDataModelArray = [autoTableModel]()     //定義autoTableModel的陣列。

這是我們自定義的假資料,我們也定義了一個model型別的陣列,下面我們來儲存資料

 for i in 0..<username.count {
        let tableModel = autoTableModel()  //定義model
        tableModel.name = username[i] as NSString!
        tableModel.content = contentLabelArray[i] as NSString!
        tableDataModelArray.append(tableModel)
    }

這段程式碼,定義一個裡面包含有model型別的陣列,實現思想大致是不變的,只是在程式碼的表達上跟oc是有不同的,所以只是熟悉swift程式碼表達的意思對於oc玩家來說就可以了,跳過了邏輯層面,swift並不難。
重點來了,tableview的幾個代理方法

 //顯示cell多少行
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return 6
}
//繪製cell
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "AutoTableViewCell", for: indexPath) as! AutoTableViewCell
    cell.headImage.image = UIImage(named: "圖片05")
    cell.tableViewModel = tableDataModelArray[indexPath.row]
    //cell.userContent.text = contentLabelArray[indexPath.row]

    return cell
}
//選中cell
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("選中\(indexPath.row)")
}
//預設行高
@objc(tableView:estimatedHeightForRowAtIndexPath:) public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return 100
}

這些tableview的代理,跟oc的思路一樣,只不過表達方式改變了一些, 可以對應研究。我在定義cell的代理中,寫了 cell.tableViewModel = tableDataModelArray[indexPath.row]
這樣的程式碼,我是需要在tableviewCell裡實現的,我不想在這個controller裡實現。另外,如果需要利用autoLayout的方法得到行高,需要走預設行高的代理,而不是自定義行高的代理。這點注意。

下面展示tableviewCel裡的程式碼,在這裡是需要實現展示圖片的collection跟評論區域的tableview的。對,我是在這裡寫的。

import UIKit
class AutoTableViewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource, UICollectionViewDelegate, UICollectionViewDataSource{
@IBOutlet var headImage: UIImageView!
@IBOutlet var userName: UILabel!
@IBOutlet var userContent: UILabel!
@IBOutlet var imageCollection: UICollectionView!
@IBOutlet var commentsTableView: UITableView!
@IBOutlet var collectionViewHeight: NSLayoutConstraint!
@IBOutlet var commentsViewHeight: NSLayoutConstraint!
var contentLabelArray = ["我終於醒來了,我的青春不在了。我不該哭泣,我該鼓掌。請你也為我鼓掌。","不爭,是一種慈悲 不辯,是一種智慧 不聞,是一種清淨 不看,是一種自在","說的有有理"]
var tableViewModel:autoTableModel {
    set {
        userName.text = newValue.name as String?            //注意newValue
        userContent.text = newValue.content as String?
    }
    get {
        return self.tableViewModel
    }
}
override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
    commentsTableView.delegate = self;
    commentsTableView.dataSource = self;
    imageCollection.delegate = self;
    imageCollection.dataSource = self;
}
override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    // Configure the view for the selected state
}

我並沒有展示全,我只是想主要展示

 var tableViewModel:autoTableModel {
    set {
        userName.text = newValue.name as String?            //注意newValue
        userContent.text = newValue.content as String?
    }
    get {
        return self.tableViewModel
    }
}

這個方法,我們在AutoTableViewCell檔案裡定義處理,在controller檔案裡存入資料來源。此外關於在cell裡的collection跟tableview如果處理以及一些資料結構,我們下次再說。