519 lines
23 KiB
Swift
519 lines
23 KiB
Swift
//
|
||
// SOSPracticeView.swift
|
||
// QuickLocation
|
||
//
|
||
|
||
import UIKit
|
||
import SwiftyUserDefaults
|
||
|
||
final class SOSPracticeView: UIView, UIScrollViewDelegate {
|
||
|
||
private enum State {
|
||
case guide
|
||
case practiceIdle
|
||
case practiceCountdown
|
||
case success
|
||
}
|
||
|
||
private let guideView = UIView()
|
||
private let practiceIdleView = UIView()
|
||
private let practiceCountdownView = UIView()
|
||
private let successView = UIView()
|
||
private let guideScrollView = UIScrollView()
|
||
private let pageControl = UIPageControl()
|
||
private let practicePulseView = SOSPulseButtonView()
|
||
private let practiceCountdownRing = SOSCountdownRingView()
|
||
private let practiceSlider = SOSSlideCancelView(thumbImageName: "SOS/slider")
|
||
private var state: State = .guide
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
backgroundColor = .white
|
||
setupUI()
|
||
setupActions()
|
||
setState(.guide)
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
override func didMoveToWindow() {
|
||
super.didMoveToWindow()
|
||
if window == nil {
|
||
practiceCountdownRing.stop()
|
||
}
|
||
}
|
||
|
||
private func setupUI() {
|
||
[guideView, practiceIdleView, practiceCountdownView, successView].forEach {
|
||
addSubview($0)
|
||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||
NSLayoutConstraint.activate([
|
||
$0.topAnchor.constraint(equalTo: topAnchor),
|
||
$0.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||
$0.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||
$0.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||
])
|
||
}
|
||
|
||
setupGuideView()
|
||
setupPracticeIdleView()
|
||
setupPracticeCountdownView()
|
||
setupSuccessView()
|
||
}
|
||
|
||
private func setupGuideView() {
|
||
guideScrollView.isPagingEnabled = true
|
||
guideScrollView.showsHorizontalScrollIndicator = false
|
||
guideScrollView.bounces = false
|
||
guideScrollView.delegate = self
|
||
guideView.addSubview(guideScrollView)
|
||
guideScrollView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let pageStack = UIStackView()
|
||
pageStack.axis = .horizontal
|
||
pageStack.alignment = .fill
|
||
pageStack.distribution = .fill
|
||
pageStack.spacing = 0
|
||
guideScrollView.addSubview(pageStack)
|
||
pageStack.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
for index in 1...3 {
|
||
let page = makeGuidePage(index: index)
|
||
pageStack.addArrangedSubview(page)
|
||
page.widthAnchor.constraint(equalTo: guideScrollView.frameLayoutGuide.widthAnchor).isActive = true
|
||
}
|
||
|
||
pageControl.numberOfPages = 3
|
||
pageControl.currentPage = 0
|
||
pageControl.currentPageIndicatorTintColor = UIColor(hexStr: "#4CC7F8")
|
||
pageControl.pageIndicatorTintColor = UIColor(hexStr: "#D9D9D9")
|
||
pageControl.isUserInteractionEnabled = false
|
||
guideView.addSubview(pageControl)
|
||
pageControl.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let startButton = UIButton(type: .custom)
|
||
startButton.setTitle("开始练习", for: .normal)
|
||
startButton.setTitleColor(.white, for: .normal)
|
||
startButton.titleLabel?.font = FontManager.boboBold(18)
|
||
startButton.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||
startButton.cornerRadius = 20
|
||
startButton.addTarget(self, action: #selector(startPractice), for: .touchUpInside)
|
||
guideView.addSubview(startButton)
|
||
startButton.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
NSLayoutConstraint.activate([
|
||
guideScrollView.topAnchor.constraint(equalTo: guideView.topAnchor),
|
||
guideScrollView.leadingAnchor.constraint(equalTo: guideView.leadingAnchor),
|
||
guideScrollView.trailingAnchor.constraint(equalTo: guideView.trailingAnchor),
|
||
guideScrollView.bottomAnchor.constraint(equalTo: pageControl.topAnchor, constant: -8),
|
||
|
||
pageStack.topAnchor.constraint(equalTo: guideScrollView.contentLayoutGuide.topAnchor),
|
||
pageStack.leadingAnchor.constraint(equalTo: guideScrollView.contentLayoutGuide.leadingAnchor),
|
||
pageStack.trailingAnchor.constraint(equalTo: guideScrollView.contentLayoutGuide.trailingAnchor),
|
||
pageStack.bottomAnchor.constraint(equalTo: guideScrollView.contentLayoutGuide.bottomAnchor),
|
||
pageStack.heightAnchor.constraint(equalTo: guideScrollView.frameLayoutGuide.heightAnchor),
|
||
|
||
pageControl.centerXAnchor.constraint(equalTo: guideView.centerXAnchor),
|
||
pageControl.bottomAnchor.constraint(equalTo: startButton.topAnchor, constant: -14),
|
||
|
||
startButton.leadingAnchor.constraint(equalTo: guideView.leadingAnchor, constant: 30),
|
||
startButton.trailingAnchor.constraint(equalTo: guideView.trailingAnchor, constant: -30),
|
||
startButton.heightAnchor.constraint(equalToConstant: 56),
|
||
startButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -20)
|
||
])
|
||
}
|
||
|
||
private func makeGuidePage(index: Int) -> UIView {
|
||
let page = UIView()
|
||
|
||
let verticalScrollView = UIScrollView()
|
||
verticalScrollView.showsVerticalScrollIndicator = false
|
||
verticalScrollView.alwaysBounceVertical = false
|
||
page.addSubview(verticalScrollView)
|
||
verticalScrollView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let contentView = UIView()
|
||
verticalScrollView.addSubview(contentView)
|
||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let imageView = UIImageView(image: UIImage(named: "SOS/sos_guide_\(index)"))
|
||
imageView.contentMode = .scaleAspectFit
|
||
contentView.addSubview(imageView)
|
||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let bulletList = makeBulletList()
|
||
contentView.addSubview(bulletList)
|
||
bulletList.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
NSLayoutConstraint.activate([
|
||
verticalScrollView.topAnchor.constraint(equalTo: page.topAnchor),
|
||
verticalScrollView.leadingAnchor.constraint(equalTo: page.leadingAnchor),
|
||
verticalScrollView.trailingAnchor.constraint(equalTo: page.trailingAnchor),
|
||
verticalScrollView.bottomAnchor.constraint(equalTo: page.bottomAnchor),
|
||
|
||
contentView.topAnchor.constraint(equalTo: verticalScrollView.contentLayoutGuide.topAnchor),
|
||
contentView.leadingAnchor.constraint(equalTo: verticalScrollView.contentLayoutGuide.leadingAnchor),
|
||
contentView.trailingAnchor.constraint(equalTo: verticalScrollView.contentLayoutGuide.trailingAnchor),
|
||
contentView.bottomAnchor.constraint(equalTo: verticalScrollView.contentLayoutGuide.bottomAnchor),
|
||
contentView.widthAnchor.constraint(equalTo: verticalScrollView.frameLayoutGuide.widthAnchor),
|
||
contentView.heightAnchor.constraint(greaterThanOrEqualTo: verticalScrollView.frameLayoutGuide.heightAnchor),
|
||
|
||
imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 36),
|
||
imageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
imageView.widthAnchor.constraint(equalToConstant: 240),
|
||
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
|
||
|
||
bulletList.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 28),
|
||
bulletList.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 45),
|
||
bulletList.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -30),
|
||
bulletList.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -16)
|
||
])
|
||
return page
|
||
}
|
||
|
||
private func makeBulletList() -> UIStackView {
|
||
let stack = UIStackView()
|
||
stack.axis = .vertical
|
||
stack.alignment = .leading
|
||
stack.distribution = .fillEqually
|
||
stack.spacing = 10
|
||
|
||
["夜间独自行走", "遇见突发危机事件", "遇到医疗紧急情况"].forEach { text in
|
||
let label = UILabel()
|
||
label.textColor = UIColor(hexStr: "#333333")
|
||
label.font = .systemFont(ofSize: 16, weight: .regular)
|
||
let attributed = NSMutableAttributedString(string: "✦ \(text)")
|
||
attributed.addAttribute(
|
||
.foregroundColor,
|
||
value: UIColor(hexStr: "#11B8F4"),
|
||
range: NSRange(location: 0, length: 1)
|
||
)
|
||
label.attributedText = attributed
|
||
stack.addArrangedSubview(label)
|
||
}
|
||
return stack
|
||
}
|
||
|
||
private func setupPracticeIdleView() {
|
||
let contentView = makeVerticalContentView(in: practiceIdleView)
|
||
let modeBadge = SOSPracticeModeBadge()
|
||
contentView.addSubview(modeBadge)
|
||
modeBadge.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
contentView.addSubview(practicePulseView)
|
||
practicePulseView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let hintLabel = UILabel()
|
||
hintLabel.textAlignment = .center
|
||
hintLabel.font = .systemFont(ofSize: 20, weight: .regular)
|
||
let hint = NSMutableAttributedString(
|
||
string: "轻点感叹号发送SOS",
|
||
attributes: [.foregroundColor: UIColor(hexStr: "#333333")]
|
||
)
|
||
hint.addAttribute(
|
||
.foregroundColor,
|
||
value: UIColor(hexStr: "#FF383C"),
|
||
range: NSRange(location: 7, length: 3)
|
||
)
|
||
hintLabel.attributedText = hint
|
||
contentView.addSubview(hintLabel)
|
||
hintLabel.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let pulseWidth = practicePulseView.widthAnchor.constraint(equalTo: contentView.widthAnchor, constant: -30)
|
||
pulseWidth.priority = .defaultHigh
|
||
NSLayoutConstraint.activate([
|
||
modeBadge.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 28),
|
||
modeBadge.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
modeBadge.widthAnchor.constraint(equalToConstant: 139),
|
||
modeBadge.heightAnchor.constraint(equalToConstant: 40),
|
||
|
||
practicePulseView.topAnchor.constraint(equalTo: modeBadge.bottomAnchor, constant: 18),
|
||
practicePulseView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
pulseWidth,
|
||
practicePulseView.widthAnchor.constraint(lessThanOrEqualToConstant: 345),
|
||
practicePulseView.heightAnchor.constraint(equalTo: practicePulseView.widthAnchor),
|
||
|
||
hintLabel.topAnchor.constraint(equalTo: practicePulseView.bottomAnchor, constant: 16),
|
||
hintLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
hintLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -20)
|
||
])
|
||
}
|
||
|
||
private func setupPracticeCountdownView() {
|
||
let contentView = makeVerticalContentView(in: practiceCountdownView)
|
||
let modeBadge = SOSPracticeModeBadge()
|
||
contentView.addSubview(modeBadge)
|
||
modeBadge.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
contentView.addSubview(practiceCountdownRing)
|
||
practiceCountdownRing.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let titleLabel = UILabel()
|
||
titleLabel.attributedText = Self.cancelTitle
|
||
titleLabel.font = .systemFont(ofSize: 22, weight: .regular)
|
||
titleLabel.textAlignment = .center
|
||
contentView.addSubview(titleLabel)
|
||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let bodyLabel = UILabel()
|
||
bodyLabel.text = "10秒后,会将你的SOS和位置发送到你的圈子和紧急联系人。"
|
||
bodyLabel.textColor = UIColor(hexStr: "#273246")
|
||
bodyLabel.font = .systemFont(ofSize: 15, weight: .regular)
|
||
bodyLabel.textAlignment = .center
|
||
bodyLabel.numberOfLines = 0
|
||
contentView.addSubview(bodyLabel)
|
||
bodyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
contentView.addSubview(practiceSlider)
|
||
practiceSlider.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let ringWidth = practiceCountdownRing.widthAnchor.constraint(equalTo: contentView.widthAnchor, constant: -30)
|
||
ringWidth.priority = .defaultHigh
|
||
NSLayoutConstraint.activate([
|
||
modeBadge.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 28),
|
||
modeBadge.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
modeBadge.widthAnchor.constraint(equalToConstant: 139),
|
||
modeBadge.heightAnchor.constraint(equalToConstant: 40),
|
||
|
||
practiceCountdownRing.topAnchor.constraint(equalTo: modeBadge.bottomAnchor, constant: 8),
|
||
practiceCountdownRing.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
ringWidth,
|
||
practiceCountdownRing.widthAnchor.constraint(lessThanOrEqualToConstant: 345),
|
||
practiceCountdownRing.heightAnchor.constraint(equalTo: practiceCountdownRing.widthAnchor),
|
||
|
||
titleLabel.topAnchor.constraint(equalTo: practiceCountdownRing.bottomAnchor, constant: 2),
|
||
titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
|
||
bodyLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 24),
|
||
bodyLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 40),
|
||
bodyLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -40),
|
||
|
||
practiceSlider.topAnchor.constraint(greaterThanOrEqualTo: bodyLabel.bottomAnchor, constant: 28),
|
||
practiceSlider.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 30),
|
||
practiceSlider.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -30),
|
||
practiceSlider.heightAnchor.constraint(equalToConstant: 56),
|
||
practiceSlider.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -(kSafeBottomMargin + 20))
|
||
])
|
||
}
|
||
|
||
private func setupSuccessView() {
|
||
let contentView = makeVerticalContentView(in: successView)
|
||
let modeBadge = SOSPracticeModeBadge()
|
||
contentView.addSubview(modeBadge)
|
||
modeBadge.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let imageView = UIImageView(image: UIImage(named: "SOS/sos_practice_success"))
|
||
imageView.contentMode = .scaleAspectFit
|
||
contentView.addSubview(imageView)
|
||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let titleLabel = UILabel()
|
||
titleLabel.textAlignment = .center
|
||
titleLabel.font = .systemFont(ofSize: 22, weight: .regular)
|
||
let title = NSMutableAttributedString(
|
||
string: "求救成功",
|
||
attributes: [.foregroundColor: UIColor(hexStr: "#333333")]
|
||
)
|
||
title.addAttribute(
|
||
.foregroundColor,
|
||
value: UIColor(hexStr: "#FF383C"),
|
||
range: NSRange(location: 2, length: 2)
|
||
)
|
||
titleLabel.attributedText = title
|
||
contentView.addSubview(titleLabel)
|
||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let subtitleLabel = UILabel()
|
||
subtitleLabel.text = "太好了!您可以使用sos了"
|
||
subtitleLabel.textColor = UIColor(hexStr: "#273246")
|
||
subtitleLabel.font = .systemFont(ofSize: 16, weight: .regular)
|
||
subtitleLabel.textAlignment = .center
|
||
contentView.addSubview(subtitleLabel)
|
||
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let doneButton = UIButton(type: .custom)
|
||
doneButton.setTitle("我知道了", for: .normal)
|
||
doneButton.setTitleColor(.white, for: .normal)
|
||
doneButton.titleLabel?.font = FontManager.boboBold(18)
|
||
doneButton.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||
doneButton.cornerRadius = 20
|
||
doneButton.addTarget(self, action: #selector(finishPractice), for: .touchUpInside)
|
||
contentView.addSubview(doneButton)
|
||
doneButton.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
NSLayoutConstraint.activate([
|
||
modeBadge.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 28),
|
||
modeBadge.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
modeBadge.widthAnchor.constraint(equalToConstant: 139),
|
||
modeBadge.heightAnchor.constraint(equalToConstant: 40),
|
||
|
||
imageView.topAnchor.constraint(equalTo: modeBadge.bottomAnchor, constant: 72),
|
||
imageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
imageView.widthAnchor.constraint(equalToConstant: 270),
|
||
imageView.heightAnchor.constraint(equalToConstant: 190),
|
||
|
||
titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 52),
|
||
titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
|
||
subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 34),
|
||
subtitleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||
|
||
doneButton.topAnchor.constraint(greaterThanOrEqualTo: subtitleLabel.bottomAnchor, constant: 30),
|
||
doneButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 30),
|
||
doneButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -30),
|
||
doneButton.heightAnchor.constraint(equalToConstant: 56),
|
||
doneButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -(kSafeBottomMargin + 20))
|
||
])
|
||
}
|
||
|
||
private func makeVerticalContentView(in container: UIView) -> UIView {
|
||
let scrollView = UIScrollView()
|
||
scrollView.showsVerticalScrollIndicator = false
|
||
scrollView.alwaysBounceVertical = false
|
||
container.addSubview(scrollView)
|
||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
let contentView = UIView()
|
||
scrollView.addSubview(contentView)
|
||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
NSLayoutConstraint.activate([
|
||
scrollView.topAnchor.constraint(equalTo: container.topAnchor),
|
||
scrollView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
||
scrollView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
||
scrollView.bottomAnchor.constraint(equalTo: container.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),
|
||
contentView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.frameLayoutGuide.heightAnchor)
|
||
])
|
||
return contentView
|
||
}
|
||
|
||
private func setupActions() {
|
||
practicePulseView.onTap = { [weak self] in
|
||
guard let self, self.state == .practiceIdle else { return }
|
||
self.setState(.practiceCountdown)
|
||
}
|
||
practiceCountdownRing.onCompleted = { [weak self] in
|
||
guard let self, self.state == .practiceCountdown else { return }
|
||
self.setState(.success)
|
||
}
|
||
practiceSlider.onCompleted = { [weak self] in
|
||
guard let self, self.state == .practiceCountdown else { return }
|
||
self.practiceCountdownRing.stop()
|
||
self.setState(.practiceIdle)
|
||
}
|
||
}
|
||
|
||
private func setState(_ state: State) {
|
||
self.state = state
|
||
guideView.isHidden = state != .guide
|
||
practiceIdleView.isHidden = state != .practiceIdle
|
||
practiceCountdownView.isHidden = state != .practiceCountdown
|
||
successView.isHidden = state != .success
|
||
|
||
switch state {
|
||
case .guide:
|
||
practiceCountdownRing.stop()
|
||
practicePulseView.setAnimationEnabled(false)
|
||
case .practiceIdle:
|
||
practiceCountdownRing.stop()
|
||
practiceSlider.configure(style: .practiceCountdown)
|
||
practiceSlider.setInteractionEnabled(true)
|
||
practicePulseView.setShowsHand(true)
|
||
practicePulseView.setAnimationEnabled(true)
|
||
case .practiceCountdown:
|
||
practicePulseView.setAnimationEnabled(false)
|
||
practiceSlider.configure(style: .practiceCountdown)
|
||
practiceSlider.setInteractionEnabled(true)
|
||
practiceCountdownRing.start(duration: 10)
|
||
case .success:
|
||
practiceCountdownRing.stop(reset: false)
|
||
practicePulseView.setAnimationEnabled(false)
|
||
}
|
||
}
|
||
|
||
@objc private func startPractice() {
|
||
setState(.practiceIdle)
|
||
}
|
||
|
||
@objc private func finishPractice() {
|
||
var completedUsers = Defaults[\.sosIsPracticeList]
|
||
if !completedUsers.contains(AppContextManager.shared.userId) {
|
||
completedUsers.append(AppContextManager.shared.userId)
|
||
Defaults[\.sosIsPracticeList] = completedUsers
|
||
}
|
||
|
||
UIView.animate(withDuration: 0.25, animations: {
|
||
self.alpha = 0
|
||
}, completion: { _ in
|
||
self.isHidden = true
|
||
self.alpha = 1
|
||
})
|
||
}
|
||
|
||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||
guard scrollView === guideScrollView, scrollView.bounds.width > 0 else { return }
|
||
pageControl.currentPage = Int(round(scrollView.contentOffset.x / scrollView.bounds.width))
|
||
}
|
||
|
||
private static var cancelTitle: NSAttributedString {
|
||
let title = NSMutableAttributedString(
|
||
string: "滑动取消",
|
||
attributes: [.foregroundColor: UIColor(hexStr: "#333333")]
|
||
)
|
||
title.addAttribute(
|
||
.foregroundColor,
|
||
value: UIColor(hexStr: "#FF383C"),
|
||
range: NSRange(location: 2, length: 2)
|
||
)
|
||
return title
|
||
}
|
||
}
|
||
|
||
private final class SOSPracticeModeBadge: UIView {
|
||
|
||
private let gradientLayer = CAGradientLayer()
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
gradientLayer.colors = [
|
||
UIColor(hexStr: "#DDF4FF").cgColor,
|
||
UIColor(hexStr: "#52C5F4").cgColor,
|
||
UIColor(hexStr: "#DDF4FF").cgColor
|
||
]
|
||
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
|
||
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
|
||
layer.addSublayer(gradientLayer)
|
||
|
||
let label = UILabel()
|
||
label.text = "练习模式"
|
||
label.textColor = .white
|
||
label.font = .systemFont(ofSize: 16, weight: .semibold)
|
||
label.textAlignment = .center
|
||
addSubview(label)
|
||
label.translatesAutoresizingMaskIntoConstraints = false
|
||
NSLayoutConstraint.activate([
|
||
label.topAnchor.constraint(equalTo: topAnchor),
|
||
label.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||
label.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||
label.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||
])
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
override func layoutSubviews() {
|
||
super.layoutSubviews()
|
||
gradientLayer.frame = bounds
|
||
}
|
||
}
|