logo头像
Snippet 博客主题

自定义表单控件LFForm

效果图如下

image.png

分析

使用tableView来写,自定义不同类型的cell,写好统一的base控制器和行模型和组模型,使用MVVM模式来写会更好些,子类只要配置好数据源就可以
输入控件统一使用textView,因为有可能要显示多行
###文件目录如图
image.png

自定义base控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//
// LFFormController.swift
// MyDoYu
//
// Created by 刘刘峰 on 2019/11/24.
// Copyright © 2019 刘刘峰. All rights reserved.
//

import UIKit

class LFFormController: BaseViewController {

var mdataSource:[LFSectionModel]?

lazy var imageArray = [ZYPhotoModel]()

var viewModel:LFFormViewModel = {
return LFFormViewModel()
}()

lazy var tableView:UITableView = {
let tableView = UITableView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0), style: UITableView.Style.grouped)
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView()
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false

tableView.register(LFFormBaseCell.self, forCellReuseIdentifier: NSStringFromClass(LFFormBaseCell.self))
tableView.register(LFFormImageCell.self, forCellReuseIdentifier: NSStringFromClass(LFFormImageCell.self))
tableView.register(LFFormInputCell.self, forCellReuseIdentifier: NSStringFromClass(LFFormInputCell.self))
tableView.register(LFFormTextViewInputCell.self, forCellReuseIdentifier: NSStringFromClass(LFFormTextViewInputCell.self))
tableView.register(LFFormPlainCell.self, forCellReuseIdentifier: NSStringFromClass(LFFormPlainCell.self))
tableView.register(LFFormIconCell.self, forCellReuseIdentifier: NSStringFromClass(LFFormIconCell.self))
tableView.register(LFImageCollectionCell.self, forCellReuseIdentifier: NSStringFromClass(LFImageCollectionCell.self))
tableView.register(LFFormSexSelectCell.self, forCellReuseIdentifier: NSStringFromClass(LFFormSexSelectCell.self))

return tableView
}()

override func viewDidLoad() {
super.viewDidLoad()

self.view .addSubview(self.tableView)

self.tableView.snp_makeConstraints { (make) in
make.left.right.top.bottom.equalToSuperview()
}
self.tableView.uHead.beginRefreshing()

//回调通知刷新
viewModel.updateDataBlock = {[unowned self] in
self.tableView.reloadData()
}

self.loadData()
}


func loadData() {

viewModel.loadData()
}

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.view.endEditing(false)

}
}

extension LFFormController :UITableViewDataSource,UITableViewDelegate {

func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.mdataSource?.count ?? 0
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let sectionModel = viewModel.mdataSource?[indexPath.section]
let rowModel = sectionModel?.rows[indexPath.row]
if rowModel?.cellType == FormCellType.textScroll {
//自动计算高度
return LFFormTextViewInputCell.heightWithModel(rowModel: rowModel!)
}
return rowModel!.rowHeight
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionModel = viewModel.mdataSource?[section]
return sectionModel?.rows.count ?? 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let sectionModel = viewModel.mdataSource?[indexPath.section]
let rowModel = sectionModel?.rows[indexPath.row]

if rowModel?.cellType == FormCellType.text {
let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(LFFormInputCell.self), for: indexPath)as?LFFormInputCell
cell?.rowModel = rowModel!

return cell!

}else if rowModel?.cellType == FormCellType.textScroll {
let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(LFFormTextViewInputCell.self), for: indexPath)as?LFFormTextViewInputCell
cell?.rowModel = rowModel!

return cell!

}else if rowModel?.cellType == FormCellType.headImage {
let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(LFFormImageCell.self), for: indexPath)as?LFFormImageCell
cell?.rowModel = rowModel!

return cell!

}else if rowModel?.cellType == FormCellType.icon {
let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(LFFormIconCell.self), for: indexPath)as?LFFormIconCell
cell?.rowModel = rowModel!

return cell!

}
else if rowModel?.cellType == FormCellType.plain {
let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(LFFormPlainCell.self), for: indexPath)as?LFFormPlainCell
cell?.rowModel = rowModel!

return cell!

}

else if rowModel?.cellType == FormCellType.collectionImageCell {
let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(LFImageCollectionCell.self), for: indexPath)as?LFImageCollectionCell
cell?.rowModel = rowModel!
self.imageArray = (cell?.rowModel!.imageArray)!
return cell!

}else if rowModel?.cellType == FormCellType.sexCell {
let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(LFFormSexSelectCell.self), for: indexPath)as?LFFormSexSelectCell
cell?.rowModel = rowModel!

return cell!

}
else {

let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(LFFormBaseCell.self), for: indexPath)as?LFFormBaseCell
cell?.rowModel = rowModel!
return cell!

}

}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionModel = viewModel.mdataSource?[section]
return sectionModel?.headerView
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let sectionModel = viewModel.mdataSource?[section]
return sectionModel?.footerView
}

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let sectionModel = viewModel.mdataSource?[section]

return sectionModel?.headerHeight ?? 0
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
let sectionModel = viewModel.mdataSource?[section]

return sectionModel?.footerHeight ?? 0
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

let sectionModel = viewModel.mdataSource?[indexPath.section]
let rowModel = sectionModel?.rows[indexPath.row]
guard rowModel?.canEdit == false else {
return
}
self.view.endEditing(false)


}


}

写这种表单或者个人信息页面等都可以继承这个控制器,然后在子类重写一下loadData方法即可,也可以自己再按自己需求添加自定义的cell

自定义base类Cell

自定义cell比较简单我就不一一贴代码只贴一个base基类
比较核心的代码:监听textView的文字的变化并使用rowModel更新文字

1
2
3
4
5
6
7
8
9
10
11
12
func textDidChange(text: String?) {
rowModel?.text = text ?? ""
//通知tableView刷新
let view = self.superview
if view != nil {
let tableView = self.superTableView()
UIView.performWithoutAnimation {
tableView?.beginUpdates()
tableView?.endUpdates()
}
}
}

textView自适应cell的高度

有时候有需求是cell的高度随着输入框输入文字而变化,核心代码
在heightForRowAt这个方法里计算高度,但是我们要给textView约束

  • 计算高度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    let sectionModel = viewModel.mdataSource?[indexPath.section]
    let rowModel = sectionModel?.rows[indexPath.row]
    if rowModel?.cellType == FormCellType.textScroll {
    //自动计算高度
    return LFFormTextViewInputCell.heightWithModel(rowModel: rowModel!)
    }
    return rowModel!.rowHeight
    }
  • textView添加约束
    给一个最小高度,超出最小高度之后需要更改约束那么使用约束的优先级即可

    1
    2
    3
    4
    5
    6
    7
    8
    self.textView.snp_makeConstraints { (make) in
    make.left.equalToSuperview().offset(16)
    make.right.equalToSuperview().offset(-16)
    make.top.equalTo(titleLab.snp_bottom).offset(10)
    make.height.greaterThanOrEqualTo(60).priorityHigh()
    make.bottom.equalToSuperview().priorityLow()

    }
  • textView自动更新高度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    func textViewDidChange(_ textView: UITextView) {
    //获取frame的值
    let frame = textView.frame
    //定义一个constrainSize值用于计算textview的高度

    let constrainSize = CGSize(width:frame.size.width,height:CGFloat(MAXFLOAT))

    //获取textview的真实高度

    var size = textView.sizeThatFits(constrainSize)

    //如果textview的高度大于最大高度高度就为最大高度并可以滚动,否则不能滚动

    if size.height >= maxHeight{

    size.height = maxHeight

    textView.isScrollEnabled = true

    }else{

    textView.isScrollEnabled = false

    }

    //重新设置textview的高度

    textView.frame.size.height = size.height

    currentLength = textView.text.length

    self.lengthLab.text = "\(currentLength)/\(maxLength)"


    }
  • BaseCell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    //
    // LFFormBaseCell.swift
    // PXSSwift
    //
    // Created by Pro on 2020/4/11.
    // Copyright © 2020 刘刘峰. All rights reserved.
    //

    import UIKit

    enum FormCellType:String {
    case plain = "无标题输入框"
    case text = "标题+输入框"
    case picker = "选择器"
    case icon = "图标+文字"
    case headImage = "头像"
    case textScroll = "滚动输入框"
    case collectionImageCell = "图片上传"
    case imageTextCell = "配套设施图片文字"
    case sexCell = "性别选择"

    }
    class LFFormBaseCell: UITableViewCell,LFTextViewDelegate {

    var rowModel:LFRowModel? {
    didSet {
    self.cellType = self.rowModel!.cellType
    self.titleLab.text = self.rowModel?.title

    let bool = self.rowModel?.text?.isBlank
    self.textView.placeholderLabel.isHidden = !(bool ?? true)

    self.textView.placeholderLabel.text = self.rowModel?.detailText
    self.textView.text = self.rowModel?.text


    self.textView.maxLength = self.rowModel?.maxLength ?? 20
    self.textView.keyboardType = self.rowModel!.keyboardType

    //设置右边箭头按钮或者单位
    self.canEdit = self.rowModel?.canEdit

    if self.rowModel?.unit?.length ?? 0 > 0 {
    self.unit = self.rowModel?.unit
    }

    if self.rowModel?.rightImage?.length ?? 0 > 0 {
    self.rightImage = self.rowModel?.rightImage

    }

    self.iconImageV.isHidden = self.rowModel?.iconHiden ?? false
    if self.cellType == .headImage {
    self.headImageV.kf.setImage(with: URL(string: self.rowModel?.headImage ?? ""))
    }else if self.cellType == .icon {
    self.iconImageV.image = UIImage.init(named: self.rowModel?.iconImage ?? "")
    }
    }
    }
    var maxLength:Int?{
    didSet {
    self.textView.maxLength = self.maxLength ?? 10
    }
    }
    //单位
    var unit:String? {
    didSet {
    self.rightBtn.setTitle(self.unit, for: UIControl.State.normal)
    self.rightBtn.setTitleColor(AppColor.darkgGray, for: UIControl.State.normal)
    self.rightBtn.titleLabel?.font = FONT(font: 14)
    //设置了单位就不设置图片
    self.rightBtn.setImage(UIImage.init(named: ""), for: UIControl.State.normal)
    }
    }
    //cell类型
    var cellType :FormCellType {
    didSet {
    self.setupUI()
    }
    }
    //是否可编辑
    var canEdit:Bool?{
    didSet {
    self.textView.isUserInteractionEnabled = self.canEdit ?? true
    if self.textView.isUserInteractionEnabled == false {
    // self.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
    //设置了图片就不设置单位
    self.rightBtn.setTitle("", for: UIControl.State.normal)

    self.rightBtn.setImage(UIImage.init(named: "mine_Arrow"), for: UIControl.State.normal)
    }

    }
    }

    //标题
    var titleLab:UILabel = {
    var label = UILabel()
    label.textColor = AppColor.black
    label.font = UIFont.systemFont(ofSize: 16)
    label.textAlignment = NSTextAlignment.left
    return label
    }()

    //详细描述
    var detailLab:UILabel = {
    var label = UILabel()
    label.textColor = AppColor.darkgGray
    label.font = UIFont.systemFont(ofSize: 14)
    label.textAlignment = NSTextAlignment.left
    return label
    }()

    //右边单位
    var rightBtn:UIButton = {
    var button = UIButton()
    button.sizeToFit()
    return button
    }()

    var rightImage:String? {
    didSet {
    self.rightBtn.setImage(UIImage.init(named: self.rightImage ?? " "), for: UIControl.State.normal)
    self.rightBtn.addTarget(self, action: #selector(rightBtnClick(sender:)), for: UIControl.Event.touchUpInside)
    }
    }

    var textView:LFTextView = {
    var textView = LFTextView()
    textView.textColor = AppColor.darkgGray
    textView.textAlignment = NSTextAlignment.right
    textView.font = UIFont.systemFont(ofSize: 14)
    textView.placeholderLabel.text = "请输入"
    textView.placeholderLabel.font = FONT(font: 14)
    textView.placeholderLabel.textAlignment = NSTextAlignment.right

    return textView
    }()

    var iconImageV:UIImageView = {
    var imageView = UIImageView()
    return imageView
    }()

    var headImageV:UIImageView = {
    var imageView = UIImageView()
    return imageView
    }()
    var botline:UIView = {
    var view = UIView()
    view.backgroundColor = AppColor.lineColor
    return view
    }()



    var callBlock:LFCallBack?

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    self.cellType = .plain
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    self.selectionStyle = .none

    self.contentView.addSubview(self.botline)
    self.botline.snp_makeConstraints { (make) in
    make.left.equalToSuperview().offset(16)
    make.right.equalToSuperview().offset(-16)
    make.height.equalTo(0.5)
    make.bottom.equalToSuperview()
    }

    }

    required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }

    func setupUI() {

    }


    }

    //MARK: 监听
    extension LFFormBaseCell {
    func textDidChange(text: String?) {
    rowModel?.text = text ?? ""
    //通知tableView刷新高度
    let view = self.superview
    if view != nil {
    let tableView = self.superTableView()
    UIView.performWithoutAnimation {
    tableView?.beginUpdates()
    tableView?.endUpdates()
    }
    }
    }

    @objc func rightBtnClick(sender:UIButton) {
    if (callBlock != nil) {
    callBlock!()
    }
    }


    }

    extension LFFormBaseCell {
    //返回cell所在的UITableView
    func superTableView() -> UITableView? {
    for view in sequence(first: self.superview, next: { $0?.superview }) {
    if let tableView = view as? UITableView {
    return tableView
    }
    }
    return nil
    }
    }

baseModel类

  • 行模型 可以根据自己需求添加属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    //
    // LFRowModel.swift
    // MyDoYu
    //
    // Created by 刘刘峰 on 2019/11/24.
    // Copyright © 2019 刘刘峰. All rights reserved.
    //

    import UIKit

    class LFRowModel:NSObject {
    var title:String?//标题
    var text:String?
    var detailText:String? //占位文字
    var rowHeight :CGFloat = 54.0
    var cellType:FormCellType = .plain
    var canEdit:Bool = true //是否可编辑
    var maxLength:Int = 20
    var unit:String? //单位
    var value:String?
    var headImage:String?//头像
    var iconImage:String?//左侧icon
    var rightImage:String?//右侧icon

    var sexValue:Int? //性别 0 未知 1男 2女

    var keyboardType:UIKeyboardType = .default
    var needHud:Bool = true //是否弹提示
    var iconHiden:Bool = false //是否隐藏星号
    var showTips:String?//弹出提示

    //上传图片数据源
    var imageArray = [ZYPhotoModel]()

    init(title: String? = nil, detailText:String, cellType: FormCellType) {
    self.title = title
    self.detailText = detailText
    self.cellType = cellType
    super.init()
    }

    }
  • 组模型
    包含 组头标题组尾标题和高度等,也可以传入自定义的view

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //
    // LFSectionModel.swift
    //
    // Created by 刘刘峰 on 2019/11/24.
    // Copyright © 2019 刘刘峰. All rights reserved.
    //

    import UIKit

    class LFSectionModel:NSObject {
    var headerTitle:String?
    var footerTitle:String?
    var rows = [LFRowModel]()
    var headerView: UIView?
    var footerView: UIView?
    var headerHeight:CGFloat = 0
    var footerHeight:CGFloat = 0

    init(rows:[LFRowModel]) {
    self.rows = rows
    super.init()
    }
    }

照片选择自适应cell高度

主要布局cell里面嵌套 UICollectionView,图片选择框架可以自己找一些比较出名的框架,

###demo效果如图
image.png

微信打赏

赞赏是不耍流氓的鼓励