原創(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è)試
添加子控件
-
UIButtonextension
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_enter和dispatch_group_leave無(wú)比成對(duì)出現(xiàn)
-
-
SDWebImage- 下載圖像時(shí)一定注意圖像不一定都會(huì)被正確下載
-
SDWebImageManager.sharedManager().downloadImageWithURL是 SDWebImage 的核心下載函數(shù) -
SDWebImageManager.sharedManager().imageCache.imageFromDiskCacheForKey使用圖像完整的URL字符串檢查是否存在圖像的磁盤緩存 -
SDWebImage使用MD5對(duì)URL 字符串編碼并作為緩存圖像的文件名