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)
|
||
}
|
||
}
|
||
}
|