Skip to content

Commit

Permalink
[#72]Feat: 'x' 버튼 이벤트 구현
Browse files Browse the repository at this point in the history
- timer 클래스 생성
- timer에서 start()함수를 호출하면 disposable의 타이머 구독 생성
- pause에서는 현재 시간을 갱신하고 disposable의 구독을 해제하고 참조도 제거
- 로티 애니메이션 추가
- 디자인 변경으로 유저 카드 흔들리는 애니메이션 제거
- timeStart로 dimview를 hidden했었으나 currentTime이 BehaviorRelay로 되어 있어서 최초에 한 번 호출로 인해 dimview가 사라짐
- isTimerActive에서 처리하도록 해서 문제 해결
  • Loading branch information
Minny27 committed Mar 22, 2024
1 parent 65d480a commit 5659dae
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
return pauseView
}()

lazy var rejectLottieView: LottieAnimationView = {
let lottieAnimationView = LottieAnimationView()
lottieAnimationView.animation = AnimationAsset.unlike.animation
lottieAnimationView.isHidden = true
lottieAnimationView.contentMode = .scaleAspectFit
return lottieAnimationView
}()

lazy var userInfoCollectionView: UserInfoCollectionView = {
let collectionView = UserInfoCollectionView()
collectionView.layer.cornerRadius = 20
Expand All @@ -69,11 +77,7 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
override func makeUI() {
self.layer.cornerRadius = 20

self.contentView.addSubview(profileCollectionView)
self.contentView.addSubview(cardTimeView)
self.contentView.addSubview(userInfoBoxView)
self.contentView.addSubview(userInfoCollectionView)
self.contentView.addSubview(pauseView)
self.contentView.addSubviews([profileCollectionView, cardTimeView, userInfoBoxView, userInfoBoxView, userInfoCollectionView, rejectLottieView, pauseView])

profileCollectionView.snp.makeConstraints {
$0.edges.equalToSuperview()
Expand All @@ -100,6 +104,11 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
$0.edges.equalToSuperview()
}

self.rejectLottieView.snp.makeConstraints {
$0.center.equalToSuperview()
$0.width.height.equalTo(188) // TODO: 사이즈 수정 예정
}

self.profileCollectionView.showDimView()

self.setDataSource()
Expand All @@ -111,14 +120,20 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
}

func bind<O>(
_ viewModel: FallinguserCollectionViewCellModel,
_ viewModel: FallingUserCollectionViewCellModel,
timerActiveTrigger: Driver<Bool>,
timeOverSubject: PublishSubject<Void>,
profileDoubleTapTriggerObserver: PublishSubject<Void>,
fallingCellButtonAction: O
) where O: ObserverType, O.Element == FallingCellButtonAction {
let input = FallinguserCollectionViewCellModel.Input(
timerActiveTrigger: timerActiveTrigger
let rejectButtonTrigger = userInfoBoxView.rejectButton.rx.tap.mapToVoid().asDriverOnErrorJustEmpty()

let likeButtonTrigger = userInfoBoxView.likeButton.rx.tap.mapToVoid().asDriverOnErrorJustEmpty()

let input = FallingUserCollectionViewCellModel.Input(
timerActiveTrigger: timerActiveTrigger,
rejectButtonTrigger: rejectButtonTrigger,
likeButtonTrigger: likeButtonTrigger
)

let output = viewModel
Expand All @@ -132,17 +147,14 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
.drive(self.rx.timeState)
.disposed(by: self.disposeBag)

output.timeStart
.drive(with: self) { owner, _ in
owner.profileCollectionView.hiddenDimView()
}
.disposed(by: disposeBag)

output.timeZero
.drive(timeOverSubject)
.disposed(by: disposeBag)

output.isTimerActive
.do(onNext: { _ in
self.profileCollectionView.hiddenDimView()
})
.drive(pauseView.rx.isHidden)
.disposed(by: disposeBag)

Expand Down Expand Up @@ -175,24 +187,20 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
.disposed(by: disposeBag)

userInfoBoxView.infoButton.rx.tap.asDriver()
.scan(true, accumulator: { value, _ in
return !value
})
.scan(true) { value, _ in return !value }
.drive(userInfoCollectionView.rx.isHidden)
.disposed(by: disposeBag)

userInfoBoxView.refuseButton.rx.tapGesture()
.when(.recognized)
output.rejectButtonAction
.compactMap { [weak self] _ in self?.indexPath }
.map { FallingCellButtonAction.refuse($0) }
.bind(to: fallingCellButtonAction)
.map { FallingCellButtonAction.reject($0) }
.drive(fallingCellButtonAction)
.disposed(by: disposeBag)

userInfoBoxView.likeButton.rx.tapGesture()
.when(.recognized)
output.likeButtonAction
.compactMap { [weak self] _ in self?.indexPath }
.map { FallingCellButtonAction.like($0) }
.bind(to: fallingCellButtonAction)
.drive(fallingCellButtonAction)
.disposed(by: disposeBag)
}

Expand Down Expand Up @@ -262,8 +270,6 @@ extension Reactive where Base: FallingUserCollectionViewCell {
base.cardTimeView.timerView.dotLayer.position = base.dotPosition(progress: strokeEnd, rect: base.cardTimeView.timerView.bounds)

base.cardTimeView.timerView.strokeLayer.strokeEnd = strokeEnd

base.profileCollectionView.transform = base.profileCollectionView.transform.rotated(by: timeState.rotateAngle)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// FallinguserCollectionViewCellModel.swift
// FallingUserCollectionViewCellModel.swift
// FallingInterface
//
// Created by SeungMin on 1/11/24.
Expand Down Expand Up @@ -89,46 +89,40 @@ enum TimeState {
return round((value / 2 - 1) / 5 * 1000) / 1000
}
}
}

final private class Timer {
private var disposable: Disposable? = nil

// .pi / 360 => 1도
var rotateAngle: CGFloat {
switch self {
case .four:
// 8 10 => 초당 0.5도 (편도 / 2 1회)
return .pi / 360 / 200
case .three(let value):
// 6 8 => 초당 2도 (왕복 1회)
let time = round((4-value/2) * 100) / 100
if time <= 0.5 { return -.pi / 360 / 50 }
else { return .pi / 360 / 50 }
case .two(let value):
// 4 6 => 초당 4도 (왕복 2회)
let time = round((3-value/2) * 100) / 100
if time <= 0.25 { return -.pi / 360 / 25 }
else if 0.25 < time && time <= 0.5 { return .pi / 360 / 25 }
else if 0.5 < time && time <= 0.75 { return -.pi / 360 / 25 }
else { return .pi / 360 / 25 }
case .one(let value):
// 2 4 => 초당 8도 (왕복 4회)
let time = round((2-value/2) * 1000) / 1000
if time <= 0.125 { return -.pi / 360 / 12.5 }
else if 0.125 < time && time <= 0.25 { return .pi / 360 / 12.5 }
else if 0.25 < time && time <= 0.375 { return -.pi / 360 / 12.5 }
else if 0.375 < time && time <= 0.5 { return .pi / 360 / 12.5 }
else if 0.5 < time && time <= 0.625 { return -.pi / 360 / 12.5 }
else if 0.625 < time && time <= 0.75 { return .pi / 360 / 12.5 }
else if 0.75 < time && time <= 0.875 { return -.pi / 360 / 12.5 }
else { return .pi / 360 / 12.5 }
case .zero:
// 1 2 => 초당 1도
return -.pi / 360 / 100
default:
return 0
let currentTime = BehaviorRelay<Double>(value: 13.0)
private var startTime: Double

init(startTime: Double) {
self.startTime = startTime
}

func start() {
guard disposable == nil else { return }

disposable = Observable<Int>.interval(.milliseconds(10),
scheduler: MainScheduler.instance)
.take(Int(startTime * 100) + 1)
.map { [weak self] value in
guard let self = self else { return 0.0 }
return round((self.startTime * 100 - Double(value))) / 100
}
.debug()
.bind(to: currentTime)
}

func pause() {
startTime = currentTime.value
disposable?.dispose()
disposable = nil
}
}

final class FallinguserCollectionViewCellModel: ViewModelType {
final class FallingUserCollectionViewCellModel: ViewModelType {
let userDomain: FallingUser

init(userDomain: FallingUser) {
Expand All @@ -139,53 +133,55 @@ final class FallinguserCollectionViewCellModel: ViewModelType {

struct Input {
let timerActiveTrigger: Driver<Bool>
let rejectButtonTrigger: Driver<Void>
let likeButtonTrigger: Driver<Void>
}

struct Output {
let user: Driver<FallingUser>
let timeState: Driver<TimeState>
let timeStart: Driver<Void>
let timeZero: Driver<Void>
let isTimerActive: Driver<Bool>
let rejectButtonAction: Driver<Void>
let likeButtonAction: Driver<Void>
}

func transform(input: Input) -> Output {
var currentTime: Double = 13.0
var startTime: Double = 13.0
let timer = Timer(startTime: 13.0)
let user = Driver.just(self.userDomain)

let timerActiveTrigger = input.timerActiveTrigger
.asObservable()

let timer = timerActiveTrigger
.flatMapLatest { value in
let rejectButtonAction = input.rejectButtonTrigger
.do(onNext: { _ in
timer.pause()
timer.currentTime.accept(-1.0) // reject 시에는 0.5초 후에 넘어 가야하는 제약이 있어서, 0초로 설정하지 않았고, reject 버튼 이벤트에 대한 처리는 상위 뷰에서 따로 처리하고 있음.
})

let likeButtonAction = input.likeButtonTrigger

let timeActiveAction = timerActiveTrigger
.do { value in
if !value {
startTime = currentTime // 타이머가 멈췄을 때 현재 시간으로 갱신
return Driver.just(currentTime)
timer.pause()
} else {
return Observable<Int>.interval(.milliseconds(10),
scheduler: MainScheduler.instance)
.take(Int(startTime * 100) + 1) // 시간의 총 개수
.map { value in
currentTime = round((startTime * 100 - Double(value))) / 100 // 현재 시간 갱신
return currentTime
}
.debug()
.asDriver(onErrorJustReturn: currentTime)
timer.start()
}
}.asDriver(onErrorJustReturn: currentTime)
}

let time = timer.currentTime.asDriver(onErrorJustReturn: 0.0)

let timeState = time.map { TimeState(rawValue: $0) }
let timeZero = time.filter { $0 == 0.0 }.map { _ in }
let isTimerActive = timeActiveAction.asDriver(onErrorJustReturn: true)

let timeState = timer.map { TimeState(rawValue: $0) }
let timeStart = timer.filter { $0 == 13.0 }.map { _ in }
let timeZero = timer.filter { $0 == 0.0 }.map { _ in }
let isTimerActive = timerActiveTrigger.asDriver(onErrorJustReturn: true)

return Output(
user: user,
timeState: timeState,
timeStart: timeStart,
timeZero: timeZero,
isTimerActive: isTimerActive
isTimerActive: isTimerActive,
rejectButtonAction: rejectButtonAction,
likeButtonAction: likeButtonAction
)
}
}

0 comments on commit 5659dae

Please sign in to comment.