175 lines
5.9 KiB
Swift
175 lines
5.9 KiB
Swift
//
|
||
// StatisticsDailyListView.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 数据统计日明细列表区域,含时间范围选择与分页列表。
|
||
final class StatisticsDailyListView: UIView {
|
||
|
||
var onRangeTap: (() -> Void)?
|
||
var onRefresh: (() -> Void)?
|
||
var onLoadMore: (() -> Void)?
|
||
|
||
private let titleLabel = UILabel()
|
||
private let rangeButton = UIControl()
|
||
private let rangeLabel = UILabel()
|
||
private let arrowView = UIImageView()
|
||
private let divider = UIView()
|
||
private let tableView = UITableView(frame: .zero, style: .plain)
|
||
private let refreshControl = UIRefreshControl()
|
||
private let footerIndicator = UIActivityIndicatorView(style: .medium)
|
||
|
||
private var items: [StatisticsDailyItem] = []
|
||
private var showsLoadMoreFooter = false
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
setupUI()
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
func apply(rangeText: String, items: [StatisticsDailyItem], isRefreshing: Bool, canLoadMore: Bool) {
|
||
rangeLabel.text = rangeText
|
||
self.items = items
|
||
showsLoadMoreFooter = canLoadMore
|
||
tableView.reloadData()
|
||
updateFooterVisibility()
|
||
if !isRefreshing {
|
||
refreshControl.endRefreshing()
|
||
}
|
||
}
|
||
|
||
private func setupUI() {
|
||
backgroundColor = .white
|
||
layer.cornerRadius = 12
|
||
clipsToBounds = true
|
||
|
||
titleLabel.text = "时间范围"
|
||
titleLabel.font = .systemFont(ofSize: 14, weight: .bold)
|
||
titleLabel.textColor = .black
|
||
|
||
rangeButton.backgroundColor = UIColor(hex: 0xF4F4F4)
|
||
rangeButton.layer.cornerRadius = 4
|
||
rangeButton.addTarget(self, action: #selector(rangeTapped), for: .touchUpInside)
|
||
|
||
rangeLabel.font = .systemFont(ofSize: 14)
|
||
rangeLabel.textColor = UIColor(hex: 0x4B5563)
|
||
rangeLabel.text = "今日"
|
||
|
||
arrowView.image = UIImage(systemName: "chevron.down")
|
||
arrowView.tintColor = UIColor(hex: 0x4B5563)
|
||
arrowView.contentMode = .scaleAspectFit
|
||
|
||
divider.backgroundColor = UIColor(hex: 0xF4F4F4)
|
||
|
||
tableView.backgroundColor = .clear
|
||
tableView.separatorStyle = .none
|
||
tableView.dataSource = self
|
||
tableView.delegate = self
|
||
tableView.register(StatisticsDailyCell.self, forCellReuseIdentifier: StatisticsDailyCell.reuseIdentifier)
|
||
tableView.refreshControl = refreshControl
|
||
refreshControl.addTarget(self, action: #selector(refreshPulled), for: .valueChanged)
|
||
|
||
footerIndicator.hidesWhenStopped = true
|
||
|
||
addSubview(titleLabel)
|
||
addSubview(rangeButton)
|
||
rangeButton.addSubview(rangeLabel)
|
||
rangeButton.addSubview(arrowView)
|
||
addSubview(divider)
|
||
addSubview(tableView)
|
||
|
||
titleLabel.snp.makeConstraints { make in
|
||
make.top.leading.equalToSuperview().inset(16)
|
||
}
|
||
rangeButton.snp.makeConstraints { make in
|
||
make.centerY.equalTo(titleLabel)
|
||
make.trailing.equalToSuperview().inset(16)
|
||
make.height.equalTo(36)
|
||
make.width.greaterThanOrEqualTo(166)
|
||
}
|
||
rangeLabel.snp.makeConstraints { make in
|
||
make.leading.equalToSuperview().inset(12)
|
||
make.centerY.equalToSuperview()
|
||
make.trailing.lessThanOrEqualTo(arrowView.snp.leading).offset(-8)
|
||
}
|
||
arrowView.snp.makeConstraints { make in
|
||
make.trailing.equalToSuperview().inset(12)
|
||
make.centerY.equalToSuperview()
|
||
make.size.equalTo(16)
|
||
}
|
||
divider.snp.makeConstraints { make in
|
||
make.top.equalTo(titleLabel.snp.bottom).offset(12)
|
||
make.leading.trailing.equalToSuperview().inset(16)
|
||
make.height.equalTo(1)
|
||
}
|
||
tableView.snp.makeConstraints { make in
|
||
make.top.equalTo(divider.snp.bottom).offset(12)
|
||
make.leading.trailing.bottom.equalToSuperview().inset(16)
|
||
}
|
||
}
|
||
|
||
@objc private func rangeTapped() {
|
||
onRangeTap?()
|
||
}
|
||
|
||
@objc private func refreshPulled() {
|
||
onRefresh?()
|
||
}
|
||
|
||
private func updateFooterVisibility() {
|
||
if showsLoadMoreFooter {
|
||
footerIndicator.startAnimating()
|
||
tableView.tableFooterView = footerContainer()
|
||
} else {
|
||
footerIndicator.stopAnimating()
|
||
tableView.tableFooterView = UIView(frame: .zero)
|
||
}
|
||
}
|
||
|
||
private func footerContainer() -> UIView {
|
||
let container = UIView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: 56))
|
||
container.addSubview(footerIndicator)
|
||
footerIndicator.center = CGPoint(x: container.bounds.midX, y: container.bounds.midY)
|
||
footerIndicator.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
|
||
return container
|
||
}
|
||
}
|
||
|
||
extension StatisticsDailyListView: UITableViewDataSource, UITableViewDelegate {
|
||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||
items.count
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||
guard
|
||
let cell = tableView.dequeueReusableCell(
|
||
withIdentifier: StatisticsDailyCell.reuseIdentifier,
|
||
for: indexPath
|
||
) as? StatisticsDailyCell
|
||
else {
|
||
return UITableViewCell()
|
||
}
|
||
cell.configure(with: items[indexPath.row], showsDivider: indexPath.row < items.count - 1)
|
||
return cell
|
||
}
|
||
|
||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||
guard showsLoadMoreFooter else { return }
|
||
let offsetY = scrollView.contentOffset.y
|
||
let contentHeight = scrollView.contentSize.height
|
||
let frameHeight = scrollView.frame.size.height
|
||
guard contentHeight > frameHeight else { return }
|
||
if offsetY > contentHeight - frameHeight - 120 {
|
||
onLoadMore?()
|
||
}
|
||
}
|
||
}
|