Skip to content

Commit b1f4410

Browse files
committed
fix: 감정등록 화면 QA 대응 (무한 스크롤 끊김, 스크롤 가능 영역 확장)
- 구슬 collectionView 하단의 안내 문구에서도 collectionView의 스크롤이 가능하도록 수정 - 무한 스크롤이 끊기는 현상 개선 - 구슬 스크롤 시 진동 피드백으로 사용자경험 향상
1 parent ba3a066 commit b1f4410

2 files changed

Lines changed: 75 additions & 15 deletions

File tree

Projects/Presentation/Sources/EmotionRegister/View/EmotionCollectionViewLayout.swift

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
import UIKit
99

1010
final class EmotionCollectionViewLayout: UICollectionViewFlowLayout {
11-
private let itemWidthRatio: CGFloat = 0.5
12-
private let lineSpacing: CGFloat = 16
11+
private enum Layout {
12+
static let itemWidthRatio: CGFloat = 0.5
13+
static let lineSpacing: CGFloat = 16
14+
static let bottomSpacing: CGFloat = 100
15+
}
16+
1317
private var baseDistance: CGFloat { itemSize.width + minimumLineSpacing }
1418

1519
override class var layoutAttributesClass: AnyClass { EmotionCollectionViewLayoutAttributes.self }
@@ -19,14 +23,18 @@ final class EmotionCollectionViewLayout: UICollectionViewFlowLayout {
1923
guard let collectionView else { return }
2024

2125
scrollDirection = .horizontal
22-
minimumLineSpacing = lineSpacing
26+
minimumLineSpacing = Layout.lineSpacing
2327

24-
let itemWidth = floor(collectionView.bounds.width * itemWidthRatio)
25-
let itemHeight = collectionView.bounds.height
28+
let itemWidth = floor(collectionView.bounds.width * Layout.itemWidthRatio)
29+
let itemHeight = collectionView.bounds.height - Layout.bottomSpacing
2630
itemSize = CGSize(width: itemWidth, height: itemHeight)
2731

2832
let insetX = (collectionView.bounds.width - itemWidth) / 2
29-
sectionInset = UIEdgeInsets(top: 0, left: insetX, bottom: 0, right: insetX)
33+
sectionInset = UIEdgeInsets(
34+
top: 0,
35+
left: insetX,
36+
bottom: Layout.bottomSpacing,
37+
right: insetX)
3038

3139
collectionView.decelerationRate = .fast
3240
}
@@ -75,4 +83,14 @@ final class EmotionCollectionViewLayout: UICollectionViewFlowLayout {
7583
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
7684
return true
7785
}
86+
87+
func fetchcurrentIndex() -> Int? {
88+
guard let collectionView else { return nil }
89+
90+
let centerX = collectionView.contentOffset.x + (collectionView.bounds.width / 2)
91+
let insetX = (collectionView.bounds.width - itemSize.width) / 2
92+
93+
let relativeX = centerX - insetX
94+
return Int(round(relativeX / baseDistance))
95+
}
7896
}

Projects/Presentation/Sources/EmotionRegister/View/EmotionRegistrationViewController.swift

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
1818
static let smallMarbleImageSize: CGFloat = 40
1919
static let emotionLabelWidth: CGFloat = 92
2020
static let emotionLabelHeight: CGFloat = 36
21-
static let emotionCollectionViewHeight: CGFloat = 191
21+
static let emotionCollectionViewHeight: CGFloat = 291
2222
static let emotionMarbleImageViewSize: CGFloat = 140
2323
static let emotionMarbleImageViewTopSpacing: CGFloat = 13
2424
static let speechImageTopSpacing: CGFloat = 26
@@ -33,11 +33,12 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
3333
static let foromThumbLeadingSpacing: CGFloat = 71
3434
static let handMarbleImageViewBottomSpacing: CGFloat = 7
3535
static let handMarbleImageViewTopSpacing: CGFloat = 50
36-
static let infoLabelTopSpacing: CGFloat = 30
36+
static let infoLabelTopSpacing: CGFloat = -70
3737
static let doubleChevronIconSize: CGFloat = 24
3838
static let doubleChevronIconHorizontalSpacing: CGFloat = 26
3939
static let doubleChevronIconTopSpacing: CGFloat = 10
4040
static let infoViewSize: CGFloat = 0
41+
static let infoSwipeOverlayViewHeight = 100
4142
}
4243

4344
private let smallMarbleScrollView = UIScrollView()
@@ -55,14 +56,20 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
5556
private let leftDoubleChevronImageView = UIImageView()
5657
private let rightDoubleChevronImageView = UIImageView()
5758
private let downDoubleChevronImageView = UIImageView()
59+
private let infoSwipeOverlayView = UIView()
60+
private let hapticGenerator = UISelectionFeedbackGenerator()
61+
private let itemSetCount = 7
5862
private var isMarbleHeld = false
5963
private var marbleImageViewMidY: CGFloat?
6064
private var marbleImageViewPanGesture: UIPanGestureRecognizer?
65+
private var infoSwipeOverlayViewPanGesturePanGesture: UIPanGestureRecognizer?
6166
private var emotion: Emotion?
6267
private var dataSource: UICollectionViewDiffableDataSource<Int, Emotion>?
6368
private var cancellables: Set<AnyCancellable>
69+
private var lastMarbleCellIndex: Int = 0
70+
6471
var itemCount: Int {
65-
return (dataSource?.snapshot().numberOfItems ?? 0) / 3
72+
return (dataSource?.snapshot().numberOfItems ?? 0) / itemSetCount
6673
}
6774

6875

@@ -121,6 +128,10 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
121128
speechLabel.font = BitnagilFont.init(style:.cafe24Title2, weight: .light).font
122129
speechLabel.textColor = .clear
123130

131+
infoSwipeOverlayView.backgroundColor = .clear
132+
infoSwipeOverlayView.isUserInteractionEnabled = false
133+
infoSwipeOverlayView.backgroundColor = .clear
134+
124135
infoLabel.numberOfLines = 0
125136
infoLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font
126137
infoLabel.textColor = BitnagilColor.gray50
@@ -160,6 +171,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
160171
view.addSubview(handMarbleView)
161172
view.addSubview(thumbImageView)
162173
view.addSubview(infoView)
174+
view.addSubview(infoSwipeOverlayView)
163175
smallMarbleScrollView.addSubview(smallMarbleStackView)
164176
infoView.addSubview(infoLabel)
165177
infoView.addSubview(leftDoubleChevronImageView)
@@ -258,6 +270,12 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
258270
make.bottom.equalTo(thumbImageView.snp.bottom).offset(-Layout.handMarbleImageViewBottomSpacing)
259271
make.size.equalTo(Layout.emotionMarbleImageViewSize)
260272
}
273+
274+
infoSwipeOverlayView.snp.makeConstraints { make in
275+
make.horizontalEdges.equalToSuperview()
276+
make.bottom.equalTo(emotionCollectionView.snp.bottom)
277+
make.height.equalTo(Layout.infoSwipeOverlayViewHeight)
278+
}
261279
}
262280

263281
override func bind() {
@@ -267,11 +285,22 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
267285
guard let self = self else { return }
268286

269287
let originalEmotionCount = emotions.count
270-
let tripledEmotions = emotions.map({ $0.copy() }) + emotions + emotions.map({ $0.copy() })
288+
let centerIndex = itemSetCount / 2 + 1
289+
let multipliedEmotions = (0..<7).flatMap { i in
290+
i == centerIndex ? emotions : emotions.map { $0.copy() }
291+
}
292+
293+
guard
294+
let layout = emotionCollectionView.collectionViewLayout as? EmotionCollectionViewLayout,
295+
let currentIndex = layout.fetchcurrentIndex()
296+
else { return }
297+
298+
lastMarbleCellIndex = currentIndex
299+
print(lastMarbleCellIndex)
271300

272301
var snapshot = NSDiffableDataSourceSnapshot<Int, Emotion>()
273302
snapshot.appendSections([0])
274-
snapshot.appendItems(tripledEmotions)
303+
snapshot.appendItems(multipliedEmotions)
275304
self.dataSource?.apply(snapshot, animatingDifferences: false)
276305

277306
self.scrollToIndex(index: originalEmotionCount)
@@ -350,8 +379,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
350379
}
351380

352381
private func configureMarbleImageView() {
353-
marbleImageViewPanGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(gesture:)))
354-
marbleImageViewPanGesture?.cancelsTouchesInView = false
382+
marbleImageViewPanGesture = UIPanGestureRecognizer(target: self, action: #selector(handleEmotionCollectionViewPanning(gesture:)))
355383

356384
handMarbleView.layer.cornerRadius = Layout.emotionMarbleImageViewSize / 2
357385
handMarbleView.layer.masksToBounds = true
@@ -377,7 +405,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
377405
}
378406
}
379407

380-
@objc private func handlePan(gesture: UIPanGestureRecognizer) {
408+
@objc private func handleEmotionCollectionViewPanning(gesture: UIPanGestureRecognizer) {
381409
guard let marbleImageViewMidY else { return }
382410

383411
switch gesture.state {
@@ -454,8 +482,22 @@ extension EmotionRegistrationViewController: UICollectionViewDelegate {
454482
}
455483

456484
extension EmotionRegistrationViewController: UIScrollViewDelegate {
485+
func scrollViewDidScroll(_ scrollView: UIScrollView) {
486+
guard
487+
let layout = emotionCollectionView.collectionViewLayout as? EmotionCollectionViewLayout,
488+
let currentIndex = layout.fetchcurrentIndex(),
489+
lastMarbleCellIndex != currentIndex
490+
else { return }
491+
492+
lastMarbleCellIndex = currentIndex
493+
hapticGenerator.selectionChanged()
494+
hapticGenerator.prepare()
495+
496+
}
497+
457498
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
458499
emotionMarbleImageView.isHidden = true
500+
hapticGenerator.prepare()
459501
}
460502

461503
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
@@ -470,7 +512,7 @@ extension EmotionRegistrationViewController: UIScrollViewDelegate {
470512
else { return }
471513

472514
let index = indexPath.row % itemCount
473-
if indexPath.row < itemCount / 3 || indexPath.row >= itemCount * 2 / 3 {
515+
if indexPath.row < itemCount / itemSetCount || indexPath.row >= itemCount * (itemSetCount / 2 + 1) / itemSetCount {
474516
scrollToIndex(index: index)
475517
}
476518
viewModel.action(input: .selectEmotion(index: index))

0 commit comments

Comments
 (0)