LNCountrySelectPanel.swift 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. //
  2. // LNCountrySelectPanel.swift
  3. // Gami
  4. //
  5. // Created by OneeChan on 2026/1/15.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. class LNCountrySelectPanel: LNPopupView {
  11. private let tableView = UITableView(frame: .zero, style: .grouped)
  12. private var sections: [(code: String, list: [LNCountryCodeVO])] = []
  13. private let sectionIndex = UIStackView()
  14. var handler: ((LNCountryCodeVO) -> Void)?
  15. override init(frame: CGRect) {
  16. super.init(frame: frame)
  17. setupViews()
  18. }
  19. func update(_ sections: [(code: String, list: [LNCountryCodeVO])]) {
  20. self.sections = sections
  21. tableView.reloadData()
  22. sectionIndex.arrangedSubviews.forEach {
  23. sectionIndex.removeArrangedSubview($0)
  24. $0.removeFromSuperview()
  25. }
  26. for (index, section) in sections.enumerated() {
  27. let item = buildSectionIndexItem(section.code)
  28. sectionIndex.addArrangedSubview(item)
  29. item.onTap { [weak self] in
  30. guard let self else { return }
  31. let indexPath = IndexPath(row: 0, section: index)
  32. tableView.scrollToRow(at: indexPath, at: .top, animated: false)
  33. }
  34. }
  35. }
  36. required init?(coder: NSCoder) {
  37. fatalError("init(coder:) has not been implemented")
  38. }
  39. }
  40. extension LNCountrySelectPanel: UITableViewDataSource, UITableViewDelegate {
  41. func numberOfSections(in tableView: UITableView) -> Int {
  42. sections.count
  43. }
  44. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  45. sections[section].list.count
  46. }
  47. func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  48. 56
  49. }
  50. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  51. let cell = tableView.dequeueReusableCell(withIdentifier: LNCountryCodeCell.className, for: indexPath) as! LNCountryCodeCell
  52. let item = sections[indexPath.section].list[indexPath.row]
  53. cell.update(item)
  54. return cell
  55. }
  56. func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  57. 30
  58. }
  59. func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  60. let container = UIView()
  61. let titleLabel = UILabel()
  62. titleLabel.font = .body_l
  63. titleLabel.textColor = .text_3
  64. titleLabel.text = sections[section].code
  65. container.addSubview(titleLabel)
  66. titleLabel.snp.makeConstraints { make in
  67. make.centerY.equalToSuperview()
  68. make.leading.equalToSuperview().offset(16)
  69. }
  70. return container
  71. }
  72. func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
  73. 0
  74. }
  75. func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
  76. nil
  77. }
  78. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  79. tableView.deselectRow(at: indexPath, animated: false)
  80. let item = sections[indexPath.section].list[indexPath.row]
  81. dismiss()
  82. handler?(item)
  83. }
  84. }
  85. extension LNCountrySelectPanel {
  86. private func setupViews() {
  87. let titleView = buildTitleView()
  88. container.addSubview(titleView)
  89. titleView.snp.makeConstraints { make in
  90. make.horizontalEdges.equalToSuperview()
  91. make.top.equalToSuperview()
  92. }
  93. let listView = buildListView()
  94. container.addSubview(listView)
  95. listView.snp.makeConstraints { make in
  96. make.horizontalEdges.equalToSuperview()
  97. make.top.equalTo(titleView.snp.bottom)
  98. make.bottom.equalToSuperview()
  99. }
  100. let sectionIndex = buildSectionIndex()
  101. container.addSubview(sectionIndex)
  102. sectionIndex.snp.makeConstraints { make in
  103. make.centerY.equalToSuperview()
  104. make.trailing.equalToSuperview()
  105. }
  106. let closeButton = UIButton()
  107. closeButton.setImage(.init(systemName: "xmark")?.withRenderingMode(.alwaysTemplate), for: .normal)
  108. closeButton.tintColor = .text_2
  109. closeButton.addAction(UIAction(handler: { [weak self] _ in
  110. guard let self else { return }
  111. dismiss()
  112. }), for: .touchUpInside)
  113. container.addSubview(closeButton)
  114. closeButton.snp.makeConstraints { make in
  115. make.top.equalToSuperview().offset(13)
  116. make.trailing.equalToSuperview().offset(-13)
  117. make.width.height.equalTo(24)
  118. }
  119. }
  120. private func buildTitleView() -> UIView {
  121. let container = UIView()
  122. container.snp.makeConstraints { make in
  123. make.height.equalTo(50)
  124. }
  125. let titleLabel = UILabel()
  126. titleLabel.text = .init(key: "B00024")
  127. titleLabel.font = .heading_h3
  128. titleLabel.textColor = .text_5
  129. container.addSubview(titleLabel)
  130. titleLabel.snp.makeConstraints { make in
  131. make.center.equalToSuperview()
  132. }
  133. return container
  134. }
  135. private func buildListView() -> UIView {
  136. tableView.dataSource = self
  137. tableView.delegate = self
  138. tableView.separatorStyle = .none
  139. tableView.backgroundColor = .fill
  140. tableView.register(LNCountryCodeCell.self, forCellReuseIdentifier: LNCountryCodeCell.className)
  141. tableView.contentInset = .init(top: 0, left: 0, bottom: -commonBottomInset, right: 0)
  142. return tableView
  143. }
  144. private func buildSectionIndex() -> UIView {
  145. sectionIndex.axis = .vertical
  146. sectionIndex.snp.makeConstraints { make in
  147. make.width.equalTo(32)
  148. make.height.equalTo(0).priority(.low)
  149. }
  150. return sectionIndex
  151. }
  152. private func buildSectionIndexItem(_ title: String) -> UIView {
  153. let container = UIView()
  154. container.backgroundColor = .fill
  155. container.snp.makeConstraints { make in
  156. make.width.equalTo(32)
  157. make.height.equalTo(30)
  158. }
  159. let label = UILabel()
  160. label.text = title
  161. label.font = .body_l
  162. label.textColor = .text_3
  163. container.addSubview(label)
  164. label.snp.makeConstraints { make in
  165. make.center.equalToSuperview()
  166. }
  167. return container
  168. }
  169. }
  170. private class LNCountryCodeCell: UITableViewCell {
  171. private let icon = UIImageView()
  172. private let nameLabel = UILabel()
  173. override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
  174. super.init(style: style, reuseIdentifier: reuseIdentifier)
  175. setupViews()
  176. }
  177. func update(_ item: LNCountryCodeVO) {
  178. icon.sd_setImage(with: URL(string: item.icon))
  179. nameLabel.text = "\(item.name) \(item.num)"
  180. }
  181. required init?(coder: NSCoder) {
  182. fatalError("init(coder:) has not been implemented")
  183. }
  184. private func setupViews() {
  185. icon.layer.cornerRadius = 12
  186. icon.clipsToBounds = true
  187. contentView.addSubview(icon)
  188. icon.snp.makeConstraints { make in
  189. make.leading.equalToSuperview().offset(16)
  190. make.centerY.equalToSuperview()
  191. make.width.height.equalTo(24)
  192. }
  193. nameLabel.font = .body_l
  194. nameLabel.textColor = .text_5
  195. contentView.addSubview(nameLabel)
  196. nameLabel.snp.makeConstraints { make in
  197. make.centerY.equalToSuperview()
  198. make.leading.equalTo(icon.snp.trailing).offset(12)
  199. }
  200. }
  201. }
  202. #if DEBUG
  203. import SwiftUI
  204. struct LNCountrySelectPanelPreview: UIViewRepresentable {
  205. func makeUIView(context: Context) -> some UIView {
  206. let container = UIView()
  207. container.backgroundColor = .lightGray
  208. let view = LNCountrySelectPanel()
  209. view.containerHeight = .percent(0.7)
  210. view.popup(container)
  211. return container
  212. }
  213. func updateUIView(_ uiView: UIViewType, context: Context) { }
  214. }
  215. #Preview(body: {
  216. LNCountrySelectPanelPreview()
  217. })
  218. #endif