710 lines
28 KiB
Swift
710 lines
28 KiB
Swift
|
//
|
|||
|
// YFLDragCardContainer.swift
|
|||
|
// Crush
|
|||
|
//
|
|||
|
// Created by AI Assistant on 2024/12/19.
|
|||
|
// Copyright © 2024年 Crush. All rights reserved.
|
|||
|
//
|
|||
|
|
|||
|
import UIKit
|
|||
|
import SnapKit
|
|||
|
|
|||
|
// MARK: - 数据源协议
|
|||
|
protocol YFLDragCardContainerDataSource: AnyObject {
|
|||
|
/// 数据源个数
|
|||
|
func numberOfRowsInYFLDragCardContainer(_ container: YFLDragCardContainer) -> Int
|
|||
|
|
|||
|
/// 显示数据源
|
|||
|
func container(_ container: YFLDragCardContainer, viewForRowsAt index: Int) -> YFLDragCardView
|
|||
|
}
|
|||
|
|
|||
|
// MARK: - 代理协议
|
|||
|
protocol YFLDragCardContainerDelegate: AnyObject {
|
|||
|
/// 点击卡片回调
|
|||
|
func container(_ container: YFLDragCardContainer, didSelectRowAt index: Int)
|
|||
|
|
|||
|
/// 拖到最后一张卡片 YES,空,可继续调用reloadData分页数据
|
|||
|
func container(_ container: YFLDragCardContainer, dataSourceIsEmpty isEmpty: Bool)
|
|||
|
|
|||
|
/// 当前cardview 是否可以拖拽,默认YES
|
|||
|
func container(_ container: YFLDragCardContainer, canDragForCardView cardView: YFLDragCardView) -> Bool
|
|||
|
|
|||
|
/// 卡片处于拖拽中回调
|
|||
|
func container(_ container: YFLDragCardContainer, dargingForCardView cardView: YFLDragCardView, direction: ContainerDragDirection, widthRate: Float, heightRate: Float)
|
|||
|
|
|||
|
/// 询问这次滑动的方向是否生效,控制 v2.6 ld add
|
|||
|
func container(_ container: YFLDragCardContainer, canDragFinishForDirection direction: ContainerDragDirection, forCardView cardView: YFLDragCardView) -> Bool
|
|||
|
|
|||
|
/// 卡片拖拽结束回调(卡片消失)
|
|||
|
func container(_ container: YFLDragCardContainer, dragDidFinshForDirection direction: ContainerDragDirection, forCardView cardView: YFLDragCardView)
|
|||
|
|
|||
|
/// 卡片回看 v2.6 添加
|
|||
|
func container(_ container: YFLDragCardContainer, lookingBack direction: ContainerDragDirection, forCardView cardView: YFLDragCardView)
|
|||
|
|
|||
|
func container(_ container: YFLDragCardContainer, enterSmallCardMode smallCardMode: Bool, forCardView cardView: YFLDragCardView)
|
|||
|
}
|
|||
|
|
|||
|
// MARK: - 主容器类
|
|||
|
class YFLDragCardContainer: UIView {
|
|||
|
|
|||
|
// MARK: - 公开属性
|
|||
|
private(set) var tmpStoreNopeCardView: YFLDragCardView?
|
|||
|
private(set) var smallCardMode: Bool = false
|
|||
|
private(set) var isMoveIng: Bool = false
|
|||
|
|
|||
|
weak var dataSource: YFLDragCardContainerDataSource?
|
|||
|
weak var delegate: YFLDragCardContainerDelegate?
|
|||
|
|
|||
|
// MARK: - 私有属性
|
|||
|
private var cards: [YFLDragCardView] = []
|
|||
|
private var direction: ContainerDragDirection = .default
|
|||
|
private var loadedIndex: Int = 0
|
|||
|
private var firstCardFrame: CGRect = .zero
|
|||
|
private var lastCardFrame: CGRect = .zero
|
|||
|
private var cardCenter: CGPoint = .zero
|
|||
|
private var lastCardTransform: CGAffineTransform = .identity
|
|||
|
private var configure: YFLDragConfigure!
|
|||
|
|
|||
|
// 🔥 根据上下文推测:动画相关视图
|
|||
|
private var likeOrDislikeShowLogoView: UIView!
|
|||
|
private var likeOrDislikeShowImageView: UIImageView!
|
|||
|
private var likeLottieView: UIView! // 🔥 推测为Lottie动画视图
|
|||
|
private var dislikeLottieView: UIView! // <EFBFBD><EFBFBD> 推测为Lottie动画视图
|
|||
|
private var superlikeLottieView: UIView! // 🔥 推测为Lottie动画视图
|
|||
|
private var superlikeWordsShowBg: UIImageView!
|
|||
|
private var superlikeWordsLabel: UILabel!
|
|||
|
private var superLikeCountLeft: UILabel!
|
|||
|
|
|||
|
// v2.8
|
|||
|
private var currentIntialGestureDirection: ContainerDragDirection = .default
|
|||
|
private var canMoveView: Bool = true
|
|||
|
|
|||
|
// MARK: - 初始化方法
|
|||
|
convenience init(frame: CGRect) {
|
|||
|
self.init(frame: frame, configure: YFLDragCardContainer.setDefaultsConfigures())
|
|||
|
}
|
|||
|
|
|||
|
init(frame: CGRect, configure: YFLDragConfigure) {
|
|||
|
super.init(frame: frame)
|
|||
|
initDataConfigure(configure)
|
|||
|
}
|
|||
|
|
|||
|
required init?(coder: NSCoder) {
|
|||
|
super.init(coder: coder)
|
|||
|
initDataConfigure(YFLDragCardContainer.setDefaultsConfigures())
|
|||
|
}
|
|||
|
|
|||
|
// MARK: - 私有方法
|
|||
|
private static func setDefaultsConfigures() -> YFLDragConfigure {
|
|||
|
let configure = YFLDragConfigure()
|
|||
|
configure.visableCount = 3
|
|||
|
configure.containerEdge = 16.0
|
|||
|
configure.cardEdge = 0.01
|
|||
|
configure.cardCornerRadius = 8.0
|
|||
|
configure.cardCornerBorderWidth = 0.0
|
|||
|
configure.cardBordColor = UIColor.clear
|
|||
|
configure.cardVTopEdage = 0
|
|||
|
configure.cardVBottomEdage = 12
|
|||
|
return configure
|
|||
|
}
|
|||
|
|
|||
|
private func initDataConfigure(_ configure: YFLDragConfigure) {
|
|||
|
resetInitData()
|
|||
|
initialLikeDislikesShowViews()
|
|||
|
cards = []
|
|||
|
backgroundColor = UIColor.white
|
|||
|
self.configure = configure
|
|||
|
}
|
|||
|
|
|||
|
private func initialLikeDislikesShowViews() {
|
|||
|
likeOrDislikeShowLogoView = UIView()
|
|||
|
likeOrDislikeShowLogoView.backgroundColor = UIColor.clear
|
|||
|
likeOrDislikeShowLogoView.isUserInteractionEnabled = false
|
|||
|
likeOrDislikeShowLogoView.alpha = 0
|
|||
|
likeOrDislikeShowLogoView.layer.zPosition = 10
|
|||
|
insertSubview(likeOrDislikeShowLogoView, at: 0)
|
|||
|
|
|||
|
likeOrDislikeShowLogoView.snp.makeConstraints { make in
|
|||
|
make.center.equalToSuperview()
|
|||
|
}
|
|||
|
|
|||
|
// 🔥 根据上下文推测:创建Lottie动画视图
|
|||
|
likeLottieView = createLottieView(animationName: "LottieResources/kuolie_swipe_like")
|
|||
|
dislikeLottieView = createLottieView(animationName: "LottieResources/kuolie_swipe_dislike")
|
|||
|
superlikeLottieView = createLottieView(animationName: "LottieResources/kuolie_superlike")
|
|||
|
|
|||
|
// 🔥 根据上下文推测:创建超级喜欢文字背景
|
|||
|
superlikeWordsShowBg = UIImageView()
|
|||
|
superlikeWordsShowBg.image = UIImage(named: "kuolie_superlike_words_bg")
|
|||
|
superlikeLottieView.addSubview(superlikeWordsShowBg)
|
|||
|
superlikeWordsShowBg.snp.makeConstraints { make in
|
|||
|
make.centerX.equalToSuperview()
|
|||
|
make.bottom.equalToSuperview().offset(8)
|
|||
|
make.size.equalTo(CGSize(width: 186, height: 32))
|
|||
|
}
|
|||
|
|
|||
|
// 🔥 根据上下文推测:创建超级喜欢文字标签
|
|||
|
superlikeWordsLabel = UILabel()
|
|||
|
superlikeWordsLabel.textColor = UIColor.white
|
|||
|
superlikeWordsLabel.font = UIFont.systemFont(ofSize: 16) // 🔥 推测字体
|
|||
|
superlikeWordsLabel.text = "SUPER LIKE" // 🔥 推测文字
|
|||
|
superlikeLottieView.addSubview(superlikeWordsLabel)
|
|||
|
superlikeWordsLabel.snp.makeConstraints { make in
|
|||
|
make.center.equalTo(superlikeWordsShowBg)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private func createLottieView(animationName: String) -> UIView {
|
|||
|
// 🔥 根据上下文推测:创建Lottie动画视图
|
|||
|
let view = UIView()
|
|||
|
view.contentMode = .scaleAspectFill
|
|||
|
likeOrDislikeShowLogoView.addSubview(view)
|
|||
|
view.isHidden = true
|
|||
|
view.snp.makeConstraints { make in
|
|||
|
make.center.equalTo(likeOrDislikeShowLogoView)
|
|||
|
make.size.equalTo(CGSize(width: 120, height: 120))
|
|||
|
}
|
|||
|
return view
|
|||
|
}
|
|||
|
|
|||
|
/// 重置数据(为了二次调用reload)
|
|||
|
private func resetInitData() {
|
|||
|
loadedIndex = 0
|
|||
|
resetCards()
|
|||
|
direction = .default
|
|||
|
isMoveIng = false
|
|||
|
}
|
|||
|
|
|||
|
/// 清掉所有card view
|
|||
|
private func resetCards() {
|
|||
|
for view in cards {
|
|||
|
view.removeFromSuperview()
|
|||
|
}
|
|||
|
cards.removeAll()
|
|||
|
}
|
|||
|
|
|||
|
/// 添加子视图
|
|||
|
private func addSubViews() {
|
|||
|
guard let dataSource = dataSource else { return }
|
|||
|
|
|||
|
let sum = dataSource.numberOfRowsInYFLDragCardContainer(self)
|
|||
|
let preLoadViewCount = min(sum, configure.visableCount)
|
|||
|
|
|||
|
// 预防越界
|
|||
|
if loadedIndex < sum {
|
|||
|
// 当手势滑动,加载第四个,最多创建4个。不存在内存warning。(手势停止,滑动的view没消失,需要干掉多创建的+1)
|
|||
|
let targetCount = isMoveIng ? preLoadViewCount + 1 : preLoadViewCount
|
|||
|
for i in cards.count..<targetCount {
|
|||
|
let cardView = dataSource.container(self, viewForRowsAt: loadedIndex)
|
|||
|
addAdaptForDragCardView(cardView)
|
|||
|
cards.append(cardView)
|
|||
|
loadedIndex += 1
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// 布局子视图
|
|||
|
private func resetLayoutSubviews() {
|
|||
|
// 动画时允许用户交流,比如触摸 | 时间曲线函数,缓入缓出,中间快
|
|||
|
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.6, options: [.allowUserInteraction, .curveEaseInOut]) {
|
|||
|
|
|||
|
if let delegate = self.delegate, let firstCard = self.cards.first {
|
|||
|
delegate.container(self, dargingForCardView: firstCard, direction: self.direction, widthRate: 0, heightRate: 0)
|
|||
|
}
|
|||
|
|
|||
|
for (i, cardView) in self.cards.enumerated() {
|
|||
|
cardView.transform = .identity
|
|||
|
var frame = self.firstCardFrame
|
|||
|
|
|||
|
switch i {
|
|||
|
case 0:
|
|||
|
cardView.frame = frame
|
|||
|
case 1:
|
|||
|
cardView.transform = CGAffineTransform(scaleX: secondCardScale, y: 1)
|
|||
|
case 2:
|
|||
|
cardView.transform = CGAffineTransform(scaleX: thirdCardScale, y: 1)
|
|||
|
if self.lastCardFrame.isEmpty {
|
|||
|
self.lastCardFrame = frame
|
|||
|
self.lastCardTransform = cardView.transform
|
|||
|
}
|
|||
|
default:
|
|||
|
break
|
|||
|
}
|
|||
|
|
|||
|
cardView.originTransForm = cardView.transform
|
|||
|
}
|
|||
|
} completion: { finished in
|
|||
|
let isEmpty = self.cards.isEmpty
|
|||
|
self.delegate?.container(self, dataSourceIsEmpty: isEmpty)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private func recordFrame(_ cardView: YFLDragCardView) {
|
|||
|
if loadedIndex >= 3 {
|
|||
|
cardView.frame = lastCardFrame
|
|||
|
} else {
|
|||
|
let frame = cardView.frame
|
|||
|
if firstCardFrame.isEmpty {
|
|||
|
firstCardFrame = frame
|
|||
|
cardCenter = cardView.center
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// 移动卡片
|
|||
|
private func moveIngStatusChange(_ scale: Float) {
|
|||
|
// 如果正在移动,添加第四个
|
|||
|
if !isMoveIng {
|
|||
|
isMoveIng = true
|
|||
|
addSubViews()
|
|||
|
} else {
|
|||
|
// 第四个加载完,立马改变没作用在手势上其他cardview的scale
|
|||
|
let absScale = min(abs(scale), boundaryRation)
|
|||
|
let transFormtxPoor = (secondCardScale - thirdCardScale) / (boundaryRation / absScale)
|
|||
|
let frameYPoor: CGFloat = 0
|
|||
|
|
|||
|
for (index, cardView) in cards.enumerated() {
|
|||
|
guard index > 0 else { continue }
|
|||
|
|
|||
|
switch index {
|
|||
|
case 1:
|
|||
|
let scaleTransform = CGAffineTransform(scaleX: transFormtxPoor + secondCardScale, y: 1)
|
|||
|
let translateTransform = CGAffineTransform(translationX: 0, y: -frameYPoor)
|
|||
|
cardView.transform = scaleTransform.concatenating(translateTransform)
|
|||
|
case 2:
|
|||
|
let scaleTransform = CGAffineTransform(scaleX: transFormtxPoor + thirdCardScale, y: 1)
|
|||
|
let translateTransform = CGAffineTransform(translationX: 0, y: -frameYPoor)
|
|||
|
cardView.transform = scaleTransform.concatenating(translateTransform)
|
|||
|
case 3:
|
|||
|
cardView.transform = lastCardTransform
|
|||
|
default:
|
|||
|
break
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 手势结束
|
|||
|
private func panGesturemMoveFinishOrCancle(_ cardView: YFLDragCardView, direction: ContainerDragDirection, scale: Float, isDisappear: Bool) {
|
|||
|
if !isDisappear { // 没有消失,恢复原样
|
|||
|
if currentIntialGestureDirection == .left || currentIntialGestureDirection == .right || currentIntialGestureDirection == .default {
|
|||
|
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.6, options: [.allowUserInteraction, .curveEaseInOut]) {
|
|||
|
self.subRestoreCardState()
|
|||
|
} completion: { finished in
|
|||
|
}
|
|||
|
} else { // 其他情况下没有动画
|
|||
|
subRestoreCardState()
|
|||
|
}
|
|||
|
} else {
|
|||
|
// v2.6
|
|||
|
if let delegate = delegate {
|
|||
|
if !delegate.container(self, canDragFinishForDirection: direction, forCardView: cardView) {
|
|||
|
subRestoreCardState()
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
delegate?.container(self, dragDidFinshForDirection: self.direction, forCardView: cardView)
|
|||
|
|
|||
|
if direction == .left {
|
|||
|
tmpStoreNopeCardView = cardView
|
|||
|
}
|
|||
|
|
|||
|
let flag = direction == .left ? -1 : 2
|
|||
|
let screenWidth = UIScreen.main.bounds.width
|
|||
|
|
|||
|
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.curveLinear, .allowUserInteraction]) {
|
|||
|
self.isMoveIng = true
|
|||
|
cardView.center = CGPoint(x: CGFloat(flag) * screenWidth, y: CGFloat(flag) * screenWidth / CGFloat(scale) + self.cardCenter.y)
|
|||
|
} completion: { finished in
|
|||
|
self.isMoveIng = false
|
|||
|
cardView.removeFromSuperview()
|
|||
|
}
|
|||
|
|
|||
|
if let index = cards.firstIndex(of: cardView) {
|
|||
|
cards.remove(at: index)
|
|||
|
}
|
|||
|
isMoveIng = false
|
|||
|
resetLayoutSubviews()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private func subRestoreCardState() {
|
|||
|
// 干掉多创建的第四个.重置标量
|
|||
|
if isMoveIng && cards.count > configure.visableCount {
|
|||
|
if let lastView = cards.last {
|
|||
|
lastView.removeFromSuperview()
|
|||
|
cards.removeLast()
|
|||
|
loadedIndex = lastView.tag
|
|||
|
}
|
|||
|
}
|
|||
|
isMoveIng = false
|
|||
|
resetLayoutSubviews()
|
|||
|
}
|
|||
|
|
|||
|
// MARK: - 公开方法
|
|||
|
func reloadData() {
|
|||
|
guard let dataSource = dataSource else {
|
|||
|
assertionFailure("check dataSource and dataSource Methods!")
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
resetInitData()
|
|||
|
addSubViews()
|
|||
|
resetLayoutSubviews()
|
|||
|
}
|
|||
|
|
|||
|
func getCurrentShowCardView() -> YFLDragCardView? {
|
|||
|
return cards.first
|
|||
|
}
|
|||
|
|
|||
|
func getCurrentShowCardViewIndex() -> Int {
|
|||
|
return cards.first?.tag ?? 0
|
|||
|
}
|
|||
|
|
|||
|
/// 手动控制滑动,且不回调代理方法
|
|||
|
func removeCardViewForDirection(_ direction: ContainerDragDirection) {
|
|||
|
guard !isMoveIng else { return }
|
|||
|
|
|||
|
let screenWidth = UIScreen.main.bounds.width
|
|||
|
var cardCenter = CGPoint.zero
|
|||
|
var flag = 0
|
|||
|
|
|||
|
guard let currentShowCardView = cards.first else { return }
|
|||
|
|
|||
|
switch direction {
|
|||
|
case .left:
|
|||
|
cardCenter = CGPoint(x: -screenWidth / 2.0, y: self.cardCenter.y)
|
|||
|
flag = -1
|
|||
|
tmpStoreNopeCardView = currentShowCardView
|
|||
|
case .right:
|
|||
|
cardCenter = CGPoint(x: screenWidth * 1.5, y: self.cardCenter.y)
|
|||
|
flag = 1
|
|||
|
default:
|
|||
|
break
|
|||
|
}
|
|||
|
|
|||
|
UIView.animate(withDuration: 0.35) {
|
|||
|
self.isMoveIng = true
|
|||
|
let translate = CGAffineTransform(translationX: CGFloat(flag) * 20, y: 0)
|
|||
|
currentShowCardView.transform = translate.rotated(by: CGFloat(flag) * .pi / 4 / 4)
|
|||
|
currentShowCardView.center = cardCenter
|
|||
|
} completion: { finished in
|
|||
|
self.isMoveIng = false
|
|||
|
currentShowCardView.removeFromSuperview()
|
|||
|
if let index = self.cards.firstIndex(of: currentShowCardView) {
|
|||
|
self.cards.remove(at: index)
|
|||
|
}
|
|||
|
self.addSubViews()
|
|||
|
self.resetLayoutSubviews()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// app控制滑动,且回调滑动Finish代理
|
|||
|
func removeCardViewWithCallDelegateForDirection(_ direction: ContainerDragDirection) {
|
|||
|
guard !isMoveIng else { return }
|
|||
|
|
|||
|
let screenWidth = UIScreen.main.bounds.width
|
|||
|
var cardCenter = CGPoint.zero
|
|||
|
var flag = 0
|
|||
|
|
|||
|
guard let currentShowCardView = cards.first else { return }
|
|||
|
|
|||
|
switch direction {
|
|||
|
case .left:
|
|||
|
cardCenter = CGPoint(x: -screenWidth / 2.0, y: self.cardCenter.y)
|
|||
|
flag = -1
|
|||
|
tmpStoreNopeCardView = currentShowCardView
|
|||
|
case .right:
|
|||
|
cardCenter = CGPoint(x: screenWidth * 1.5, y: self.cardCenter.y)
|
|||
|
flag = 1
|
|||
|
default:
|
|||
|
break
|
|||
|
}
|
|||
|
|
|||
|
UIView.animate(withDuration: 0.35) {
|
|||
|
let translate = CGAffineTransform(translationX: CGFloat(flag) * 20, y: 0)
|
|||
|
currentShowCardView.transform = translate.rotated(by: CGFloat(flag) * .pi / 4 / 4)
|
|||
|
currentShowCardView.center = cardCenter
|
|||
|
} completion: { finished in
|
|||
|
currentShowCardView.removeFromSuperview()
|
|||
|
if let index = self.cards.firstIndex(of: currentShowCardView) {
|
|||
|
self.cards.remove(at: index)
|
|||
|
}
|
|||
|
self.addSubViews()
|
|||
|
self.resetLayoutSubviews()
|
|||
|
}
|
|||
|
|
|||
|
delegate?.container(self, dragDidFinshForDirection: direction, forCardView: currentShowCardView)
|
|||
|
}
|
|||
|
|
|||
|
/// 手动回看 Meet 卡片,将之前存的临时CardView 回看回来
|
|||
|
func addCardView(_ cardView: YFLDragCardView?, fromDirection direction: ContainerDragDirection) {
|
|||
|
guard !isMoveIng else { return }
|
|||
|
|
|||
|
let targetCardView = cardView ?? tmpStoreNopeCardView
|
|||
|
guard let cardView = targetCardView else { return }
|
|||
|
|
|||
|
let screenWidth = UIScreen.main.bounds.width
|
|||
|
var cardCenter = CGPoint.zero
|
|||
|
var flag = 0
|
|||
|
|
|||
|
switch direction {
|
|||
|
case .left:
|
|||
|
cardCenter = CGPoint(x: -screenWidth / 2.0, y: self.cardCenter.y)
|
|||
|
flag = -1
|
|||
|
tmpStoreNopeCardView = cardView
|
|||
|
case .right:
|
|||
|
cardCenter = CGPoint(x: screenWidth * 1.5, y: self.cardCenter.y)
|
|||
|
flag = 1
|
|||
|
default:
|
|||
|
break
|
|||
|
}
|
|||
|
|
|||
|
cards.insert(cardView, at: 0)
|
|||
|
addSubview(cardView)
|
|||
|
|
|||
|
cardView.center = cardCenter
|
|||
|
let translate = CGAffineTransform(translationX: CGFloat(flag) * 20, y: 0)
|
|||
|
cardView.transform = translate.rotated(by: CGFloat(flag) * .pi / 4 / 4)
|
|||
|
|
|||
|
cardCenter = CGPoint(x: self.cardCenter.x, y: self.cardCenter.y)
|
|||
|
|
|||
|
UIView.animate(withDuration: 0.35) {
|
|||
|
cardView.transform = .identity
|
|||
|
cardView.center = cardCenter
|
|||
|
self.isMoveIng = true
|
|||
|
} completion: { finished in
|
|||
|
self.isMoveIng = false
|
|||
|
self.tmpStoreNopeCardView = nil
|
|||
|
}
|
|||
|
|
|||
|
delegate?.container(self, lookingBack: direction, forCardView: cardView)
|
|||
|
}
|
|||
|
|
|||
|
func showNopeLogo(_ show: Bool, widthRate: CGFloat) {
|
|||
|
if widthRate == 0 {
|
|||
|
if !isLottieAnimationPlaying(dislikeLottieView) { // 🔥 推测方法
|
|||
|
UIView.animate(withDuration: 0.25) {
|
|||
|
self.likeOrDislikeShowLogoView.alpha = 0
|
|||
|
}
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
if show {
|
|||
|
likeLottieView.isHidden = true
|
|||
|
superlikeLottieView.isHidden = true
|
|||
|
dislikeLottieView.isHidden = false
|
|||
|
likeOrDislikeShowLogoView.isHidden = false
|
|||
|
likeOrDislikeShowLogoView.alpha = widthRate > 0.2 ? 1 : (widthRate / 0.2)
|
|||
|
superLikeCountLeft.isHidden = true
|
|||
|
} else {
|
|||
|
UIView.animate(withDuration: 0.45) {
|
|||
|
self.likeOrDislikeShowLogoView.alpha = 0
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func showLikeLogo(_ show: Bool, widthRate: CGFloat) {
|
|||
|
if widthRate == 0 {
|
|||
|
if !isLottieAnimationPlaying(likeLottieView) { // 🔥 推测方法
|
|||
|
UIView.animate(withDuration: 0.25) {
|
|||
|
self.likeOrDislikeShowLogoView.alpha = 0
|
|||
|
}
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
if show {
|
|||
|
likeLottieView.isHidden = false
|
|||
|
dislikeLottieView.isHidden = true
|
|||
|
superlikeLottieView.isHidden = true
|
|||
|
likeOrDislikeShowLogoView.alpha = widthRate > 0.2 ? 1 : (widthRate / 0.2)
|
|||
|
superLikeCountLeft.isHidden = true
|
|||
|
} else {
|
|||
|
UIView.animate(withDuration: 0.45) {
|
|||
|
self.likeOrDislikeShowLogoView.alpha = 0
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func showNopeLottie() {
|
|||
|
likeLottieView.isHidden = true
|
|||
|
superlikeLottieView.isHidden = true
|
|||
|
dislikeLottieView.isHidden = false
|
|||
|
stopLottieAnimation(dislikeLottieView) // 🔥 推测方法
|
|||
|
likeOrDislikeShowLogoView.alpha = 1
|
|||
|
|
|||
|
playLottieAnimation(dislikeLottieView) { [weak self] finish in
|
|||
|
if finish {
|
|||
|
self?.likeOrDislikeShowLogoView.alpha = 0
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func showLikeLottie() {
|
|||
|
dislikeLottieView.isHidden = true
|
|||
|
superlikeLottieView.isHidden = true
|
|||
|
likeLottieView.isHidden = false
|
|||
|
stopLottieAnimation(likeLottieView) // 🔥 推测方法
|
|||
|
likeOrDislikeShowLogoView.alpha = 1
|
|||
|
|
|||
|
playLottieAnimation(likeLottieView) { [weak self] finish in
|
|||
|
if finish {
|
|||
|
self?.likeOrDislikeShowLogoView.alpha = 0
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func showSuperLikeLottie(complete: @escaping (Bool) -> Void) {
|
|||
|
dislikeLottieView.isHidden = true
|
|||
|
likeLottieView.isHidden = true
|
|||
|
superlikeLottieView.isHidden = false
|
|||
|
stopLottieAnimation(superlikeLottieView) // 🔥 推测方法
|
|||
|
likeOrDislikeShowLogoView.alpha = 1
|
|||
|
|
|||
|
playLottieAnimation(superlikeLottieView) { [weak self] finish in
|
|||
|
self?.likeOrDislikeShowLogoView.alpha = 0
|
|||
|
complete(finish)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func addAdaptForDragCardView(_ cardView: YFLDragCardView) {
|
|||
|
let y = configure.containerEdge + configure.cardVTopEdage
|
|||
|
let width = frame.size.width - 2 * configure.containerEdge
|
|||
|
let height = frame.size.height - 2 * configure.containerEdge - configure.cardVTopEdage - configure.cardVBottomEdage
|
|||
|
|
|||
|
cardView.frame = CGRect(x: configure.containerEdge, y: y, width: width, height: height)
|
|||
|
cardView.setConfigure(configure)
|
|||
|
cardView.YFLDragCardViewLayoutSubviews()
|
|||
|
|
|||
|
recordFrame(cardView)
|
|||
|
cardView.tag = loadedIndex
|
|||
|
|
|||
|
// 添加拖拽手势
|
|||
|
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
|
|||
|
cardView.addGestureRecognizer(panGesture)
|
|||
|
addSubview(cardView)
|
|||
|
sendSubviewToBack(cardView)
|
|||
|
}
|
|||
|
|
|||
|
func clearTmpStoreNopeCardView() {
|
|||
|
tmpStoreNopeCardView?.removeFromSuperview()
|
|||
|
tmpStoreNopeCardView = nil
|
|||
|
}
|
|||
|
|
|||
|
func switchAllCardsToSmallCardMode(_ smallMode: Bool) {
|
|||
|
guard smallCardMode != smallMode else { return }
|
|||
|
|
|||
|
smallCardMode = smallMode
|
|||
|
delegate?.container(self, enterSmallCardMode: smallCardMode, forCardView: getCurrentShowCardView() ?? YFLDragCardView())
|
|||
|
}
|
|||
|
|
|||
|
// MARK: - 手势处理
|
|||
|
@objc private func handleTapGesture(_ tap: UITapGestureRecognizer) {
|
|||
|
delegate?.container(self, didSelectRowAt: tap.view?.tag ?? 0)
|
|||
|
}
|
|||
|
|
|||
|
@objc private func handlePanGesture(_ pan: UIPanGestureRecognizer) {
|
|||
|
guard let cardView = pan.view as? YFLDragCardView else { return }
|
|||
|
|
|||
|
let canEdit = delegate?.container(self, canDragForCardView: cardView) ?? true
|
|||
|
|
|||
|
if canEdit {
|
|||
|
switch pan.state {
|
|||
|
case .began:
|
|||
|
let point = pan.translation(in: self)
|
|||
|
doVDirectionLogicByPoint(point, forCardView: cardView)
|
|||
|
|
|||
|
case .changed:
|
|||
|
guard currentIntialGestureDirection != .up && currentIntialGestureDirection != .down else { return }
|
|||
|
|
|||
|
if let delegate = delegate {
|
|||
|
// 计算横向滑动比例 >0 向右 <0 向左
|
|||
|
let horizionSliderRate = Float((pan.view?.center.x ?? 0) - cardCenter.x) / Float(cardCenter.x)
|
|||
|
let verticalSliderRate = Float((pan.view?.center.y ?? 0) - cardCenter.y) / Float(cardCenter.y)
|
|||
|
|
|||
|
// 正在滑动,需要创建第四个。
|
|||
|
moveIngStatusChange(horizionSliderRate)
|
|||
|
|
|||
|
if currentIntialGestureDirection == .default {
|
|||
|
// 再次决定方向
|
|||
|
let point = pan.translation(in: self)
|
|||
|
doVDirectionLogicByPoint(point, forCardView: cardView)
|
|||
|
} else { // 已经确定了方向,且走到这儿,肯定是左右滑
|
|||
|
// 以自身的左上角为原点;每次移动后,原点都置0;计算的是相对于上一个位置的偏移;
|
|||
|
let point = pan.translation(in: self)
|
|||
|
cardView.center = CGPoint(x: (pan.view?.center.x ?? 0) + point.x * 0.5, y: pan.view?.center.y ?? 0)
|
|||
|
|
|||
|
// 当angle为正值时,逆时针旋转坐标系统,反之顺时针旋转坐标系统
|
|||
|
let rotationAngle = ((pan.view?.center.x ?? 0) - cardCenter.x) / cardCenter.x * (.pi / 4 / 12)
|
|||
|
cardView.transform = cardView.originTransForm.rotated(by: rotationAngle)
|
|||
|
pan.setTranslation(.zero, in: self) // 设置坐标原点位上次的坐标
|
|||
|
}
|
|||
|
|
|||
|
if horizionSliderRate > 0 {
|
|||
|
direction = .right
|
|||
|
} else if horizionSliderRate < 0 {
|
|||
|
direction = .left
|
|||
|
} else {
|
|||
|
direction = .default
|
|||
|
}
|
|||
|
|
|||
|
if currentIntialGestureDirection == .default {
|
|||
|
currentIntialGestureDirection = direction
|
|||
|
}
|
|||
|
|
|||
|
delegate.container(self, dargingForCardView: cardView, direction: direction, widthRate: horizionSliderRate, heightRate: verticalSliderRate)
|
|||
|
}
|
|||
|
|
|||
|
case .ended, .cancelled:
|
|||
|
// 还原card位置,或者消失card
|
|||
|
let horizionSliderRate = Float((pan.view?.center.x ?? 0) - cardCenter.x) / Float(cardCenter.x)
|
|||
|
let moveY = (pan.view?.center.y ?? 0) - cardCenter.y
|
|||
|
let moveX = (pan.view?.center.x ?? 0) - cardCenter.x
|
|||
|
|
|||
|
panGesturemMoveFinishOrCancle(cardView, direction: direction, scale: Float(moveX / moveY), isDisappear: abs(horizionSliderRate) > boundaryRation)
|
|||
|
|
|||
|
for per in cards { // 上下滑完后,不隐藏后面的cardView
|
|||
|
per.isHidden = false
|
|||
|
}
|
|||
|
|
|||
|
currentIntialGestureDirection = .default
|
|||
|
|
|||
|
default:
|
|||
|
break
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// MARK: - 辅助方法
|
|||
|
private func doVDirectionLogicByPoint(_ point: CGPoint, forCardView cardView: YFLDragCardView) {
|
|||
|
// option2: 不单独处理竖直方向手势
|
|||
|
if point.x == 0 {
|
|||
|
currentIntialGestureDirection = .default
|
|||
|
} else if point.x < 0 {
|
|||
|
currentIntialGestureDirection = .left
|
|||
|
} else {
|
|||
|
currentIntialGestureDirection = .right
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 🔥 根据上下文推测的Lottie动画相关方法
|
|||
|
private func isLottieAnimationPlaying(_ view: UIView) -> Bool {
|
|||
|
// 🔥 推测:检查Lottie动画是否正在播放
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
private func stopLottieAnimation(_ view: UIView) {
|
|||
|
// 🔥 推测:停止Lottie动画
|
|||
|
}
|
|||
|
|
|||
|
private func playLottieAnimation(_ view: UIView, completion: @escaping (Bool) -> Void) {
|
|||
|
// 🔥 推测:播放Lottie动画
|
|||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
|||
|
completion(true)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|