Skip to content

Commit

Permalink
[#69]Feat: 이벤트 버벅이는 문제 수정 및 UserInfoCollectionView 디자인 반영
Browse files Browse the repository at this point in the history
- 더블 클릭 이벤트와 중첩되서 infoBoxView에 있는 버튼을 클릭했을 때 버벅였던 것으로 확인
- infoBoxView를 profile collection view와 분리
- UserInfoCollectionView에서도 중지가 가능하도록 구현은 했으나
- UserInfoCollectionView에서도 report 버튼이 있다보니 없앴음
- 더블 클릭 트리거를 cell에 넘겨준 것을 구독해서 멈춤 뷰 혹은 프로필 탭 시 이벤트 전달하는 방식으로 구현
- 원형 타이머 dot과 stoke가 이루는 각도를 30도로 가정해서 위치 수정
- 0초 위치 수정
  • Loading branch information
Minny27 committed Mar 12, 2024
1 parent a257787 commit 4a92a9a
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 52 deletions.
35 changes: 26 additions & 9 deletions Projects/Features/Falling/Src/Home/FallingHomeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import Core
import DSKit
import FallingInterface

enum FallingCellButtonAction {
case info(IndexPath)
case refuse(IndexPath)
case like(IndexPath)
}

final class FallingHomeViewController: TFBaseViewController {
private let viewModel: FallingHomeViewModel
private var dataSource: DataSource!
Expand Down Expand Up @@ -47,24 +53,25 @@ final class FallingHomeViewController: TFBaseViewController {

let initialTrigger = Driver<Void>.just(())
let timerOverTrigger = timeOverSubject.asDriverOnErrorJustEmpty()
let fallingCellButtonAction = PublishSubject<FallingCellButtonAction>()

let viewWillDisAppearTrigger = self.rx.viewWillDisAppear.map { _ in false }.asDriverOnErrorJustEmpty()
let timerActiveRelay = BehaviorRelay(value: true)
let cardDoubleTapTrigger = self.homeView.collectionView.rx
.tapGesture(configuration: { gestureRecognizer, delegate in
gestureRecognizer.numberOfTapsRequired = 2
})
.when(.recognized)
let profileDoubleTapTriggerObserver = PublishSubject<Void>()

let profileDoubleTapTrigger = profileDoubleTapTriggerObserver
.withLatestFrom(timerActiveRelay) { !$1 }
.asDriverOnErrorJustEmpty()

Driver.merge(cardDoubleTapTrigger, viewWillDisAppearTrigger)
Driver.merge(profileDoubleTapTrigger, viewWillDisAppearTrigger)
.drive(timerActiveRelay)
.disposed(by: disposeBag)

let input = FallingHomeViewModel.Input(
initialTrigger: initialTrigger,
timeOverTrigger: timerOverTrigger)
timeOverTrigger: timerOverTrigger,
cellButtonAction: fallingCellButtonAction.asDriverOnErrorJustEmpty()
)

let output = viewModel.transform(input: input)

Expand All @@ -78,8 +85,10 @@ final class FallingHomeViewController: TFBaseViewController {

cell.bind(
FallinguserCollectionViewCellModel(userDomain: item),
timerActiveTrigger,
scrollToNextObserver: timeOverSubject
timerActiveTrigger: timerActiveTrigger,
timeOverSubject: timeOverSubject,
profileDoubleTapTriggerObserver: profileDoubleTapTriggerObserver,
fallingCellButtonAction: fallingCellButtonAction
)
}

Expand Down Expand Up @@ -117,6 +126,14 @@ final class FallingHomeViewController: TFBaseViewController {
animated: true
)})
.disposed(by: self.disposeBag)

output.info
.drive(with: self) { owner, indexPath in
guard let cell = owner.homeView.collectionView.cellForItem(at: indexPath) as? FallingUserCollectionViewCell
else { return }
cell.userInfoCollectionView.isHidden.toggle()
}
.disposed(by: disposeBag)
}
}

Expand Down
12 changes: 11 additions & 1 deletion Projects/Features/Falling/Src/Home/FallingHomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ final class FallingHomeViewModel: ViewModelType {
struct Input {
let initialTrigger: Driver<Void>
let timeOverTrigger: Driver<Void>
let cellButtonAction: Driver<FallingCellButtonAction>
}

struct Output {
let userList: Driver<[FallingUser]>
let nextCardIndexPath: Driver<IndexPath>
let info: Driver<IndexPath>
}

init(fallingUseCase: FallingUseCaseInterface) {
Expand Down Expand Up @@ -59,10 +61,18 @@ final class FallingHomeViewModel: ViewModelType {
updateScrollIndexTrigger
).withLatestFrom(currentIndexRelay.asDriver(onErrorJustReturn: 0)
.map { IndexPath(row: $0, section: 0) })

let info = input.cellButtonAction
.compactMap { action -> IndexPath? in
if case let .info(indexPath) = action {
return indexPath
} else { return nil }
}

return Output(
userList: userList,
nextCardIndexPath: nextCardIndexPath
nextCardIndexPath: nextCardIndexPath,
info: info
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ struct FallingUserCollectionViewCellObserver {
final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
private var dataSource: DataSource!

private var indexPath: IndexPath? {
guard let collectionView = self.superview as? UICollectionView,
let indexPath = collectionView.indexPath(for: self) else {
TFLogger.ui.error("indexPath 얻기 실패")
return nil
}
return indexPath
}

lazy var profileCollectionView: TFBaseCollectionView = {
let layout = UICollectionViewCompositionalLayout.horizontalListLayout()

Expand All @@ -32,14 +41,6 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
return collectionView
}()

var photos: [UserProfilePhoto] = [] {
didSet {
userInfoBoxView.pageControl.currentPage = 0
userInfoBoxView.pageControl.numberOfPages = oldValue.count
// collectionView.reloadData()
}
}

lazy var userInfoBoxView = UserInfoBoxView()

lazy var cardTimeView = CardTimeView()
Expand All @@ -56,12 +57,22 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
return pauseView
}()

lazy var userInfoCollectionView: UserInfoCollectionView = {
let collectionView = UserInfoCollectionView()
collectionView.layer.cornerRadius = 20
collectionView.clipsToBounds = true
collectionView.collectionView.backgroundColor = DSKitAsset.Color.DimColor.default.color
collectionView.isHidden = true
return collectionView
}()

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)

profileCollectionView.snp.makeConstraints {
Expand All @@ -74,17 +85,24 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
}

self.userInfoBoxView.snp.makeConstraints {
$0.height.equalTo(145)
$0.leading.trailing.equalToSuperview().inset(16)
$0.bottom.equalToSuperview().inset(12)
}

userInfoCollectionView.snp.makeConstraints {
$0.leading.trailing.equalToSuperview().inset(10)
$0.height.equalTo(300)
$0.bottom.equalTo(userInfoBoxView.snp.top).offset(-8)
}

self.pauseView.snp.makeConstraints {
$0.edges.equalToSuperview()
}

self.configureDataSource()

self.profileCollectionView.showDimView()

self.setDataSource()
}

override func prepareForReuse() {
Expand All @@ -94,10 +112,14 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {

func bind<O>(
_ viewModel: FallinguserCollectionViewCellModel,
_ timerTrigger: Driver<Bool>,
scrollToNextObserver: O
) where O: ObserverType, O.Element == Void {
let input = FallinguserCollectionViewCellModel.Input(timerActiveTrigger: timerTrigger)
timerActiveTrigger: Driver<Bool>,
timeOverSubject: PublishSubject<Void>,
profileDoubleTapTriggerObserver: PublishSubject<Void>,
fallingCellButtonAction: O
) where O: ObserverType, O.Element == FallingCellButtonAction {
let input = FallinguserCollectionViewCellModel.Input(
timerActiveTrigger: timerActiveTrigger
)

let output = viewModel
.transform(input: input)
Expand All @@ -111,36 +133,89 @@ final class FallingUserCollectionViewCell: TFBaseCollectionViewCell {
.disposed(by: self.disposeBag)

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

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

output.isTimerActive
.drive(pauseView.rx.isHidden)
.disposed(by: disposeBag)

userInfoBoxView.infoButton.rx.tap.asDriver()
.scan(true) { lastValue, _ in
return !lastValue
let profileDoubleTapTrigger = self.profileCollectionView.rx
.tapGesture(configuration: { gestureRecognizer, delegate in
gestureRecognizer.numberOfTapsRequired = 2
})
.when(.recognized)
.mapToVoid()
.asDriverOnErrorJustEmpty()

let pauseViewDoubleTapTrigger = self.pauseView.rx
.tapGesture(configuration: { gestureRecognizer, delegate in
gestureRecognizer.numberOfTapsRequired = 2
})
.when(.recognized)
.mapToVoid()
.asDriverOnErrorJustEmpty()

Driver.merge(profileDoubleTapTrigger, pauseViewDoubleTapTrigger)
.map { _ in }
.drive(profileDoubleTapTriggerObserver)
.disposed(by: disposeBag)

profileCollectionView.rx.didEndDisplayingCell.asDriver()
.debug()
.drive(with: self) { owner, indexPath in
self.userInfoBoxView.pageControl.currentPage
}
.drive(userInfoBoxView.tagCollectionView.rx.isHidden)
.disposed(by: disposeBag)

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

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

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

func bind(userProfilePhotos: [UserProfilePhoto]) {
var snapshot = Snapshot()
snapshot.appendSections([.profile])
snapshot.appendItems(userProfilePhotos)
self.dataSource.apply(snapshot)

userInfoBoxView.pageControl.numberOfPages = userProfilePhotos.count
}

func dotPosition(progress: Double, rect: CGRect) -> CGPoint {
var progress = progress
// progress가 -0.05미만 혹은 1이상은 점(dot)을 0초에 위치시키기 위함
let strokeRange: Range<Double> = -0.05..<0.95
if !(strokeRange ~= progress) { progress = 0.95 }
func dotPosition(progress: CGFloat, rect: CGRect) -> CGPoint {
let progress = round(progress * 100) / 100 // 오차를 줄이기 위함
let radius = CGFloat(rect.height / 2 - cardTimeView.timerView.strokeLayer.lineWidth / 2)
let angle = 2 * CGFloat.pi * CGFloat(progress) - CGFloat.pi / 2
let dotX = radius * cos(angle + 0.35)
let dotY = radius * sin(angle + 0.35)

var angle = 2 * CGFloat.pi * progress - CGFloat.pi / 2 + CGFloat.pi / 6 // 두 원의 중점과 원점이 이루는 각도를 30도로 가정
if angle <= -CGFloat.pi / 2 || CGFloat.pi * 1.5 <= angle {
angle = -CGFloat.pi / 2 // 정점 각도
}

let dotX = radius * cos(angle)
let dotY = radius * sin(angle)

let point = CGPoint(x: dotX, y: dotY)

Expand All @@ -156,7 +231,7 @@ extension FallingUserCollectionViewCell {
typealias DataSource = UICollectionViewDiffableDataSource<FallingProfileSection, Model>
typealias Snapshot = NSDiffableDataSourceSnapshot<FallingProfileSection, Model>

func configureDataSource() {
func setDataSource() {
let profileCellRegistration = UICollectionView.CellRegistration<ProfileCollectionViewCell, Model> { cell, indexPath, item in
cell.bind(imageURL: item.url)
}
Expand All @@ -165,13 +240,6 @@ extension FallingUserCollectionViewCell {
return collectionView.dequeueConfiguredReusableCell(using: profileCellRegistration, for: indexPath, item: itemIdentifier)
})
}

func setupDataSource(userProfilePhotos: [UserProfilePhoto]) {
var snapshot = Snapshot()
snapshot.appendSections([.profile])
snapshot.appendItems(userProfilePhotos)
self.dataSource.apply(snapshot)
}
}

extension Reactive where Base: FallingUserCollectionViewCell {
Expand All @@ -188,10 +256,9 @@ extension Reactive where Base: FallingUserCollectionViewCell {

base.cardTimeView.timerView.timerLabel.text = timeState.getText

base.cardTimeView.progressView.progress = CGFloat(timeState.getProgress)
base.cardTimeView.progressView.progress = timeState.getProgress

// TimerView Animation은 소수점 둘째 자리까지 표시해야 오차가 발생하지 않음
let strokeEnd = round(CGFloat(timeState.getProgress) * 100) / 100
let strokeEnd = timeState.getProgress
base.cardTimeView.timerView.dotLayer.position = base.dotPosition(progress: strokeEnd, rect: base.cardTimeView.timerView.bounds)

base.cardTimeView.timerView.strokeLayer.strokeEnd = strokeEnd
Expand All @@ -203,9 +270,9 @@ extension Reactive where Base: FallingUserCollectionViewCell {

var user: Binder<FallingUser> {
return Binder(self.base) { (base, user) in
base.photos = user.userProfilePhotos
base.setupDataSource(userProfilePhotos: user.userProfilePhotos)
base.bind(userProfilePhotos: user.userProfilePhotos)
base.userInfoBoxView.bind(user)
base.userInfoCollectionView.bind(user)
}
}
}

0 comments on commit 4a92a9a

Please sign in to comment.