jsdw_ios/QuickLocation/Section/PopupWindow/PopupWindow.swift

1419 lines
48 KiB
Swift

//
// PopupWindow.swift
// QuickLocation
//
// Created by on 2026/7/2.
//
import UIKit
import RxSwift
import RxCocoa
import Lottie
enum PopupType {
///
case search
///
case register
///
case vip
}
class PopupWindow: UIView {
private static let shared = PopupWindow(frame: CGRect(origin: .zero, size: kScreenSize))
var disposeBag = DisposeBag()
private var popupType: PopupType? {
didSet {
switch popupType {
case .search:
searchPopupView.isHidden = false
earthLottieView.play()
case .register:
if Bool.random() {
registerPopupView.isHidden = true
register2PopupView.isHidden = false
slideInRegister2Popup()
} else {
register2PopupView.isHidden = true
registerPopupView.isHidden = false
slideInRegisterPopup()
}
case .vip:
let roll = Int.random(in: 0...3)
switch roll {
case 0:
unlockVip2View.isHidden = true
unlockVip3View.isHidden = true
unlockVip4View.isHidden = true
unlockVip1View.isHidden = false
slideInUnlockVip1Popup()
case 1:
unlockVip1View.isHidden = true
unlockVip3View.isHidden = true
unlockVip4View.isHidden = true
unlockVip2View.isHidden = false
slideInUnlockVip2Popup()
case 2:
unlockVip1View.isHidden = true
unlockVip2View.isHidden = true
unlockVip4View.isHidden = true
unlockVip3View.isHidden = false
slideInUnlockVip3Popup()
default:
unlockVip1View.isHidden = true
unlockVip2View.isHidden = true
unlockVip3View.isHidden = true
unlockVip4View.isHidden = false
slideInUnlockVip4Popup()
}
default:
break
}
}
}
private static var dismissCompletion: (() -> Void)?
static func show(popupType: PopupType, dismissCompletion: (() -> Void)? = nil) {
Self.dismissCompletion = dismissCompletion
guard let superView = kKeyWindow else {
return
}
if PopupWindow.shared.superview != nil {
PopupWindow.shared.removeFromSuperview()
PopupWindow.shared.bgView.frame = .zero
}
PopupWindow.shared.resetPopupState()
PopupWindow.shared.popupType = popupType
PopupWindow.shared.bgView.alpha = 1
PopupWindow.shared.bgView.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
superView.addSubview(PopupWindow.shared)
superView.bringSubviewToFront(PopupWindow.shared)
UIView.animate(withDuration: 0.25) {
PopupWindow.shared.bgView.alpha = 1
}
}
private func resetPopupState() {
stopRegisterStarFlash()
stopRegister1LottieAlternate()
stopUnlockVip1Animations()
stopUnlockVip2Animations()
stopUnlockVip3Animations()
stopUnlockVip4Animations()
registerPopupView.transform = .identity
register2PopupView.transform = .identity
unlockVip1View.transform = .identity
unlockVip2View.transform = .identity
unlockVip3View.transform = .identity
unlockVip4View.transform = .identity
registerStar1.transform = .identity
// register2Star1.transform = .identity
searchPopupView.isHidden = true
registerPopupView.isHidden = true
register2PopupView.isHidden = true
unlockVip1View.isHidden = true
unlockVip2View.isHidden = true
unlockVip3View.isHidden = true
unlockVip4View.isHidden = true
bgView.isUserInteractionEnabled = true
}
///
static func dismiss() {
guard PopupWindow.shared.superview != nil else { return }
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
PopupWindow.shared.bgView.alpha = 0
} completion: { _ in
PopupWindow.shared.resetPopupState()
PopupWindow.shared.removeFromSuperview()
let completion = Self.dismissCompletion
Self.dismissCompletion = nil
completion?()
}
}
private func setupRx() {
registerCloseBtn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.activePopupView == 2 ? self?.slideOutRegister2Popup() : self?.slideOutRegisterPopup()
})
.disposed(by: disposeBag)
registerBtn.rx.tap
.subscribe(onNext: { [weak self] _ in
NotificationCenter.default.post(name: .pausePopupQueue, object: nil)
self?.activePopupView == 2 ? self?.slideOutRegister2Popup() : self?.slideOutRegisterPopup()
let topVC = UIViewController.currentViewController()
if !(topVC is LoginViewController) {
AppRouter.push(Route.login)
}
})
.disposed(by: disposeBag)
register2CloseBtn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.slideOutRegister2Popup()
})
.disposed(by: disposeBag)
register2Btn.rx.tap
.subscribe(onNext: { [weak self] _ in
NotificationCenter.default.post(name: .pausePopupQueue, object: nil)
self?.slideOutRegister2Popup()
let topVC = UIViewController.currentViewController()
if !(topVC is LoginViewController) {
AppRouter.push(Route.login)
}
})
.disposed(by: disposeBag)
vip1CloseBtn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.slideOutUnlockVip1Popup()
})
.disposed(by: disposeBag)
vip1UnlockBtn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.slideOutUnlockVip1Popup()
let topVC = UIViewController.currentViewController()
if !(topVC is VipRechargeVC) {
AppRouter.push(Route.vipRecharge)
}
})
.disposed(by: disposeBag)
unlockVip2CloseBtn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.slideOutUnlockVip2Popup()
})
.disposed(by: disposeBag)
unlockVip2Btn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.slideOutUnlockVip2Popup()
let topVC = UIViewController.currentViewController()
if !(topVC is VipRechargeVC) {
AppRouter.push(Route.vipRecharge)
}
})
.disposed(by: disposeBag)
unlockVip3CloseBtn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.slideOutUnlockVip3Popup()
})
.disposed(by: disposeBag)
unlockVip3Btn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.slideOutUnlockVip3Popup()
let topVC = UIViewController.currentViewController()
if !(topVC is VipRechargeVC) {
AppRouter.push(Route.vipRecharge)
}
})
.disposed(by: disposeBag)
unlockVip4CloseBtn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.slideOutUnlockVip4Popup()
})
.disposed(by: disposeBag)
unlockVip4Btn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.slideOutUnlockVip4Popup()
let topVC = UIViewController.currentViewController()
if !(topVC is VipRechargeVC) {
AppRouter.push(Route.vipRecharge)
}
})
.disposed(by: disposeBag)
}
private lazy var bgView: UIView = {
let view = UIView()
view.backgroundColor = .black.withAlphaComponent(0.5)
return view
}()
// MARK: -
lazy var searchPopupView: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.isHidden = true
let bgImgView = UIImageView(image: UIImage(named: "PopupWindow/search_bg"))
bgImgView.contentMode = .scaleAspectFill
bgImgView.rx.tapGesture.subscribe(onNext: { _ in
PopupWindow.dismiss()
AppRouter.push(Route.searchLocation)
}).disposed(by: disposeBag)
view.addSubview(bgImgView)
bgImgView.layoutChain
.edges(excludingEdge: .bottom)
.heightToWidth(828/770)
let btn = UIButton(type: .custom)
btn.backgroundColor = .clear
btn.setBackgroundImage(UIImage(named: "Group/close"), for: .normal)
btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 100, bottom: 100, right: 100)
btn.rx.tap.subscribe(onNext: { _ in
PopupWindow.dismiss()
}).disposed(by: disposeBag)
view.addSubview(btn)
btn.layoutChain
.topToBottomOfView(bgImgView, offset: 0)
.centerX()
.width(22)
.height(22)
.bottom()
// let peopleIcon = UIImageView(image: UIImage(named: "PopupWindow/search_people"))
// peopleIcon.contentMode = .scaleAspectFill
// view.addSubview(peopleIcon)
// peopleIcon.layoutChain
// .top(20)
// .width(73)
// .height(95)
// .rightToCenterXOfView(view)
// let eartIcon = UIImageView(image: UIImage(named: "PopupWindow/search_earth"))
// eartIcon.contentMode = .scaleAspectFill
// view.addSubview(eartIcon)
// eartIcon.layoutChain
// .topToView(peopleIcon, offset: 12)
// .width(88)
// .height(81)
// .leftToCenterXOfView(view)
view.addSubview(earthLottieView)
earthLottieView.layoutChain
.top()
.edgesHorzontal(50)
.heightToWidth(95/155)
return view
}()
lazy var earthLottieView: LottieAnimationView = {
let view = LottieAnimationView(name: "dialog_phone_search")
view.loopMode = .loop
return view
}()
// MARK: - 1
lazy var unlockVip1View: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.isHidden = true
let bgImg = UIImageView(image: UIImage(named: "PopupWindow/vip_bg"))
bgImg.contentMode = .scaleAspectFill
view.addSubview(bgImg)
bgImg.layoutChain
.top(45)
.edgesHorzontal()
.bottom()
view.addSubview(vip1HeartIcon)
vip1HeartIcon.layoutChain
.top()
.left(21)
.width(116)
.height(99)
let logoImg = UIImageView(image: UIImage(named: "PopupWindow/vip1_logo"))
logoImg.contentMode = .scaleAspectFill
view.addSubview(logoImg)
logoImg.layoutChain
.topToView(bgImg, offset: 40)
.centerX()
.width(125)
.height(42)
view.addSubview(vip1Tips2Img)
vip1Tips2Img.layoutChain
.topToBottomOfView(logoImg, offset: 30)
.centerX()
.width(71)
.height(26)
view.addSubview(vip1Tips1Img)
vip1Tips1Img.layoutChain
.rightToLeftOfView(vip1Tips2Img, offset: -24)
.centerY(vip1Tips2Img)
.width(71)
.height(26)
view.addSubview(vip1Tips3Img)
vip1Tips3Img.layoutChain
.leftToRightOfView(vip1Tips2Img, offset: 24)
.centerY(vip1Tips2Img)
.width(71)
.height(26)
let textLab = UILabel()
textLab.text = "看看TA真实走过哪里"
textLab.font = .systemFont(ofSize: 14, weight: .medium)
textLab.textColor = UIColor(hexStr: "#16B3FF")
view.addSubview(textLab)
textLab.layoutChain
.topToBottomOfView(vip1Tips2Img, offset: 20)
.centerX()
view.addSubview(vip1CloseBtn)
vip1CloseBtn.layoutChain
.topToView(bgImg, offset: 16)
.right(17)
view.addSubview(vip1UnlockBtn)
vip1UnlockBtn.layoutChain
.width(224)
.height(50)
.centerX()
.bottom(kSafeBottomMargin + 15)
return view
}()
lazy var vip1HeartIcon: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/vip1_heart")
view.contentMode = .scaleAspectFill
return view
}()
lazy var vip1Tips1Img: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/vip1_tips_1")
view.contentMode = .scaleAspectFill
return view
}()
lazy var vip1Tips2Img: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/vip1_tips_2")
view.contentMode = .scaleAspectFill
return view
}()
lazy var vip1Tips3Img: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/vip1_tips_3")
view.contentMode = .scaleAspectFill
return view
}()
lazy var vip1CloseBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "PopupWindow/close"), for: .normal)
btn.extendEdgeInsets = UIEdgeInsets(top: 30, left: 30, bottom: 30, right: 17)
return btn
}()
lazy var vip1UnlockBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("立即解锁", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
btn.cornerRadius = 25
return btn
}()
private func startUnlockVip1Animations() {
stopUnlockVip1Animations()
let heartAnimation = CABasicAnimation(keyPath: "transform.scale")
heartAnimation.fromValue = 1.0
heartAnimation.toValue = 1.2
heartAnimation.duration = 0.55
heartAnimation.autoreverses = true
heartAnimation.repeatCount = .infinity
heartAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
vip1HeartIcon.layer.add(heartAnimation, forKey: "vip1HeartBeat")
addVip1TipSwingAnimation(to: vip1Tips1Img, from: -0.12, to: 0.12, duration: 0.55)
addVip1TipSwingAnimation(to: vip1Tips2Img, from: -0.12, to: 0.12, duration: 0.55)
addVip1TipSwingAnimation(to: vip1Tips3Img, from: -0.12, to: 0.12, duration: 0.55)
}
private func addVip1TipSwingAnimation(to imageView: UIImageView, from: CGFloat, to: CGFloat, duration: CFTimeInterval) {
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.fromValue = from
animation.toValue = to
animation.duration = duration
animation.autoreverses = true
animation.repeatCount = .infinity
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
imageView.layer.add(animation, forKey: "vip1TipSwing")
}
private func stopUnlockVip1Animations() {
vip1HeartIcon.layer.removeAnimation(forKey: "vip1HeartBeat")
vip1Tips1Img.layer.removeAnimation(forKey: "vip1TipSwing")
vip1Tips2Img.layer.removeAnimation(forKey: "vip1TipSwing")
vip1Tips3Img.layer.removeAnimation(forKey: "vip1TipSwing")
}
private let unlockVip1PopupHeight: CGFloat = 339
private func slideInUnlockVip1Popup() {
stopUnlockVip1Animations()
bgView.isUserInteractionEnabled = false
unlockVip1View.isHidden = false
unlockVip1View.layoutIfNeeded()
unlockVip1View.transform = CGAffineTransform(translationX: 0, y: unlockVip1PopupHeight)
UIView.animate(withDuration: 0.45, delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.5,
options: .curveEaseOut) {
self.unlockVip1View.transform = .identity
} completion: { _ in
self.bgView.isUserInteractionEnabled = true
self.startUnlockVip1Animations()
}
}
private func slideOutUnlockVip1Popup() {
stopUnlockVip1Animations()
bgView.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) {
self.unlockVip1View.transform = CGAffineTransform(translationX: 0, y: self.unlockVip1PopupHeight)
} completion: { _ in
Self.dismiss()
}
}
// MARK: - 2
private let unlockVip2PopupHeight: CGFloat = 412
lazy var unlockVip2View: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.isHidden = true
let bgImg = UIImageView(image: UIImage(named: "PopupWindow/vip2_bg"))
bgImg.contentMode = .scaleAspectFill
view.addSubview(bgImg)
bgImg.layoutChain
.top(84)
.edgesHorzontal()
.bottom()
let titleLab = UILabel()
let text = "会员升级人次"
let attr = NSMutableAttributedString(string: text)
let strokeAttrs: [NSAttributedString.Key: Any] = [
.strokeWidth: -5,
.strokeColor: UIColor.black.cgColor,
.foregroundColor: UIColor.white,
.font: UIFont(name: "zihunbiantaoti", size: 20) ?? .systemFont(ofSize: 20)
]
attr.setAttributes(strokeAttrs, range: NSRange(location: 0, length: text.count))
titleLab.attributedText = attr
view.addSubview(titleLab)
titleLab.layoutChain
.top(106)
.left(29)
let countTitleLab = UILabel()
let countText = "已突破"
let countAttr = NSMutableAttributedString(string: countText)
let countStrokeAttrs: [NSAttributedString.Key: Any] = [
.strokeWidth: -5,
.strokeColor: UIColor.black.cgColor,
.foregroundColor: UIColor.white,
.font: UIFont(name: "zihunbiantaoti", size: 22) ?? .systemFont(ofSize: 22)
]
countAttr.setAttributes(countStrokeAttrs, range: NSRange(location: 0, length: countText.count))
countTitleLab.attributedText = countAttr
view.addSubview(countTitleLab)
countTitleLab.layoutChain
.topToBottomOfView(titleLab, offset: 22)
.leftToView(titleLab)
view.addSubview(unlockVip2CountLab)
unlockVip2CountLab.layoutChain
.leftToRightOfView(countTitleLab)
.bottomToView(countTitleLab, offset: 3)
view.addSubview(unlockVip2LogoImg)
unlockVip2LogoImg.layoutChain
.top()
.right()
.width(178)
.height(199)
let textImg = UIImageView(image: UIImage(named: "PopupWindow/vip2_text"))
view.addSubview(textImg)
textImg.layoutChain
.topToBottomOfView(unlockVip2LogoImg, offset: 15)
.centerX()
.width(275)
.height(41)
view.addSubview(unlockVip2CloseBtn)
unlockVip2CloseBtn.layoutChain
.bottom(kSafeBottomMargin + 15)
.left(30)
.height(50)
view.addSubview(unlockVip2Btn)
unlockVip2Btn.layoutChain
.bottomToView(unlockVip2CloseBtn)
.right(30)
.height(50)
.widthToView(unlockVip2CloseBtn)
unlockVip2CloseBtn.layoutChain.rightToLeftOfView(unlockVip2Btn, offset: -10)
return view
}()
lazy var unlockVip2LogoImg: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/vip2_logo")
view.contentMode = .scaleAspectFill
return view
}()
private var unlockVip2CountTarget: Int = 0
private var unlockVip2CountTimer: Timer?
lazy var unlockVip2CountLab: UILabel = {
let label = UILabel()
unlockVip2CountTarget = Int.random(in: 10000...15000)
let text = "0+"
let attr = NSMutableAttributedString(string: text)
let strokeAttrs: [NSAttributedString.Key: Any] = [
.strokeWidth: -5,
.strokeColor: UIColor.black.cgColor,
.foregroundColor: UIColor(hexStr: "#7CE7FC"),
.font: UIFont(name: "zihunbiantaoti", size: 34) ?? .systemFont(ofSize: 34)
]
attr.setAttributes(strokeAttrs, range: NSRange(location: 0, length: text.count))
label.attributedText = attr
return label
}()
lazy var unlockVip2CloseBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("稍后再说", for: .normal)
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
btn.backgroundColor = .white
btn.cornerRadius = 25
btn.borderWidth = 0.5
btn.borderColor = UIColor(hexStr: "#16B3FF")
return btn
}()
lazy var unlockVip2Btn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("立即升级", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
btn.cornerRadius = 25
return btn
}()
private func startUnlockVip2Animations() {
stopUnlockVip2Animations()
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.fromValue = -0.08
animation.toValue = 0.08
animation.duration = 0.7
animation.autoreverses = true
animation.repeatCount = .infinity
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
unlockVip2LogoImg.layer.add(animation, forKey: "unlockVip2LogoSwing")
startUnlockVip2CountAnimation()
}
private func stopUnlockVip2Animations() {
unlockVip2LogoImg.layer.removeAnimation(forKey: "unlockVip2LogoSwing")
unlockVip2CountTimer?.invalidate()
unlockVip2CountTimer = nil
}
private func startUnlockVip2CountAnimation() {
unlockVip2CountTimer?.invalidate()
let target = unlockVip2CountTarget
let duration: TimeInterval = 2.0
let interval: TimeInterval = 0.025
let totalSteps = Int(duration / interval)
var step = 0
unlockVip2CountTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] timer in
step += 1
let progress = min(Double(step) / Double(totalSteps), 1.0)
let eased = 1.0 - pow(1.0 - progress, 3)
let current = Int(Double(target) * eased)
let text = "\(current)+"
let attr = NSMutableAttributedString(string: text)
let strokeAttrs: [NSAttributedString.Key: Any] = [
.strokeWidth: -3,
.strokeColor: UIColor.black.cgColor,
.foregroundColor: UIColor(hexStr: "#7CE7FC"),
.font: UIFont(name: "zihunbiantaoti", size: 34) ?? .systemFont(ofSize: 34)
]
attr.setAttributes(strokeAttrs, range: NSRange(location: 0, length: text.count))
self?.unlockVip2CountLab.attributedText = attr
if progress >= 1.0 {
timer.invalidate()
self?.unlockVip2CountTimer = nil
}
}
}
private func slideInUnlockVip2Popup() {
stopUnlockVip2Animations()
unlockVip2CountTarget = Int.random(in: 10000...15000)
let attr = NSMutableAttributedString(string: "0+")
let strokeAttrs: [NSAttributedString.Key: Any] = [
.strokeWidth: -3,
.strokeColor: UIColor.black.cgColor,
.foregroundColor: UIColor(hexStr: "#7CE7FC"),
.font: UIFont(name: "zihunbiantaoti", size: 34) ?? .systemFont(ofSize: 34)
]
attr.setAttributes(strokeAttrs, range: NSRange(location: 0, length: 2))
unlockVip2CountLab.attributedText = attr
bgView.isUserInteractionEnabled = false
unlockVip2View.isHidden = false
unlockVip2View.layoutIfNeeded()
unlockVip2View.transform = CGAffineTransform(translationX: 0, y: unlockVip2PopupHeight)
UIView.animate(withDuration: 0.45, delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.5,
options: .curveEaseOut) {
self.unlockVip2View.transform = .identity
} completion: { _ in
self.bgView.isUserInteractionEnabled = true
self.startUnlockVip2Animations()
}
}
private func slideOutUnlockVip2Popup() {
stopUnlockVip2Animations()
bgView.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) {
self.unlockVip2View.transform = CGAffineTransform(translationX: 0, y: self.unlockVip2PopupHeight)
} completion: { _ in
Self.dismiss()
}
}
// MARK: - 3
private let unlockVip3PopupHeight: CGFloat = 385
lazy var unlockVip3View: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.isHidden = true
let bgImg = UIImageView(image: UIImage(named: "PopupWindow/vip3_bg"))
bgImg.contentMode = .scaleAspectFill
view.addSubview(bgImg)
bgImg.layoutChain
.top(45)
.edgesHorzontal()
.bottom()
view.addSubview(unlockVip3LottieView)
unlockVip3LottieView.layoutChain
.top(-120)
.right(-110)
.width(420)
.height(370)
view.addSubview(unlockVip3CloseBtn)
unlockVip3CloseBtn.layoutChain
.bottom(kSafeBottomMargin + 15)
.left(30)
.height(50)
view.addSubview(unlockVip3Btn)
unlockVip3Btn.layoutChain
.bottomToView(unlockVip3CloseBtn)
.right(30)
.height(50)
.widthToView(unlockVip3CloseBtn)
unlockVip3CloseBtn.layoutChain.rightToLeftOfView(unlockVip3Btn, offset: -10)
return view
}()
lazy var unlockVip3LottieView: LottieAnimationView = {
let view = LottieAnimationView(name: "dialog_buy_vip_3")
view.loopMode = .loop
return view
}()
lazy var unlockVip3CloseBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("稍后再说", for: .normal)
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
btn.backgroundColor = .white
btn.cornerRadius = 25
btn.borderWidth = 0.5
btn.borderColor = UIColor(hexStr: "#16B3FF")
return btn
}()
lazy var unlockVip3Btn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("立即升级", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
btn.cornerRadius = 25
return btn
}()
private func stopUnlockVip3Animations() {
unlockVip3LottieView.stop()
}
private func slideInUnlockVip3Popup() {
stopUnlockVip3Animations()
bgView.isUserInteractionEnabled = false
unlockVip3View.isHidden = false
unlockVip3View.layoutIfNeeded()
unlockVip3View.transform = CGAffineTransform(translationX: 0, y: unlockVip3PopupHeight)
UIView.animate(withDuration: 0.45, delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.5,
options: .curveEaseOut) {
self.unlockVip3View.transform = .identity
} completion: { _ in
self.bgView.isUserInteractionEnabled = true
self.unlockVip3LottieView.play()
}
}
private func slideOutUnlockVip3Popup() {
stopUnlockVip3Animations()
bgView.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) {
self.unlockVip3View.transform = CGAffineTransform(translationX: 0, y: self.unlockVip3PopupHeight)
} completion: { _ in
Self.dismiss()
}
}
// MARK: - 4
private let unlockVip4PopupHeight: CGFloat = 368
lazy var unlockVip4View: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.isHidden = true
let bgImg = UIImageView(image: UIImage(named: "PopupWindow/vip4_bg"))
bgImg.contentMode = .scaleAspectFill
view.addSubview(bgImg)
bgImg.layoutChain
.top()
.edgesHorzontal()
.bottom()
view.addSubview(unlockVip4AnimateView)
unlockVip4AnimateView.layoutChain
.top(143)
.rightToCenterXOfView(view, offset: 50)
.width(60)
.height(52)
let label = UILabel()
label.text = "终身会员"
label.font = .systemFont(ofSize: 14, weight: .medium)
view.addSubview(label)
label.layoutChain
.leftToRightOfView(unlockVip4AnimateView, offset: 17)
.bottomToView(unlockVip4AnimateView)
view.addSubview(unlockVip4DiscountLab)
unlockVip4DiscountLab.layoutChain
.bottomToView(label, offset: 10)
.leftToRightOfView(label)
let unitLab = UILabel()
unitLab.text = ""
unitLab.font = .systemFont(ofSize: 14, weight: .medium)
view.addSubview(unitLab)
unitLab.layoutChain
.bottomToView(label, offset: 0)
.leftToRightOfView(unlockVip4DiscountLab)
view.addSubview(unlockVip4CloseBtn)
unlockVip4CloseBtn.layoutChain
.top()
.right(18)
.width(22)
.height(22)
view.addSubview(unlockVip4Btn)
unlockVip4Btn.layoutChain
.centerX()
.width(224)
.height(50)
.bottom(49)
return view
}()
lazy var unlockVip4AnimateView: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/vip4_animate")
return view
}()
lazy var unlockVip4DiscountLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 44, weight: .bold)
label.textColor = UIColor(hexStr: "#FF4E41")
return label
}()
lazy var unlockVip4CloseBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "PopupWindow/close"), for: .normal)
btn.extendEdgeInsets = UIEdgeInsets(top: 30, left: 30, bottom: 30, right: 18)
return btn
}()
lazy var unlockVip4Btn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("立即升级", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
btn.cornerRadius = 25
return btn
}()
private func startUnlockVip4Animations() {
stopUnlockVip4Animations()
let animateScale = CABasicAnimation(keyPath: "transform.scale")
animateScale.fromValue = 1.0
animateScale.toValue = 1.15
animateScale.duration = 0.6
animateScale.autoreverses = true
animateScale.repeatCount = .infinity
animateScale.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
unlockVip4AnimateView.layer.add(animateScale, forKey: "unlockVip4AnimateScale")
let btnScale = CABasicAnimation(keyPath: "transform.scale")
btnScale.fromValue = 1.0
btnScale.toValue = 1.08
btnScale.duration = 0.8
btnScale.autoreverses = true
btnScale.repeatCount = .infinity
btnScale.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
unlockVip4Btn.layer.add(btnScale, forKey: "unlockVip4BtnScale")
}
private func stopUnlockVip4Animations() {
unlockVip4AnimateView.layer.removeAnimation(forKey: "unlockVip4AnimateScale")
unlockVip4Btn.layer.removeAnimation(forKey: "unlockVip4BtnScale")
}
private func slideInUnlockVip4Popup() {
if let systemConfig = AppContextManager.shared.systemConfig,
let timeConfig = systemConfig.timeConfig {
unlockVip4DiscountLab.text = (String(format: "%.1f", timeConfig.min_discount/10))
}
stopUnlockVip4Animations()
bgView.isUserInteractionEnabled = false
unlockVip4View.isHidden = false
unlockVip4View.layoutIfNeeded()
unlockVip4View.transform = CGAffineTransform(translationX: 0, y: unlockVip4PopupHeight)
UIView.animate(withDuration: 0.45, delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.5,
options: .curveEaseOut) {
self.unlockVip4View.transform = .identity
} completion: { _ in
self.bgView.isUserInteractionEnabled = true
self.startUnlockVip4Animations()
}
}
private func slideOutUnlockVip4Popup() {
stopUnlockVip4Animations()
bgView.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) {
self.unlockVip4View.transform = CGAffineTransform(translationX: 0, y: self.unlockVip4PopupHeight)
} completion: { _ in
Self.dismiss()
}
}
// MARK: - 1
lazy var registerPopupView: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.isHidden = true
let bgImg = UIImageView(image: UIImage(named: "PopupWindow/register_bg"))
bgImg.contentMode = .scaleAspectFill
view.addSubview(bgImg)
bgImg.layoutChain
.top(51)
.edgesHorzontal()
.bottom()
let logoImg = UIImageView(image: UIImage(named: "PopupWindow/register_logo"))
logoImg.contentMode = .scaleAspectFill
view.addSubview(logoImg)
logoImg.layoutChain
.top()
.centerX()
.width(250)
.height(123)
let textImg = UIImageView(image: UIImage(named: "PopupWindow/register_text"))
textImg.contentMode = .scaleAspectFill
view.addSubview(textImg)
textImg.layoutChain
.topToBottomOfView(logoImg, offset: 20)
.width(272)
.height(50)
.centerX()
view.addSubview(register1UpLottieView)
register1UpLottieView.layoutChain
.bottomToTopOfView(logoImg, offset: 120)
.edgesHorzontal()
.height(200)
view.addSubview(register1DownLottieView)
register1DownLottieView.layoutChain
.topToBottomOfView(logoImg, offset: -140)
.edgesHorzontal()
.height(200)
// view.addSubview(registerStar1)
// registerStar1.layoutChain
// .top(19)
// .leftToView(logoImg, offset: -31)
// .width(68)
// .heightToWidth(1.0)
view.addSubview(registerStar3)
registerStar3.layoutChain
.top(23)
.rightToView(logoImg, offset: -9)
.width(26)
.height(20)
view.addSubview(registerStar2)
registerStar2.layoutChain
.top(31)
.rightToView(logoImg, offset: 20)
.width(48)
.height(44)
view.addSubview(registerCloseBtn)
registerCloseBtn.layoutChain
.topToBottomOfView(textImg, offset: 30)
.left(30)
.height(50)
view.addSubview(registerBtn)
registerBtn.layoutChain
.topToBottomOfView(textImg, offset: 30)
.right(30)
.height(50)
.widthToView(registerCloseBtn)
registerCloseBtn.layoutChain.rightToLeftOfView(registerBtn, offset: -10)
return view
}()
lazy var register1UpLottieView: LottieAnimationView = {
let view = LottieAnimationView(name: "dialog_register_tip1_1")
return view
}()
lazy var register1DownLottieView: LottieAnimationView = {
let view = LottieAnimationView(name: "dialog_register_tip1_2")
return view
}()
lazy var registerStar1: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/star_1")
return view
}()
lazy var registerStar2: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/star_2")
return view
}()
lazy var registerStar3: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/star_3")
return view
}()
lazy var registerCloseBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("稍后再说", for: .normal)
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
btn.backgroundColor = .white
btn.cornerRadius = 25
btn.borderWidth = 0.5
btn.borderColor = UIColor(hexStr: "#16B3FF")
return btn
}()
lazy var registerBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("免费注册", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
btn.cornerRadius = 25
return btn
}()
private var register1LottieAlternating = false
private func startRegister1LottieAlternate() {
stopRegister1LottieAlternate()
register1LottieAlternating = true
playRegister1Up()
}
private func playRegister1Up() {
guard register1LottieAlternating else { return }
register1UpLottieView.isHidden = false
register1DownLottieView.isHidden = true
register1UpLottieView.play { [weak self] _ in
self?.playRegister1Down()
}
}
private func playRegister1Down() {
guard register1LottieAlternating else { return }
register1UpLottieView.isHidden = true
register1DownLottieView.isHidden = false
register1DownLottieView.play { [weak self] _ in
self?.playRegister1Up()
}
}
private func stopRegister1LottieAlternate() {
register1LottieAlternating = false
register1UpLottieView.stop()
register1DownLottieView.stop()
register1UpLottieView.isHidden = true
register1DownLottieView.isHidden = true
}
private func startRegisterStarFlash() {
stopRegisterStarFlash()
let (star2, star3) = activePopupView == 2 ? (register2Star2, register2Star3) : (registerStar2, registerStar3)
let animation2 = CABasicAnimation(keyPath: "opacity")
animation2.fromValue = 1.0
animation2.toValue = 0.0
animation2.duration = 0.4
animation2.autoreverses = true
animation2.repeatCount = .infinity
star2.layer.add(animation2, forKey: "starFlash")
let animation3 = CABasicAnimation(keyPath: "opacity")
animation3.fromValue = 0.0
animation3.toValue = 1.0
animation3.duration = 0.4
animation3.autoreverses = true
animation3.repeatCount = .infinity
star3.layer.add(animation3, forKey: "starFlash")
}
private func stopRegisterStarFlash() {
registerStar2.layer.removeAnimation(forKey: "starFlash")
registerStar3.layer.removeAnimation(forKey: "starFlash")
register2Star2.layer.removeAnimation(forKey: "starFlash")
register2Star3.layer.removeAnimation(forKey: "starFlash")
}
private func animateRegisterStarSlideIn() {
UIView.animate(withDuration: 0.5, delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.6,
options: .curveEaseOut) {
self.registerStar1.transform = .identity
}
}
private let registerPopupHeight: CGFloat = 308
private var activePopupView = 1
private func slideInRegisterPopup() {
activePopupView = 1
stopRegisterStarFlash()
bgView.isUserInteractionEnabled = false
registerPopupView.isHidden = false
registerPopupView.layoutIfNeeded()
// registerStar1.transform = CGAffineTransform(translationX: -(registerStar1.frame.maxX + 30), y: 0)
registerPopupView.transform = CGAffineTransform(translationX: 0, y: registerPopupHeight)
UIView.animate(withDuration: 0.45, delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.5,
options: .curveEaseOut) {
self.registerPopupView.transform = .identity
} completion: { _ in
self.bgView.isUserInteractionEnabled = true
self.startRegisterStarFlash()
self.startRegister1LottieAlternate()
// self.animateRegisterStarSlideIn()
}
}
private func slideOutRegisterPopup() {
stopRegisterStarFlash()
stopRegister1LottieAlternate()
bgView.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) {
self.registerPopupView.transform = CGAffineTransform(translationX: 0, y: self.registerPopupHeight)
} completion: { _ in
Self.dismiss()
}
}
private func slideInRegister2Popup() {
activePopupView = 2
stopRegisterStarFlash()
bgView.isUserInteractionEnabled = false
register2PopupView.isHidden = false
register2PopupView.layoutIfNeeded()
// register2Star1.transform = CGAffineTransform(translationX: -(register2Star1.frame.maxX + 30), y: 0)
register2PopupView.transform = CGAffineTransform(translationX: 0, y: register2PopupView.bounds.height)
UIView.animate(withDuration: 0.45, delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.5,
options: .curveEaseOut) {
self.register2PopupView.transform = .identity
} completion: { _ in
self.bgView.isUserInteractionEnabled = true
self.startRegisterStarFlash()
self.register2LottieView.play()
// self.animateRegister2StarSlideIn()
}
}
private func slideOutRegister2Popup() {
stopRegisterStarFlash()
bgView.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) {
self.register2PopupView.transform = CGAffineTransform(translationX: 0, y: self.register2PopupView.bounds.height)
} completion: { _ in
Self.dismiss()
}
}
private func animateRegister2StarSlideIn() {
UIView.animate(withDuration: 0.55, delay: 0.1,
usingSpringWithDamping: 0.65,
initialSpringVelocity: 0.4,
options: .curveEaseOut) {
self.register2Star1.transform = .identity
}
}
// MARK: - 2
lazy var register2PopupView: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.isHidden = true
let bgImg = UIImageView(image: UIImage(named: "PopupWindow/register_bg"))
bgImg.contentMode = .scaleAspectFill
view.addSubview(bgImg)
bgImg.layoutChain
.top(58)
.edgesHorzontal()
.bottom()
let logoImg = UIImageView(image: UIImage(named: "PopupWindow/register2_logo"))
logoImg.contentMode = .scaleAspectFill
view.addSubview(register2LottieView)
view.addSubview(logoImg)
logoImg.layoutChain
.top()
.centerX()
.width(243)
.height(97)
let textLab = UILabel()
textLab.text = "注册后即可保存历史轨迹、多人 共享地图、跨设备同步。"
textLab.font = .systemFont(ofSize: 16, weight: .medium)
textLab.textColor = UIColor(hexStr: "#16B3FF")
textLab.numberOfLines = 0
view.addSubview(textLab)
textLab.layoutChain
.topToBottomOfView(logoImg, offset: 33)
.edgesHorzontal(75)
register2LottieView.layoutChain
.top()
.rightToLeftOfView(logoImg, offset: 45)
.width(100)
.heightToWidth(1.0)
view.addSubview(register2Star3)
register2Star3.layoutChain
.top(25)
.rightToView(logoImg, offset: -6)
.width(26)
.height(20)
view.addSubview(register2Star2)
register2Star2.layoutChain
.top(32)
.rightToView(logoImg, offset: 25)
.width(48)
.height(44)
view.addSubview(register2Btn)
register2Btn.layoutChain
.topToBottomOfView(textLab, offset: 30)
.width(224)
.height(50)
.centerX()
view.addSubview(register2CloseBtn)
register2CloseBtn.layoutChain
.topToView(bgImg, offset: 32)
.right(15)
return view
}()
lazy var register2LottieView: LottieAnimationView = {
let view = LottieAnimationView(name: "dialog_register_tip2")
view.loopMode = .loop
return view
}()
lazy var register2Star1: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/register_heart")
return view
}()
lazy var register2Star2: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/star_2")
return view
}()
lazy var register2Star3: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "PopupWindow/star_3")
return view
}()
lazy var register2CloseBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "PopupWindow/close"), for: .normal)
btn.extendEdgeInsets = UIEdgeInsets(top: 30, left: 30, bottom: 30, right: 15)
return btn
}()
lazy var register2Btn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("免费注册", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
btn.cornerRadius = 25
return btn
}()
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
addSubview(bgView)
bgView.addSubview(searchPopupView)
bgView.addSubview(unlockVip1View)
bgView.addSubview(unlockVip2View)
bgView.addSubview(unlockVip3View)
bgView.addSubview(unlockVip4View)
bgView.addSubview(registerPopupView)
bgView.addSubview(register2PopupView)
searchPopupView.layoutChain
.centerY()
.centerX()
registerPopupView.layoutChain
.edgesHorzontal()
.bottom()
.height(308)
register2PopupView.layoutChain
.edgesHorzontal()
.bottom()
.height(295)
unlockVip1View.layoutChain
.edgesHorzontal()
.bottom()
.height(unlockVip1PopupHeight)
unlockVip2View.layoutChain
.edgesHorzontal()
.bottom()
.height(unlockVip2PopupHeight)
unlockVip3View.layoutChain
.edgesHorzontal()
.bottom()
.height(unlockVip3PopupHeight)
unlockVip4View.layoutChain
.edgesHorzontal()
.bottom()
.height(unlockVip4PopupHeight)
setupRx()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
}