原创微博内容的 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. 添加约束
// 添加原创微博内容的约束
originalView.snp_makeConstraints { (make) -> Void in
make.top.equalTo(contentView.snp_top)
make.width.equalTo(self.snp_width)
make.height.equalTo(47)
}
}
原创微博内容布局
- 设置数据
/// 微博视图模型
var statusViewModel: HMStatusViewModel? {
didSet {
originalView.statusViewModel = statusViewModel
}
}
- 懒加载控件
// MARK: - 懒加载控件
/// 头像
private lazy var iconView = UIImageView()
/// 姓名
private lazy var nameLabel = UILabel(color: UIColor.darkGrayColor(), fontSize: 14)
/// 微博认证
private lazy var verifiedIconView: UIImageView = UIImageView(image: UIImage(named: "avatar_vip"))
/// VIP图标
private lazy var memberIconView: UIImageView = UIImageView(image: UIImage(named: "common_icon_membership_level1"))
/// 时间
private lazy var timeLabel = UILabel(color: UIColor.orangeColor(), fontSize: 10)
/// 来源
private lazy var sourceLabel = UILabel(color: UIColor.darkGrayColor(), fontSize: 10)
- 抽取部分常量到
HMStatusCell
中
// 头像的大小与宽度
let HMStatusHeadImageWH: CGFloat = 35
// 昵称字体大小
let HMStatusNameFontSize: CGFloat = 14
// 来源与时间的字体大小
let HMStatusSourceFontSize: CGFloat = 10
// 微博正文字体大小
let HMStatusContentFontSize: CGFloat = 15
- 定义间距常量 (定义到
HMStatusCell
里)
/// 控件间距
let HMStatusCellMargin: CGFloat = 10
- 添加控件 & 自动布局
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)
}
// 认证图标
verifiedIconView.snp_makeConstraints { (make) -> Void in
make.centerX.equalTo(self.iconView.snp_trailing)
make.centerY.equalTo(self.iconView.snp_bottom)
}
// 会员图标
memberIconView.snp_makeConstraints { (make) -> Void in
make.centerY.equalTo(self.nameLabel.snp_centerY)
make.leading.equalTo(self.nameLabel.snp_trailing).offset(HMStatusCellMargin)
}
// 时间
timeLabel.snp_makeConstraints { (make) -> Void in
make.leading.equalTo(self.nameLabel.snp_leading)
make.bottom.equalTo(self.iconView.snp_bottom)
}
// 来源
sourceLabel.snp_makeConstraints { (make) -> Void in
make.leading.equalTo(self.timeLabel.snp_trailing).offset(HMStatusCellMargin)
make.centerY.equalTo(self.timeLabel.snp_centerY)
}
}
- 设置原创微博数据
/// 微博视图模型
var statusViewModel: HMStatusViewModel? {
didSet{
// 昵称
nameLabel.text = statusViewModel?.status?.user?.name
// TODO: 需要处理细节
timeLabel.text = "刚刚"
sourceLabel.text = "来自 weibo.com"
}
}
- 设置 tableView 的行高为200
// TODO: 测试行高
tableView.rowHeight = 200
运行测试
设置顶部数据
- 在
HMStatusViewModel
模型中添加userProfileUrl
属性
/// 用户头像URL
var userProfileUrl: NSURL? {
return NSURL(string: status?.user?.profile_image_url ?? "")
}
- 在
HMStatusOriginalView
中设置头像
iconView.sd_setImageWithURL(statusViewModel?.userProfileUrl, placeholderImage: UIImage(named: "avatar_default_small"))
- 在
HMStatusViewModel
模型中添加userVerifiedImage
属性
/// 用户认证图像
/// 认证类型 -1:没有认证,1,认证用户,2,3,5: 企业认证,220: 达人
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
/// 会员图像
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
}
- 调整后的设置数据方法
/// 微博视图模型
var statusViewModel: StatusViewModel? {
didSet {
iconView.sd_setImageWithURL(statusViewModel?.userProfileUrl)
nameLabel.text = statusViewModel?.status?.user?.name
vipIconView.image = statusViewModel?.userVipImage
memberIconView.image = statusViewModel?.userMemberImage
// TODO: - 设置文字细节
timeLabel.text = "刚刚"
sourceLabel.text = "来自 皮皮时光机"
}
}
正文文字
添加正文文字 label 到 HMStatusOriginalView
- 扩展 便利构造函数
/// 遍历构造函数
///
/// - 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)
- 自动布局
addSubview(contentLabel)
// 微博文字
contentLabel.snp_makeConstraints { (make) -> Void in
make.leading.equalTo(self.iconView.snp_leading)
make.top.equalTo(self.iconView.snp_bottom).offset(HMStatusCellMargin)
}
- 关键:添加底部约束
// 约束当前 View 的底部与正文内容的底部一样
snp_makeConstraints { (make) -> Void in
make.bottom.equalTo(contentLabel.snp_bottom)
}
- 更改
HMStatusCell
中 原创微博View 的约束
// 添加原创微博内容的约束
originalView.snp_makeConstraints { (make) -> Void in
// 关键:约束原创微博整体 View 的顶部
make.top.equalTo(contentView.snp_top)
make.width.equalTo(contentView.snp_width)
}
// 约束当前 contenView 关键:底部等于 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 的行高计算方式
// 跟据 AutoLayout 约束的高度自动计算
tableView.rowHeight = UITableViewAutomaticDimension
// 预估行高
tableView.estimatedRowHeight = 200
运行测试 --> 添加原创微博 View 的底部约束,可以让cell的高度以原创微博 View 最大的Y值来计算
底部ToolBar
- 数据格式
属性名 | 类型 | 说明 |
---|---|---|
reposts_count | int | 转发数 |
comments_count | int | 评论数 |
attitudes_count | int | 表态数 |
定义 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()
- 添加约束(并且更改底部约束)
// 添加原创微博内容的约束
originalView.snp_makeConstraints { (make) -> Void in
// 关键:约束原创微博整体 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)
}
// 约束当前 contenView 关键:底部等于 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)
}
运行测试
添加子控件
-
UIButton
extension
extension UIButton {
/// 便利构造一个Button
///
/// - parameter title: 标题文字
/// - 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)
// 如果有图片,则设置image
if let imageN = imageName {
imageView?.image = UIImage(named: imageN)
}
}
}
- 新增一个添加子控件的方法
/// 添加子控件
///
/// - parameter title: 显示的文字
/// - parameter image: 显示的图片
///
/// - returns: 将添加的 button 返回
private func addChildButton(title: String, image: String) -> UIButton {
let button = UIButton(title: title, fontSize: 14, color: UIColor.darkGrayColor())
// 设置不同状态的背景颜色
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
}
- 定义按钮属性
/// 转发按钮
var retweetButton: UIButton?
/// 评论按钮
var commentButton: UIButton?
/// 表态按钮
var attituedButton: UIButton?
- 添加子控件
private func setupUI(){
// 1.添加子控件
retweetButton = addChildButton("转发", image: "timeline_icon_retweet")
commentButton = addChildButton("评论", image: "timeline_icon_comment")
attituedButton = addChildButton("赞", image: "timeline_icon_unlike")
}
设置约束
- 设置思路:
转发按钮
-> A,评论按钮
-> B,赞按钮
-> C- A 按钮的左边紧贴父控件的左边,顶部和高度与父控件对齐
- B 按钮的左边紧贴 A 按钮的右边,右边紧贴 C 按钮的左边, 顶部和高度与父控件对齐
- C 按钮的右边紧贴父控件的右边,顶部和高度与父控件对齐
- 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)
}
运行测试
- 添加分割线
/// 添加分割线
private func addSpliteView() -> UIImageView {
let image = UIImageView(image: UIImage(named: "timeline_card_bottom_line"))
addSubview(image)
return image
}
- 设置约束
// 3.添加分割线
let sp1 = addSpliteView()
let sp2 = addSpliteView()
// 4.设置分割线的约束
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)
}
设置数据
- 在
HMStatus
模型中添加以下属性
/// 转发数
var reposts_count: Int = 0
/// 评论数
var comments_count: Int = 0
/// 表态数
var attitudes_count: Int = 0
- 添加视图模型到
HMStatusToolBar
中
/// 视图模型
var statusViewModel: HMStatusViewModel?
-
HMStatusCell
里面设置此属性
/// 微博视图模型
var statusViewModel: HMStatusViewModel? {
didSet{
// 设置原创微博内容的视图模型
originalView.statusViewModel = statusViewModel
// 设置底部 ToolBar 的视图模型
statusToolBar.statusViewModel = statusViewModel
}
}
-
数量显示逻辑
- 小于 10000,直接显示数字
- 大于 10000,小于 11000,显示
1万
- 大于 11000,小于 20000,显示
1.x万
- 其他同理
在
视图模型
里面添加repostsCountString
属性
/// 转发数量
var repostsCountString: String {
// 默认显示 `转发`
var result = "转发"
let count = status?.reposts_count ?? 0
// 如果数量为 0,直接显示 `转发`
if count == 0 {
return result
}
// 如果数量大于10000,再做处理
if count > 10000 {
// 先除以1000返回一个整数,再除以10,返回一个小数
let res = Float(count / 1000) / 10
// 拼接字符串
result = "\(res)万"
}else{
result = "\(status!.reposts_count)"
}
return result
}
- 添加测试数据(在
HMStatus
中添加reposts_count
的 didSet 方法)
/// 转发数
var reposts_count: Int = 0 {
didSet{
reposts_count = 10009
}
}
测试发现显示
1.0万
- 添加判断小数为 0 的逻辑
// 查看是否包含 .0 万
if result.containsString(".0万") {
result = result.stringByReplacingOccurrencesOfString(".0", withString: "")
}
- 在
视图模型
里面添加repostsCountString
和 `` 属性
/// 评论数量
var commentsCountString: String {
return "评论"
}
/// 点赞数量
var attitudesCountString: String {
return "赞"
}
- 抽取处理数量逻辑的方法
/// 处理 转发\评论\赞 数量逻辑
///
/// - parameter count: 对应数量
/// - parameter defaultString: 默认显示的文字
///
private func countString(count: Int, defaultString: String) -> String {
var result = defaultString
if count == 0 {
return result
}
// 如果数量大于10000,再做处理
if count > 10000 {
let res = Float(count / 1000) / 10
result = "\(res)万"
// 查看是否包含 .0 万
if result.containsString(".0万") {
result = result.stringByReplacingOccurrencesOfString(".0", withString: "")
}
}else{
result = "\(count)"
}
return result
}
- 更改三个属性的 get 方法
/// 转发数量
var repostsCountString: String {
let count = status?.reposts_count ?? 0
return countString(count, defaultString: "转发")
}
/// 评论数量
var commentsCountString: String {
let count = status?.comments_count ?? 0
return countString(count, defaultString: "评论")
}
/// 点赞数量
var attitudesCountString: String {
let count = status?.attitudes_count ?? 0
return countString(count, defaultString: "赞")
}
运行测试(也可以将这三个属性设置成存储型属性)
转发微博内容
数据模型准备
- 添加转发微博属性
/// 转发微博
var retweeted_status: HMStatus?
- 在
setValue(value: AnyObject?, forKey key: String)
函数中增加一下代码
// 2. 转发微博
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: - 懒加载控件
/// 转发微博正文内容
private lazy var contentLabel = UILabel(color: UIColor.darkGrayColor(), fontSize: 15, layoutWidth: UIScreen.mainScreen().bounds.width - 2 * HMStatusCellMargin)
- 添加控件设置约束
private func setupUI(){
backgroundColor = UIColor(white: 0.9, alpha: 1)
// 添加子控件
addSubview(contentLabel)
// 设置约束
contentLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(self.snp_top).offset(HMStatusCellMargin)
make.leading.equalTo(self.snp_leading).offset(HMStatusCellMargin)
}
// 设置当前View的底部为内容的底部加上间距
snp_makeConstraints { (make) -> Void in
make.bottom.equalTo(contentLabel.snp_bottom).offset(HMStatusCellMargin)
}
}
- 在
HMStatusViewModel
添加retweetText
属性
// 转发微博内容
var retweetText: String? {
if let retStatus = self.status?.retweeted_status where retStatus.text != nil {
return "@\(retStatus.user!.name!):\(retStatus.text!)"
}
}
- 设置数据
/// 微博视图模型
var statusViewModel: HMStatusViewModel?{
didSet{
contentLabel.text = statusViewModel!.retweetText
}
}
更新 HMStatusCell
- 添加转发微博控件
// 转发微博
private lazy var retweetView: HMStatusRetweetView = HMStatusRetweetView()
...
private func setupUI(){
// 添加控件
contentView.addSubview(originalView)
contentView.addSubview(retweetView)
contentView.addSubview(statusToolBar)
// 添加原创微博内容的约束
originalView.snp_makeConstraints { (make) -> Void in
// 关键:约束原创微博整体 View 的顶部
make.top.equalTo(contentView.snp_top)
make.width.equalTo(contentView.snp_width)
}
// 添加转发微博内容的约束
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)
}
}
- 设置数据(需要判断是否有转发微博)
/// 微博视图模型
var statusViewModel: HMStatusViewModel? {
didSet{
// 设置视图模型
originalView.statusViewModel = statusViewModel
statusToolBar.statusViewModel = statusViewModel
// 如果有转发微博
if statusViewModel?.status?.retweeted_status != nil {
retweetView.hidden = false
// 设置转发微博的视图模型
retweetView.statusViewModel = statusViewModel
// TODO:需要更新约束,statusToolBar的顶部要与转发微博底部对齐
}else{
// 没有转发微博,隐藏转发微博的View
retweetView.hidden = true
// TODO:需要更新约束,statusToolBar的顶部要与原创微博底部对齐
}
}
}
- 定义变量记住
statusToolBar
的顶部约束
// toolBar 顶部约束
var toolBarTopConstraints: Constraint?
...
// 在约束toolBar顶部约束的时候记录
// 底部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)
}
- 根据是否有转发微博更新约束
// 先让之前记录的约束失效 -> 约束的时候重新记录
toolBarTopConstraints?.uninstall()
// 如果有转发微博
if statusViewModel?.status?.retweeted_status != nil {
retweetView.hidden = false
// 设置转发微博的视图模型
retweetView.statusViewModel = statusViewModel
statusToolBar.snp_updateConstraints(closure: { (make) -> Void in
toolBarTopConstraints = make.top.equalTo(retweetView.snp_bottom).constraint
})
}else{
// 没有转发微博,隐藏转发微博的View
retweetView.hidden = true
// 更新约束
statusToolBar.snp_updateConstraints(closure: { (make) -> Void in
toolBarTopConstraints = make.top.equalTo(originalView.snp_bottom).constraint
})
}
运行测试
微博配图
数据
配图数据对应的字段 pic_urls
,格式为:
pic_urls: [
{
thumbnail_pic: "http://ww2.sinaimg.cn/thumbnail/005Ko17Djw1exjar89996j30b40b440s.jpg"
},
{
thumbnail_pic: "http://ww2.sinaimg.cn/thumbnail/005Ko17Djw1exjar89996j30b40b440s.jpg"
}
]
- 定义
pic_urls
内部的数据模型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
模型中增加配图数组模型
/// 配图模型数组
var pic_urls: [HMStatusPhotoInfo]?
- 在
setValue(value: AnyObject?, forKey key: String)
函数中增加一下代码
if key == "pic_urls" {
var tempArray = [HMStatusPhotoInfo]()
// 遍历字典转模型
for value in value as! [[String: AnyObject]] {
tempArray.append(HMStatusPhotoInfo(dictionary: value))
}
pic_urls = tempArray
}
思路
- 图片可以有多张可以使用 UICollectionView 实现
- 根据原创微博(转发微博)是否有配图去显示或者隐藏控件
控件显示实现
- 定义
HMStatusPictureView
class HMStatusPictureView: UICollectionView {
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: UICollectionViewFlowLayout())
// 为了测试,设置背景颜色为随机色
backgroundColor = RandomColor()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
原创微博
- 在
HMStatusOriginalView
中懒加载控件
/// 配图视图
private lazy var pictureView: HMStatusPictureView = HMStatusPictureView()
- 添加控件并设置约束
// 配图视图
pictureView.snp_makeConstraints { (make) -> Void in
// 先写死一个宽高
make.size.equalTo(CGSizeMake(100, 100))
make.leading.equalTo(contentLabel.snp_leading)
make.top.equalTo(contentLabel.snp_bottom).offset(HMStatusCellMargin)
}
下一步就需要根据原创微博是否有配图去动态更新当前原创微博View的高度
如果有:原创微博 View 的底部是相对于配图控件来说的
如果没有:原创微博 View 的底部是相对于微博内容控件来说的
所以需要在初始化控件的时候记录当前 View 底部的约束
记录底部的约束
/// 当前 View 的底部约束
private var bottomConstraint: Constraint?
...
// 约束当前 View 的底部与正文内容的底部一样 并 记录该约束
snp_makeConstraints { (make) -> Void in
self.bottomConstraint = make.bottom.equalTo(contentLabel.snp_bottom).offset(HMStatusCellMargin).constraint
}
- 在设置视图模型的时候判断是否有配图去更新约束
// 先移除之前的约束
bottomConstraint?.uninstall()
// 配图视图
if let picUrls = statusViewModel?.status?.pic_urls where picUrls.count > 0 {
pictureView.hidden = false
// 有配图,更新约束 -> 更新当前 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
})
}
运行测试:原创微博有配图,就会显示配图控件
- 根据配图的张数计算控件大小
- 添加方法
calcViewSize()
到HMStatusPictureView
中
- 添加方法
/// 根据图片个数计算当前View的大小
private func calcViewSize() -> CGSize {
// 获取到配图张数
let count = pic_urls?.count ?? 0
// 计算出每一个条目的宽高
// 每一个条目之间的间距
let HMStatusPictureItemMargin: CGFloat = 5
// 每一个Item的宽高
let HMStatusPictureItemWH = (SCREENW - 2 * HMStatusCellMargin - 2 * HMStatusPictureItemMargin) / 3
// 计算出多少列
let col = count == 4 ? 2 : (count > 3 ? 3 : count)
let row = count == 4 ? 2 : ((count - 1) / 3 + 1)
// 计算出当前控件的宽度
let width = HMStatusPictureItemWH * CGFloat(col) + CGFloat(col - 1) * HMStatusPictureItemMargin;
let height = HMStatusPictureItemWH * CGFloat(row) + CGFloat(row - 1) * HMStatusPictureItemMargin;
return CGSizeMake(width, height)
}
- 定义配图数据的属性
/// 配图
var pic_urls: [HMStatusPhotoInfo]?
-
HMStatusOriginalView
设置数据的时候给配图 View 设置数据
// 设置数据
pictureView.pic_urls = picUrls
- 在设置数据的时候去更新当前配图控件的大小约束
/// 配图
var pic_urls: [HMStatusPhotoInfo]? {
didSet{
// 在设置配图的时候计算当前 View 的大小
snp_updateConstraints { (make) -> Void in
make.size.equalTo(calcViewSize())
}
}
}
- 为了测试方便,添加一个测试 label 到配图控件里面,显示当前配图控件里面需要展示几张图片
/// 测试:用于显示张数的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)
}
...
// 在设置数据的时候,让 label 显示配图张数
var pic_urls: [HMStatusPhotoInfo]? {
didSet{
...
label.text = "\(pic_urls!.count)"
}
}
运行测试
转发微博配图
- 添加控件思路与原创微博一样
注意:设置数据的时候一定要设置成转发微博的数据
图片显示
- 定义可重用 ID
// 可重用ID
private let HMStatusPictureCellId = "HMStatusPictureCellId"
- 设置数据源以及代理,设置每一个 item 的宽度
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: UICollectionViewFlowLayout())
...
// 设置代理与数据源都是自己
self.delegate = self
self.dataSource = self
// 设置layout
let layout = collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSizeMake(HMStatusPictureItemWH, HMStatusPictureItemWH)
// 设置间隔
layout.minimumInteritemSpacing = HMStatusPictureItemMargin
layout.minimumLineSpacing = HMStatusPictureItemMargin
// 注册cell
self.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: HMStatusPictureCellId)
}
- 实现两个数据源方法
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)
// 为了查看出效果,设置 cell 的背景颜色为随机色
cell.backgroundColor = RandomColor()
return cell
}
}
运行测试
- 自定义 Cell
HMStatusPictureCell
private class HMStatusPictureCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
// 设置背景颜色为随机颜色
backgroundColor = RandomColor()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
- 更改注册的cell
// 注册cell
self.registerClass(HMStatusPictureCell.self, forCellWithReuseIdentifier: HMStatusPictureCellId)
运行测试
- 添加图片控件
// 懒加载控件
private lazy var imageView: UIImageView = {
let imageView = UIImageView()
// 设置imageView的显示模式
imageView.contentMode = UIViewContentMode.ScaleAspectFill
// 切掉多余部分
imageView.clipsToBounds = true
return imageView;
}()
...
// 添加控件并设置约束
// 添加子控件
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
/// 设置数据模型
var photoInfo: HMStatusPhotoInfo?
- 在数据源方法里面设置数据
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(HMStatusPictureCellId, forIndexPath: indexPath) as! HMStatusPictureCell
// 设置数据
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 图标
/// gif 图标
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");
}
- 设置配图控件的背景颜色
/// 原创微博配图控件
private lazy var pictureView: HMStatusPictureView = {
let pictureView = HMStatusPictureView()
pictureView.backgroundColor = UIColor.whiteColor();
return pictureView;
}()
...
/// 转发微博配图控件
private lazy var pictureView: HMStatusPictureView = {
let pictureView = HMStatusPictureView()
pictureView.backgroundColor = UIColor(white: 0.95, alpha: 1);
return pictureView;
}()
其他细节
- 取消分隔线
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
- 增加 cell 分隔视图
// 设置 cell 的contentView的背景颜色
// 设置背景颜色
contentView.backgroundColor = UIColor(white: 240 / 255, alpha: 1)
// 设置原创微博背景色为白色 在 `HMStatusOriginalView`
backgroundColor = UIColor.whiteColor()
// 更改原创微博距离顶部的间距 在 `HMStatusCell`
originalView.snp_makeConstraints { (make) -> Void in
// 距离顶部有间距
make.top.equalTo(contentView.snp_top).offset(HMStatusCellMargin)
...
}
- 设置 tableView 的背景色
tableView.backgroundColor = UIColor(white: 240 / 255, alpha: 1)
- 抽取颜色的方法 ->
CommonTools.swift
- RGBColor & 随机颜色
/// RGB颜色
func RGB(r r: CGFloat, g: CGFloat, b: CGFloat) -> UIColor {
return UIColor(red: r / 256, green: g / 256, blue: b / 256, alpha: 1)
}
/// 随机颜色
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
单张图片
目标
- 将单张图片提前缓存到本地,以便判断大小
- 复习 gcd 的
dispatch_group
- 熟悉 SDWebImage 的其他函数应用
预先加载图片说明
- 新浪微博的数据接口并没有返回每一张图片的尺寸
- 而对于保存在远程服务器的图片而言,客户端是无法获知服务器上的图片大小的
- 因此要实现单图都效果,需要先将图片缓存到本地
代码实现
缓存单张图片
- 在
HMStatusListViewModel
中增加cacheSingleImage
函数 - 调整
loadData
函数,调用cacheSingleImage
函数缓存单张图片
// 拼接数据
self.statusList = dataList + self.statusList
// 缓存图片
self.cacheSingleImage(dataList)
- 缓存图片并且回调
private func cacheSingleImage(array: [StatusViewModel]) {
// 1. 遍历数组
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: [], // 选项
progress: nil) // 进度
{ (image, error, _, _, _) in // 完成回调
if let img = image,
data = UIImagePNGRepresentation(img) {
print(data.length)
}
}
}
}
- 添加
dispatch_group
和数据长度
// 0. 调度组
let group = dispatch_group_create()
// 缓存数据长度
var dataLength = 0
- 下载图像之前入组,下载图像最后一行出组
// 3> 下载图片
dispatch_group_enter(group)
SDWebImageManager.sharedManager().downloadImageWithURL(
url, // URL
options: [], // 选项
progress: nil) // 进度
{ (image, error, _, _, _) in // 完成回调
// 不是每次图像都能下载成功
if let img = image,
data = UIImagePNGRepresentation(img) {
// 累加长度
dataLength += data.length
}
// 出组
dispatch_group_leave(group)
}
- 修改函数定义,增加完成回调参数
private func cacheSingleImage(dataArray: [HMStatusViewModel],completion: (isSuccessed: Bool)->()){
- 完成回调
// 2. 监听调度组完成
dispatch_group_notify(group, dispatch_get_main_queue()) {
print("缓存图像大小 \(dataLength / 1024) K")
completion(isSuccessed: true)
}
- 修改函数调用
// 3. 拼接数据
self.statusList = tempArray + self.statusList
// 4. 缓存图片
self.cacheSingleImage(tempArray, completion: finished)
修改单张图片显示
- 修改
calcViewSize
函数
// 2. 单图
if count == 1 {
// 临时设置单图大小
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
}
- 细节处理,防止图片过窄或者太宽
// 过窄处理 - 针对长图
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)
}
小结
-
dispatch_group
-
dispatch_group_enter
后续的 block 执行会受group
监听 -
block
的最后一句必须是dispatch_group_leave
,通知group
该任务完成 -
dispatch_group_enter
和dispatch_group_leave
无比成对出现
-
-
SDWebImage
- 下载图像时一定注意图像不一定都会被正确下载
-
SDWebImageManager.sharedManager().downloadImageWithURL
是 SDWebImage 的核心下载函数 -
SDWebImageManager.sharedManager().imageCache.imageFromDiskCacheForKey
使用图像完整的URL字符串
检查是否存在图像的磁盘缓存 -
SDWebImage
使用MD5
对URL 字符串
编码并作为缓存图像的文件名