7.新浪微博Swift项目第七天

第七天

第七天我们的4个任务 1.转发微博处理, 2.单图处理, 3.缓存行高和性能优化, 4.自定义刷新控件. 下边直接开始:

转发微博的处理

  1. 首先我们将原创微博cell的xib复制一份,添加灰色背景和转发文字的label // 图

  2. 给我们的model添加一个新的属性retweeted_status存储被转发微博的数据,其数据结构跟status是一样的

    /// 被转发的原创微博
    var retweeted_status: XQWBStatus?
    
  3. 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
    
  4. viewModel中添加计算型属性 picURLsretweetedText

    // 计算型属性,根据是否存在转发微博返回原创微博的pic_urls 或 转发微博的pic_urls
    var picURLs:[XQWBStatusPicture]? {
        return status.retweeted_status?.pic_urls ?? status.pic_urls
    }
    // 被转发的文字
    var retweetedText: String?
    
  5. 最后在cell中给新的控件进行赋值

    // 配图视图的数据
    pictureView.viewModel = viewModel
    // 被转发的文字
    retweetedLabel?.text = viewModel?.retweetedText
    

单图处理

对于单图处理,我们需要先拿到图片,才能得到图片的宽和高,这里我们需要用SDWebImage的方法和GCD调度组来完成图片下载,然后在进行下一步的处理

  1. 遍历我们的listViewModel,使用SDWebImageGCD调度组下载图片
    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)
        }
    }
    
  2. 拿到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进行认真的处理,每一个控件的高度都需要认真计算

  1. 计算行高
    // 根据当前视图计算行高
    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
    }
    
  2. 在代理方法中返回我们的行高,并且取消动态行高
    // 返回具体的高度
      override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let viewModel = listViewModel.statusList[indexPath.row]
        return viewModel.rowHeight
    }
    // 取消动态行高
    // tableView?.rowHeight = UITableViewAutomaticDimension
    
  3. 要记得将我们在xib中设置的底部约束取消掉
  4. 其他性能优化离屏渲染栅格化
    • 这2个功能看起来很高端,其实代码很简单, 只需要在awakeFromNib中设置几个属性就可以
    override func awakeFromNib() {
        super.awakeFromNib()
        // 打开离屏渲染
        self.layer.drawsAsynchronously = true
        // 栅格化
        self.layer.shouldRasterize = true
        // 设置屏幕的分辨率
        self.layer.rasterizationScale = UIScreen.main.scale
    }
    

自定义刷新控件

由于系统刷新控件存在一定局限性,我们自定义一个刷新控件,通常许多app都有这样的控件

  1. 准备好刷新控件需要的类
    • 新建一个刷新控件XQRefreshControl 继承自UIControl (Control主要控制刷新操作)
    • 新建一个XQRefreshView 关联刷新控件,(View主要控制刷新UI的更新)
    • 创建一个刷新控件的xib关联view
  2. 通常情况下,刷新控件一般需要监听scrollViewcontentOffset的位置,然后进行对应的操作
    • 添加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    
           }
           ```
      
  3. 在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
            }
        }
    }
    

总结

今天的代码都不难,但是缓存行高和性能优化在开发中比较常用,面试也可能会被问到,需要大家多注意

往期内容: Swift新浪微博项目更新目录

项目git地址

转载于:https://my.oschina.net/ozawa4865/blog/1545530

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值