252 lines
8.7 KiB
Swift
252 lines
8.7 KiB
Swift
//
|
|
// FeatureIntroView.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import UIKit
|
|
import SDCycleScrollView
|
|
|
|
struct FeatureIntroItem {
|
|
let imageName: String
|
|
let accessibilityTitle: String
|
|
let aspectRatio: CGFloat
|
|
let action: FeatureIntroAction
|
|
}
|
|
|
|
enum FeatureIntroAction {
|
|
case signIn
|
|
case createBubble
|
|
case lockDistract
|
|
case pigeonMessage
|
|
case searchLocation
|
|
case sos
|
|
case placeholder
|
|
}
|
|
|
|
final class FeatureIntroView: UIView {
|
|
|
|
var onTapItem: ((FeatureIntroAction) -> Void)?
|
|
|
|
private let headerBackgroundView = UIView()
|
|
private let headerBgImg = UIImageView(image: UIImage(named: "Explore/header_bg"))
|
|
private let titleContainer = UIStackView()
|
|
private let titleLineView = UIView()
|
|
private let titleLabel = UILabel()
|
|
private let sheetView = UIView()
|
|
private let scrollView = UIScrollView()
|
|
private let contentView = UIView()
|
|
private let gridStack = UIStackView()
|
|
|
|
private static let allItems: [FeatureIntroItem] = [
|
|
FeatureIntroItem(
|
|
imageName: "Explore/sign_in_card",
|
|
accessibilityTitle: "还在吗",
|
|
aspectRatio: 127 / 166,
|
|
action: .signIn
|
|
),
|
|
FeatureIntroItem(
|
|
imageName: "Explore/pigeon_message_card",
|
|
accessibilityTitle: "飞鸽传书",
|
|
aspectRatio: 127 / 166,
|
|
action: .pigeonMessage
|
|
),
|
|
FeatureIntroItem(
|
|
imageName: "Explore/lock_distract_card",
|
|
accessibilityTitle: "一键锁机",
|
|
aspectRatio: 130 / 166,
|
|
action: .lockDistract
|
|
),
|
|
FeatureIntroItem(
|
|
imageName: "Explore/bubble_card",
|
|
accessibilityTitle: "隐身气泡",
|
|
aspectRatio: 130 / 166,
|
|
action: .createBubble
|
|
),
|
|
FeatureIntroItem(
|
|
imageName: "Explore/search_location_card",
|
|
accessibilityTitle: "查位置",
|
|
aspectRatio: 130 / 166,
|
|
action: .searchLocation
|
|
),
|
|
FeatureIntroItem(
|
|
imageName: "Explore/sos_card",
|
|
accessibilityTitle: "SOS",
|
|
aspectRatio: 130 / 166,
|
|
action: .sos
|
|
)
|
|
]
|
|
|
|
private var displayedItems: [FeatureIntroItem] = []
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
setupUI()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private func setupUI() {
|
|
backgroundColor = .white
|
|
|
|
addSubview(headerBackgroundView)
|
|
headerBackgroundView.backgroundColor = .clear
|
|
|
|
headerBgImg.contentMode = .scaleAspectFill
|
|
headerBgImg.clipsToBounds = true
|
|
headerBackgroundView.addSubview(headerBgImg)
|
|
headerBackgroundView.addSubview(cycleScrollView)
|
|
|
|
sheetView.backgroundColor = .white
|
|
sheetView.layer.cornerRadius = 30
|
|
sheetView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
|
sheetView.clipsToBounds = true
|
|
addSubview(sheetView)
|
|
sheetView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
scrollView.alwaysBounceVertical = false
|
|
scrollView.showsVerticalScrollIndicator = false
|
|
scrollView.contentInset.bottom = 90
|
|
sheetView.addSubview(scrollView)
|
|
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
scrollView.addSubview(contentView)
|
|
contentView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
gridStack.axis = .vertical
|
|
gridStack.alignment = .fill
|
|
gridStack.distribution = .fill
|
|
gridStack.spacing = 13
|
|
contentView.addSubview(gridStack)
|
|
gridStack.translatesAutoresizingMaskIntoConstraints = false
|
|
reloadFeatureItems()
|
|
|
|
headerBackgroundView.layoutChain
|
|
.edges(excludingEdge: .bottom)
|
|
.heightToWidth(190 / 375)
|
|
|
|
headerBgImg.layoutChain.edges()
|
|
|
|
cycleScrollView.layoutChain.edges()
|
|
|
|
sheetView.layoutChain
|
|
.topToBottomOfView(headerBackgroundView, offset: -30)
|
|
.edges(excludingEdge: .top)
|
|
|
|
NSLayoutConstraint.activate([
|
|
scrollView.topAnchor.constraint(equalTo: sheetView.topAnchor),
|
|
scrollView.leadingAnchor.constraint(equalTo: sheetView.leadingAnchor),
|
|
scrollView.trailingAnchor.constraint(equalTo: sheetView.trailingAnchor),
|
|
scrollView.bottomAnchor.constraint(equalTo: sheetView.bottomAnchor),
|
|
|
|
contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
|
|
contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
|
|
contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
|
|
contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
|
|
contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),
|
|
|
|
gridStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 19),
|
|
gridStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15),
|
|
gridStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15),
|
|
gridStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
|
|
])
|
|
}
|
|
|
|
func reloadFeatureItems() {
|
|
let hideSearch = AppContextManager.shared.systemConfig?.isIntercept == true
|
|
displayedItems = Self.allItems.filter { !hideSearch || $0.action != .searchLocation }
|
|
rebuildGrid()
|
|
}
|
|
|
|
private func rebuildGrid() {
|
|
gridStack.arrangedSubviews.forEach {
|
|
gridStack.removeArrangedSubview($0)
|
|
$0.removeFromSuperview()
|
|
}
|
|
|
|
let columns = 2
|
|
var index = 0
|
|
while index < displayedItems.count {
|
|
let rowStack = UIStackView()
|
|
rowStack.axis = .horizontal
|
|
rowStack.alignment = .fill
|
|
rowStack.distribution = .fillEqually
|
|
rowStack.spacing = 13
|
|
|
|
for _ in 0..<columns {
|
|
if index < displayedItems.count {
|
|
let item = displayedItems[index]
|
|
let card = FeatureIntroCardView(item: item)
|
|
card.tag = index
|
|
card.addTarget(self, action: #selector(cardTapped(_:)), for: .touchUpInside)
|
|
rowStack.addArrangedSubview(card)
|
|
card.heightAnchor.constraint(equalTo: card.widthAnchor, multiplier: item.aspectRatio).isActive = true
|
|
} else {
|
|
rowStack.addArrangedSubview(UIView())
|
|
}
|
|
index += 1
|
|
}
|
|
gridStack.addArrangedSubview(rowStack)
|
|
}
|
|
}
|
|
|
|
@objc private func cardTapped(_ sender: FeatureIntroCardView) {
|
|
guard displayedItems.indices.contains(sender.tag) else { return }
|
|
onTapItem?(displayedItems[sender.tag].action)
|
|
}
|
|
|
|
// MARK: - 轮播图
|
|
lazy var cycleScrollView: SDCycleScrollView = {
|
|
let view = SDCycleScrollView(frame: .zero)
|
|
view.backgroundColor = .clear
|
|
view.scrollDirection = .horizontal
|
|
// view.showPageControl = true
|
|
view.bannerImageViewContentMode = .scaleAspectFill
|
|
view.autoScrollTimeInterval = 5
|
|
// view.currentPageDotColor = UIColor(hexStr: "#16B3FF")
|
|
// view.pageDotColor = UIColor(hexStr: "#7AD6FF").withAlphaComponent(0.4)
|
|
// view.pageControlDotSize = CGSize(width: 8, height: 8)
|
|
view.clipsToBounds = false
|
|
// view.pageControlBottomOffset = -34
|
|
view.isHidden = true
|
|
return view
|
|
}()
|
|
}
|
|
|
|
final class FeatureIntroCardView: UIControl {
|
|
|
|
private let imageView = UIImageView()
|
|
|
|
init(item: FeatureIntroItem) {
|
|
super.init(frame: .zero)
|
|
|
|
isAccessibilityElement = true
|
|
accessibilityLabel = item.accessibilityTitle
|
|
accessibilityTraits = .button
|
|
|
|
imageView.image = UIImage(named: item.imageName)
|
|
imageView.contentMode = .scaleAspectFit
|
|
imageView.isUserInteractionEnabled = false
|
|
addSubview(imageView)
|
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
NSLayoutConstraint.activate([
|
|
imageView.topAnchor.constraint(equalTo: topAnchor),
|
|
imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
imageView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
])
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override var isHighlighted: Bool {
|
|
didSet {
|
|
imageView.alpha = isHighlighted ? 0.78 : 1
|
|
}
|
|
}
|
|
}
|