jsdw_ios/QuickLocation/Section/Mine/Feedback/FeedbackView.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")
}
}