399 lines
15 KiB
Swift
399 lines
15 KiB
Swift
//
|
|
// FeedbackView.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import UIKit
|
|
|
|
final class FeedbackView: UIView {
|
|
|
|
let navView = BaseNavigationView(title: "问题反馈")
|
|
let detailTextView = UITextView()
|
|
let contactTextField = UITextField()
|
|
let saveBtn = UIButton(type: .custom)
|
|
private(set) var categoryButtons: [UIButton] = []
|
|
|
|
var onAddImage: (() -> Void)?
|
|
var onDeleteImage: ((Int) -> Void)?
|
|
|
|
private let scrollView = UIScrollView()
|
|
private let contentView = UIView()
|
|
private let detailPlaceholderLabel = UILabel()
|
|
private let countLabel = UILabel()
|
|
private let imagesCollectionView: UICollectionView
|
|
private var images: [UIImage] = []
|
|
|
|
override init(frame: CGRect) {
|
|
let layout = UICollectionViewFlowLayout()
|
|
layout.scrollDirection = .horizontal
|
|
layout.minimumLineSpacing = 8
|
|
layout.minimumInteritemSpacing = 8
|
|
layout.itemSize = CGSize(width: 100, height: 100)
|
|
imagesCollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
|
super.init(frame: frame)
|
|
backgroundColor = UIColor(hexStr: "#F8F8F8")
|
|
setupUI()
|
|
updateCategory(selectedIndex: 0)
|
|
updateDetail(text: "")
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private func setupUI() {
|
|
addSubview(navView)
|
|
navView.layoutChain.top().edgesHorzontal().height(kNaviHeight)
|
|
|
|
saveBtn.setTitle("保存", for: .normal)
|
|
saveBtn.setTitleColor(.white, for: .normal)
|
|
saveBtn.titleLabel?.font = FontManager.boboBold(18)
|
|
saveBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
|
saveBtn.layer.cornerRadius = 24
|
|
saveBtn.clipsToBounds = true
|
|
addSubview(saveBtn)
|
|
saveBtn.layoutChain
|
|
.left(28)
|
|
.right(28)
|
|
.bottom(kSafeBottomMargin + 22)
|
|
.height(56)
|
|
|
|
scrollView.alwaysBounceVertical = false
|
|
scrollView.keyboardDismissMode = .onDrag
|
|
scrollView.showsVerticalScrollIndicator = false
|
|
addSubview(scrollView)
|
|
scrollView.layoutChain
|
|
.topToBottomOfView(navView)
|
|
.edgesHorzontal()
|
|
.bottomToTopOfView(saveBtn, offset: -12)
|
|
|
|
scrollView.addSubview(contentView)
|
|
contentView.layoutChain.edges().widthToView(scrollView)
|
|
|
|
let typeTitle = makeSectionTitle(required: true, title: "您想反馈的功能类型")
|
|
contentView.addSubview(typeTitle)
|
|
typeTitle.layoutChain.top(14).left(16).right(16)
|
|
|
|
let titles = ["功能问题", "优化建议", "其他"]
|
|
categoryButtons = titles.enumerated().map { index, title in
|
|
let button = UIButton(type: .custom)
|
|
button.tag = index
|
|
button.setTitle(title, for: .normal)
|
|
button.titleLabel?.font = .systemFont(ofSize: 15, weight: .medium)
|
|
button.layer.cornerRadius = 12
|
|
button.layer.borderWidth = 1
|
|
contentView.addSubview(button)
|
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
return button
|
|
}
|
|
|
|
let firstButton = categoryButtons[0]
|
|
let secondButton = categoryButtons[1]
|
|
let thirdButton = categoryButtons[2]
|
|
NSLayoutConstraint.activate([
|
|
firstButton.topAnchor.constraint(equalTo: typeTitle.bottomAnchor, constant: 12),
|
|
firstButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
|
firstButton.heightAnchor.constraint(equalToConstant: 34),
|
|
secondButton.topAnchor.constraint(equalTo: firstButton.topAnchor),
|
|
secondButton.leadingAnchor.constraint(equalTo: firstButton.trailingAnchor, constant: 16),
|
|
secondButton.widthAnchor.constraint(equalTo: firstButton.widthAnchor),
|
|
secondButton.heightAnchor.constraint(equalTo: firstButton.heightAnchor),
|
|
thirdButton.topAnchor.constraint(equalTo: firstButton.topAnchor),
|
|
thirdButton.leadingAnchor.constraint(equalTo: secondButton.trailingAnchor, constant: 16),
|
|
thirdButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
|
|
thirdButton.widthAnchor.constraint(equalTo: firstButton.widthAnchor),
|
|
thirdButton.heightAnchor.constraint(equalTo: firstButton.heightAnchor)
|
|
])
|
|
|
|
let detailTitle = makeSectionTitle(required: true, title: "请补充详细问题或意见")
|
|
contentView.addSubview(detailTitle)
|
|
detailTitle.layoutChain
|
|
.topToBottomOfView(firstButton, offset: 20)
|
|
.left(16)
|
|
.right(16)
|
|
|
|
let detailContainer = UIView()
|
|
detailContainer.backgroundColor = .white
|
|
detailContainer.layer.cornerRadius = 16
|
|
contentView.addSubview(detailContainer)
|
|
detailContainer.layoutChain
|
|
.topToBottomOfView(detailTitle, offset: 12)
|
|
.left(16)
|
|
.right(16)
|
|
.height(150)
|
|
|
|
detailTextView.backgroundColor = .clear
|
|
detailTextView.font = .systemFont(ofSize: 14, weight: .medium)
|
|
detailTextView.textColor = UIColor(hexStr: "#293445")
|
|
detailTextView.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 28, right: 10)
|
|
detailTextView.textContainer.lineFragmentPadding = 0
|
|
detailTextView.isScrollEnabled = true
|
|
detailTextView.returnKeyType = .default
|
|
detailContainer.addSubview(detailTextView)
|
|
detailTextView.layoutChain.edges()
|
|
|
|
detailPlaceholderLabel.text = "请在这里输入内容"
|
|
detailPlaceholderLabel.font = .systemFont(ofSize: 14, weight: .medium)
|
|
detailPlaceholderLabel.textColor = UIColor(hexStr: "#B5B5B5")
|
|
detailPlaceholderLabel.isUserInteractionEnabled = false
|
|
detailContainer.addSubview(detailPlaceholderLabel)
|
|
detailPlaceholderLabel.layoutChain.top(12).left(14).right(14)
|
|
|
|
countLabel.font = .systemFont(ofSize: 13, weight: .medium)
|
|
countLabel.textColor = UIColor(hexStr: "#767676")
|
|
countLabel.textAlignment = .right
|
|
detailContainer.addSubview(countLabel)
|
|
countLabel.layoutChain.right(14).bottom(10)
|
|
|
|
let imageTitle = makeSectionTitle(
|
|
required: false,
|
|
title: "请提供相关问题的图片",
|
|
detail: "(选填,最多可上传三张)"
|
|
)
|
|
contentView.addSubview(imageTitle)
|
|
imageTitle.layoutChain
|
|
.topToBottomOfView(detailContainer, offset: 18)
|
|
.left(16)
|
|
.right(16)
|
|
|
|
imagesCollectionView.backgroundColor = .clear
|
|
imagesCollectionView.isScrollEnabled = false
|
|
imagesCollectionView.showsHorizontalScrollIndicator = false
|
|
imagesCollectionView.dataSource = self
|
|
imagesCollectionView.delegate = self
|
|
imagesCollectionView.register(
|
|
FeedbackImageCell.self,
|
|
forCellWithReuseIdentifier: FeedbackImageCell.reuseIdentifier
|
|
)
|
|
imagesCollectionView.register(
|
|
FeedbackAddImageCell.self,
|
|
forCellWithReuseIdentifier: FeedbackAddImageCell.reuseIdentifier
|
|
)
|
|
contentView.addSubview(imagesCollectionView)
|
|
imagesCollectionView.layoutChain
|
|
.topToBottomOfView(imageTitle, offset: 12)
|
|
.left(16)
|
|
.right(16)
|
|
.height(100)
|
|
|
|
let contactTitle = makeSectionTitle(
|
|
required: false,
|
|
title: "联系方式",
|
|
detail: "(选填,可以留下您的手机号、微信、邮箱)"
|
|
)
|
|
contentView.addSubview(contactTitle)
|
|
contactTitle.layoutChain
|
|
.topToBottomOfView(imagesCollectionView, offset: 18)
|
|
.left(16)
|
|
.right(16)
|
|
|
|
contactTextField.backgroundColor = .white
|
|
contactTextField.layer.cornerRadius = 20
|
|
contactTextField.font = .systemFont(ofSize: 14, weight: .medium)
|
|
contactTextField.textColor = UIColor(hexStr: "#293445")
|
|
contactTextField.returnKeyType = .done
|
|
contactTextField.clearButtonMode = .whileEditing
|
|
contactTextField.attributedPlaceholder = NSAttributedString(
|
|
string: "请输入您的联系方式",
|
|
attributes: [
|
|
.foregroundColor: UIColor(hexStr: "#B5B5B5"),
|
|
.font: UIFont.systemFont(ofSize: 14, weight: .medium)
|
|
]
|
|
)
|
|
contactTextField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 16, height: 1))
|
|
contactTextField.leftViewMode = .always
|
|
contactTextField.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 16, height: 1))
|
|
contactTextField.rightViewMode = .unlessEditing
|
|
contentView.addSubview(contactTextField)
|
|
contactTextField.layoutChain
|
|
.topToBottomOfView(contactTitle, offset: 12)
|
|
.left(16)
|
|
.right(16)
|
|
.height(40)
|
|
.bottom(24)
|
|
}
|
|
|
|
private func makeSectionTitle(required: Bool, title: String, detail: String? = nil) -> UILabel {
|
|
let label = UILabel()
|
|
label.numberOfLines = 0
|
|
|
|
let text = NSMutableAttributedString()
|
|
if required {
|
|
text.append(NSAttributedString(
|
|
string: "*",
|
|
attributes: [
|
|
.font: UIFont.systemFont(ofSize: 17, weight: .medium),
|
|
.foregroundColor: UIColor(hexStr: "#F64545")
|
|
]
|
|
))
|
|
}
|
|
text.append(NSAttributedString(
|
|
string: title,
|
|
attributes: [
|
|
.font: UIFont.systemFont(ofSize: 16, weight: .medium),
|
|
.foregroundColor: UIColor(hexStr: "#293445")
|
|
]
|
|
))
|
|
if let detail {
|
|
text.append(NSAttributedString(
|
|
string: detail,
|
|
attributes: [
|
|
.font: UIFont.systemFont(ofSize: 12, weight: .medium),
|
|
.foregroundColor: UIColor(hexStr: "#B5B5B5")
|
|
]
|
|
))
|
|
}
|
|
label.attributedText = text
|
|
return label
|
|
}
|
|
|
|
func updateCategory(selectedIndex: Int) {
|
|
for (index, button) in categoryButtons.enumerated() {
|
|
let isSelected = index == selectedIndex
|
|
button.backgroundColor = isSelected ? UIColor(hexStr: "#08B2F5") : .clear
|
|
button.layer.borderColor = UIColor(
|
|
hexStr: isSelected ? "#08B2F5" : "#D6D6D6"
|
|
).cgColor
|
|
button.setTitleColor(
|
|
isSelected ? .white : UIColor(hexStr: "#293445"),
|
|
for: .normal
|
|
)
|
|
}
|
|
}
|
|
|
|
func updateDetail(text: String) {
|
|
detailPlaceholderLabel.isHidden = !text.isEmpty
|
|
countLabel.text = "\(text.count)/200"
|
|
}
|
|
|
|
func updateImages(_ images: [UIImage]) {
|
|
self.images = Array(images.prefix(3))
|
|
imagesCollectionView.reloadData()
|
|
}
|
|
|
|
func setSubmitting(_ submitting: Bool) {
|
|
saveBtn.isEnabled = !submitting
|
|
saveBtn.alpha = submitting ? 0.65 : 1
|
|
}
|
|
}
|
|
|
|
extension FeedbackView: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
|
|
|
|
func collectionView(
|
|
_ collectionView: UICollectionView,
|
|
layout collectionViewLayout: UICollectionViewLayout,
|
|
sizeForItemAt indexPath: IndexPath
|
|
) -> CGSize {
|
|
let width = min(100, floor((collectionView.bounds.width - 16) / 3))
|
|
return CGSize(width: width, height: 100)
|
|
}
|
|
|
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
images.count + (images.count < 3 ? 1 : 0)
|
|
}
|
|
|
|
func collectionView(
|
|
_ collectionView: UICollectionView,
|
|
cellForItemAt indexPath: IndexPath
|
|
) -> UICollectionViewCell {
|
|
if indexPath.item < images.count {
|
|
guard let cell = collectionView.dequeueReusableCell(
|
|
withReuseIdentifier: FeedbackImageCell.reuseIdentifier,
|
|
for: indexPath
|
|
) as? FeedbackImageCell else {
|
|
return UICollectionViewCell()
|
|
}
|
|
cell.configure(image: images[indexPath.item])
|
|
cell.onDelete = { [weak self, weak cell] in
|
|
guard let self, let cell,
|
|
let currentIndexPath = collectionView.indexPath(for: cell) else { return }
|
|
self.onDeleteImage?(currentIndexPath.item)
|
|
}
|
|
return cell
|
|
}
|
|
|
|
guard let cell = collectionView.dequeueReusableCell(
|
|
withReuseIdentifier: FeedbackAddImageCell.reuseIdentifier,
|
|
for: indexPath
|
|
) as? FeedbackAddImageCell else {
|
|
return UICollectionViewCell()
|
|
}
|
|
return cell
|
|
}
|
|
|
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
guard indexPath.item == images.count, images.count < 3 else { return }
|
|
onAddImage?()
|
|
}
|
|
}
|
|
|
|
private final class FeedbackImageCell: UICollectionViewCell {
|
|
static let reuseIdentifier = "FeedbackImageCell"
|
|
|
|
var onDelete: (() -> Void)?
|
|
|
|
private let imageView = UIImageView()
|
|
private let deleteButton = UIButton(type: .custom)
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
|
|
imageView.contentMode = .scaleAspectFill
|
|
imageView.layer.cornerRadius = 16
|
|
imageView.clipsToBounds = true
|
|
contentView.addSubview(imageView)
|
|
imageView.layoutChain.edges()
|
|
|
|
let symbolConfig = UIImage.SymbolConfiguration(pointSize: 10, weight: .bold)
|
|
deleteButton.setImage(UIImage(systemName: "xmark", withConfiguration: symbolConfig), for: .normal)
|
|
deleteButton.tintColor = UIColor(hexStr: "#293445")
|
|
deleteButton.backgroundColor = UIColor.white.withAlphaComponent(0.92)
|
|
deleteButton.layer.cornerRadius = 11
|
|
deleteButton.layer.shadowColor = UIColor.black.withAlphaComponent(0.12).cgColor
|
|
deleteButton.layer.shadowOpacity = 1
|
|
deleteButton.layer.shadowRadius = 3
|
|
deleteButton.layer.shadowOffset = CGSize(width: 0, height: 1)
|
|
deleteButton.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
|
|
contentView.addSubview(deleteButton)
|
|
deleteButton.layoutChain.top(6).right(6).width(22).height(22)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func prepareForReuse() {
|
|
super.prepareForReuse()
|
|
imageView.image = nil
|
|
onDelete = nil
|
|
}
|
|
|
|
func configure(image: UIImage) {
|
|
imageView.image = image
|
|
}
|
|
|
|
@objc private func deleteTapped() {
|
|
onDelete?()
|
|
}
|
|
}
|
|
|
|
private final class FeedbackAddImageCell: UICollectionViewCell {
|
|
static let reuseIdentifier = "FeedbackAddImageCell"
|
|
|
|
private let addImageView = UIImageView(image: UIImage(named: "Mine/feedback_add"))
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
|
|
contentView.backgroundColor = .white
|
|
contentView.layer.cornerRadius = 16
|
|
addImageView.contentMode = .scaleAspectFit
|
|
contentView.addSubview(addImageView)
|
|
addImageView.layoutChain.centerX().centerY().width(48).height(48)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
}
|