09-自定義 cell

原創(chuàng)微博內(nèi)容的 View

  • HMStatusOriginalView
class HMStatusOriginalView: UIView {

    /// 微博視圖模型
    var statusViewModel: HMStatusViewModel?

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupUI() {
        backgroundColor = UIColor.redColor()
    }
}
  • 定義懶加載控件
// MARK: - 懶加載控件
private lazy var originalView: HMStatusOriginalView = HMStatusOriginalView()
  • 添加頂部視圖
private func setupUI() {
    // 1. 添加控件
    contentView.addSubview(originalView)

    // 2. 添加約束
    // 添加原創(chuàng)微博內(nèi)容的約束
    originalView.snp_makeConstraints { (make) -> Void in
        make.top.equalTo(contentView.snp_top)
        make.width.equalTo(self.snp_width)
        make.height.equalTo(47)
    }
}

原創(chuàng)微博內(nèi)容布局

  • 設(shè)置數(shù)據(jù)
/// 微博視圖模型
var statusViewModel: HMStatusViewModel? {
    didSet {
        originalView.statusViewModel = statusViewModel
    }
}
  • 懶加載控件
// MARK: - 懶加載控件
/// 頭像
private lazy var iconView = UIImageView()
/// 姓名
private lazy var nameLabel = UILabel(color: UIColor.darkGrayColor(), fontSize: 14)
/// 微博認(rèn)證
private lazy var verifiedIconView: UIImageView = UIImageView(image: UIImage(named: "avatar_vip"))
/// VIP圖標(biāo)
private lazy var memberIconView: UIImageView = UIImageView(image: UIImage(named: "common_icon_membership_level1"))
/// 時(shí)間
private lazy var timeLabel = UILabel(color: UIColor.orangeColor(), fontSize: 10)
/// 來(lái)源
private lazy var sourceLabel = UILabel(color: UIColor.darkGrayColor(), fontSize: 10)
  • 抽取部分常量到 HMStatusCell
// 頭像的大小與寬度
let HMStatusHeadImageWH: CGFloat = 35
// 昵稱字體大小
let HMStatusNameFontSize: CGFloat = 14
// 來(lái)源與時(shí)間的字體大小
let HMStatusSourceFontSize: CGFloat = 10
// 微博正文字體大小
let HMStatusContentFontSize: CGFloat = 15
  • 定義間距常量 (定義到 HMStatusCell 里)
/// 控件間距
let HMStatusCellMargin: CGFloat = 10
  • 添加控件 & 自動(dòng)布局
private func setupUI() {
    // 1. 添加控件
    addSubview(iconView)
    addSubview(nameLabel)
    addSubview(verifiedIconView)
    addSubview(memberIconView)
    addSubview(timeLabel)
    addSubview(sourceLabel)

    // 2. 添加約束
    // 頭像
    iconView.snp_makeConstraints { (make) -> Void in
        make.leading.equalTo(HMStatusCellMargin)
        make.top.equalTo(HMStatusCellMargin)
        make.size.equalTo(CGSizeMake(35, 35))
    }
    // 名稱
    nameLabel.snp_makeConstraints { (make) -> Void in
        make.leading.equalTo(self.iconView.snp_trailing).offset(HMStatusCellMargin)
        make.top.equalTo(self.iconView.snp_top)
    }
    // 認(rèn)證圖標(biāo)
    verifiedIconView.snp_makeConstraints { (make) -> Void in
        make.centerX.equalTo(self.iconView.snp_trailing)
        make.centerY.equalTo(self.iconView.snp_bottom)
    }
    // 會(huì)員圖標(biāo)
    memberIconView.snp_makeConstraints { (make) -> Void in
        make.centerY.equalTo(self.nameLabel.snp_centerY)
        make.leading.equalTo(self.nameLabel.snp_trailing).offset(HMStatusCellMargin)
    }

    // 時(shí)間
    timeLabel.snp_makeConstraints { (make) -> Void in
        make.leading.equalTo(self.nameLabel.snp_leading)
        make.bottom.equalTo(self.iconView.snp_bottom)
    }
    // 來(lái)源
    sourceLabel.snp_makeConstraints { (make) -> Void in
        make.leading.equalTo(self.timeLabel.snp_trailing).offset(HMStatusCellMargin)
        make.centerY.equalTo(self.timeLabel.snp_centerY)
    }

}
  • 設(shè)置原創(chuàng)微博數(shù)據(jù)
/// 微博視圖模型
var statusViewModel: HMStatusViewModel? {
    didSet{
        // 昵稱
        nameLabel.text = statusViewModel?.status?.user?.name

        // TODO: 需要處理細(xì)節(jié)
        timeLabel.text = "剛剛"
        sourceLabel.text = "來(lái)自 weibo.com"
    }
}
  • 設(shè)置 tableView 的行高為200
// TODO: 測(cè)試行高
tableView.rowHeight = 200

運(yùn)行測(cè)試

設(shè)置頂部數(shù)據(jù)

  • HMStatusViewModel 模型中添加 userProfileUrl 屬性
/// 用戶頭像URL
var userProfileUrl: NSURL? {
    return NSURL(string: status?.user?.profile_image_url ?? "")
}
  • HMStatusOriginalView 中設(shè)置頭像
iconView.sd_setImageWithURL(statusViewModel?.userProfileUrl, placeholderImage: UIImage(named: "avatar_default_small"))
  • HMStatusViewModel 模型中添加 userVerifiedImage 屬性
/// 用戶認(rèn)證圖像
/// 認(rèn)證類型 -1:沒有認(rèn)證,1,認(rèn)證用戶,2,3,5: 企業(yè)認(rèn)證,220: 達(dá)人
var userVerifiedImage: UIImage? {
    switch status?.user?.verified ?? 0 {
    case 1:
        return UIImage(named: "avatar_vip")
    case 2,3,5:
        return UIImage(named: "avatar_enterprise_vip")
    case 220:
        return UIImage(named: "avatar_grassroot")
    default:
        return nil
    }
}
  • HMStatusViewModel 模型中添加 userMemberImage
/// 會(huì)員圖像
var userMemberImage: UIImage? {
    if status?.user?.mbtype > 2 && status?.user?.mbrank > 0 && status?.user?.mbrank < 7 {
        return UIImage(named: "common_icon_membership_level\(status!.user!.mbrank)")
    }
    return nil
}
  • 調(diào)整后的設(shè)置數(shù)據(jù)方法
/// 微博視圖模型
var statusViewModel: StatusViewModel? {
    didSet {
        iconView.sd_setImageWithURL(statusViewModel?.userProfileUrl)
        nameLabel.text = statusViewModel?.status?.user?.name
        vipIconView.image = statusViewModel?.userVipImage
        memberIconView.image = statusViewModel?.userMemberImage

        // TODO: - 設(shè)置文字細(xì)節(jié)
        timeLabel.text = "剛剛"
        sourceLabel.text = "來(lái)自 皮皮時(shí)光機(jī)"
    }
}

正文文字

添加正文文字 label 到 HMStatusOriginalView

  • 擴(kuò)展 便利構(gòu)造函數(shù)
/// 遍歷構(gòu)造函數(shù)
///
/// - parameter color:    顏色
/// - parameter fontSize: 字體大小
///
/// - returns: UILabel
convenience init(color: UIColor, fontSize: CGFloat, layoutWidth: CGFloat = 0) {
    self.init()

    textColor = color
    font = UIFont.systemFontOfSize(fontSize)

    if layoutWidth > 0 {
        numberOfLines = 0
        preferredMaxLayoutWidth = layoutWidth
    }
}
  • 懶加載方法
/// 微博正文
private lazy var contentLabel: UILabel = UILabel(color: UIColor.darkGrayColor(), fontSize: 15, layoutWidth: UIScreen.mainScreen().bounds.width - 2 * HMStatusCellMargin)
  • 自動(dòng)布局
addSubview(contentLabel)

//  微博文字
contentLabel.snp_makeConstraints { (make) -> Void in
    make.leading.equalTo(self.iconView.snp_leading)
    make.top.equalTo(self.iconView.snp_bottom).offset(HMStatusCellMargin)
}
  • 關(guān)鍵:添加底部約束
// 約束當(dāng)前 View 的底部與正文內(nèi)容的底部一樣
snp_makeConstraints { (make) -> Void in
    make.bottom.equalTo(contentLabel.snp_bottom)
}
  • 更改 HMStatusCell 中 原創(chuàng)微博View 的約束
// 添加原創(chuàng)微博內(nèi)容的約束
originalView.snp_makeConstraints { (make) -> Void in
    // 關(guān)鍵:約束原創(chuàng)微博整體 View 的頂部
    make.top.equalTo(contentView.snp_top)
    make.width.equalTo(contentView.snp_width)
}

// 約束當(dāng)前 contenView 關(guān)鍵:底部等于 originalView的底部
contentView.snp_makeConstraints { (make) -> Void in
    make.width.equalTo(self.snp_width)
    make.top.equalTo(self.snp_top)
    make.bottom.equalTo(originalView.snp_bottom)
}
  • 更改 HMHomeTableViewController 中 tableView 的行高計(jì)算方式
// 跟據(jù) AutoLayout 約束的高度自動(dòng)計(jì)算
tableView.rowHeight = UITableViewAutomaticDimension
// 預(yù)估行高
tableView.estimatedRowHeight = 200

運(yùn)行測(cè)試 --> 添加原創(chuàng)微博 View 的底部約束,可以讓cell的高度以原創(chuàng)微博 View 最大的Y值來(lái)計(jì)算

底部ToolBar

  • 數(shù)據(jù)格式
屬性名 類型 說明
reposts_count int 轉(zhuǎn)發(fā)數(shù)
comments_count int 評(píng)論數(shù)
attitudes_count int 表態(tài)數(shù)

定義 HMStatusToolBar

class HMStatusToolBar: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.redColor()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
  • HMStatusCell 添加懶加載
// 底部工具條
private lazy var statusToolBar: HMStatusToolBar = HMStatusToolBar()
  • 添加約束(并且更改底部約束)
// 添加原創(chuàng)微博內(nèi)容的約束
originalView.snp_makeConstraints { (make) -> Void in
    // 關(guān)鍵:約束原創(chuàng)微博整體 View 的頂部
    make.top.equalTo(contentView.snp_top)
    make.width.equalTo(contentView.snp_width)
}

// 底部toolBar
statusToolBar.snp_makeConstraints { (make) -> Void in
    make.top.equalTo(originalView.snp_bottom).offset(HMStatusCellMargin)
    make.width.equalTo(originalView.snp_width)
    make.height.equalTo(35)
}

// 約束當(dāng)前 contenView 關(guān)鍵:底部等于 statusToolBar 的底部
contentView.snp_makeConstraints { (make) -> Void in
    make.width.equalTo(self.snp_width)
    make.top.equalTo(self.snp_top)
    make.bottom.equalTo(statusToolBar.snp_bottom)
}

運(yùn)行測(cè)試

添加子控件

  • UIButton extension
extension UIButton {

    /// 便利構(gòu)造一個(gè)Button
    ///
    /// - parameter title:     標(biāo)題文字
    /// - parameter fontSize:  字體大小
    /// - parameter color:     文字顏色
    /// - parameter imageName: 圖片名稱
    ///
    convenience init(title: String, fontSize: CGFloat, color: UIColor, imageName: String? = nil){
        self.init()
        setTitle(title, forState: UIControlState.Normal)
        titleLabel?.font = UIFont.systemFontOfSize(fontSize)
        setTitleColor(color, forState: UIControlState.Normal)
        // 如果有圖片,則設(shè)置image
        if let imageN = imageName {
            imageView?.image = UIImage(named: imageN)
        }
    }
}
  • 新增一個(gè)添加子控件的方法
/// 添加子控件
///
/// - parameter title: 顯示的文字
/// - parameter image: 顯示的圖片
///
/// - returns: 將添加的 button 返回
private func addChildButton(title: String, image: String) -> UIButton {

    let button = UIButton(title: title, fontSize: 14, color: UIColor.darkGrayColor())

    // 設(shè)置不同狀態(tài)的背景顏色
    button.setBackgroundImage(UIImage(named: "timeline_card_bottom_background_highlighted"), forState: UIControlState.Highlighted)
    button.setBackgroundImage(UIImage(named: "timeline_card_bottom_background"), forState: UIControlState.Normal)

    button.setImage(UIImage(named: image), forState: UIControlState.Normal)
    addSubview(button)
    return button
}
  • 定義按鈕屬性
/// 轉(zhuǎn)發(fā)按鈕
var retweetButton: UIButton?
/// 評(píng)論按鈕
var commentButton: UIButton?
/// 表態(tài)按鈕
var attituedButton: UIButton?
  • 添加子控件
private func setupUI(){
    // 1.添加子控件
    retweetButton = addChildButton("轉(zhuǎn)發(fā)", image: "timeline_icon_retweet")
    commentButton = addChildButton("評(píng)論", image: "timeline_icon_comment")
    attituedButton = addChildButton("贊", image: "timeline_icon_unlike")
}

設(shè)置約束

  • 設(shè)置思路:轉(zhuǎn)發(fā)按鈕 -> A,評(píng)論按鈕 -> B,贊按鈕 -> C
    • A 按鈕的左邊緊貼父控件的左邊,頂部和高度與父控件對(duì)齊
    • B 按鈕的左邊緊貼 A 按鈕的右邊,右邊緊貼 C 按鈕的左邊, 頂部和高度與父控件對(duì)齊
    • C 按鈕的右邊緊貼父控件的右邊,頂部和高度與父控件對(duì)齊
    • A 按鈕的寬度 等于 B按鈕的寬度,C 按鈕的寬度等于 B 按鈕的寬度
// 2.添加約束
retweetButton!.snp_makeConstraints { (make) -> Void in
    make.top.equalTo(self.snp_top)
    make.leading.equalTo(self.snp_leading)
    make.height.equalTo(self.snp_height)
    make.width.equalTo(commentButton!.snp_width)
}
commentButton!.snp_makeConstraints { (make) -> Void in
    make.leading.equalTo(retweetButton!.snp_trailing)
    make.trailing.equalTo(attituedButton!.snp_leading)
    make.top.equalTo(retweetButton!.snp_top)
    make.height.equalTo(self.snp_height)
}
attituedButton!.snp_makeConstraints { (make) -> Void in
    make.trailing.equalTo(self.snp_trailing)
    make.height.equalTo(self.snp_height)
    make.top.equalTo(retweetButton!.snp_top)
    make.width.equalTo(commentButton!.snp_width)
}

運(yùn)行測(cè)試

  • 添加分割線
/// 添加分割線
private func addSpliteView() -> UIImageView {
    let image = UIImageView(image: UIImage(named: "timeline_card_bottom_line"))
    addSubview(image)
    return image
}
  • 設(shè)置約束
// 3.添加分割線
let sp1 = addSpliteView()
let sp2 = addSpliteView()

// 4.設(shè)置分割線的約束

sp1.snp_makeConstraints { (make) -> Void in
    make.centerX.equalTo(self.retweetButton!.snp_trailing)
    make.centerY.equalTo(self.retweetButton!.snp_centerY)
}
sp2.snp_makeConstraints { (make) -> Void in
    make.centerX.equalTo(self.commentButton!.snp_trailing)
    make.centerY.equalTo(self.commentButton!.snp_centerY)
}

設(shè)置數(shù)據(jù)

  • HMStatus 模型中添加以下屬性
/// 轉(zhuǎn)發(fā)數(shù)
var reposts_count: Int = 0
/// 評(píng)論數(shù)
var comments_count: Int = 0
/// 表態(tài)數(shù)
var attitudes_count: Int = 0
  • 添加視圖模型到 HMStatusToolBar
/// 視圖模型
var statusViewModel: HMStatusViewModel?
  • HMStatusCell 里面設(shè)置此屬性
/// 微博視圖模型
var statusViewModel: HMStatusViewModel? {
    didSet{
        // 設(shè)置原創(chuàng)微博內(nèi)容的視圖模型
        originalView.statusViewModel = statusViewModel
        // 設(shè)置底部 ToolBar 的視圖模型
        statusToolBar.statusViewModel = statusViewModel
    }
}
  • 數(shù)量顯示邏輯

    • 小于 10000,直接顯示數(shù)字
    • 大于 10000,小于 11000,顯示 1萬(wàn)
    • 大于 11000,小于 20000,顯示 1.x萬(wàn)
    • 其他同理
  • 視圖模型 里面添加 repostsCountString 屬性

/// 轉(zhuǎn)發(fā)數(shù)量
var repostsCountString: String {
    // 默認(rèn)顯示 `轉(zhuǎn)發(fā)`
    var result = "轉(zhuǎn)發(fā)"
    let count = status?.reposts_count ?? 0

    // 如果數(shù)量為 0,直接顯示 `轉(zhuǎn)發(fā)`
    if count == 0 {
        return result
    }

    // 如果數(shù)量大于10000,再做處理
    if count > 10000 {
        // 先除以1000返回一個(gè)整數(shù),再除以10,返回一個(gè)小數(shù)
        let res = Float(count / 1000) / 10
        // 拼接字符串
        result = "\(res)萬(wàn)"
    }else{
        result = "\(status!.reposts_count)"
    }
    return result
}
  • 添加測(cè)試數(shù)據(jù)(在 HMStatus 中添加 reposts_count 的 didSet 方法)
/// 轉(zhuǎn)發(fā)數(shù)
var reposts_count: Int = 0 {
    didSet{
        reposts_count = 10009
    }
}

測(cè)試發(fā)現(xiàn)顯示 1.0萬(wàn)

  • 添加判斷小數(shù)為 0 的邏輯
// 查看是否包含 .0 萬(wàn)
if result.containsString(".0萬(wàn)") {
    result = result.stringByReplacingOccurrencesOfString(".0", withString: "")
}
  • 視圖模型 里面添加 repostsCountString 和 `` 屬性
/// 評(píng)論數(shù)量
var commentsCountString: String {
    return "評(píng)論"
}
/// 點(diǎn)贊數(shù)量
var attitudesCountString: String {
    return "贊"
}
  • 抽取處理數(shù)量邏輯的方法
/// 處理 轉(zhuǎn)發(fā)\評(píng)論\贊 數(shù)量邏輯
///
/// - parameter count:          對(duì)應(yīng)數(shù)量
/// - parameter defaultString:  默認(rèn)顯示的文字
///
private func countString(count: Int, defaultString: String) -> String {

    var result = defaultString
    if count == 0 {
        return result
    }

    // 如果數(shù)量大于10000,再做處理
    if count > 10000 {
        let res = Float(count / 1000) / 10
        result = "\(res)萬(wàn)"
        // 查看是否包含 .0 萬(wàn)
        if result.containsString(".0萬(wàn)") {
            result = result.stringByReplacingOccurrencesOfString(".0", withString: "")
        }
    }else{
        result = "\(count)"
    }
    return result
}
  • 更改三個(gè)屬性的 get 方法
/// 轉(zhuǎn)發(fā)數(shù)量
var repostsCountString: String {
    let count = status?.reposts_count ?? 0
    return countString(count, defaultString: "轉(zhuǎn)發(fā)")
}
/// 評(píng)論數(shù)量
var commentsCountString: String {
    let count = status?.comments_count ?? 0
    return countString(count, defaultString: "評(píng)論")
}
/// 點(diǎn)贊數(shù)量
var attitudesCountString: String {
    let count = status?.attitudes_count ?? 0
    return countString(count, defaultString: "贊")
}

運(yùn)行測(cè)試(也可以將這三個(gè)屬性設(shè)置成存儲(chǔ)型屬性)

轉(zhuǎn)發(fā)微博內(nèi)容

數(shù)據(jù)模型準(zhǔn)備

  • 添加轉(zhuǎn)發(fā)微博屬性
/// 轉(zhuǎn)發(fā)微博
var retweeted_status: HMStatus?
  • setValue(value: AnyObject?, forKey key: String) 函數(shù)中增加一下代碼
// 2. 轉(zhuǎn)發(fā)微博
if key == "retweeted_status" {
    retweeted_status = Status(dict: value as! [String: AnyObject])
    return
}

新建 HMStatusRetweetView

class HMStatusRetweetView: UIView {

    /// 微博視圖模型
    var statusViewModel: HMStatusViewModel?

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupUI(){
        backgroundColor = UIColor(white: 0.95, alpha: 1)
    }
}
  • 懶加載控件
// MARK: - 懶加載控件
/// 轉(zhuǎn)發(fā)微博正文內(nèi)容
private lazy var contentLabel = UILabel(color: UIColor.darkGrayColor(), fontSize: 15, layoutWidth: UIScreen.mainScreen().bounds.width - 2 * HMStatusCellMargin)
  • 添加控件設(shè)置約束
private func setupUI(){
    backgroundColor = UIColor(white: 0.9, alpha: 1)

    // 添加子控件
    addSubview(contentLabel)

    // 設(shè)置約束
    contentLabel.snp_makeConstraints { (make) -> Void in
        make.top.equalTo(self.snp_top).offset(HMStatusCellMargin)
        make.leading.equalTo(self.snp_leading).offset(HMStatusCellMargin)
    }
    // 設(shè)置當(dāng)前View的底部為內(nèi)容的底部加上間距
    snp_makeConstraints { (make) -> Void in
        make.bottom.equalTo(contentLabel.snp_bottom).offset(HMStatusCellMargin)
    }
}
  • HMStatusViewModel 添加 retweetText 屬性
// 轉(zhuǎn)發(fā)微博內(nèi)容
var retweetText: String? {
    if let retStatus = self.status?.retweeted_status where retStatus.text != nil  {
        return "@\(retStatus.user!.name!):\(retStatus.text!)"
    }
}
  • 設(shè)置數(shù)據(jù)
/// 微博視圖模型
var statusViewModel: HMStatusViewModel?{
    didSet{
        contentLabel.text = statusViewModel!.retweetText
    }
}

更新 HMStatusCell

  • 添加轉(zhuǎn)發(fā)微博控件
// 轉(zhuǎn)發(fā)微博
private lazy var retweetView: HMStatusRetweetView = HMStatusRetweetView()
...
private func setupUI(){
    // 添加控件
    contentView.addSubview(originalView)
    contentView.addSubview(retweetView)
    contentView.addSubview(statusToolBar)


    // 添加原創(chuàng)微博內(nèi)容的約束
    originalView.snp_makeConstraints { (make) -> Void in
        // 關(guān)鍵:約束原創(chuàng)微博整體 View 的頂部
        make.top.equalTo(contentView.snp_top)
        make.width.equalTo(contentView.snp_width)
    }
    // 添加轉(zhuǎn)發(fā)微博內(nèi)容的約束
    retweetView.snp_makeConstraints { (make) -> Void in
        make.top.equalTo(originalView.snp_bottom)
        make.leading.equalTo(originalView.snp_leading)
        make.width.equalTo(originalView.snp_width)
    }
    // 底部toolBar的約束
    statusToolBar.snp_makeConstraints { (make) -> Void in
        make.top.equalTo(retweetView.snp_bottom).constraint
        make.width.equalTo(originalView.snp_width)
        make.height.equalTo(35)
        make.bottom.equalTo(contentView.snp_bottom)
    }
}
  • 設(shè)置數(shù)據(jù)(需要判斷是否有轉(zhuǎn)發(fā)微博)
/// 微博視圖模型
var statusViewModel: HMStatusViewModel? {
    didSet{
        // 設(shè)置視圖模型
        originalView.statusViewModel = statusViewModel
        statusToolBar.statusViewModel = statusViewModel

        // 如果有轉(zhuǎn)發(fā)微博
        if statusViewModel?.status?.retweeted_status != nil {
            retweetView.hidden = false
            // 設(shè)置轉(zhuǎn)發(fā)微博的視圖模型
            retweetView.statusViewModel = statusViewModel
            // TODO:需要更新約束,statusToolBar的頂部要與轉(zhuǎn)發(fā)微博底部對(duì)齊
        }else{
            // 沒有轉(zhuǎn)發(fā)微博,隱藏轉(zhuǎn)發(fā)微博的View
            retweetView.hidden = true
            // TODO:需要更新約束,statusToolBar的頂部要與原創(chuàng)微博底部對(duì)齊

        }
    }
}
  • 定義變量記住 statusToolBar 的頂部約束
// toolBar 頂部約束
var toolBarTopConstraints: Constraint?

...
// 在約束toolBar頂部約束的時(shí)候記錄
// 底部toolBar
statusToolBar.snp_makeConstraints { (make) -> Void in
    self.toolBarTopConstraints = make.top.equalTo(retweetView.snp_bottom).constraint
    make.width.equalTo(originalView.snp_width)
    make.height.equalTo(35)
    make.bottom.equalTo(contentView.snp_bottom)
}
  • 根據(jù)是否有轉(zhuǎn)發(fā)微博更新約束
// 先讓之前記錄的約束失效 -> 約束的時(shí)候重新記錄
toolBarTopConstraints?.uninstall()
// 如果有轉(zhuǎn)發(fā)微博
if statusViewModel?.status?.retweeted_status != nil {
    retweetView.hidden = false
    // 設(shè)置轉(zhuǎn)發(fā)微博的視圖模型
    retweetView.statusViewModel = statusViewModel
    statusToolBar.snp_updateConstraints(closure: { (make) -> Void in
        toolBarTopConstraints = make.top.equalTo(retweetView.snp_bottom).constraint
    })
}else{
    // 沒有轉(zhuǎn)發(fā)微博,隱藏轉(zhuǎn)發(fā)微博的View
    retweetView.hidden = true
    // 更新約束
    statusToolBar.snp_updateConstraints(closure: { (make) -> Void in
        toolBarTopConstraints = make.top.equalTo(originalView.snp_bottom).constraint
    })
}

運(yùn)行測(cè)試

微博配圖

數(shù)據(jù)

配圖數(shù)據(jù)對(duì)應(yīng)的字段 pic_urls,格式為:

pic_urls: [
    {
        thumbnail_pic: "http://ww2.sinaimg.cn/thumbnail/005Ko17Djw1exjar89996j30b40b440s.jpg"
    },
    {
        thumbnail_pic: "http://ww2.sinaimg.cn/thumbnail/005Ko17Djw1exjar89996j30b40b440s.jpg"
    }
]
  • 定義 pic_urls 內(nèi)部的數(shù)據(jù)模型 HMStatusPhotoInfo
class HMStatusPhotoInfo: NSObject {

    /// 約略圖地址
    var thumbnail_pic: String?

    init(dictionary: [String: AnyObject]){
        super.init()
        setValuesForKeysWithDictionary(dictionary)
    }

    override func setValue(value: AnyObject?, forUndefinedKey key: String) {}
}
  • HMStatus 模型中增加配圖數(shù)組模型
/// 配圖模型數(shù)組
var pic_urls: [HMStatusPhotoInfo]?
  • setValue(value: AnyObject?, forKey key: String) 函數(shù)中增加一下代碼
if key == "pic_urls" {
    var tempArray = [HMStatusPhotoInfo]()
    // 遍歷字典轉(zhuǎn)模型
    for value in value as! [[String: AnyObject]] {
        tempArray.append(HMStatusPhotoInfo(dictionary: value))
    }
    pic_urls = tempArray
}

思路

  • 圖片可以有多張可以使用 UICollectionView 實(shí)現(xiàn)
  • 根據(jù)原創(chuàng)微博(轉(zhuǎn)發(fā)微博)是否有配圖去顯示或者隱藏控件

控件顯示實(shí)現(xiàn)

  • 定義 HMStatusPictureView
class HMStatusPictureView: UICollectionView {

    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: UICollectionViewFlowLayout())
        // 為了測(cè)試,設(shè)置背景顏色為隨機(jī)色
        backgroundColor = RandomColor()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

原創(chuàng)微博

  • HMStatusOriginalView 中懶加載控件
/// 配圖視圖
private lazy var pictureView: HMStatusPictureView = HMStatusPictureView()
  • 添加控件并設(shè)置約束
// 配圖視圖
pictureView.snp_makeConstraints { (make) -> Void in
    // 先寫死一個(gè)寬高
    make.size.equalTo(CGSizeMake(100, 100))
    make.leading.equalTo(contentLabel.snp_leading)
    make.top.equalTo(contentLabel.snp_bottom).offset(HMStatusCellMargin)
}

下一步就需要根據(jù)原創(chuàng)微博是否有配圖去動(dòng)態(tài)更新當(dāng)前原創(chuàng)微博View的高度

  • 如果有:原創(chuàng)微博 View 的底部是相對(duì)于配圖控件來(lái)說的

  • 如果沒有:原創(chuàng)微博 View 的底部是相對(duì)于微博內(nèi)容控件來(lái)說的

  • 所以需要在初始化控件的時(shí)候記錄當(dāng)前 View 底部的約束

  • 記錄底部的約束

/// 當(dāng)前 View 的底部約束
private var bottomConstraint: Constraint?
...
// 約束當(dāng)前 View 的底部與正文內(nèi)容的底部一樣 并 記錄該約束
snp_makeConstraints { (make) -> Void in
    self.bottomConstraint = make.bottom.equalTo(contentLabel.snp_bottom).offset(HMStatusCellMargin).constraint
}
  • 在設(shè)置視圖模型的時(shí)候判斷是否有配圖去更新約束
// 先移除之前的約束
bottomConstraint?.uninstall()
// 配圖視圖
if let picUrls = statusViewModel?.status?.pic_urls where picUrls.count > 0 {
    pictureView.hidden = false
    // 有配圖,更新約束 -> 更新當(dāng)前 View 底部的約束
    self.snp_updateConstraints(closure: { (make) -> Void in
        self.bottomConstraint = make.bottom.equalTo(pictureView.snp_bottom).offset(HMStatusCellMargin).constraint
    })
}else{
    // 沒有配圖,隱藏配圖控件
    pictureView.hidden = true
    self.snp_updateConstraints(closure: { (make) -> Void in
        self.bottomConstraint = make.bottom.equalTo(contentLabel.snp_bottom).offset(HMStatusCellMargin).constraint
    })
}

運(yùn)行測(cè)試:原創(chuàng)微博有配圖,就會(huì)顯示配圖控件

  • 根據(jù)配圖的張數(shù)計(jì)算控件大小
    • 添加方法 calcViewSize()HMStatusPictureView
/// 根據(jù)圖片個(gè)數(shù)計(jì)算當(dāng)前View的大小
private func calcViewSize() -> CGSize {

    // 獲取到配圖張數(shù)
    let count = pic_urls?.count ?? 0
    // 計(jì)算出每一個(gè)條目的寬高

    // 每一個(gè)條目之間的間距
    let HMStatusPictureItemMargin: CGFloat = 5
    // 每一個(gè)Item的寬高
    let HMStatusPictureItemWH = (SCREENW - 2 * HMStatusCellMargin - 2 * HMStatusPictureItemMargin) / 3

    // 計(jì)算出多少列
    let col = count == 4 ? 2 : (count > 3 ? 3 : count)
    let row = count == 4 ? 2 : ((count - 1) / 3 + 1)

    // 計(jì)算出當(dāng)前控件的寬度
    let width = HMStatusPictureItemWH * CGFloat(col) + CGFloat(col - 1) * HMStatusPictureItemMargin;
    let height = HMStatusPictureItemWH * CGFloat(row) + CGFloat(row - 1) * HMStatusPictureItemMargin;

    return CGSizeMake(width, height)
}
  • 定義配圖數(shù)據(jù)的屬性
/// 配圖
var pic_urls: [HMStatusPhotoInfo]?
  • HMStatusOriginalView 設(shè)置數(shù)據(jù)的時(shí)候給配圖 View 設(shè)置數(shù)據(jù)
// 設(shè)置數(shù)據(jù)
pictureView.pic_urls = picUrls
  • 在設(shè)置數(shù)據(jù)的時(shí)候去更新當(dāng)前配圖控件的大小約束
/// 配圖
var pic_urls: [HMStatusPhotoInfo]? {
    didSet{

        // 在設(shè)置配圖的時(shí)候計(jì)算當(dāng)前 View 的大小
        snp_updateConstraints { (make) -> Void in
            make.size.equalTo(calcViewSize())
        }
    }
}
  • 為了測(cè)試方便,添加一個(gè)測(cè)試 label 到配圖控件里面,顯示當(dāng)前配圖控件里面需要展示幾張圖片
/// 測(cè)試:用于顯示張數(shù)的label
private lazy var label: UILabel = {
    let label = UILabel()
    label.textColor = UIColor.blackColor()
    label.font = UIFont.systemFontOfSize(30)
    return label
}()
...
// 添加控件以及添加約束
addSubview(label);
label.snp_makeConstraints { (make) -> Void in
    make.center.equalTo(self.snp_center)
}
...
// 在設(shè)置數(shù)據(jù)的時(shí)候,讓 label 顯示配圖張數(shù)
var pic_urls: [HMStatusPhotoInfo]? {
    didSet{
        ...
        label.text = "\(pic_urls!.count)"
    }
}

運(yùn)行測(cè)試

轉(zhuǎn)發(fā)微博配圖

  • 添加控件思路與原創(chuàng)微博一樣

注意:設(shè)置數(shù)據(jù)的時(shí)候一定要設(shè)置成轉(zhuǎn)發(fā)微博的數(shù)據(jù)

圖片顯示

  • 定義可重用 ID
// 可重用ID
private let HMStatusPictureCellId = "HMStatusPictureCellId"
  • 設(shè)置數(shù)據(jù)源以及代理,設(shè)置每一個(gè) item 的寬度
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
    super.init(frame: frame, collectionViewLayout: UICollectionViewFlowLayout())
    ...
    // 設(shè)置代理與數(shù)據(jù)源都是自己
    self.delegate = self
    self.dataSource = self

    // 設(shè)置layout
    let layout = collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = CGSizeMake(HMStatusPictureItemWH, HMStatusPictureItemWH)
    // 設(shè)置間隔
    layout.minimumInteritemSpacing = HMStatusPictureItemMargin
    layout.minimumLineSpacing = HMStatusPictureItemMargin
    // 注冊(cè)cell
    self.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: HMStatusPictureCellId)
}
  • 實(shí)現(xiàn)兩個(gè)數(shù)據(jù)源方法
extension HMStatusPictureView {

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return pic_urls?.count ?? 0
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(HMStatusPictureCellId, forIndexPath: indexPath)
        // 為了查看出效果,設(shè)置 cell 的背景顏色為隨機(jī)色
        cell.backgroundColor = RandomColor()
        return cell
    }
}

運(yùn)行測(cè)試

  • 自定義 Cell HMStatusPictureCell
private class HMStatusPictureCell: UICollectionViewCell {

    override init(frame: CGRect) {
        super.init(frame: frame)
        // 設(shè)置背景顏色為隨機(jī)顏色
        backgroundColor = RandomColor()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
  • 更改注冊(cè)的cell
// 注冊(cè)cell
self.registerClass(HMStatusPictureCell.self, forCellWithReuseIdentifier: HMStatusPictureCellId)

運(yùn)行測(cè)試

  • 添加圖片控件
// 懶加載控件
private lazy var imageView: UIImageView = {
    let imageView = UIImageView()
    // 設(shè)置imageView的顯示模式
    imageView.contentMode = UIViewContentMode.ScaleAspectFill
    // 切掉多余部分
    imageView.clipsToBounds = true
    return imageView;
}()
...
// 添加控件并設(shè)置約束
// 添加子控件
contentView.addSubview(imageView)
// 添加約束
imageView.snp_makeConstraints { (make) -> Void in
    make.size.equalTo(contentView.snp_size)
    make.leading.equalTo(contentView.snp_leading)
    make.top.equalTo(contentView.snp_top)
}
  • 添加屬性 photoInfo
/// 設(shè)置數(shù)據(jù)模型
var photoInfo: HMStatusPhotoInfo?
  • 在數(shù)據(jù)源方法里面設(shè)置數(shù)據(jù)
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(HMStatusPictureCellId, forIndexPath: indexPath) as! HMStatusPictureCell
    // 設(shè)置數(shù)據(jù)
    cell.photoInfo = pic_urls![indexPath.row]
    return cell
}
  • 顯示圖片
var photoInfo: HMStatusPhotoInfo? {
    didSet{
        if let urlString = photoInfo?.thumbnail_pic{
            imageView.sd_setImageWithURL(NSURL(string: urlString), placeholderImage: UIImage(named: "timeline_image_placeholder"))
        }
    }
}
  • 添加 gif 圖標(biāo)
/// gif 圖標(biāo)
private lazy var gifIcon: UIImageView = UIImageView(image: UIImage(named: "timeline_image_gif"))
...
/// 添加控件
contentView.addSubview(gifIcon)
...
/// 添加約束
gifIcon.snp_makeConstraints { (make) -> Void in
    make.trailing.equalTo(contentView.snp_trailing)
    make.bottom.equalTo(contentView.snp_bottom)
}
...
/// 顯示邏輯
if let urlString = photoInfo?.thumbnail_pic{
    imageView.sd_setImageWithURL(NSURL(string: urlString), placeholderImage: UIImage(named: "timeline_image_placeholder"))
    gifIcon.hidden = !urlString.hasSuffix(".gif");
}
  • 設(shè)置配圖控件的背景顏色
/// 原創(chuàng)微博配圖控件
private lazy var pictureView: HMStatusPictureView = {
    let pictureView = HMStatusPictureView()
    pictureView.backgroundColor = UIColor.whiteColor();
    return pictureView;
}()
...
/// 轉(zhuǎn)發(fā)微博配圖控件
private lazy var pictureView: HMStatusPictureView = {
    let pictureView = HMStatusPictureView()
    pictureView.backgroundColor = UIColor(white: 0.95, alpha: 1);
    return pictureView;
}()

其他細(xì)節(jié)

  • 取消分隔線
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
  • 增加 cell 分隔視圖
// 設(shè)置 cell 的contentView的背景顏色
// 設(shè)置背景顏色
contentView.backgroundColor = UIColor(white: 240 / 255, alpha: 1)

// 設(shè)置原創(chuàng)微博背景色為白色 在 `HMStatusOriginalView`
backgroundColor = UIColor.whiteColor()

// 更改原創(chuàng)微博距離頂部的間距 在 `HMStatusCell`
originalView.snp_makeConstraints { (make) -> Void in
    // 距離頂部有間距
    make.top.equalTo(contentView.snp_top).offset(HMStatusCellMargin)
    ...
}
  • 設(shè)置 tableView 的背景色
tableView.backgroundColor = UIColor(white: 240 / 255, alpha: 1)
  • 抽取顏色的方法 -> CommonTools.swift
    • RGBColor & 隨機(jī)顏色
/// RGB顏色
func RGB(r r: CGFloat, g: CGFloat, b: CGFloat) -> UIColor {
    return UIColor(red: r / 256, green: g / 256, blue: b / 256, alpha: 1)
}

/// 隨機(jī)顏色
func RandomColor() -> UIColor {
    return RGB(r: CGFloat(random()) % 256, g: CGFloat(random()) % 256, b: CGFloat(random()) % 256)
}
  • 抽取屏幕寬度/高度 -> CommonTools.swift
/// 屏幕寬高
let SCREENW = UIScreen.mainScreen().bounds.size.width
let SCREENH = UIScreen.mainScreen().bounds.size.height

單張圖片

目標(biāo)

  • 將單張圖片提前緩存到本地,以便判斷大小
  • 復(fù)習(xí) gcd 的 dispatch_group
  • 熟悉 SDWebImage 的其他函數(shù)應(yīng)用

預(yù)先加載圖片說明

  • 新浪微博的數(shù)據(jù)接口并沒有返回每一張圖片的尺寸
  • 而對(duì)于保存在遠(yuǎn)程服務(wù)器的圖片而言,客戶端是無(wú)法獲知服務(wù)器上的圖片大小的
  • 因此要實(shí)現(xiàn)單圖都效果,需要先將圖片緩存到本地

代碼實(shí)現(xiàn)

緩存單張圖片

  • HMStatusListViewModel 中增加 cacheSingleImage 函數(shù)
  • 調(diào)整 loadData 函數(shù),調(diào)用 cacheSingleImage 函數(shù)緩存單張圖片
// 拼接數(shù)據(jù)
self.statusList = dataList + self.statusList

// 緩存圖片
self.cacheSingleImage(dataList)
  • 緩存圖片并且回調(diào)
private func cacheSingleImage(array: [StatusViewModel]) {

    // 1. 遍歷數(shù)組
    for vm in array {
        // 1> 只緩存單張圖片
        if vm.thumbnailUrls?.count != 1 {
            continue
        }

        // 2> 獲取 url
        let url = vm.thumbnailUrls![0]

        print("要緩存的 \(url)")

        // 3> 下載圖片
        SDWebImageManager.sharedManager().downloadImageWithURL(
            url,                                // URL
            options: [],  // 選項(xiàng)
            progress: nil)                      // 進(jìn)度
            { (image, error, _, _, _)  in       // 完成回調(diào)
                if let img = image,
                    data = UIImagePNGRepresentation(img) {
                        print(data.length)
                }
        }
    }
}
  • 添加 dispatch_group 和數(shù)據(jù)長(zhǎng)度
// 0. 調(diào)度組
let group = dispatch_group_create()
// 緩存數(shù)據(jù)長(zhǎng)度
var dataLength = 0
  • 下載圖像之前入組,下載圖像最后一行出組
// 3> 下載圖片
dispatch_group_enter(group)
SDWebImageManager.sharedManager().downloadImageWithURL(
    url,                                // URL
    options: [],  // 選項(xiàng)
    progress: nil)                      // 進(jìn)度
    { (image, error, _, _, _)  in       // 完成回調(diào)

        // 不是每次圖像都能下載成功
        if let img = image,
            data = UIImagePNGRepresentation(img) {

                // 累加長(zhǎng)度
                dataLength += data.length
        }

        // 出組
        dispatch_group_leave(group)
}
  • 修改函數(shù)定義,增加完成回調(diào)參數(shù)
private func cacheSingleImage(dataArray: [HMStatusViewModel],completion: (isSuccessed: Bool)->()){
  • 完成回調(diào)
// 2. 監(jiān)聽調(diào)度組完成
dispatch_group_notify(group, dispatch_get_main_queue()) {
    print("緩存圖像大小 \(dataLength / 1024) K")
    completion(isSuccessed: true)
}
  • 修改函數(shù)調(diào)用
// 3. 拼接數(shù)據(jù)
self.statusList = tempArray + self.statusList

// 4. 緩存圖片
self.cacheSingleImage(tempArray, completion: finished)

修改單張圖片顯示

  • 修改 calcViewSize 函數(shù)
// 2. 單圖
if count == 1 {
    // 臨時(shí)設(shè)置單圖大小
    var size = CGSize(width: 150, height: 120)

    // 提取單圖
    if let key = pic_urls.first?.thumbnail_pic {
        size = SDWebImageManager.sharedManager().imageCache.imageFromDiskCacheForKey(key).size
    }

    layout.itemSize = size
    return size
}
  • 細(xì)節(jié)處理,防止圖片過窄或者太寬
// 過窄處理 - 針對(duì)長(zhǎng)圖
if size.width < 150 {
    let w: CGFloat = 150
    let h = size.height * w / size.width
    size = CGSize(width: w, height: h > 230 ? 230 : h)
}else if size.width > 200 {
    // 過寬的圖片
    let w: CGFloat = 200
    let h = size.height * w / size.width
    size = CGSize(width: w, height: h)
}

小結(jié)

  • dispatch_group

    • dispatch_group_enter 后續(xù)的 block 執(zhí)行會(huì)受 group 監(jiān)聽
    • block 的最后一句必須是 dispatch_group_leave,通知 group 該任務(wù)完成
    • dispatch_group_enterdispatch_group_leave 無(wú)比成對(duì)出現(xiàn)
  • SDWebImage

    • 下載圖像時(shí)一定注意圖像不一定都會(huì)被正確下載
    • SDWebImageManager.sharedManager().downloadImageWithURL 是 SDWebImage 的核心下載函數(shù)
    • SDWebImageManager.sharedManager().imageCache.imageFromDiskCacheForKey 使用圖像完整的 URL字符串 檢查是否存在圖像的磁盤緩存
    • SDWebImage 使用 MD5 對(duì) URL 字符串編碼并作為緩存圖像的文件名
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,063評(píng)論 25 709
  • 我們?cè)谏弦黄锻ㄟ^代碼自定義不等高cell》中學(xué)習(xí)了tableView的相關(guān)知識(shí),本文將在上文的基礎(chǔ)上,利用sto...
    啊世ka閱讀 1,653評(píng)論 2 7
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,378評(píng)論 4 61
  • 不知何時(shí),朋友圈流行起一類網(wǎng)文“懂事的孩子,可憐、不幸福、自卑······”。這些文章把孩子的不幸福感歸根于原生家...
    卓子云閱讀 2,025評(píng)論 5 2
  • 田生萬(wàn)物-蜜橘停售 荔枝:下午四點(diǎn),所有人員開會(huì)嘍! 勤勤:1(田生萬(wàn)物暗號(hào):收到) ...
    勤勤_390d閱讀 291評(píng)論 0 0

友情鏈接更多精彩內(nèi)容