第七天
第七天我们的4个任务 1.转发微博处理, 2.单图处理, 3.缓存行高和性能优化, 4.自定义刷新控件. 下边直接开始:
转发微博的处理
-
首先我们将原创微博cell的xib复制一份,添加灰色背景和转发文字的label // 图
-
给我们的model添加一个新的属性
retweeted_status
存储被转发微博的数据,其数据结构跟status
是一样的/// 被转发的原创微博 var retweeted_status: XQWBStatus?
-
在
controller
中注册新的cell,并且根据retweeted_status
判断加载哪一个xib// 注册cell tableView?.register(UINib(nibName: "XQWBStatusNormalCell", bundle: nil), forCellReuseIdentifier: originalCellID) tableView?.register(UINib(nibName: "XQWBStatusRetweetedCell", bundle: nil), forCellReuseIdentifier: retweededCellID) // 根据 retweeted_status 判断加载的cell let viewModel = listViewModel.statusList[indexPath.row] let cellID = (viewModel.status.retweeted_status != nil) ? retweededCellID : originalCellID
-
在
viewModel
中添加计算型属性picURLs
和retweetedText
// 计算型属性,根据是否存在转发微博返回原创微博的pic_urls 或 转发微博的pic_urls var picURLs:[XQWBStatusPicture]? { return status.retweeted_status?.pic_urls ?? status.pic_urls } // 被转发的文字 var retweetedText: String?
-
最后在cell中给新的控件进行赋值
// 配图视图的数据 pictureView.viewModel = viewModel // 被转发的文字 retweetedLabel?.text = viewModel?.retweetedText
单图处理
对于单图处理,我们需要先拿到图片,才能得到图片的宽和高,这里我们需要用
SDWebImage
的方法和GCD调度组
来完成图片下载,然后在进行下一步的处理
- 遍历我们的
listViewModel
,使用SDWebImage
和GCD调度组
下载图片fileprivate func cacheSingleImage(list: [XQWBStatusViewModel], finish:@escaping ( _ isSuccess: Bool, _ shouldRefresh: Bool)->()) { // 调度组 let group = DispatchGroup() // 记录数据长度 var length = 0 // 便利数组,单张的缓存 for vm in list { // 1. 判断图片数量 if vm.picURLs?.count != 1 { continue } // 2. 图片模型 guard let pic = vm.picURLs![0].thumbnail_pic, let url = URL(string: pic) else { continue } // 下载图片 // 入组 group.enter() SDWebImageManager.shared().imageDownloader?.downloadImage(with: url, options: [], progress: nil, completed: { (image, _, _, _) in if let img = image, let data = UIImagePNGRepresentation(img) { length += data.count // 拿到image 以后 对配图视图进行修改 vm.updataSingleImageSize(image: img) } // 出组 group.leave() }) } // 对我们的group进行监测,只有group里边的所有线程都完成以后就会调用这个方法 group.notify(queue: DispatchQueue.main) { print("图像缓存 \(length/1024) K") finish(true, true) } }
- 拿到image 以后 对配图视图进行修改
// 使用单个图像更新视图大小 func updataSingleImageSize(image: UIImage) { // 拿到原图的大小 var size = image.size // 设置最大最小值 let maxWidth: CGFloat = 300 let minWidth: CGFloat = 40 // 进行过宽或过窄的处理 if size.width > maxWidth { size.width = maxWidth size.height = image.size.height / image.size.width * size.width } if size.width < minWidth { size.width = minWidth size.height = image.size.height / image.size.width * size.width / 4 } size.height += OutterMargin // 修改配图视图的大小 pictureViewSize = size }
缓存行高和性能优化
缓存行高需要我们对我们的cell进行认真的处理,每一个控件的高度都需要认真计算
- 计算行高
// 根据当前视图计算行高 func updataRowHeight() { // 定义一些常量 let margin: CGFloat = 12 let iconHeight: CGFloat = 34 let toolBarHeight: CGFloat = 35 let viewSize = CGSize(width: UIScreen.cz_screenWidth() - 2 * margin, height: CGFloat(MAXFLOAT)) let originalFont = UIFont.systemFont(ofSize: 15) let retweetedFont = UIFont.systemFont(ofSize: 15) var height:CGFloat = 0 // 1.顶部高度 height = 2 * margin + iconHeight + margin // 2.正文 if let text = status.text { // 计算文本高度 height += (text as NSString).boundingRect(with: viewSize, options: [.usesLineFragmentOrigin], attributes: [NSFontAttributeName: originalFont], context: nil).height } // 3.是否转发微博 if status.retweeted_status != nil { height += 2 + margin if let text = retweetedText { height += (text as NSString).boundingRect(with: viewSize, options: [.usesLineFragmentOrigin], attributes: [NSFontAttributeName: retweetedFont], context: nil).height } } // 4.配图视图 height += pictureViewSize.height // 5.底部 height += margin height += toolBarHeight // 6.使用属性记录 rowHeight = height }
- 在代理方法中返回我们的行高,并且取消动态行高
// 返回具体的高度 override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let viewModel = listViewModel.statusList[indexPath.row] return viewModel.rowHeight } // 取消动态行高 // tableView?.rowHeight = UITableViewAutomaticDimension
- 要记得将我们在xib中设置的底部约束取消掉
- 其他性能优化
离屏渲染
和栅格化
- 这2个功能看起来很高端,其实代码很简单, 只需要在
awakeFromNib
中设置几个属性就可以
override func awakeFromNib() { super.awakeFromNib() // 打开离屏渲染 self.layer.drawsAsynchronously = true // 栅格化 self.layer.shouldRasterize = true // 设置屏幕的分辨率 self.layer.rasterizationScale = UIScreen.main.scale }
- 这2个功能看起来很高端,其实代码很简单, 只需要在
自定义刷新控件
由于系统刷新控件存在一定局限性,我们自定义一个刷新控件,通常许多app都有这样的控件
- 准备好刷新控件需要的类
- 新建一个刷新控件XQRefreshControl 继承自UIControl (Control主要控制刷新操作)
- 新建一个XQRefreshView 关联刷新控件,(View主要控制刷新UI的更新)
- 创建一个刷新控件的xib关联view
- 通常情况下,刷新控件一般需要监听scrollView的contentOffset的位置,然后进行对应的操作
- 添加KVO
// 记录父视图 scrollView = sv // KVO 监听 contentOffset scrollView?.addObserver(self, forKeyPath: "contentOffset", options: [], context: nil)
- 实现KVO的方法
// KVO 调用的方法 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard let sv = scrollView else { return } let height = -(sv.contentInset.top + sv.contentOffset.y) if height < 0 { return } // 根据高度改变frame self.frame = CGRect(x: 0,y: -height,width: sv.bounds.width, height: height) // 判断临界点 if sv.isDragging { if height > XQRefreshOffset && (refreshView.refreshState == .Normal) { refreshView.refreshState = .Pulling }else if height <= XQRefreshOffset && (refreshView.refreshState == .Pulling) { refreshView.refreshState = .Normal } }else { if refreshView.refreshState == .Pulling { beginRefreshing() // 发送刷新数据的事件 sendActions(for: .valueChanged) } } }
- 数显开始刷新和结束刷新的方法
// 开始刷新 func beginRefreshing() { // 判断父视图 guard let sv = scrollView else { return } if refreshView.refreshState == .WillRefresh { return } // 设置刷新视图 refreshView.refreshState = .WillRefresh // 调整表格间距 var inset = sv.contentInset inset.top += XQRefreshOffset sv.contentInset = inset } // 结束刷新 func endRefreshing() { guard let sv = scrollView else { return } if refreshView.refreshState != .WillRefresh { return } // 回复刷新视图的状态 refreshView.refreshState = .Normal // 回复表格的contentInset var inset = sv.contentInset inset.top -= XQRefreshOffset sv.contentInset = inset } ```
- 添加KVO
- 在view的** XQRefreshState属性的 didSet**方法中进行UI的刷新
var refreshState: XQRefreshState = .Normal { didSet { switch refreshState { case .Normal: tipIcon.isHidden = false indicator.stopAnimating() tipLabel.text = "继续使劲拉" UIView.animate(withDuration: 0.25, animations: { self.tipIcon.transform = CGAffineTransform.identity }) case .Pulling: tipLabel.text = "放手就刷新" UIView.animate(withDuration: 0.25, animations: { self.tipIcon.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi-0.0001)) }) case .WillRefresh: tipLabel.text = "正在刷新中" // 隐藏提示图标 tipIcon.isHidden = true // 显示菊花 indicator.startAnimating() indicator.isHidden = false } } }