feat: add live module and optimize report list
This commit is contained in:
@ -9,8 +9,9 @@ import UIKit
|
||||
/// 我的举报列表页,支持按状态筛选并进入举报详情。
|
||||
final class WildPhotographerReportListViewController: BaseViewController {
|
||||
private let viewModel: WildPhotographerReportListViewModel
|
||||
private let segmentedControl = UISegmentedControl(items: WildReportListFilter.allCases.map(\.rawValue))
|
||||
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||
private let scrollView = UIScrollView()
|
||||
private let stack = UIStackView()
|
||||
private let filterTabBar = WildReportFilterTabBarView()
|
||||
|
||||
init(homeViewModel: WildPhotographerReportHomeViewModel) {
|
||||
self.viewModel = WildPhotographerReportListViewModel(homeViewModel: homeViewModel)
|
||||
@ -28,132 +29,134 @@ final class WildPhotographerReportListViewController: BaseViewController {
|
||||
|
||||
override func setupUI() {
|
||||
view.backgroundColor = AppColor.pageBackgroundSoft
|
||||
segmentedControl.selectedSegmentIndex = 0
|
||||
segmentedControl.addTarget(self, action: #selector(filterChanged), for: .valueChanged)
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
scrollView.alwaysBounceVertical = true
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 14
|
||||
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.separatorStyle = .none
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.register(WildReportListCell.self, forCellReuseIdentifier: WildReportListCell.reuseIdentifier)
|
||||
tableView.contentInset = UIEdgeInsets(top: AppSpacing.sm, left: 0, bottom: AppSpacing.lg, right: 0)
|
||||
filterTabBar.onSelect = { [weak self] filter in
|
||||
self?.viewModel.selectFilter(filter)
|
||||
}
|
||||
|
||||
view.addSubview(segmentedControl)
|
||||
view.addSubview(tableView)
|
||||
view.addSubview(scrollView)
|
||||
scrollView.addSubview(stack)
|
||||
reloadContent(animated: false)
|
||||
}
|
||||
|
||||
override func setupConstraints() {
|
||||
segmentedControl.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.sm)
|
||||
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.height.equalTo(36)
|
||||
scrollView.snp.makeConstraints { make in
|
||||
make.edges.equalTo(view.safeAreaLayoutGuide)
|
||||
}
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.top.equalTo(segmentedControl.snp.bottom).offset(AppSpacing.xs)
|
||||
make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide)
|
||||
stack.snp.makeConstraints { make in
|
||||
make.top.equalTo(scrollView.contentLayoutGuide).offset(14)
|
||||
make.leading.equalTo(scrollView.contentLayoutGuide).offset(18)
|
||||
make.trailing.equalTo(scrollView.contentLayoutGuide).inset(18)
|
||||
make.bottom.equalTo(scrollView.contentLayoutGuide).inset(24)
|
||||
make.width.equalTo(scrollView.frameLayoutGuide).offset(-36)
|
||||
}
|
||||
}
|
||||
|
||||
override func bindActions() {
|
||||
viewModel.onStateChange = { [weak self] in
|
||||
Task { @MainActor in self?.tableView.reloadData() }
|
||||
Task { @MainActor in self?.reloadContent(animated: true) }
|
||||
}
|
||||
viewModel.onShowMessage = { [weak self] message in
|
||||
Task { @MainActor in self?.showToast(message) }
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func filterChanged() {
|
||||
let filter = WildReportListFilter.allCases[segmentedControl.selectedSegmentIndex]
|
||||
viewModel.selectFilter(filter)
|
||||
}
|
||||
}
|
||||
|
||||
extension WildPhotographerReportListViewController: UITableViewDataSource, UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
let count = viewModel.filteredReports.count
|
||||
tableView.backgroundView = count == 0 ? WildReportEmptyView(text: "暂无相关举报记录") : nil
|
||||
return count
|
||||
@MainActor
|
||||
private func reloadContent(animated: Bool) {
|
||||
let changes: () -> Void = { [weak self] in
|
||||
self?.rebuildContent()
|
||||
}
|
||||
guard animated else {
|
||||
changes()
|
||||
return
|
||||
}
|
||||
UIView.transition(
|
||||
with: stack,
|
||||
duration: 0.22,
|
||||
options: [.transitionCrossDissolve, .allowUserInteraction],
|
||||
animations: changes
|
||||
)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: WildReportListCell.reuseIdentifier, for: indexPath) as! WildReportListCell
|
||||
let record = viewModel.filteredReports[indexPath.row]
|
||||
cell.apply(record: record)
|
||||
cell.onShare = { [weak self] in self?.viewModel.shareReport(record) }
|
||||
return cell
|
||||
private func rebuildContent() {
|
||||
stack.arrangedSubviews.forEach { view in
|
||||
stack.removeArrangedSubview(view)
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
|
||||
filterTabBar.apply(selectedFilter: viewModel.selectedFilter)
|
||||
stack.addArrangedSubview(filterTabBar)
|
||||
|
||||
let reports = viewModel.filteredReports
|
||||
if reports.isEmpty {
|
||||
stack.addArrangedSubview(WildReportListEmptyView())
|
||||
} else {
|
||||
reports.forEach { record in
|
||||
let card = WildReportListCardView(record: record)
|
||||
card.onOpen = { [weak self] in self?.openDetail(record) }
|
||||
card.onShare = { [weak self] in self?.viewModel.shareReport(record) }
|
||||
stack.addArrangedSubview(card)
|
||||
}
|
||||
}
|
||||
|
||||
let footer = WildReportUI.label(viewModel.footerTip, font: .app(.caption), color: UIColor(hex: 0x9CA3AF), lines: 0)
|
||||
footer.textAlignment = .center
|
||||
let footerWrap = UIView()
|
||||
footerWrap.addSubview(footer)
|
||||
footer.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview().offset(8)
|
||||
make.leading.trailing.bottom.equalToSuperview()
|
||||
}
|
||||
stack.addArrangedSubview(footerWrap)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
let record = viewModel.filteredReports[indexPath.row]
|
||||
private func openDetail(_ record: WildReportRecord) {
|
||||
navigationController?.pushViewController(
|
||||
WildPhotographerReportDetailViewController(record: record, homeViewModel: viewModel.homeViewModel),
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
UITableView.automaticDimension
|
||||
}
|
||||
}
|
||||
|
||||
/// 我的举报列表卡片 Cell。
|
||||
final class WildReportListCell: UITableViewCell {
|
||||
static let reuseIdentifier = "WildReportListCell"
|
||||
/// 我的举报筛选栏,使用四段等宽胶囊按钮展示状态过滤。
|
||||
private final class WildReportFilterTabBarView: UIView {
|
||||
var onSelect: ((WildReportListFilter) -> Void)?
|
||||
private let stack = UIStackView()
|
||||
private var buttons: [WildReportListFilter: UIButton] = [:]
|
||||
|
||||
private let card = WildReportCardView(spacing: AppSpacing.sm)
|
||||
private let titleLabel = UILabel()
|
||||
private let statusView = WildReportStatusView()
|
||||
private let typeLabel = UILabel()
|
||||
private let locationLabel = UILabel()
|
||||
private let timeLabel = UILabel()
|
||||
private let descLabel = UILabel()
|
||||
private let materialLabel = UILabel()
|
||||
var onShare: (() -> Void)?
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = 12
|
||||
layer.borderWidth = 1
|
||||
layer.borderColor = AppColor.cardOutline.cgColor
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
contentView.addSubview(card)
|
||||
card.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(UIEdgeInsets(top: AppSpacing.xs, left: AppSpacing.md, bottom: AppSpacing.xs, right: AppSpacing.md))
|
||||
stack.axis = .horizontal
|
||||
stack.spacing = 0
|
||||
stack.distribution = .fillEqually
|
||||
addSubview(stack)
|
||||
stack.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(4)
|
||||
}
|
||||
|
||||
let header = UIStackView()
|
||||
header.axis = .horizontal
|
||||
header.alignment = .center
|
||||
header.spacing = AppSpacing.sm
|
||||
titleLabel.font = .app(.title)
|
||||
titleLabel.textColor = AppColor.textPrimary
|
||||
header.addArrangedSubview(titleLabel)
|
||||
header.addArrangedSubview(UIView())
|
||||
statusView.snp.makeConstraints { make in
|
||||
make.width.equalTo(64)
|
||||
make.height.equalTo(24)
|
||||
}
|
||||
header.addArrangedSubview(statusView)
|
||||
|
||||
[typeLabel, locationLabel, timeLabel, descLabel, materialLabel].forEach { label in
|
||||
label.font = .app(.body)
|
||||
label.textColor = AppColor.textSecondary
|
||||
label.numberOfLines = 0
|
||||
WildReportListFilter.allCases.forEach { filter in
|
||||
let button = UIButton(type: .system)
|
||||
button.setTitle(filter.rawValue, for: .normal)
|
||||
button.titleLabel?.adjustsFontSizeToFitWidth = true
|
||||
button.titleLabel?.minimumScaleFactor = 0.85
|
||||
button.layer.cornerRadius = 8
|
||||
button.addTarget(self, action: #selector(filterTapped(_:)), for: .touchUpInside)
|
||||
buttons[filter] = button
|
||||
stack.addArrangedSubview(button)
|
||||
}
|
||||
|
||||
let shareButton = UIButton(type: .system)
|
||||
shareButton.setTitle("分享", for: .normal)
|
||||
shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
|
||||
shareButton.titleLabel?.font = .app(.captionMedium)
|
||||
shareButton.addTarget(self, action: #selector(shareTapped), for: .touchUpInside)
|
||||
|
||||
card.stack.addArrangedSubview(header)
|
||||
card.stack.addArrangedSubview(typeLabel)
|
||||
card.stack.addArrangedSubview(locationLabel)
|
||||
card.stack.addArrangedSubview(timeLabel)
|
||||
card.stack.addArrangedSubview(descLabel)
|
||||
card.stack.addArrangedSubview(materialLabel)
|
||||
card.stack.addArrangedSubview(shareButton)
|
||||
snp.makeConstraints { make in
|
||||
make.height.equalTo(48)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
@ -161,14 +164,202 @@ final class WildReportListCell: UITableViewCell {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(record: WildReportRecord) {
|
||||
titleLabel.text = record.title
|
||||
statusView.apply(status: record.status)
|
||||
typeLabel.text = "举报类型:\(record.reportType.rawValue)"
|
||||
locationLabel.text = "位置:\(record.location)"
|
||||
timeLabel.text = "提交时间:\(record.submitTime)"
|
||||
descLabel.text = record.description
|
||||
materialLabel.text = "材料:\(record.imageCount)张图片 \(record.videoCount)段视频"
|
||||
func apply(selectedFilter: WildReportListFilter) {
|
||||
buttons.forEach { filter, button in
|
||||
let selected = filter == selectedFilter
|
||||
button.backgroundColor = selected ? UIColor(hex: 0xEAF3FF) : .clear
|
||||
button.setTitleColor(selected ? AppColor.primary : UIColor(hex: 0x6B7280), for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 13, weight: selected ? .bold : .medium)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func filterTapped(_ sender: UIButton) {
|
||||
guard let filter = buttons.first(where: { $0.value == sender })?.key else { return }
|
||||
onSelect?(filter)
|
||||
}
|
||||
}
|
||||
|
||||
/// 我的举报列表卡片,展示一条举报记录并提供详情与分享入口。
|
||||
private final class WildReportListCardView: UIControl {
|
||||
var onOpen: (() -> Void)?
|
||||
var onShare: (() -> Void)?
|
||||
|
||||
private let record: WildReportRecord
|
||||
private let scenicName: String
|
||||
private let detailAddress: String
|
||||
private let statusColor: UIColor
|
||||
|
||||
init(record: WildReportRecord) {
|
||||
self.record = record
|
||||
let location = WildReportLocationParser.split(record.location)
|
||||
self.scenicName = location.scenic
|
||||
self.detailAddress = location.detail
|
||||
self.statusColor = record.status.displayColor
|
||||
super.init(frame: .zero)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet {
|
||||
alpha = isHighlighted ? 0.82 : 1
|
||||
}
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = 16
|
||||
layer.borderWidth = 1
|
||||
layer.borderColor = AppColor.cardOutline.cgColor
|
||||
addTarget(self, action: #selector(openTapped), for: .touchUpInside)
|
||||
|
||||
let contentStack = UIStackView()
|
||||
contentStack.axis = .vertical
|
||||
contentStack.spacing = 13
|
||||
addSubview(contentStack)
|
||||
contentStack.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(15)
|
||||
}
|
||||
|
||||
contentStack.addArrangedSubview(makeHeader())
|
||||
contentStack.addArrangedSubview(makeMetaRows())
|
||||
contentStack.addArrangedSubview(makeLocationRow())
|
||||
}
|
||||
|
||||
private func makeHeader() -> UIView {
|
||||
let row = UIStackView()
|
||||
row.axis = .horizontal
|
||||
row.alignment = .top
|
||||
row.spacing = 12
|
||||
|
||||
let iconBox = UIView()
|
||||
iconBox.backgroundColor = statusColor.withAlphaComponent(0.12)
|
||||
iconBox.layer.cornerRadius = 12
|
||||
let icon = UIImageView(image: UIImage(systemName: "camera.fill"))
|
||||
icon.tintColor = statusColor
|
||||
icon.contentMode = .scaleAspectFit
|
||||
iconBox.addSubview(icon)
|
||||
icon.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
make.size.equalTo(20)
|
||||
}
|
||||
iconBox.snp.makeConstraints { make in
|
||||
make.size.equalTo(44)
|
||||
}
|
||||
row.addArrangedSubview(iconBox)
|
||||
|
||||
let textStack = UIStackView()
|
||||
textStack.axis = .vertical
|
||||
textStack.spacing = 8
|
||||
let titleStatusRow = UIStackView()
|
||||
titleStatusRow.axis = .horizontal
|
||||
titleStatusRow.alignment = .top
|
||||
titleStatusRow.spacing = 8
|
||||
|
||||
let titleLabel = WildReportUI.label(record.title, font: .app(.title), color: AppColor.textPrimary, lines: 2)
|
||||
titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
titleStatusRow.addArrangedSubview(titleLabel)
|
||||
titleStatusRow.addArrangedSubview(makeStatusAndShareGroup())
|
||||
textStack.addArrangedSubview(titleStatusRow)
|
||||
textStack.addArrangedSubview(makeScenicPill())
|
||||
row.addArrangedSubview(textStack)
|
||||
return row
|
||||
}
|
||||
|
||||
private func makeStatusAndShareGroup() -> UIView {
|
||||
let group = UIStackView()
|
||||
group.axis = .horizontal
|
||||
group.alignment = .center
|
||||
group.spacing = 6
|
||||
group.setContentHuggingPriority(.required, for: .horizontal)
|
||||
|
||||
let status = PaddingLabel(insets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8))
|
||||
status.text = record.status.rawValue
|
||||
status.font = .systemFont(ofSize: 11, weight: .semibold)
|
||||
status.textColor = statusColor
|
||||
status.textAlignment = .center
|
||||
status.backgroundColor = statusColor.withAlphaComponent(0.12)
|
||||
status.layer.cornerRadius = 12
|
||||
status.clipsToBounds = true
|
||||
status.snp.makeConstraints { make in
|
||||
make.height.equalTo(24)
|
||||
}
|
||||
|
||||
let shareButton = UIButton(type: .system)
|
||||
shareButton.backgroundColor = AppColor.primaryLight
|
||||
shareButton.layer.cornerRadius = 14
|
||||
shareButton.tintColor = AppColor.primary
|
||||
shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
|
||||
shareButton.accessibilityLabel = "分享举报"
|
||||
shareButton.addTarget(self, action: #selector(shareTapped), for: .touchUpInside)
|
||||
shareButton.snp.makeConstraints { make in
|
||||
make.size.equalTo(28)
|
||||
}
|
||||
|
||||
group.addArrangedSubview(status)
|
||||
group.addArrangedSubview(shareButton)
|
||||
return group
|
||||
}
|
||||
|
||||
private func makeScenicPill() -> UIView {
|
||||
let label = PaddingLabel(insets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8))
|
||||
label.text = scenicName
|
||||
label.font = .systemFont(ofSize: 12, weight: .semibold)
|
||||
label.textColor = AppColor.primary
|
||||
label.backgroundColor = AppColor.primary.withAlphaComponent(0.10)
|
||||
label.layer.cornerRadius = 11
|
||||
label.clipsToBounds = true
|
||||
label.snp.makeConstraints { make in
|
||||
make.height.equalTo(22)
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
private func makeMetaRows() -> UIView {
|
||||
let stack = UIStackView()
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 8
|
||||
stack.addArrangedSubview(WildReportListMetaRowView(title: "举报编号", value: record.id))
|
||||
stack.addArrangedSubview(WildReportListMetaRowView(title: "举报时间", value: record.submitTime))
|
||||
return stack
|
||||
}
|
||||
|
||||
private func makeLocationRow() -> UIView {
|
||||
let row = UIStackView()
|
||||
row.axis = .horizontal
|
||||
row.alignment = .center
|
||||
row.spacing = 6
|
||||
row.layoutMargins = UIEdgeInsets(top: 2, left: 0, bottom: 0, right: 0)
|
||||
row.isLayoutMarginsRelativeArrangement = true
|
||||
|
||||
let icon = UIImageView(image: UIImage(systemName: "mappin.and.ellipse"))
|
||||
icon.tintColor = UIColor(hex: 0x9CA3AF)
|
||||
icon.contentMode = .scaleAspectFit
|
||||
icon.snp.makeConstraints { make in
|
||||
make.size.equalTo(14)
|
||||
}
|
||||
let addressLabel = WildReportUI.label(detailAddress, font: .app(.caption), color: AppColor.textSecondary)
|
||||
addressLabel.lineBreakMode = .byTruncatingTail
|
||||
let chevron = UIImageView(image: UIImage(systemName: "chevron.right"))
|
||||
chevron.tintColor = UIColor(hex: 0xC4CAD4)
|
||||
chevron.contentMode = .scaleAspectFit
|
||||
chevron.snp.makeConstraints { make in
|
||||
make.size.equalTo(13)
|
||||
}
|
||||
|
||||
row.addArrangedSubview(icon)
|
||||
row.addArrangedSubview(addressLabel)
|
||||
row.addArrangedSubview(UIView())
|
||||
row.addArrangedSubview(chevron)
|
||||
return row
|
||||
}
|
||||
|
||||
@objc private func openTapped() {
|
||||
onOpen?()
|
||||
}
|
||||
|
||||
@objc private func shareTapped() {
|
||||
@ -176,6 +367,68 @@ final class WildReportListCell: UITableViewCell {
|
||||
}
|
||||
}
|
||||
|
||||
/// 我的举报列表信息行,展示举报编号、举报时间等左右结构内容。
|
||||
private final class WildReportListMetaRowView: UIView {
|
||||
init(title: String, value: String) {
|
||||
super.init(frame: .zero)
|
||||
|
||||
let titleLabel = WildReportUI.label(title, font: .app(.caption), color: UIColor(hex: 0x9CA3AF))
|
||||
let valueLabel = WildReportUI.label(value, font: .systemFont(ofSize: 13, weight: .medium), color: AppColor.textPrimary)
|
||||
valueLabel.textAlignment = .right
|
||||
valueLabel.adjustsFontSizeToFitWidth = true
|
||||
valueLabel.minimumScaleFactor = 0.8
|
||||
|
||||
addSubview(titleLabel)
|
||||
addSubview(valueLabel)
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.leading.top.bottom.equalToSuperview()
|
||||
}
|
||||
valueLabel.snp.makeConstraints { make in
|
||||
make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing).offset(12)
|
||||
make.trailing.top.bottom.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
/// 我的举报列表空态卡片。
|
||||
private final class WildReportListEmptyView: UIView {
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = 16
|
||||
|
||||
let stack = UIStackView()
|
||||
stack.axis = .vertical
|
||||
stack.alignment = .center
|
||||
stack.spacing = 10
|
||||
let icon = UIImageView(image: UIImage(systemName: "doc.text.magnifyingglass"))
|
||||
icon.tintColor = UIColor(hex: 0xB6BECA)
|
||||
icon.contentMode = .scaleAspectFit
|
||||
icon.snp.makeConstraints { make in
|
||||
make.size.equalTo(36)
|
||||
}
|
||||
let label = WildReportUI.label("暂无相关举报记录", font: .systemFont(ofSize: 15, weight: .semibold), color: AppColor.textSecondary)
|
||||
stack.addArrangedSubview(icon)
|
||||
stack.addArrangedSubview(label)
|
||||
|
||||
addSubview(stack)
|
||||
stack.snp.makeConstraints { make in
|
||||
make.top.bottom.equalToSuperview().inset(48)
|
||||
make.leading.trailing.equalToSuperview().inset(16)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
/// 举报详情页。
|
||||
final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
private let viewModel: WildPhotographerReportDetailViewModel
|
||||
|
||||
Reference in New Issue
Block a user