-
Notifications
You must be signed in to change notification settings - Fork 1
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
DiffableDataSource + Compositional Layout으로 마이그레이션 #58
Changes from 1 commit
1c723f1
f4f50cc
bc73245
48740fa
45b09b9
4edc82e
1fe4ab1
8d7df71
566d14a
a251041
8f5b787
f19057b
142c69a
1d07f1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ import FallingInterface | |
|
||
final class FallingHomeViewController: TFBaseViewController { | ||
private let viewModel: FallingHomeViewModel | ||
private var dataSource: UICollectionViewDiffableDataSource<FallingProfileSection, FallingUser>! | ||
private var dataSource: DataSource! | ||
private lazy var homeView = FallingHomeView() | ||
|
||
init(viewModel: FallingHomeViewModel) { | ||
|
@@ -36,16 +36,18 @@ final class FallingHomeViewController: TFBaseViewController { | |
let mindImageView = UIImageView(image: DSKitAsset.Image.Icons.mind.image) | ||
let mindImageItem = UIBarButtonItem(customView: mindImageView) | ||
|
||
let notificationButtonItem = UIBarButtonItem(image: DSKitAsset.Image.Icons.bell.image, style: .plain, target: nil, action: nil) | ||
let notificationButtonItem = UIBarButtonItem.noti | ||
|
||
navigationItem.leftBarButtonItem = mindImageItem | ||
navigationItem.rightBarButtonItem = notificationButtonItem | ||
} | ||
|
||
override func bindViewModel() { | ||
let timeOverSubject = PublishSubject<Void>() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bindViewModel에서 timeOver 트리거를 PublishSubject로 만들어서 cell에서 timeZero와 바인딩하는 방식 좋은 거 같습니다! |
||
|
||
let initialTrigger = Driver<Void>.just(()) | ||
let timerOverTrigger = self.rx.timeOverTrigger.asDriver() | ||
let timerOverTrigger = timeOverSubject.asDriverOnErrorJustEmpty() | ||
|
||
let viewWillAppearTrigger = self.rx.viewWillAppear.map { _ in true }.asDriverOnErrorJustEmpty() | ||
let viewWillDisAppearTrigger = self.rx.viewWillDisAppear.map { _ in false }.asDriverOnErrorJustEmpty() | ||
let timerActiveRelay = BehaviorRelay(value: true) | ||
|
@@ -56,7 +58,7 @@ final class FallingHomeViewController: TFBaseViewController { | |
.when(.recognized) | ||
.withLatestFrom(timerActiveRelay) { !$1 } | ||
.asDriverOnErrorJustEmpty() | ||
|
||
cardDoubleTapTrigger | ||
.drive(timerActiveRelay) | ||
.disposed(by: disposeBag) | ||
|
@@ -65,61 +67,57 @@ final class FallingHomeViewController: TFBaseViewController { | |
.drive(timerActiveRelay) | ||
.disposed(by: disposeBag) | ||
|
||
let input = FallingHomeViewModel.Input(initialTrigger: initialTrigger, | ||
timeOverTrigger: timerOverTrigger) | ||
let input = FallingHomeViewModel.Input( | ||
initialTrigger: initialTrigger, | ||
timeOverTrigger: timerOverTrigger) | ||
|
||
let output = viewModel.transform(input: input) | ||
|
||
var usersCount = 0 | ||
|
||
let profileCellRegistration = UICollectionView.CellRegistration<FallingUserCollectionViewCell, FallingUser> { [weak self] cell, indexPath, item in | ||
let observer = FallingUserCollectionViewCellObserver( | ||
userCardScrollIndex: output.userCardScrollIndex.asObservable(), | ||
timerActiveTrigger: timerActiveRelay.asObservable() | ||
let profileCellRegistration = UICollectionView.CellRegistration<CellType, ModelType> { cell, indexPath, item in | ||
let timerActiveTrigger = Driver.combineLatest( | ||
output.userCardScrollIndex, | ||
timerActiveRelay.asDriver() | ||
) | ||
.filter { indexPath.row == $0.0 } | ||
.map { $0.1 } | ||
.debug("cell timer active") | ||
|
||
cell.bind( | ||
FallinguserCollectionViewCellModel(userDomain: item), | ||
timerActiveTrigger, | ||
scrollToNextObserver: timeOverSubject | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
cell.bind(model: item) | ||
cell.bind(observer, | ||
index: indexPath, | ||
usersCount: usersCount) | ||
cell.delegate = self | ||
} | ||
|
||
dataSource = UICollectionViewDiffableDataSource(collectionView: homeView.collectionView, cellProvider: { collectionView, indexPath, itemIdentifier in | ||
dataSource = DataSource(collectionView: homeView.collectionView, cellProvider: { collectionView, indexPath, itemIdentifier in | ||
return collectionView.dequeueConfiguredReusableCell(using: profileCellRegistration, for: indexPath, item: itemIdentifier) | ||
}) | ||
|
||
output.userList | ||
.drive(with: self, onNext: { this, list in | ||
usersCount = list.count | ||
var snapshot = NSDiffableDataSourceSnapshot<FallingProfileSection, FallingUser>() | ||
var snapshot = Snapshot() | ||
snapshot.appendSections([.profile]) | ||
snapshot.appendItems(list) | ||
this.dataSource.apply(snapshot) | ||
}).disposed(by: disposeBag) | ||
|
||
output.userCardScrollIndex | ||
output.nextCardIndex | ||
.drive(with: self, onNext: { this, index in | ||
if usersCount == 0 { return } | ||
let index = index >= usersCount ? usersCount - 1 : index | ||
let indexPath = IndexPath(row: index, section: 0) | ||
this.homeView.collectionView.scrollToItem(at: indexPath, | ||
at: .top, | ||
animated: true) | ||
}) | ||
this.homeView.collectionView.scrollToItem( | ||
at: index, | ||
at: .top, | ||
animated: true)}) | ||
.disposed(by: self.disposeBag) | ||
} | ||
} | ||
|
||
extension FallingHomeViewController: TimeOverDelegate { | ||
@objc func scrollToNext() { } | ||
} | ||
// MARK: DiffableDataSource | ||
|
||
extension Reactive where Base: FallingHomeViewController { | ||
var timeOverTrigger: ControlEvent<Void> { | ||
let source = methodInvoked(#selector(Base.scrollToNext)).map { _ in } | ||
return ControlEvent(events: source) | ||
} | ||
extension FallingHomeViewController { | ||
typealias CellType = FallingUserCollectionViewCell | ||
typealias ModelType = FallingUser | ||
typealias SectionType = FallingProfileSection | ||
typealias DataSource = UICollectionViewDiffableDataSource<SectionType, ModelType> | ||
typealias Snapshot = NSDiffableDataSourceSnapshot<SectionType, ModelType> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typealias로 정의해 놓는게 깔끔하네요 |
||
} | ||
|
||
//#if DEBUG | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,53 +10,66 @@ import FallingInterface | |
|
||
import RxSwift | ||
import RxCocoa | ||
import Foundation | ||
|
||
final class FallingHomeViewModel: ViewModelType { | ||
|
||
private let fallingUseCase: FallingUseCaseInterface | ||
// weak var delegate: FallingHomeDelegate? | ||
|
||
// weak var delegate: FallingHomeDelegate? | ||
|
||
var disposeBag: DisposeBag = DisposeBag() | ||
|
||
struct Input { | ||
let initialTrigger: Driver<Void> | ||
let timeOverTrigger: Driver<Void> | ||
} | ||
|
||
struct Output { | ||
let userList: Driver<[FallingUser]> | ||
let userCardScrollIndex: Driver<Int> | ||
let nextCardIndex: Driver<IndexPath> | ||
} | ||
|
||
init(fallingUseCase: FallingUseCaseInterface) { | ||
self.fallingUseCase = fallingUseCase | ||
} | ||
|
||
func transform(input: Input) -> Output { | ||
let currentIndexRelay = BehaviorRelay<Int>(value: 0) | ||
let timeOverTrigger = input.timeOverTrigger | ||
|
||
let usersResponse = input.initialTrigger | ||
.flatMapLatest { [unowned self] _ in | ||
self.fallingUseCase.user(alreadySeenUserUUIDList: [], userDailyFallingCourserIdx: 1, size: 100) | ||
.asDriver(onErrorJustReturn: .init(selectDailyFallingIdx: 0, topicExpirationUnixTime: 0, userInfos: [])) | ||
} | ||
|
||
let userList = usersResponse.map { $0.userInfos }.asDriver() | ||
|
||
|
||
let userCount = userList.map { $0.count } | ||
|
||
let userListObservable = userList.map { _ in | ||
currentIndexRelay.accept(currentIndexRelay.value) | ||
} | ||
|
||
let nextScrollIndex = timeOverTrigger.withLatestFrom(currentIndexRelay.asDriver(onErrorJustReturn: 0)) { _, index in | ||
currentIndexRelay.accept(index + 1) | ||
} | ||
|
||
let userCardScrollIndex = Driver.merge(userListObservable, nextScrollIndex).withLatestFrom(currentIndexRelay.asDriver(onErrorJustReturn: 0)) | ||
|
||
|
||
let userCardScrollIndex = Driver.merge(userListObservable, nextScrollIndex).withLatestFrom(userCount) { _, count in | ||
let currentIndex = currentIndexRelay.value | ||
return currentIndex >= count ? count - 1 : currentIndex | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. index가 list count 보다 크거나 같을 때의 조건은 scroll될 때만 예외처리를 해놓은 것입니다. |
||
} | ||
|
||
let nextCardIndex = userCardScrollIndex | ||
.filter { $0 != 0 } | ||
.map { IndexPath(row: $0, section: 0) } | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. VC indexPath 로직 VM으로 이동 및 명시적으로 output 분리 ( SRP, 유지보수 |
||
return Output( | ||
userList: userList, | ||
userCardScrollIndex: userCardScrollIndex) | ||
userCardScrollIndex: userCardScrollIndex, | ||
nextCardIndex: nextCardIndex | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,8 +129,9 @@ final class FallinguserCollectionViewCellModel: ViewModelType { | |
|
||
struct Output { | ||
let timeState: Driver<TimeState> | ||
let timeZero: Driver<Double> | ||
let timeZero: Driver<Void> | ||
let user: Driver<FallingUser> | ||
let isDimViewHidden: Driver<Bool> | ||
} | ||
|
||
func transform(input: Input) -> Output { | ||
|
@@ -161,12 +162,18 @@ final class FallinguserCollectionViewCellModel: ViewModelType { | |
}.asDriver(onErrorJustReturn: 8.0) | ||
|
||
let timeState = timer.map { TimeState(rawValue: $0) } | ||
let timeZero = timer.filter { $0 == 0 } | ||
|
||
let timeZero = timer.filter { $0 == 0 }.map { _ in } | ||
|
||
let isDimViewHidden = Driver.merge( | ||
timerActiveTrigger.take(1).asDriverOnErrorJustEmpty(), // take(1)을 해주지 않으면 일시정지 때마다 dimmed 됨 | ||
timeZero.map { _ in false } | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DimView hidden state 관리 output There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이런 방법이 있는지 몰랐네요! |
||
|
||
return Output( | ||
timeState: timeState, | ||
timeZero: timeZero, | ||
user: user | ||
user: user, | ||
isDimViewHidden: isDimViewHidden | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ import DSKit | |
final class ProfileCollectionViewCell: UICollectionViewCell { | ||
private lazy var imageView: UIImageView = { | ||
let imageView = UIImageView() | ||
imageView.contentMode = .scaleToFill | ||
imageView.contentMode = .scaleAspectFill | ||
imageView.layer.masksToBounds = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그냥 scale보다는 AspectScale이 비율 유지할 수 있을 것 같아서 변경 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. scaleToFill 사용한 이유는 사실 이미지가 꽉 채워졌을 때를 확인하려고 썼던 거긴 해요. 고정으로 할 생각은 없었습니다. |
||
return imageView | ||
}() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UIBarButtonItem 정의되어 있는게 있는지 몰랐네요