fix: optimize report detail evidence UI
This commit is contained in:
@ -30,10 +30,20 @@ final class AllFunctionsViewController: BaseViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 全部功能列表中的展示项,包含分区信息以确保移动后重新配置操作图标。
|
||||||
|
private struct AllFunctionListItem: Hashable {
|
||||||
|
let section: AllFunctionSection
|
||||||
|
let menu: HomeMenuItem
|
||||||
|
|
||||||
|
var actionStyle: AllFunctionMenuCell.ActionStyle {
|
||||||
|
section.actionStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let viewModel = AllFunctionsViewModel()
|
private let viewModel = AllFunctionsViewModel()
|
||||||
private let collectionView: UICollectionView
|
private let collectionView: UICollectionView
|
||||||
|
|
||||||
private var dataSource: UICollectionViewDiffableDataSource<AllFunctionSection, HomeMenuItem>!
|
private var dataSource: UICollectionViewDiffableDataSource<AllFunctionSection, AllFunctionListItem>!
|
||||||
private var hasAppliedInitialSnapshot = false
|
private var hasAppliedInitialSnapshot = false
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -104,30 +114,35 @@ final class AllFunctionsViewController: BaseViewController {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func applyViewModel(animated: Bool) {
|
private func applyViewModel(animated: Bool) {
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<AllFunctionSection, HomeMenuItem>()
|
var snapshot = NSDiffableDataSourceSnapshot<AllFunctionSection, AllFunctionListItem>()
|
||||||
snapshot.appendSections([.common, .more])
|
snapshot.appendSections([.common, .more])
|
||||||
snapshot.appendItems(viewModel.commonMenus, toSection: .common)
|
snapshot.appendItems(
|
||||||
snapshot.appendItems(viewModel.moreMenus, toSection: .more)
|
viewModel.commonMenus.map { AllFunctionListItem(section: .common, menu: $0) },
|
||||||
|
toSection: .common
|
||||||
|
)
|
||||||
|
snapshot.appendItems(
|
||||||
|
viewModel.moreMenus.map { AllFunctionListItem(section: .more, menu: $0) },
|
||||||
|
toSection: .more
|
||||||
|
)
|
||||||
dataSource.apply(snapshot, animatingDifferences: animated)
|
dataSource.apply(snapshot, animatingDifferences: animated)
|
||||||
hasAppliedInitialSnapshot = true
|
hasAppliedInitialSnapshot = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeDataSource() -> UICollectionViewDiffableDataSource<AllFunctionSection, HomeMenuItem> {
|
private func makeDataSource() -> UICollectionViewDiffableDataSource<AllFunctionSection, AllFunctionListItem> {
|
||||||
let dataSource = UICollectionViewDiffableDataSource<AllFunctionSection, HomeMenuItem>(
|
let dataSource = UICollectionViewDiffableDataSource<AllFunctionSection, AllFunctionListItem>(
|
||||||
collectionView: collectionView
|
collectionView: collectionView
|
||||||
) { [weak self] collectionView, indexPath, menu in
|
) { [weak self] collectionView, indexPath, item in
|
||||||
let cell = collectionView.dequeueReusableCell(
|
let cell = collectionView.dequeueReusableCell(
|
||||||
withReuseIdentifier: AllFunctionMenuCell.reuseIdentifier,
|
withReuseIdentifier: AllFunctionMenuCell.reuseIdentifier,
|
||||||
for: indexPath
|
for: indexPath
|
||||||
) as! AllFunctionMenuCell
|
) as! AllFunctionMenuCell
|
||||||
guard let section = AllFunctionSection(rawValue: indexPath.section) else { return cell }
|
cell.apply(menu: item.menu, actionStyle: item.actionStyle)
|
||||||
cell.apply(menu: menu, actionStyle: section.actionStyle)
|
|
||||||
cell.onActionTap = {
|
cell.onActionTap = {
|
||||||
switch section {
|
switch item.section {
|
||||||
case .common:
|
case .common:
|
||||||
self?.viewModel.removeFromCommon(menu)
|
self?.viewModel.removeFromCommon(item.menu)
|
||||||
case .more:
|
case .more:
|
||||||
self?.viewModel.addToCommon(menu)
|
self?.viewModel.addToCommon(item.menu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
|
|||||||
@ -52,17 +52,23 @@ final class WildPhotographerReportListViewController: BaseViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
view.addSubview(filterTabBar)
|
||||||
view.addSubview(scrollView)
|
view.addSubview(scrollView)
|
||||||
scrollView.addSubview(stack)
|
scrollView.addSubview(stack)
|
||||||
reloadContent(animated: false)
|
reloadContent(animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func setupConstraints() {
|
override func setupConstraints() {
|
||||||
|
filterTabBar.snp.makeConstraints { make in
|
||||||
|
make.top.equalTo(view.safeAreaLayoutGuide).offset(14)
|
||||||
|
make.leading.trailing.equalToSuperview().inset(18)
|
||||||
|
}
|
||||||
scrollView.snp.makeConstraints { make in
|
scrollView.snp.makeConstraints { make in
|
||||||
make.edges.equalTo(view.safeAreaLayoutGuide)
|
make.top.equalTo(filterTabBar.snp.bottom).offset(14)
|
||||||
|
make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide)
|
||||||
}
|
}
|
||||||
stack.snp.makeConstraints { make in
|
stack.snp.makeConstraints { make in
|
||||||
make.top.equalTo(scrollView.contentLayoutGuide).offset(14)
|
make.top.equalTo(scrollView.contentLayoutGuide)
|
||||||
make.leading.equalTo(scrollView.contentLayoutGuide).offset(18)
|
make.leading.equalTo(scrollView.contentLayoutGuide).offset(18)
|
||||||
make.trailing.equalTo(scrollView.contentLayoutGuide).inset(18)
|
make.trailing.equalTo(scrollView.contentLayoutGuide).inset(18)
|
||||||
make.bottom.equalTo(scrollView.contentLayoutGuide).inset(24)
|
make.bottom.equalTo(scrollView.contentLayoutGuide).inset(24)
|
||||||
@ -104,14 +110,13 @@ final class WildPhotographerReportListViewController: BaseViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func rebuildContent() {
|
private func rebuildContent() {
|
||||||
|
filterTabBar.apply(selectedFilter: viewModel.selectedFilter)
|
||||||
|
|
||||||
stack.arrangedSubviews.forEach { view in
|
stack.arrangedSubviews.forEach { view in
|
||||||
stack.removeArrangedSubview(view)
|
stack.removeArrangedSubview(view)
|
||||||
view.removeFromSuperview()
|
view.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
filterTabBar.apply(selectedFilter: viewModel.selectedFilter)
|
|
||||||
stack.addArrangedSubview(filterTabBar)
|
|
||||||
|
|
||||||
let reports = viewModel.filteredReports
|
let reports = viewModel.filteredReports
|
||||||
if reports.isEmpty {
|
if reports.isEmpty {
|
||||||
stack.addArrangedSubview(viewModel.isLoading ? WildReportListLoadingView(text: "正在加载举报记录...") : WildReportListEmptyView())
|
stack.addArrangedSubview(viewModel.isLoading ? WildReportListLoadingView(text: "正在加载举报记录...") : WildReportListEmptyView())
|
||||||
@ -327,6 +332,8 @@ private final class WildReportListCardView: UIControl, UIGestureRecognizerDelega
|
|||||||
status.backgroundColor = statusColor.withAlphaComponent(0.12)
|
status.backgroundColor = statusColor.withAlphaComponent(0.12)
|
||||||
status.layer.cornerRadius = 12
|
status.layer.cornerRadius = 12
|
||||||
status.clipsToBounds = true
|
status.clipsToBounds = true
|
||||||
|
status.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
status.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
status.snp.makeConstraints { make in
|
status.snp.makeConstraints { make in
|
||||||
make.height.equalTo(24)
|
make.height.equalTo(24)
|
||||||
}
|
}
|
||||||
@ -348,6 +355,7 @@ private final class WildReportListCardView: UIControl, UIGestureRecognizerDelega
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func makeScenicPill() -> UIView {
|
private func makeScenicPill() -> UIView {
|
||||||
|
let wrapper = UIView()
|
||||||
let label = PaddingLabel(insets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8))
|
let label = PaddingLabel(insets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8))
|
||||||
label.text = scenicName
|
label.text = scenicName
|
||||||
label.font = .systemFont(ofSize: 12, weight: .semibold)
|
label.font = .systemFont(ofSize: 12, weight: .semibold)
|
||||||
@ -355,10 +363,15 @@ private final class WildReportListCardView: UIControl, UIGestureRecognizerDelega
|
|||||||
label.backgroundColor = AppColor.primary.withAlphaComponent(0.10)
|
label.backgroundColor = AppColor.primary.withAlphaComponent(0.10)
|
||||||
label.layer.cornerRadius = 11
|
label.layer.cornerRadius = 11
|
||||||
label.clipsToBounds = true
|
label.clipsToBounds = true
|
||||||
|
label.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
label.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
|
wrapper.addSubview(label)
|
||||||
label.snp.makeConstraints { make in
|
label.snp.makeConstraints { make in
|
||||||
|
make.top.bottom.leading.equalToSuperview()
|
||||||
|
make.trailing.lessThanOrEqualToSuperview()
|
||||||
make.height.equalTo(22)
|
make.height.equalTo(22)
|
||||||
}
|
}
|
||||||
return label
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeMetaRows() -> UIView {
|
private func makeMetaRows() -> UIView {
|
||||||
@ -811,11 +824,10 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
let section = UIStackView()
|
let section = UIStackView()
|
||||||
section.axis = .vertical
|
section.axis = .vertical
|
||||||
section.spacing = 12
|
section.spacing = 12
|
||||||
section.addArrangedSubview(makeSectionHeader(
|
section.addArrangedSubview(makeCompactMediaSectionHeader(
|
||||||
iconName: "photo.on.rectangle.angled",
|
iconName: "photo.on.rectangle.angled",
|
||||||
title: "举报人材料",
|
title: "举报人材料",
|
||||||
subtitle: "\(viewModel.imageCountText)图片 \(viewModel.videoCountText)视频",
|
summary: viewModel.reporterMediaSummary,
|
||||||
chevron: true,
|
|
||||||
action: #selector(previewTapped)
|
action: #selector(previewTapped)
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -844,15 +856,63 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
return section
|
return section
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func makeCompactMediaSectionHeader(
|
||||||
|
iconName: String,
|
||||||
|
title: String,
|
||||||
|
summary: String,
|
||||||
|
action: Selector
|
||||||
|
) -> UIView {
|
||||||
|
let control = UIControl()
|
||||||
|
control.addTarget(self, action: action, for: .touchUpInside)
|
||||||
|
|
||||||
|
let row = UIStackView()
|
||||||
|
row.axis = .horizontal
|
||||||
|
row.alignment = .center
|
||||||
|
row.spacing = 8
|
||||||
|
|
||||||
|
let icon = UIImageView(image: UIImage(systemName: iconName))
|
||||||
|
icon.tintColor = AppColor.primary
|
||||||
|
icon.contentMode = .scaleAspectFit
|
||||||
|
icon.snp.makeConstraints { make in
|
||||||
|
make.size.equalTo(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleLabel = WildReportUI.label(title, font: .systemFont(ofSize: 15, weight: .bold), color: AppColor.textPrimary)
|
||||||
|
let spacer = UIView()
|
||||||
|
let summaryLabel = WildReportUI.label(summary, font: .systemFont(ofSize: 13, weight: .medium), color: AppColor.textSecondary)
|
||||||
|
summaryLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
summaryLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
|
|
||||||
|
let chevron = UIImageView(image: UIImage(systemName: "chevron.right"))
|
||||||
|
chevron.tintColor = UIColor(hex: 0xB6BECA)
|
||||||
|
chevron.contentMode = .scaleAspectFit
|
||||||
|
chevron.snp.makeConstraints { make in
|
||||||
|
make.size.equalTo(14)
|
||||||
|
}
|
||||||
|
|
||||||
|
row.addArrangedSubview(icon)
|
||||||
|
row.addArrangedSubview(titleLabel)
|
||||||
|
row.addArrangedSubview(spacer)
|
||||||
|
row.addArrangedSubview(summaryLabel)
|
||||||
|
row.addArrangedSubview(chevron)
|
||||||
|
control.addSubview(row)
|
||||||
|
row.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
control.snp.makeConstraints { make in
|
||||||
|
make.height.equalTo(22)
|
||||||
|
}
|
||||||
|
return control
|
||||||
|
}
|
||||||
|
|
||||||
private func makePaymentScreenshotSection() -> UIView {
|
private func makePaymentScreenshotSection() -> UIView {
|
||||||
let section = UIStackView()
|
let section = UIStackView()
|
||||||
section.axis = .vertical
|
section.axis = .vertical
|
||||||
section.spacing = 12
|
section.spacing = 12
|
||||||
section.addArrangedSubview(makeSectionHeader(
|
section.addArrangedSubview(makeCompactMediaSectionHeader(
|
||||||
iconName: "creditcard.fill",
|
iconName: "creditcard.fill",
|
||||||
title: "支付截图",
|
title: "支付截图",
|
||||||
subtitle: viewModel.paymentScreenshotSummary,
|
summary: viewModel.paymentScreenshotSummary,
|
||||||
chevron: true,
|
|
||||||
action: #selector(paymentPreviewTapped)
|
action: #selector(paymentPreviewTapped)
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -924,14 +984,11 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
let section = UIStackView()
|
let section = UIStackView()
|
||||||
section.axis = .vertical
|
section.axis = .vertical
|
||||||
section.spacing = 12
|
section.spacing = 12
|
||||||
section.addArrangedSubview(makeSectionHeader(
|
section.addArrangedSubview(makeLocationHeader())
|
||||||
iconName: "mappin.circle.fill",
|
let mapPreview = WildReportDetailMapPreviewView(
|
||||||
title: "位置",
|
scenicName: viewModel.scenicAreaName,
|
||||||
subtitle: viewModel.detailAddress,
|
detailAddress: viewModel.detailAddress
|
||||||
chevron: true,
|
)
|
||||||
action: #selector(locationTapped)
|
|
||||||
))
|
|
||||||
let mapPreview = WildReportDetailMapPreviewView(locationText: viewModel.detailAddress)
|
|
||||||
section.addArrangedSubview(mapPreview)
|
section.addArrangedSubview(mapPreview)
|
||||||
mapPreview.snp.makeConstraints { make in
|
mapPreview.snp.makeConstraints { make in
|
||||||
make.height.equalTo(98)
|
make.height.equalTo(98)
|
||||||
@ -939,7 +996,66 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
return section
|
return section
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeSectionHeader(iconName: String, title: String, subtitle: String, chevron: Bool, action: Selector?) -> UIView {
|
private func makeLocationHeader() -> UIView {
|
||||||
|
let control = UIControl()
|
||||||
|
control.addTarget(self, action: #selector(locationTapped), for: .touchUpInside)
|
||||||
|
|
||||||
|
let row = UIStackView()
|
||||||
|
row.axis = .horizontal
|
||||||
|
row.alignment = .center
|
||||||
|
row.spacing = 14
|
||||||
|
|
||||||
|
let iconBox = UIView()
|
||||||
|
iconBox.backgroundColor = AppColor.primaryLight
|
||||||
|
iconBox.layer.cornerRadius = 12
|
||||||
|
let icon = UIImageView(image: UIImage(systemName: "mappin.circle.fill"))
|
||||||
|
icon.tintColor = AppColor.primary
|
||||||
|
icon.contentMode = .scaleAspectFit
|
||||||
|
iconBox.addSubview(icon)
|
||||||
|
iconBox.snp.makeConstraints { make in
|
||||||
|
make.size.equalTo(50)
|
||||||
|
}
|
||||||
|
icon.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
make.size.equalTo(22)
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleLabel = WildReportUI.label("位置", font: .systemFont(ofSize: 20, weight: .bold), color: AppColor.textPrimary)
|
||||||
|
let spacer = UIView()
|
||||||
|
let valueLabel = WildReportUI.label(viewModel.detailAddress, font: .systemFont(ofSize: 18, weight: .medium), color: AppColor.textSecondary)
|
||||||
|
valueLabel.lineBreakMode = .byTruncatingTail
|
||||||
|
valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
|
|
||||||
|
let chevron = UIImageView(image: UIImage(systemName: "chevron.right"))
|
||||||
|
chevron.tintColor = UIColor(hex: 0xB6BECA)
|
||||||
|
chevron.contentMode = .scaleAspectFit
|
||||||
|
chevron.snp.makeConstraints { make in
|
||||||
|
make.size.equalTo(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
row.addArrangedSubview(iconBox)
|
||||||
|
row.addArrangedSubview(titleLabel)
|
||||||
|
row.addArrangedSubview(spacer)
|
||||||
|
row.addArrangedSubview(valueLabel)
|
||||||
|
row.addArrangedSubview(chevron)
|
||||||
|
control.addSubview(row)
|
||||||
|
row.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
control.snp.makeConstraints { make in
|
||||||
|
make.height.equalTo(50)
|
||||||
|
}
|
||||||
|
return control
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeSectionHeader(
|
||||||
|
iconName: String,
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
trailingText: String? = nil,
|
||||||
|
chevron: Bool,
|
||||||
|
action: Selector?
|
||||||
|
) -> UIView {
|
||||||
let control = UIControl()
|
let control = UIControl()
|
||||||
if let action {
|
if let action {
|
||||||
control.addTarget(self, action: action, for: .touchUpInside)
|
control.addTarget(self, action: action, for: .touchUpInside)
|
||||||
@ -958,12 +1074,21 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
textStack.axis = .vertical
|
textStack.axis = .vertical
|
||||||
textStack.spacing = 4
|
textStack.spacing = 4
|
||||||
textStack.addArrangedSubview(WildReportUI.label(title, font: .systemFont(ofSize: 20, weight: .bold), color: AppColor.textPrimary))
|
textStack.addArrangedSubview(WildReportUI.label(title, font: .systemFont(ofSize: 20, weight: .bold), color: AppColor.textPrimary))
|
||||||
|
if !subtitle.isEmpty {
|
||||||
let subtitleLabel = WildReportUI.label(subtitle, font: .systemFont(ofSize: 18, weight: .medium), color: AppColor.textSecondary)
|
let subtitleLabel = WildReportUI.label(subtitle, font: .systemFont(ofSize: 18, weight: .medium), color: AppColor.textSecondary)
|
||||||
subtitleLabel.adjustsFontSizeToFitWidth = true
|
subtitleLabel.adjustsFontSizeToFitWidth = true
|
||||||
subtitleLabel.minimumScaleFactor = 0.8
|
subtitleLabel.minimumScaleFactor = 0.8
|
||||||
textStack.addArrangedSubview(subtitleLabel)
|
textStack.addArrangedSubview(subtitleLabel)
|
||||||
|
}
|
||||||
control.addSubview(textStack)
|
control.addSubview(textStack)
|
||||||
|
|
||||||
|
let trailingLabel = WildReportUI.label(trailingText ?? "", font: .systemFont(ofSize: 15, weight: .semibold), color: AppColor.textSecondary)
|
||||||
|
trailingLabel.textAlignment = .right
|
||||||
|
trailingLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
trailingLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
|
trailingLabel.isHidden = trailingText?.isEmpty != false
|
||||||
|
control.addSubview(trailingLabel)
|
||||||
|
|
||||||
let chevronView = UIImageView(image: UIImage(systemName: "chevron.right"))
|
let chevronView = UIImageView(image: UIImage(systemName: "chevron.right"))
|
||||||
chevronView.tintColor = AppColor.textTertiary
|
chevronView.tintColor = AppColor.textTertiary
|
||||||
chevronView.isHidden = !chevron
|
chevronView.isHidden = !chevron
|
||||||
@ -981,11 +1106,19 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
make.trailing.centerY.equalToSuperview()
|
make.trailing.centerY.equalToSuperview()
|
||||||
make.size.equalTo(18)
|
make.size.equalTo(18)
|
||||||
}
|
}
|
||||||
|
trailingLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.trailing.equalTo(chevronView.snp.leading).offset(-8)
|
||||||
|
}
|
||||||
textStack.snp.makeConstraints { make in
|
textStack.snp.makeConstraints { make in
|
||||||
make.leading.equalTo(iconBox.snp.trailing).offset(12)
|
make.leading.equalTo(iconBox.snp.trailing).offset(12)
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
|
if trailingText?.isEmpty == false {
|
||||||
|
make.trailing.lessThanOrEqualTo(trailingLabel.snp.leading).offset(-10)
|
||||||
|
} else {
|
||||||
make.trailing.equalTo(chevronView.snp.leading).offset(-10)
|
make.trailing.equalTo(chevronView.snp.leading).offset(-10)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
control.snp.makeConstraints { make in
|
control.snp.makeConstraints { make in
|
||||||
make.height.greaterThanOrEqualTo(50)
|
make.height.greaterThanOrEqualTo(50)
|
||||||
}
|
}
|
||||||
@ -993,6 +1126,7 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func makeScenicPill() -> UIView {
|
private func makeScenicPill() -> UIView {
|
||||||
|
let wrapper = UIView()
|
||||||
let pill = UIStackView()
|
let pill = UIStackView()
|
||||||
pill.axis = .horizontal
|
pill.axis = .horizontal
|
||||||
pill.alignment = .center
|
pill.alignment = .center
|
||||||
@ -1002,6 +1136,8 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
pill.backgroundColor = AppColor.primaryLight
|
pill.backgroundColor = AppColor.primaryLight
|
||||||
pill.layer.cornerRadius = 13
|
pill.layer.cornerRadius = 13
|
||||||
pill.clipsToBounds = true
|
pill.clipsToBounds = true
|
||||||
|
pill.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
pill.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
|
|
||||||
let icon = UIImageView(image: UIImage(systemName: "mappin.circle.fill"))
|
let icon = UIImageView(image: UIImage(systemName: "mappin.circle.fill"))
|
||||||
icon.tintColor = AppColor.primary
|
icon.tintColor = AppColor.primary
|
||||||
@ -1011,7 +1147,12 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
let label = WildReportUI.label(viewModel.scenicAreaName, font: .app(.captionMedium), color: AppColor.primary)
|
let label = WildReportUI.label(viewModel.scenicAreaName, font: .app(.captionMedium), color: AppColor.primary)
|
||||||
pill.addArrangedSubview(icon)
|
pill.addArrangedSubview(icon)
|
||||||
pill.addArrangedSubview(label)
|
pill.addArrangedSubview(label)
|
||||||
return pill
|
wrapper.addSubview(pill)
|
||||||
|
pill.snp.makeConstraints { make in
|
||||||
|
make.top.bottom.leading.equalToSuperview()
|
||||||
|
make.trailing.lessThanOrEqualToSuperview()
|
||||||
|
}
|
||||||
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeBottomButton(title: String, iconName: String, isPrimary: Bool) -> UIButton {
|
private func makeBottomButton(title: String, iconName: String, isPrimary: Bool) -> UIButton {
|
||||||
@ -1090,7 +1231,7 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
|||||||
|
|
||||||
@objc private func mapTapped() {
|
@objc private func mapTapped() {
|
||||||
navigationController?.pushViewController(
|
navigationController?.pushViewController(
|
||||||
WildReportRiskMapViewController(record: viewModel.record, riskPoints: homeViewModel.riskPoints),
|
WildReportRiskMapViewController(),
|
||||||
animated: true
|
animated: true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1140,7 +1281,6 @@ private final class WildReportDetailInfoRowView: UIView {
|
|||||||
|
|
||||||
let titleLabel = WildReportUI.label(title, font: .app(.captionMedium), color: AppColor.textSecondary)
|
let titleLabel = WildReportUI.label(title, font: .app(.captionMedium), color: AppColor.textSecondary)
|
||||||
let valueLabel = WildReportUI.label(value, font: .app(.subtitle), color: AppColor.textPrimary, lines: multiline ? 0 : 1)
|
let valueLabel = WildReportUI.label(value, font: .app(.subtitle), color: AppColor.textPrimary, lines: multiline ? 0 : 1)
|
||||||
valueLabel.textAlignment = multiline ? .left : .right
|
|
||||||
valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
|
|
||||||
addSubview(titleLabel)
|
addSubview(titleLabel)
|
||||||
@ -1185,7 +1325,6 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
|||||||
let previewKindText: String
|
let previewKindText: String
|
||||||
private let gradient = CAGradientLayer()
|
private let gradient = CAGradientLayer()
|
||||||
private let remoteImageView = UIImageView()
|
private let remoteImageView = UIImageView()
|
||||||
private let iconView = UIImageView()
|
|
||||||
private let footerLabel = PaddingLabel(insets: UIEdgeInsets(top: 3, left: 8, bottom: 3, right: 8))
|
private let footerLabel = PaddingLabel(insets: UIEdgeInsets(top: 3, left: 8, bottom: 3, right: 8))
|
||||||
private let playView = UIImageView(image: UIImage(systemName: "play.circle.fill"))
|
private let playView = UIImageView(image: UIImage(systemName: "play.circle.fill"))
|
||||||
|
|
||||||
@ -1194,11 +1333,11 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
|||||||
case .image(let index, let url, let fileName, let fileTypeText):
|
case .image(let index, let url, let fileName, let fileTypeText):
|
||||||
previewKindText = fileName?.isEmpty == false ? fileName! : "第\(index + 1)张举报图片"
|
previewKindText = fileName?.isEmpty == false ? fileName! : "第\(index + 1)张举报图片"
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
configure(kind: .image(index: index), footerText: fileTypeText ?? "图片 \(index + 1)", imageURL: url)
|
configure(kind: .image(index: index), footerText: fileTypeText ?? "图片 \(index + 1)", imageURL: url, showsFooter: false)
|
||||||
case .video(let index, _, let fileName, let fileTypeText):
|
case .video(let index, _, let fileName, let fileTypeText):
|
||||||
previewKindText = fileName?.isEmpty == false ? fileName! : "第\(index + 1)段举报视频"
|
previewKindText = fileName?.isEmpty == false ? fileName! : "第\(index + 1)段举报视频"
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
configure(kind: .video(index: index, duration: fileTypeText ?? "视频"), footerText: fileTypeText ?? "视频 \(index + 1)")
|
configure(kind: .video(index: index, duration: fileTypeText ?? "00:32"), footerText: fileTypeText ?? "视频 \(index + 1)", showsFooter: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1225,9 +1364,9 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
|||||||
gradient.frame = bounds
|
gradient.frame = bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configure(kind: Kind, footerText: String, imageURL: String? = nil) {
|
private func configure(kind: Kind, footerText: String, imageURL: String? = nil, showsFooter: Bool = true) {
|
||||||
clipsToBounds = true
|
clipsToBounds = true
|
||||||
layer.cornerRadius = 14
|
layer.cornerRadius = 10
|
||||||
gradient.colors = kind.gradientColors
|
gradient.colors = kind.gradientColors
|
||||||
gradient.startPoint = CGPoint(x: 0, y: 0)
|
gradient.startPoint = CGPoint(x: 0, y: 0)
|
||||||
gradient.endPoint = CGPoint(x: 1, y: 1)
|
gradient.endPoint = CGPoint(x: 1, y: 1)
|
||||||
@ -1243,11 +1382,9 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iconView.image = UIImage(systemName: kind.iconName)
|
if imageURL == nil {
|
||||||
iconView.tintColor = UIColor.white.withAlphaComponent(0.92)
|
addPlaceholderArtwork(for: kind)
|
||||||
iconView.contentMode = .scaleAspectFit
|
}
|
||||||
iconView.isHidden = imageURL != nil
|
|
||||||
addSubview(iconView)
|
|
||||||
|
|
||||||
footerLabel.text = footerText
|
footerLabel.text = footerText
|
||||||
footerLabel.font = .systemFont(ofSize: 11, weight: .bold)
|
footerLabel.font = .systemFont(ofSize: 11, weight: .bold)
|
||||||
@ -1255,59 +1392,107 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
|||||||
footerLabel.backgroundColor = UIColor.black.withAlphaComponent(0.24)
|
footerLabel.backgroundColor = UIColor.black.withAlphaComponent(0.24)
|
||||||
footerLabel.layer.cornerRadius = 9
|
footerLabel.layer.cornerRadius = 9
|
||||||
footerLabel.clipsToBounds = true
|
footerLabel.clipsToBounds = true
|
||||||
|
footerLabel.isHidden = !showsFooter
|
||||||
addSubview(footerLabel)
|
addSubview(footerLabel)
|
||||||
|
|
||||||
iconView.snp.makeConstraints { make in
|
|
||||||
make.center.equalToSuperview()
|
|
||||||
make.size.equalTo(36)
|
|
||||||
}
|
|
||||||
footerLabel.snp.makeConstraints { make in
|
footerLabel.snp.makeConstraints { make in
|
||||||
make.leading.bottom.equalToSuperview().inset(8)
|
make.leading.bottom.equalToSuperview().inset(8)
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .video(_, let duration) = kind {
|
if case .video(_, let duration) = kind {
|
||||||
|
let playCircle = UIView()
|
||||||
|
playCircle.backgroundColor = UIColor.black.withAlphaComponent(0.08)
|
||||||
|
playCircle.layer.cornerRadius = 21
|
||||||
|
playCircle.layer.borderWidth = 2.5
|
||||||
|
playCircle.layer.borderColor = UIColor.white.cgColor
|
||||||
|
addSubview(playCircle)
|
||||||
|
playView.image = UIImage(systemName: "play.fill")
|
||||||
playView.tintColor = .white
|
playView.tintColor = .white
|
||||||
addSubview(playView)
|
addSubview(playView)
|
||||||
let durationLabel = PaddingLabel(insets: UIEdgeInsets(top: 2, left: 6, bottom: 2, right: 6))
|
let durationLabel = PaddingLabel(insets: UIEdgeInsets(top: 2, left: 6, bottom: 2, right: 6))
|
||||||
durationLabel.text = duration
|
durationLabel.text = duration
|
||||||
durationLabel.font = .systemFont(ofSize: 10, weight: .bold)
|
durationLabel.font = .systemFont(ofSize: 14, weight: .semibold)
|
||||||
durationLabel.textColor = .white
|
durationLabel.textColor = .white
|
||||||
durationLabel.backgroundColor = UIColor.black.withAlphaComponent(0.34)
|
durationLabel.backgroundColor = .clear
|
||||||
durationLabel.layer.cornerRadius = 8
|
durationLabel.layer.shadowColor = UIColor.black.cgColor
|
||||||
durationLabel.clipsToBounds = true
|
durationLabel.layer.shadowOpacity = 0.25
|
||||||
|
durationLabel.layer.shadowRadius = 4
|
||||||
|
durationLabel.layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||||
addSubview(durationLabel)
|
addSubview(durationLabel)
|
||||||
playView.snp.makeConstraints { make in
|
playCircle.snp.makeConstraints { make in
|
||||||
make.center.equalToSuperview()
|
make.center.equalToSuperview()
|
||||||
make.size.equalTo(34)
|
make.size.equalTo(42)
|
||||||
|
}
|
||||||
|
playView.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalToSuperview().offset(2)
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.size.equalTo(18)
|
||||||
}
|
}
|
||||||
durationLabel.snp.makeConstraints { make in
|
durationLabel.snp.makeConstraints { make in
|
||||||
make.trailing.top.equalToSuperview().inset(8)
|
make.trailing.bottom.equalToSuperview().inset(10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func addPlaceholderArtwork(for kind: Kind) {
|
||||||
|
let index = min(kind.artworkIndex, 2)
|
||||||
|
let mountain = UIImageView(image: UIImage(systemName: "mountain.2.fill"))
|
||||||
|
mountain.tintColor = UIColor.white.withAlphaComponent(0.32)
|
||||||
|
mountain.contentMode = .scaleAspectFit
|
||||||
|
addSubview(mountain)
|
||||||
|
|
||||||
|
let camera = UIImageView(image: UIImage(systemName: "camera.fill"))
|
||||||
|
camera.tintColor = UIColor.white.withAlphaComponent(0.90)
|
||||||
|
camera.contentMode = .scaleAspectFit
|
||||||
|
addSubview(camera)
|
||||||
|
|
||||||
|
let person = UIImageView(image: UIImage(systemName: "person.fill"))
|
||||||
|
person.tintColor = UIColor(hex: 0x101827).withAlphaComponent(0.78)
|
||||||
|
person.contentMode = .scaleAspectFit
|
||||||
|
addSubview(person)
|
||||||
|
|
||||||
|
mountain.snp.makeConstraints { make in
|
||||||
|
make.width.height.equalTo(52)
|
||||||
|
make.centerX.equalToSuperview().offset(-16)
|
||||||
|
make.centerY.equalToSuperview().offset(-8)
|
||||||
|
}
|
||||||
|
camera.snp.makeConstraints { make in
|
||||||
|
make.width.height.equalTo(22)
|
||||||
|
make.trailing.equalToSuperview().inset(10)
|
||||||
|
make.centerY.equalToSuperview().offset(-18)
|
||||||
|
}
|
||||||
|
person.snp.makeConstraints { make in
|
||||||
|
make.width.height.equalTo(44)
|
||||||
|
make.trailing.equalToSuperview().inset(24 - index * 12)
|
||||||
|
make.bottom.equalToSuperview().offset(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 举报详情页缩略图类型。
|
/// 举报详情页缩略图类型。
|
||||||
private enum Kind {
|
private enum Kind {
|
||||||
case image(index: Int)
|
case image(index: Int)
|
||||||
case video(index: Int, duration: String)
|
case video(index: Int, duration: String)
|
||||||
|
|
||||||
var iconName: String {
|
var artworkIndex: Int {
|
||||||
switch self {
|
switch self {
|
||||||
case .image(let index):
|
case .image(let index), .video(let index, _):
|
||||||
return index.isMultiple(of: 2) ? "person.crop.rectangle.fill" : "camera.fill"
|
return index
|
||||||
case .video:
|
|
||||||
return "video.fill"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var gradientColors: [CGColor] {
|
var gradientColors: [CGColor] {
|
||||||
switch self {
|
switch self {
|
||||||
case .image(let index):
|
case .image(let index):
|
||||||
return index.isMultiple(of: 2)
|
switch index {
|
||||||
? [UIColor(hex: 0x75C7FF).cgColor, UIColor(hex: 0x0073FF).cgColor]
|
case 1:
|
||||||
: [UIColor(hex: 0xA6E3B8).cgColor, UIColor(hex: 0x10B981).cgColor]
|
return [UIColor(hex: 0xD7E8F8).cgColor, UIColor(hex: 0x7E9CB4).cgColor]
|
||||||
|
case 2:
|
||||||
|
return [UIColor(hex: 0xBFD9EE).cgColor, UIColor(hex: 0x435B70).cgColor]
|
||||||
|
default:
|
||||||
|
return [UIColor(hex: 0xC7DFEC).cgColor, UIColor(hex: 0x2D3D4B).cgColor]
|
||||||
|
}
|
||||||
case .video:
|
case .video:
|
||||||
return [UIColor(hex: 0x8CA8FF).cgColor, UIColor(hex: 0x4F46E5).cgColor]
|
return [UIColor(hex: 0xBFD9EE).cgColor, UIColor(hex: 0x435B70).cgColor]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1315,37 +1500,52 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
|||||||
|
|
||||||
/// 举报详情页地图预览,绘制简化道路、路径和定位点。
|
/// 举报详情页地图预览,绘制简化道路、路径和定位点。
|
||||||
private final class WildReportDetailMapPreviewView: UIView {
|
private final class WildReportDetailMapPreviewView: UIView {
|
||||||
private let locationLabel = UILabel()
|
private let scenicName: String
|
||||||
|
private let detailAddress: String
|
||||||
|
private let detailLabel = UILabel()
|
||||||
|
private let scenicLabel = UILabel()
|
||||||
private let pinView = UIImageView(image: UIImage(systemName: "mappin.circle.fill"))
|
private let pinView = UIImageView(image: UIImage(systemName: "mappin.circle.fill"))
|
||||||
|
|
||||||
init(locationText: String) {
|
init(scenicName: String, detailAddress: String) {
|
||||||
|
self.scenicName = scenicName
|
||||||
|
self.detailAddress = detailAddress
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
backgroundColor = UIColor(hex: 0xEAF7EA)
|
backgroundColor = UIColor(hex: 0xE1F4D8)
|
||||||
layer.cornerRadius = 14
|
layer.cornerRadius = 10
|
||||||
clipsToBounds = true
|
clipsToBounds = true
|
||||||
|
|
||||||
locationLabel.text = locationText
|
detailLabel.text = detailAddress.replacingOccurrences(of: "附近", with: "")
|
||||||
locationLabel.font = .systemFont(ofSize: 12, weight: .bold)
|
detailLabel.font = .systemFont(ofSize: 13, weight: .semibold)
|
||||||
locationLabel.textColor = AppColor.textPrimary
|
detailLabel.textColor = UIColor(hex: 0x64748B)
|
||||||
locationLabel.backgroundColor = UIColor.white.withAlphaComponent(0.86)
|
detailLabel.textAlignment = .center
|
||||||
locationLabel.layer.cornerRadius = 10
|
|
||||||
locationLabel.clipsToBounds = true
|
scenicLabel.text = scenicName
|
||||||
locationLabel.textAlignment = .center
|
scenicLabel.font = .systemFont(ofSize: 13, weight: .semibold)
|
||||||
|
scenicLabel.textColor = UIColor(hex: 0x418C48)
|
||||||
|
scenicLabel.textAlignment = .center
|
||||||
|
|
||||||
pinView.tintColor = AppColor.danger
|
pinView.tintColor = AppColor.danger
|
||||||
pinView.contentMode = .scaleAspectFit
|
pinView.contentMode = .scaleAspectFit
|
||||||
|
|
||||||
addSubview(locationLabel)
|
addSubview(detailLabel)
|
||||||
|
addSubview(scenicLabel)
|
||||||
addSubview(pinView)
|
addSubview(pinView)
|
||||||
locationLabel.snp.makeConstraints { make in
|
detailLabel.snp.makeConstraints { make in
|
||||||
make.leading.equalToSuperview().offset(12)
|
make.centerX.equalToSuperview().multipliedBy(0.94)
|
||||||
make.bottom.equalToSuperview().inset(10)
|
make.centerY.equalToSuperview().multipliedBy(0.64)
|
||||||
|
make.leading.greaterThanOrEqualToSuperview().offset(12)
|
||||||
|
make.trailing.lessThanOrEqualToSuperview().inset(12)
|
||||||
|
}
|
||||||
|
scenicLabel.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalToSuperview().multipliedBy(1.56)
|
||||||
|
make.centerY.equalToSuperview().multipliedBy(1.16)
|
||||||
|
make.leading.greaterThanOrEqualToSuperview().offset(12)
|
||||||
make.trailing.lessThanOrEqualToSuperview().inset(12)
|
make.trailing.lessThanOrEqualToSuperview().inset(12)
|
||||||
make.height.equalTo(24)
|
|
||||||
}
|
}
|
||||||
pinView.snp.makeConstraints { make in
|
pinView.snp.makeConstraints { make in
|
||||||
make.centerX.equalToSuperview().offset(34)
|
make.centerX.equalToSuperview().multipliedBy(1.06)
|
||||||
make.centerY.equalToSuperview().offset(-2)
|
make.centerY.equalToSuperview().multipliedBy(1.08)
|
||||||
make.size.equalTo(32)
|
make.size.equalTo(36)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1358,34 +1558,48 @@ private final class WildReportDetailMapPreviewView: UIView {
|
|||||||
super.draw(rect)
|
super.draw(rect)
|
||||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||||
|
|
||||||
context.setFillColor(UIColor(hex: 0xD9F1D8).cgColor)
|
let colors = [UIColor(hex: 0xE1F4D8).cgColor, UIColor(hex: 0xBDE6C7).cgColor] as CFArray
|
||||||
context.fill(CGRect(x: rect.width * 0.56, y: 0, width: rect.width * 0.44, height: rect.height))
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
if let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: [0, 1]) {
|
||||||
|
context.drawLinearGradient(
|
||||||
|
gradient,
|
||||||
|
start: CGPoint(x: 0, y: 0),
|
||||||
|
end: CGPoint(x: rect.width, y: rect.height),
|
||||||
|
options: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let dashedPath = UIBezierPath()
|
||||||
|
dashedPath.move(to: CGPoint(x: rect.width * 0.05, y: rect.height * 0.32))
|
||||||
|
dashedPath.addCurve(
|
||||||
|
to: CGPoint(x: rect.width * 0.95, y: rect.height * 0.22),
|
||||||
|
controlPoint1: CGPoint(x: rect.width * 0.28, y: rect.height * 0.62),
|
||||||
|
controlPoint2: CGPoint(x: rect.width * 0.56, y: rect.height * 0.02)
|
||||||
|
)
|
||||||
|
UIColor(hex: 0x65A6D7).withAlphaComponent(0.75).setStroke()
|
||||||
|
dashedPath.lineWidth = 1.4
|
||||||
|
dashedPath.setLineDash([5, 5], count: 2, phase: 0)
|
||||||
|
dashedPath.stroke()
|
||||||
|
|
||||||
let road = UIBezierPath()
|
let road = UIBezierPath()
|
||||||
road.move(to: CGPoint(x: 0, y: rect.height * 0.62))
|
road.move(to: CGPoint(x: rect.width * 0.00, y: rect.height * 0.74))
|
||||||
road.addCurve(
|
road.addCurve(
|
||||||
to: CGPoint(x: rect.width, y: rect.height * 0.24),
|
to: CGPoint(x: rect.width * 1.00, y: rect.height * 0.62),
|
||||||
controlPoint1: CGPoint(x: rect.width * 0.28, y: rect.height * 0.26),
|
controlPoint1: CGPoint(x: rect.width * 0.26, y: rect.height * 0.20),
|
||||||
controlPoint2: CGPoint(x: rect.width * 0.64, y: rect.height * 0.88)
|
controlPoint2: CGPoint(x: rect.width * 0.68, y: rect.height * 1.00)
|
||||||
)
|
)
|
||||||
UIColor.white.withAlphaComponent(0.86).setStroke()
|
UIColor.white.withAlphaComponent(0.88).setStroke()
|
||||||
road.lineWidth = 10
|
road.lineWidth = 5
|
||||||
road.stroke()
|
road.stroke()
|
||||||
UIColor(hex: 0xBFDDBD).setStroke()
|
UIColor(hex: 0x99C59C).setStroke()
|
||||||
road.lineWidth = 2
|
road.lineWidth = 1
|
||||||
road.stroke()
|
road.stroke()
|
||||||
|
|
||||||
let path = UIBezierPath()
|
let pinCenter = CGPoint(x: rect.width * 0.53, y: rect.height * 0.54)
|
||||||
path.move(to: CGPoint(x: rect.width * 0.16, y: rect.height * 0.24))
|
UIColor(hex: 0xEF4444).withAlphaComponent(0.18).setFill()
|
||||||
path.addCurve(
|
context.fillEllipse(in: CGRect(x: pinCenter.x - 32, y: pinCenter.y - 32, width: 64, height: 64))
|
||||||
to: CGPoint(x: rect.width * 0.76, y: rect.height * 0.70),
|
UIColor(hex: 0xEF4444).withAlphaComponent(0.22).setFill()
|
||||||
controlPoint1: CGPoint(x: rect.width * 0.38, y: rect.height * 0.34),
|
context.fillEllipse(in: CGRect(x: pinCenter.x - 22, y: pinCenter.y - 22, width: 44, height: 44))
|
||||||
controlPoint2: CGPoint(x: rect.width * 0.48, y: rect.height * 0.76)
|
|
||||||
)
|
|
||||||
AppColor.primary.setStroke()
|
|
||||||
path.lineWidth = 2
|
|
||||||
path.setLineDash([6, 5], count: 2, phase: 0)
|
|
||||||
path.stroke()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user