Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UserBoxView 버튼 클릭 버벅임 현상 수정 및 UserInfoCollectionView Compositional layout으로 마이그레이션 #71

Merged
merged 9 commits into from
Apr 17, 2024
Next Next commit
[#69]Feat: 이벤트 버벅이는 문제 수정 및 UserInfoCollectionView 디자인 반영
- 더블 클릭 이벤트와 중첩되서 infoBoxView에 있는 버튼을 클릭했을 때 버벅였던 것으로 확인
- infoBoxView를 profile collection view와 분리
- UserInfoCollectionView에서도 중지가 가능하도록 구현은 했으나
- UserInfoCollectionView에서도 report 버튼이 있다보니 없앴음
- 더블 클릭 트리거를 cell에 넘겨준 것을 구독해서 멈춤 뷰 혹은 프로필 탭 시 이벤트 전달하는 방식으로 구현
- 원형 타이머 dot과 stoke가 이루는 각도를 30도로 가정해서 위치 수정
- 0초 위치 수정
Minny27 committed Mar 12, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 4a92a9ac94be3bd329538fc405dd0d4685b25314
35 changes: 26 additions & 9 deletions Projects/Features/Falling/Src/Home/FallingHomeViewController.swift
Original file line number Diff line number Diff line change
@@ -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!
@@ -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)

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

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

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

12 changes: 11 additions & 1 deletion Projects/Features/Falling/Src/Home/FallingHomeViewModel.swift
Original file line number Diff line number Diff line change
@@ -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) {
@@ -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
@@ -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()

@@ -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()
@@ -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 {
@@ -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() {
@@ -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)
@@ -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)

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