之前一直是用OC语言写的,也学过一些简单的Swift语法,但是真正开始使用Swift,也就是一个月之前。所以从此之后开始改用Swift语言写博客文章。该篇文章主要说一下一般的静态页面的实现方案、优缺点分析以及我自己的实现方法。该篇文章技术含量只能算是很低,比起那些封装自己的tablView框架的博主相比,是小巫见大巫。主要是想把自己的一种思想写出来,以后碰到类似的页面能毫不犹豫的快速写出,并且写出的代码在不同的项目中都可以直接拿来使用。
一、常见的几种实现方案以及优缺点分析
使用UIScrollView来实现
实际开发中或许会有些人选择这种非常不佳的方案来实现这种静态tableView。
根据经验来看,这类写法基本不可取,主要原因有以下几点:
1、内容比较多的时候,子View数量会很多,这样一方面由于添加子View的代码过多引起 Controller 变得庞大,另一方面子View很多时,对子View的布局容易出现错误,不易排查。
2、针对第一点,可能会有开发人员将子View按行进行分块,并封装成一个较大的 View,这样会减少 UIScrollView 直接管理的子View数量,一定程度上避免了第一点问题。
3、但是,当设计师需要移动不同行的顺序,或者在某一位置添加了一行新的内容,这时候修改布局大概会令人吐血了吧。
4、如果某一行视图需要点击,则需要为该行视图添加手势或者采用 UIButton,并设置好点击的回调;当页面视图行数较多时,会导致代码杂乱。
使用xib实现
使用xib可能是个不错的选择,但是一般都是使用Autolayout进行布局,一旦布局上面出现了问题,可能不如直接使用第三方约束修更直接。
使用tableView实现
首先说明,我的最终实现肯定也是通过tableView实现的,只是在使用tableView时讲很多事件以及布局集中到一块。
相比使用xib以及UIScrollView,tableView无疑是好了很多,但是很多人在使用tableView布局这类静态cell的时候同样会遇到一些问题。响应事件不集中(cell的点击事件或cell上的子控件的点击事件),布局不同的cell要注册不同的cell,二本文恰恰就是对着几个问题进行处理。
二、我的实现方法
我的实现方法最终是这样的形式,和常规的使用tableView去实现这类静态cell相比,主要是把各种事件都统一集中起来管理(如cell的布局,cell的点击事件,UISwitch的点击事件,退出按钮的点击事件)。这样当静态页面布局有所变化的时候,只要集中更改一两块地方即可,不用再在整个项目中这里改一下哪里改一下。都在返回UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:StaticTableViewCell? = tableView.dequeueReusableCell(withIdentifier: guildCellId) as! StaticTableViewCell?
if cell == nil {
cell = StaticTableViewCell(style: .default, reuseIdentifier: guildCellId)
}
guard let subViews = cell?.contentView.subviews else { return cell!}
for sub in subViews {
sub.removeFromSuperview()
}
cell?.height = kNormalCellH
if indexPath.row == 0{//群聊名称
cell?.selectionStyle = .default
cell?.setCellStyleLeftTextAndRightImage(leftText: "群聊名称", image: #imageLiteral(resourceName: "rightArrow"), imageName: nil, urlStr: nil)
cell?.cellClickClosure = { [weak self] cell in
printLog("群名称")
}
}else if indexPath.row == 1{//群二维码
cell?.selectionStyle = .default
cell?.setCellStyleLeftTextAndRightImage(leftText: "群二维码", image: self.qrCodeImage, imageName: nil, urlStr: nil)
cell?.cellClickClosure = { [weak self] cell in
self?.showQRCodeImage()
}
}else if indexPath.row == 2{//屏蔽消息
cell?.selectionStyle = .none
guard let isON = GOLocalData.shared.isReceiveGuildMessage else { printLog("isReceiveGuildMessage为空"); return cell!}
cell?.setCellSwitchStyle(leftText: "屏蔽消息", sIsOn: isON, switchClosure: {[weak self] (isOn) in
if isOn{
print("开")
GOLocalData.shared.isReceiveGuildMessage = true
}else{
print("关")
GOLocalData.shared.isReceiveGuildMessage = false
}
})
}else if indexPath.row == 3{//退出并删除
cell?.selectionStyle = .none
cell?.height = kQuitCellH
cell?.setCellLoginOutStyle(btnTitle: "退出并删除", logoutClosure: { [weak self] in
printLog("退出按钮点击")
})
cell?.cellClickClosure = { [weak self] cell in
//printLog("退出")
}
}
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 3{
return kQuitCellH
}
return kNormalCellH
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let cell = tableView.cellForRow(at: indexPath) as! StaticTableViewCell
cell.cellClickClosure?(cell)
}
在UITableViewCell类中,我将每一个单一的控价都单独给封装起来,如箭头、头像、单独的文字label、分割线等,外部使用的时候只需要将这些当以的控件组装起来(在需要的时候更改一下frame即可),如创建一个左边是文字右边是UISwitch并带有分割线的cell,只需要在cell中单独添加一个方法调用四行代码即可。请看下面代码 func setCellSwitchStyle(leftText:String?,sIsOn:Bool,switchClosure:((_ isON: Bool)->())?)方法。
如此一来,随着项目的不断完善针对每种单一的控件我们都可以单独封装一下,之后在开发其他app的时候直接把整个UITableViewCell类拉到项目中,并可快速直接使用。其实严格意义上来说我应该通过属性记录封装每一个控件,而不是直接这样写。只是简单的写一下,主要是想说一下这种思想,相比于通篇都是if else if else if else 这样来布局的话,这样的管理稍微省了一些事。
class StaticTableViewCell: UITableViewCell {
fileprivate lazy var bag = DisposeBag()
var cellHeight: CGFloat = 0//cell的高度
var cellClickClosure: ((StaticTableViewCell)->())?//cell的点击回调
var switchClosure:((_ isOn:Bool)->())?//switch回调
var logoutClosure:(()->())?//退出按钮回调
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .gray
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
// MARK: - 对外公开的方法
extension StaticTableViewCell{
//styleOne 左边文字,右边图片(传入图片名或image)
func setCellStyleLeftTextAndRightImage(leftText:String?,image:UIImage?,imageName:String?,urlStr:String? = nil){
self.contentView.addSubview(self.createLabel(leftText: leftText))
if image != nil {
self.contentView.addSubview(self.createImageView(imageName: imageName, urlStr: nil, img: image))
}
if imageName != nil {
self.contentView.addSubview(self.createImageView(imageName: imageName, urlStr: nil, img: nil))
}
if urlStr != nil {
self.contentView.addSubview(self.createImageView(imageName: imageName, urlStr: urlStr, img: image))
}
//line
self.contentView.addSubview(gapLine())
}
//退出按钮style
func setCellLoginOutStyle(btnTitle:String?,logoutClosure:(()->())?){
self.logoutClosure = logoutClosure
self.contentView.addSubview(self.createQuitBtn(btnTitle: btnTitle))
}
//switch风格(左文字 右switch)
func setCellSwitchStyle(leftText:String?,sIsOn:Bool,switchClosure:((_ isON: Bool)->())?){
self.switchClosure = switchClosure
self.contentView.addSubview(self.createLabel(leftText: leftText))
//switch
self.contentView.addSubview(createSwitch(sIsOn: sIsOn))
//line
self.contentView.addSubview(gapLine())
}
}
// MARK: - 创建单一控件
extension StaticTableViewCell{
//创建右边的UISwitch
fileprivate func createSwitch(sIsOn:Bool) -> UISwitch {
let s = UISwitch()
//s.addTarget(self, action: #selector(switchAction(s:)), for: .valueChanged)
//FIXME:---这里实际上应该是用模型记录,暂时为了防止复用出现问题,临时用属性记录
//s.isOn = false
s.isOn = sIsOn
s.bounds = CGRect(x: 0, y: 0, width:41, height: 24)
s.center = CGPoint(x: (kScreenW - CGFloat(41/2.0) - kMargin), y: self.bounds.height/2)
//s.tintColor = ColorUtil.mainBlueColor()//边框颜色
s.onTintColor = ColorUtil.mainBlueColor()
//s.thumbTintColor = ColorUtil.mainWhiteColor()//滑块颜色
self.contentView.addSubview(s)
s.rx.value.subscribe { (event:Event) in
guard let element = event.element else {
return
}
self.switchClosure?(element)
}.addDisposableTo(bag)
return s
}
//创建退出按钮
fileprivate func createQuitBtn(btnTitle:String?)->UIButton{
let quitBtn = UIButton()
quitBtn.bounds = CGRect(x: 0, y: 0, width: 153, height: 44)
quitBtn.center = CGPoint(x: kScreenW/2.0, y: self.bounds.height/2.0)
quitBtn.setBackgroundImage(#imageLiteral(resourceName: "deleteQuit"), for: .normal)
quitBtn.setTitle(btnTitle, for: .normal)
quitBtn.titleLabel?.textColor = ColorUtil.mainWhiteColor()
quitBtn.titleLabel?.font = UIFont.systemFont(ofSize: 18)
self.contentView.addSubview(quitBtn)
quitBtn.rx.tap.subscribe { (event: Event<()>) in
self.logoutClosure?()
}.addDisposableTo(bag)
return quitBtn
}
//创建label
fileprivate func createLabel(leftText:String?)->UILabel {
let label = UILabel()
label.text = leftText
label.textColor = kTextColor
label.sizeToFit()
label.frame = CGRect(x:kMargin , y:(self.bounds.height - label.height)/2.0, width: label.width, height: label.height)
//self.contentView.addSubview(label)
return label
}
//创建imageView
fileprivate func createImageView(imageName:String? = nil, urlStr:String? = nil,img:UIImage? = nil)->UIImageView{
let IV = UIImageView()
var image:UIImage = UIImage()
if imageName != nil{
image = UIImage(named: imageName!)!
IV.image = image
}
if img != nil {
image = img!
IV.image = image
}
if urlStr != nil {
IV.kf.setImage(with: URL(string: urlStr!))
}
IV.bounds = CGRect(x: 0, y: 0, width:20, height: 20)
IV.center = CGPoint(x: kScreenW - 20/2.0 - kMargin, y: self.bounds.height/2)
return IV
}
//创建分割线
fileprivate func gapLine() -> UIView {
let view = UIView(frame: CGRect(x: kMargin, y: self.bounds.height - 1/UIScreen.main.scale, width: kScreenW - 2 * kMargin, height: 1/UIScreen.main.scale))
view.backgroundColor = kTextColor.withAlphaComponent(0.5)
return view
}
}