HYBSnapkitAutoCellHeight開源自動算行高Swift版

jopen 8年前發布 | 15K 次閱讀 開源 Swift Apple Swift開發

前言

最近還是有不少朋友老問Swift版的自動計算行高怎么做,大家使用SnapKit來自動布局時,都希望能夠自動地計算出行高,不用每次都自己去算一篇。

本篇介紹筆者所開源的基于SnapKit這套自動布局庫而寫的一個擴展,用于自動計算行高。最重要的是,只要約束正確,就可以實現自動計算行高,而且當我們需要動態修改約束時,只要統一放在配置數據的API那里修改約束一樣可以計算出正確的高度。

demo效果

千言萬語,不如一個demo效果圖,看完效果圖再繼續往下看:

庫名HYBSnapkitAutoCellHeight

名稱命名為HYBSnapkitAutoCellHeight,主要是區別于Masonry版本。關于Masonry自動計算行高的版本,大家可以閱讀: HYBMasonryAutoCellHeight ,它是Objective-C寫的。

設計思路

既然我們使用的是自動布局,那么就可以利用自動布局來準確地計算出位置。要想通過自動布局立即得到位置和大小,那么就需要調用 layoutIfNeeded 方法,這樣就可以獲取所有控件的 frame 了。

既然可以通過此方法獲取到所有控件的 frame 了,那么我們若指定某一個視圖為最后一個視圖,作為參考,那么就可以直接通過獲取該視圖的frame來計算高度了。但是,如果我們并不是與指定的視圖的位置平齊怎么辦?沒有關系,我們提供了另外一個屬性用于設置最后一個參考視圖與cell的最底部的間隔是多少。

我們指定如下兩個屬性:

 
publicvarhyb_lastViewInCell: UIView? {
  get {
    letlastView = objc_getAssociatedObject(self, &__hyb_lastViewInCellKey);
    return lastViewas? UIView
  }
  
  set {
    objc_setAssociatedObject(self,
      &__hyb_lastViewInCellKey,
      newValue,
      .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
}
 
publicvarhyb_bottomOffsetToCell: CGFloat? {
  get {
    letoffset = objc_getAssociatedObject(self, &__hyb_bottomOffsetToCell);
    return offsetas? CGFloat
  }
  
  set {
    objc_setAssociatedObject(self,
      &__hyb_bottomOffsetToCell,
      newValue,
      .OBJC_ASSOCIATION_ASSIGN);
  }
}
 

這兩個是通過runtime添加的屬性,因為擴展并不能添加屬性。由于swift中關聯值所使用的key是個指針,因為我們的參數應該是傳一個地址,比如我們定義的key是 __hyb_lastViewInCellKey ,它是String類型的,而String類型是結構體類型,但是實際應該是要傳一個指針,因此需要傳這個字符串的地址過去。

單API

對外只提供了一個API,而且還是類方法:

 
/**
唯一的類方法,用于計算行高

- parameter indexPath: index path
- parameter config:        在config中調用配置數據方法等

- returns: 所計算得到的行高
*/
publicclass funchyb_cellHeight(forIndexPathindexPath: NSIndexPath,config: ((cell: UITableViewCell) -> Void)?) -> CGFloat {
  letcell = self.init(style: .Default,reuseIdentifier: nil)
  
  if letblock = config {
    block(cell: cell);
  }
  
  return cell.hyb_calculateCellHeight(forIndexPath: indexPath)
}
 

比如,在demo中我們這樣調用的:

 
// MARK: UITableViewDelegate
functableView(tableView: UITableView, heightForRowAtIndexPathindexPath: NSIndexPath) -> CGFloat {
  letmodel = self.dataSource[indexPath.row]
  
  return TestCell.hyb_cellHeight(forIndexPath: indexPath,config: { (cell) -> Void in
    letitemCell = cellas? TestCell
    itemCell?.config(testModel: model)
  })
}
 

是不是非常簡單呢?這樣就不用擔心行高的問題了。之前我所有的項目中都是使用自己封裝的OC版本基于Masonry寫的擴展,很好用的哦!

如何在cell中自動布局

我們下面來看看要使用這個擴展,應該如何布局呢?它有四大必須,一個可選條件:

  1. 必須在 override init(style: UITableViewCellStyle, reuseIdentifier: String?) 中布局
  2. 必須指定 self.hyb_lastViewInCell
  3. 對于UILabel控件,必須指定 preferredMaxLayoutWidth ,而且它的值要與自動布局所指定計算得到的寬必須保持一致,否則都會導致計算有誤差。之前Masonry版本的,指定這個屬性只是為了適配iOS6,但是對于SnapKit版本,必須要指定。看來現在是SnapKit版本還不夠完善。
  4. 可選指定 self.hyb_bottomOffsetToCell ,默認為0

下面是demo中一個例子,這里是按順序添加約束的。

 
overrideinit(style: UITableViewCellStyle,reuseIdentifier: String?) {
  super.init(style: style,reuseIdentifier: reuseIdentifier)
  
  self.contentView.addSubview(headImageView)
  headImageView.snp_makeConstraints { (make) -> Void in
    make.left.top.equalTo(15)
    make.width.height.equalTo(80)
  }
  headImageView.image = UIImage(named: "head")
  
  // title
  self.contentView.addSubview(titleLabel)
  titleLabel.numberOfLines = 0
  titleLabel.font = UIFont.systemFontOfSize(26)
  titleLabel.snp_makeConstraints { (make) -> Void in
    make.left.equalTo(headImageView.snp_right).offset(15)
    make.right.equalTo(-15)
    make.top.equalTo(headImageView)
  }
  
  // 若不指定preferredMaxLayoutWidth屬性,則計算會不準備,使用Masonry時,指定此屬性
  // 是特別適配iOS6的,不過使用SnapKit則必須指定,否則自動計算的高度會不準確
  letwidth = UIScreen.mainScreen().bounds.size.width
  titleLabel.preferredMaxLayoutWidth = width - 30 - 15 - 80;
  
  self.contentView.addSubview(descLabel)
  descLabel.numberOfLines = 0
  descLabel.font = UIFont.systemFontOfSize(22)
  descLabel.snp_makeConstraints { (make) -> Void in
    make.left.right.equalTo(titleLabel)
    make.top.equalTo(titleLabel.snp_bottom).offset(15)
  }
  descLabel.preferredMaxLayoutWidth = titleLabel.preferredMaxLayoutWidth
  
  descLabel.userInteractionEnabled = true
  lettap = UITapGestureRecognizer(target: self,action: Selector("onTapDesc"))
  descLabel.addGestureRecognizer(tap)
  
  self.contentView.addSubview(blogSummaryLabel)
  blogSummaryLabel.numberOfLines = 0
  blogSummaryLabel.font = UIFont.systemFontOfSize(22)
  blogSummaryLabel.snp_makeConstraints { (make) -> Void in
    make.left.right.equalTo(descLabel)
    make.top.equalTo(descLabel.snp_bottom).offset(15)
  }
  blogSummaryLabel.preferredMaxLayoutWidth = titleLabel.preferredMaxLayoutWidth
  
  lettap1 = UITapGestureRecognizer(target: self,action: Selector("onTapBlog"))
  blogSummaryLabel.userInteractionEnabled = true
  blogSummaryLabel.addGestureRecognizer(tap1)
  
  self.contentView.addSubview(okButton)
  okButton.setTitleColor(UIColor.whiteColor(),forState: .Normal)
  okButton.setTitle("計算高度的參考",forState: .Normal)
  okButton.backgroundColor = UIColor.greenColor()
  okButton.snp_makeConstraints { (make) -> Void in
    make.right.equalTo(-15)
    make.height.equalTo(45)
    make.top.equalTo(blogSummaryLabel.snp_bottom).offset(15)
  }
  
  blogSummaryLabel.backgroundColor = UIColor.redColor()
  descLabel.backgroundColor = UIColor.greenColor()
  
  letlineLabel = UILabel()
  lineLabel.backgroundColor = UIColor.lightGrayColor()
  self.contentView.addSubview(lineLabel)
  lineLabel.snp_makeConstraints { [unownedself] (make) -> Void in
    make.height.equalTo(1)
    make.left.equalTo(15);
    make.right.equalTo(0)
    make.bottom.equalTo(self.contentView)
  }
  
  // 指定最后一個視圖,作為計算高度的參考
  self.hyb_lastViewInCell = okButton
  self.hyb_bottomOffsetToCell = 15
}
 

如何動態修改約束

當cell要有動態地展開、收縮等功能時,每次都自己去計算?每次修改約束很復雜?不,不,不!其實很容易的。下面看看筆者是如何做到的。demo提供了展開與收起的功能,因此使用兩個變量來記錄是展開還是收起,而且還使用了是否是同一種狀態的判斷,防止每次都重新更新,這樣體驗會好很多哦。因為我們這里只是簡單地對label操作,展開時有多少字就顯示多少,因此不能手動指定其高度。收起時,只能指定其高度,因此在收起時通過更新約束API就可以添加高度約束了。但是對于展開并不能直接使用更新約束API去掉收起操作所指定的高度,因此只能使用移除之前的所有約束而重新添加約束的API來完成:

 
// MARK: Public
funcconfig(testModelmodel: TestModel) {
  titleLabel.text = model.title
  descLabel.text = model.desc
  blogSummaryLabel.text = model.blog
  
  if model.isExpand1 != self.isExpand1 {
    self.isExpand1 = model.isExpand1
    
    if self.isExpand1 {
      descLabel.snp_remakeConstraints(closure: { (make) -> Void in
        make.left.right.equalTo(titleLabel)
        make.top.equalTo(titleLabel.snp_bottom).offset(15)
      })
    } else {
      descLabel.snp_updateConstraints(closure: { (make) -> Void in
        make.height.lessThanOrEqualTo(55)
      })
    }
  }
  
  if model.isExpand2 != self.isExpand2 {
    self.isExpand2 = model.isExpand2
    
    if self.isExpand2 {
      blogSummaryLabel.snp_remakeConstraints(closure: { (make) -> Void in
        make.left.right.equalTo(descLabel)
        make.top.equalTo(descLabel.snp_bottom).offset(15)
      })
    } else {
      blogSummaryLabel.snp_updateConstraints(closure: { (make) -> Void in
        make.height.lessThanOrEqualTo(55)
      })
    }
  }
}
 

最后

寫代碼不易,開源更不易,且用且珍惜!當使用過程中出現bug時,請一定要反饋到原作者這里,一起來維護和完善此開源小項目。

源代碼

支持cocoapods,大家可以通過下面的命令放到Podfile中:

 
pod 'HYBSnapkitAutoCellHeight', '~> 1.0.0'
 

或者直接到GITHUB下載源代碼,將HYBSnapkitAutoCellHeight文件夾放到工程中: HYBSnapkitAutoCellHeight開源

關注我

如果在使用過程中遇到問題,或者想要與我交流,可加入有問必答 QQ群: 324400294

關注微信公眾號: iOSDevShares

關注新浪微博賬號:標哥Jacky

標哥的GITHUB地址: CoderJackyHuang

支持并捐助

如果您覺得文章對您很有幫忙,希望得到您的支持。您的捐肋將會給予我最大的鼓勵,感謝您的支持!

支付寶捐助 微信捐助

來自: http://www.henishuo.com/snapkit-auto-cell-height/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!