[2020/0706/14:18] 更新:
在 Swift 中通过 switch 区别 cell,工作效率提升至 30%:
1.高复用(所有录入界面均可使用同一套区分逻辑),详情页大部分代码都可复用;
2.高扩展(因为通过cell 类名字符串区别,所以增, 删, 改(顺序位置)都不会影响到已经实现的 cell 逻辑,拥有极强的扩展性);
思路
[通过区分 cell,构建不同的显示, 基于KVC的非无限数据界面通用化.]
:
var dataModel = CouponRechargeRecordModel()
lazy var list: [[[String]]] = {
var array: [[[String]]] = [
[
["*车场" + kBlankFour, "UITableViewCell", "50.0", "请选择", "parkname", ],
["*商户" + kBlankFour, "UITableViewCell", "50.0", "请选择", "name", ],
["*缴费金额" + kBlankTwo, "UITableViewCellTextField", "50.0", "请输入", "charge_money", ],
["*券类别" + kBlankThree, "UITableViewCellSegment", "50.0", "", "categoryStr", "预定义,自定义"],
["*券类型" + kBlankThree, "UITableViewCell", "50.0", "请选择", "couponTypeValue", ],
["*面额" + kBlankFour, "UITableViewCellTextField", "50.0", "请输入", "faceValue", ],
["*张数" + kBlankFour, "UITableViewCellStep", "50.0", "请输入", "count", ],
["有效时间" + kBlankTwo, "UITableViewCellDateRange", "50.0", "", "validbtime,validetime", ],
["有效时段" + kBlankTwo, "UITableViewCellDateRange", "50.0", "", "btime,etime", ],
["备注" + kBlankFour, "UITableViewCellTextField", "50.0", "请输入", "memo", ],
],
]
return array
}()
extension IOPMerchantRechargeEntryController: UITableViewDataSource, UITableViewDelegate{
// MARK: - tableView
func numberOfSections(in tableView: UITableView) -> Int {
return list.count;
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sections = list[section]
return sections.count;
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if dataModel.categoryStr == "1" {
let sections = list[indexPath.section]
let itemList = sections[indexPath.row]
if couponFixTypeItems.contains(itemList[0]) {
return 0.0
}
}
return tableView.rowHeight
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sections = list[indexPath.section]
let itemList = sections[indexPath.row]
let value0 = itemList[0]
let value2 = itemList[2]
let value3 = itemList[3]
let value4 = itemList[4]
switch itemList[1] {
case "UITableViewCellTextField":
let cell = UITableViewCellTextField.cellWithTableView(tableView)
cell.labelLeft.font = UIFont.systemFont(ofSize: 14)
cell.labelLeft.textColor = UIColor.textColor3
cell.isHidden = value2.cgFloatValue <= 0.0
cell.hasAsterisk = value0.contains("*")
cell.textfield.rightViewMode = .never;
cell.textfield.textAlignment = .left
cell.labelLeft.text = value0
cell.textfield.placeholder = value3
cell.textfield.text = dataModel.valueText(forKeyPath: value4, defalut: "")
cell.block { (textField) in
self.dataModel.setValue(textField.text ?? "", forKeyPath: value4)
// DDLog("%@_%@_", value4, self.dataModel.valueText(forKeyPath: value4))
}
if ["charge_money", "faceValue"].contains(value4) {
cell.textfield.textAlignment = .right
if value4 == "charge_money" {
cell.textfield.asoryView(true, text: " 元")
} else if value4 == "faceValue" {
let unit = CouponRecordModel.getCouponUnit(self.dataModel.couponTypeValue)
cell.textfield.asoryView(true, text: " " + unit)
}
}
// cell.textfield.getViewLayer()
// cell.getViewLayer()
return cell
case "UITableViewCell":
let cell = UITableViewCell.dequeueReusableCell(tableView, identifier: "UITableViewCellValue1", style: .value1)
cell.textLabel?.font = UIFont.systemFont(ofSize: 15)
cell.textLabel?.textColor = UIColor.textColor3;
cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 15)
cell.detailTextLabel?.textColor = UIColor.theme
cell.accessoryType = .disclosureIndicator;
cell.textLabel?.text = value0
cell.detailTextLabel?.text = "上传"
// let fileTitles: [String] = value3.components(separatedBy: ",")
// cell.detailTextLabel?.text = fileTitles.first!
let hasAsterisk = value0.contains("*")
if hasAsterisk {
cell.textLabel?.attributedText = cell.textLabel!.text!.toAsterisk(cell.textLabel!.textColor, font: cell.textLabel!.font.pointSize)
}
let result = dataModel.valueText(forKeyPath: value4, defalut: "")
if value4 == "couponTypeValue" {
cell.detailTextLabel?.text = CouponRecordModel.getCouponDes(result, defaultValue: "请选择")
} else if value4 == "name" {
cell.detailTextLabel?.text = dataModel.clt_id == 0 ? value3 : result
} else {
cell.detailTextLabel?.text = result
}
// cell.getViewLayer()
return cell;
case "UITableViewCellSegment":
let cell = UITableViewCellSegment.dequeueReusableCell(tableView)
cell.labelLeft.font = UIFont.systemFont(ofSize: 14)
cell.labelLeft.textColor = UIColor.textColor3
cell.isHidden = itemList[2].cgFloatValue <= 0.0
cell.hasAsterisk = value0.contains("*")
cell.labelLeft.text = value0
cell.segmentCtl.items = (itemList.last! as NSString).components(separatedBy: ",")
let index = dataModel.valueText(forKeyPath: value4, defalut: "2") == "2" ? 0 : 1
cell.segmentCtl.selectedSegmentIndex = index
// cell.segmentCtl.addTarget(self, action: #selector(handleActionSender(_:)), for: .valueChanged)
cell.segmentCtl.addActionHandler({ (control) in
guard let sender = control as? UISegmentedControl else { return }
let value: String = sender.selectedSegmentIndex == 0 ? "2" : "1"
self.dataModel.setValue(value, forKeyPath: value4)
tableView.reloadData()
// DDLog("%@_%@_%@, sender.selectedSegmentIndex, value4, value)
}, for: .valueChanged)
// cell.getViewLayer()
return cell
case "UITableViewCellStep":
let cell = UITableViewCellStep.dequeueReusableCell(tableView)
cell.labelLeft.font = UIFont.systemFont(ofSize: 14)
cell.labelLeft.textColor = UIColor.textColor3
cell.isHidden = value2.cgFloatValue <= 0.0
cell.hasAsterisk = value0.contains("*")
cell.labelLeft.text = value0
cell.ppBtn.minValue = 0
cell.ppBtn.maxValue = 10
// cell.ppBtn.currentNumber = value4
cell.ppBtn.currentNumber = dataModel.valueText(forKeyPath: value4, defalut: "1")
cell.ppBtn.numberResult { (num) in
// DDLog(num)
self.dataModel.setValue(num, forKey: value4)
}
// cell.textfield.textAlignment = .right
if dataModel.categoryStr == "1" {
cell.isHidden = couponFixTypeItems.contains(itemList[0])
}
// cell.getViewLayer()
return cell
case "UITableViewCellDateRange":
let cell = UITableViewCellDateRange.dequeueReusableCell(tableView)
cell.dateRangeView.labTitle.font = UIFont.systemFont(ofSize: 14)
cell.dateRangeView.labTitle.textAlignment = .left
cell.dateRangeView.labTitle.textColor = UIColor.textColor3
cell.isHidden = value2.cgFloatValue <= 0.0
cell.hasAsterisk = value0.contains("*")
let datePickerMode: UIDatePicker.Mode = value4.contains("valid") ? .date : .time
cell.dateRangeView.datePicker.datePicker.datePickerMode = datePickerMode
cell.dateRangeView.isEmptyDate = true
cell.dateRangeView.isFuture = true
cell.dateRangeView.labTitle.text = value0
let dateTimeKeys = (itemList.last! as NSString).components(separatedBy: ",")
let beginTime: String = dataModel.valueText(forKeyPath: dateTimeKeys.first!)
let endTime: String = dataModel.valueText(forKeyPath: dateTimeKeys.last!)
if beginTime.count == 19 && endTime.count == 19 {
cell.dateRangeView.beginDate = DateFormatter.dateFromString(beginTime)
cell.dateRangeView.endDate = DateFormatter.dateFromString(endTime)
}
cell.dateRangeView.block { (dateRangeView) in
DDLog(dateRangeView.beginTime, dateRangeView.endTime)
if dateRangeView.endTime.isNewer(value: dateRangeView.beginTime) == false {
IOPProgressHUD.showText("结束时间必须大于开始时间")
return
}
if value4 == "btime,etime" {
let btime = "\((dateRangeView.beginTime as NSString).substring(with: NSMakeRange(11, 5)))"
let etime = "\((dateRangeView.endTime as NSString).substring(with: NSMakeRange(11, 5)))"
if etime.isNewer(value: btime) == false {
IOPProgressHUD.showText("结束时段必须大于开始时段")
return
}
}
self.dataModel.setValue(dateRangeView.beginTime, forKey: dateTimeKeys.first!)
self.dataModel.setValue(dateRangeView.endTime, forKey: dateTimeKeys.last!)
}
if dataModel.categoryStr == "1" {
cell.isHidden = couponFixTypeItems.contains(itemList[0])
}
// cell.getViewLayer()
return cell
default:
break
}
let cell = UITableViewCellZero.cellWithTableView(tableView)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let sections = list[indexPath.section]
let itemList = sections[indexPath.row]
switch itemList[4] {
case "couponTypeValue":
self.pickView.itemList = self.shopModel.getCouponList(self.dataModel.categoryStr)
pickView.block({ (view:NNPickListView, indexP:IndexPath) in
DDLog(indexP.string)
guard let items = view.itemList as [[String]]?,
let cellItem = items[indexP.row] as [String]?
else { return }
// self.dataModel.couponType = CouponType(rawValue: value)!
self.dataModel.couponTypeValue = cellItem[1]
self.dataModel.faceValue = self.dataModel.couponTypeValue == "2" ? 1 : 0
tableView.reloadRowList([indexPath.row, indexPath.row+1], section: indexPath.section)
})
pickView.show()
break
case "parkname":
present(parkChooseNaVC, animated: true, completion: nil)
// navigationController?.pushViewController(parkChooseVC, animated: true)
break
case "name":
present(merchantChooseNaVC, animated: true, completion: nil)
// navigationController?.pushViewController(merchantChooseVC, animated: true)
break
default:
break
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 10.01;
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return UILabel();
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0.01;
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UILabel();
}
}
步骤 一:因为使用的 KVC,所以要通过 hook处理值为空的情况:
import UIKit
@objc public extension NSObject{
/// 实例方法替换
@discardableResult
static func hookInstanceMethod(of origSel: Selector, with replSel: Selector) -> Bool {
let clz: AnyClass = classForCoder();
guard let oriMethod = class_getInstanceMethod(clz, origSel) as Method?,
let repMethod = class_getInstanceMethod(clz, replSel) as Method?
else {
print("Swizzling Method(s) not found while swizzling class \(NSStringFromClass(classForCoder())).")
return false
}
//在进行 Swizzling 的时候,需要用 class_addMethod 先进行判断一下原有类中是否有要替换方法的实现
let didAddMethod: Bool = class_addMethod(clz, origSel, method_getImplementation(repMethod), method_getTypeEncoding(repMethod))
//如果 class_addMethod 返回 yes,说明当前类中没有要替换方法的实现,所以需要在父类中查找,这时候就用到 method_getImplemetation 去获取 class_getInstanceMethod 里面的方法实现,然后再进行 class_replaceMethod 来实现 Swizzing
if didAddMethod {
class_replaceMethod(clz, replSel, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod))
} else {
method_exchangeImplementations(oriMethod, repMethod)
}
return true;
}
}
@objc public extension NSObject{
class func initializeMethod() {
if self != NSObject.self {
return
}
let onceToken = "Hook_\(NSStringFromClass(classForCoder()))";
DispatchQueue.once(token: onceToken) {
let oriSel = #selector(self.setValue(_:forUndefinedKey:))
let repSel = #selector(self.hook_setValue(_:forUndefinedKey:))
hookInstanceMethod(of: oriSel, with: repSel);
let oriSel0 = #selector(self.value(forUndefinedKey:))
let repSel0 = #selector(self.hook_value(forUndefinedKey:))
hookInstanceMethod(of: oriSel0, with: repSel0);
let oriSel1 = #selector(self.setNilValueForKey(_:))
let repSel1 = #selector(self.hook_setNilValueForKey(_:))
hookInstanceMethod(of: oriSel1, with: repSel1);
let oriSel2 = #selector(self.setValuesForKeys(_:))
let repSel2 = #selector(self.hook_setValuesForKeys(_:))
hookInstanceMethod(of: oriSel2, with: repSel2);
}
}
private func hook_setValue(_ value: Any?, forUndefinedKey key: String){
print("setValue: forUndefinedKey:, 未知键Key: \(key)");
}
private func hook_value(forUndefinedKey key: String) -> Any?{
print("valueForUndefinedKey:, 未知键: \(key)");
return nil;
}
private func hook_setNilValueForKey(_ key: String){
print("Invoke setNilValueForKey:, 不能给非指针对象(如NSInteger)赋值 nil");
return;//给一个非指针对象(如NSInteger)赋值 nil, 直接忽略
}
private func hook_setValuesForKeys(_ keyedValues: [String : Any]) {
for (key, value) in keyedValues {
// DDLog(key, value);
if value is Int || value is CGFloat || value is Double {
self.setValue("\(value)", forKey: key)
} else {
self.setValue(value, forKey: key)
}
}
}
}
步骤 二:调用 hook 方法:
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
NSObject.initializeMethod()
...
}
}
(非 demo, 是项目中相关代码, 这是重型武器, 启动慢, 威力大.)
github.swift