// // SelectCommonController.swift // Crush // // Created by Leon on 2025/7/25. // import SnapKit import UIKit protocol SelectCommonModelProtocol: AnyObject { func displayStr() -> String func sourceObj() -> Any } class SelectCommonController: CLBaseViewController { // MARK: - Properties from header var vcTitle: String? var isMultiChoose: Bool = false var atlastSelectOne: Bool = false var cellClass: UITableViewCell.Type? var datas: [SelectCommonModelProtocol] = [] var markSelected: [SelectCommonModelProtocol] = [] var tapSingleAction: ((SelectCommonModelProtocol) -> Void)? var tapMultiConfrimAction: (([SelectCommonModelProtocol]) -> Void)? var specialCellForSignInFirstOption: SelectCommonModelProtocol? // MARK: - Private Properties private lazy var titleView: TitleView = { let view = TitleView() return view }() private lazy var tableView: UITableView = { let tableView = UITableView() tableView.separatorStyle = .none tableView.backgroundColor = .clear tableView.delegate = self tableView.dataSource = self tableView.contentInsetAdjustmentBehavior = .never tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: UIWindow.safeAreaBottom, right: 0) if let cellClass = cellClass { tableView.register(cellClass, forCellReuseIdentifier: String(describing: cellClass)) } else { tableView.register(SelectCommonTableCell.self, forCellReuseIdentifier: String(describing: SelectCommonTableCell.self)) } return tableView }() private var tempSelectModels: [SelectCommonModelProtocol] = [] // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() setupUI() setupData() setupEvents() } // MARK: - Setup private func setupUI() { title = "Select" navigationView.setupBackButtonCloseIcon() view.addSubview(tableView) tableView.snp.makeConstraints { make in make.top.equalTo(navigationView.snp.bottom) make.left.right.bottom.equalToSuperview() } titleView.title = vcTitle titleView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: titleView.preCalculateHeight()) tableView.tableHeaderView = titleView if isMultiChoose { navigationView.styleMainButton.isHidden = false navigationView.styleMainButton.setTitle("Confirm", for: .normal) refreshMultiChooseConfirmBtn() } view.setNeedsDisplay() view.layoutIfNeeded() } private func tapNaviConfirm() { tapMultiConfrimAction?(tempSelectModels) markSelected = tempSelectModels dismiss(animated: true, completion: nil) } private func setupData() { if markSelected.count == 1, let firstSelected = markSelected.first, let index = datas.firstIndex(where: { $0 === firstSelected }) { tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: false) } tempSelectModels = markSelected.isEmpty ? [] : markSelected } private func setupEvents() { } // MARK: - Class Methods static func navController() -> CLNavigationController { let vc = SelectCommonController() return CLNavigationController(rootViewController: vc) } // MARK: - Helpers private func refreshMultiChooseConfirmBtn() { guard isMultiChoose else { assertionFailure("Should not call refreshMultiChooseConfirmBtn when not in multi-choose mode") return } navigationView.styleMainButton.isEnabled = atlastSelectOne ? !tempSelectModels.isEmpty : true } } // MARK: - UITableViewDelegate, UITableViewDataSource extension SelectCommonController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return datas.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let data = datas[indexPath.row] if let cellClass = cellClass as? SelectCommonBaseCell.Type { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: cellClass), for: indexPath) as! SelectCommonBaseCell cell.config(data: data) cell.configSelect(selected: markSelected.contains { $0 === data }) return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SelectCommonTableCell.self), for: indexPath) as! SelectCommonTableCell cell.isMultiChoose = isMultiChoose cell.config(data: data) cell.configSelect(selected: markSelected.contains { $0 === data }) if let specialOption = specialCellForSignInFirstOption, specialOption === data { cell.configSignInFirstMode(true) } else { cell.configSignInFirstMode(false) } return cell } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let data = datas[indexPath.row] if specialCellForSignInFirstOption != nil && specialCellForSignInFirstOption === data { return } if isMultiChoose { if let index = tempSelectModels.firstIndex(where: { $0 === data }) { tempSelectModels.remove(at: index) } else { tempSelectModels.append(data) } markSelected = tempSelectModels tableView.reloadData() refreshMultiChooseConfirmBtn() } else { markSelected = [data] tapSingleAction?(data) dismiss(animated: true, completion: nil) } } func scrollViewDidScroll(_ scrollView: UIScrollView) { if !(vcTitle?.isEmpty ?? true) { NaviAlphaHandle.changeNaviTitleAlpha(scrollView: scrollView, titleLabel: navigationView.titleLabel) } } } // MARK: - SelectCommonModel class SelectCommonModel: SelectCommonModelProtocol { var name: String? var objInfo: Any? var integerInfo: Int = 0 func displayStr() -> String { return name ?? "" } func sourceObj() -> Any { return objInfo as Any } } class SelectCommonBaseCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } func config(data: SelectCommonModelProtocol) { // Base implementation, to be overridden by subclasses } func configSelect(selected: Bool) { // Base implementation, to be overridden by subclasses } } class SelectCommonTableCell: SelectCommonBaseCell { // MARK: - Properties private lazy var label: UILabel = { let label = UILabel() label.font = .t.tbl // EPSystemToken.typography(.bodyL).font label.numberOfLines = 0 return label }() private lazy var checkButton: EPRadioButton = { let button = EPRadioButton() button.isUserInteractionEnabled = false button.isSelected = true return button }() private lazy var line: UIView = { let view = UIView() return view }() private lazy var signInFirstTipLabel: UILabel = { let label = UILabel() label.textColor = .c.cttn label.font = .t.tbs label.text = "Please Sign In" label.isHidden = true return label }() private var signInFirstMode: Bool = false var isMultiChoose: Bool = false { didSet { if isMultiChoose { checkButton.setupStyle(.rectangle) } } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.backgroundColor = .clear backgroundColor = .clear selectionStyle = .none initialViews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } // MARK: - Setup private func initialViews() { contentView.addSubview(checkButton) contentView.addSubview(label) contentView.addSubview(line) contentView.addSubview(signInFirstTipLabel) checkButton.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 20, height: 20)) make.right.equalToSuperview().offset(-24) make.centerY.equalToSuperview() } label.snp.makeConstraints { make in make.left.equalToSuperview().offset(24) make.top.equalToSuperview().offset(24) make.bottom.equalToSuperview().offset(-24) make.right.lessThanOrEqualTo(checkButton.snp.left).offset(-16) } line.snp.makeConstraints { make in make.leading.equalToSuperview().offset(24) make.trailing.equalToSuperview().offset(-24) make.bottom.equalToSuperview() make.height.equalTo(0.5) } signInFirstTipLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.right.equalToSuperview().offset(-24) } label.textColor = .c.ctpn line.backgroundColor = .c.con } // MARK: - Public Methods override func config(data: SelectCommonModelProtocol) { label.text = data.displayStr() } override func configSelect(selected: Bool) { checkButton.isHidden = !selected } func configSignInFirstMode(_ mode: Bool) { signInFirstMode = mode if mode { label.textColor = .c.cttn signInFirstTipLabel.isHidden = false } else { label.textColor = .c.ctpn signInFirstTipLabel.isHidden = true } } }