Skip to content

Commit e1d6541

Browse files
authored
[Fix] 감정등록 화면 QA 대응 (무한 스크롤 끊김, 스크롤 가능 영역 확장) (#79)
* fix: 감정등록 화면 QA 대응 (무한 스크롤 끊김, 스크롤 가능 영역 확장) - 구슬 collectionView 하단의 안내 문구에서도 collectionView의 스크롤이 가능하도록 수정 - 무한 스크롤이 끊기는 현상 개선 - 구슬 스크롤 시 진동 피드백으로 사용자경험 향상 * refactor: print 문 삭제 및 매직 넘버 삭제
1 parent ba3a066 commit e1d6541

File tree

2 files changed

+72
-15
lines changed

2 files changed

+72
-15
lines changed

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: 48 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,19 @@ 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?
6165
private var emotion: Emotion?
6266
private var dataSource: UICollectionViewDiffableDataSource<Int, Emotion>?
6367
private var cancellables: Set<AnyCancellable>
68+
private var lastMarbleCellIndex: Int = 0
69+
6470
var itemCount: Int {
65-
return (dataSource?.snapshot().numberOfItems ?? 0) / 3
71+
return (dataSource?.snapshot().numberOfItems ?? 0) / itemSetCount
6672
}
6773

6874

@@ -121,6 +127,9 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
121127
speechLabel.font = BitnagilFont.init(style:.cafe24Title2, weight: .light).font
122128
speechLabel.textColor = .clear
123129

130+
infoSwipeOverlayView.backgroundColor = .clear
131+
infoSwipeOverlayView.isUserInteractionEnabled = false
132+
124133
infoLabel.numberOfLines = 0
125134
infoLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font
126135
infoLabel.textColor = BitnagilColor.gray50
@@ -160,6 +169,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
160169
view.addSubview(handMarbleView)
161170
view.addSubview(thumbImageView)
162171
view.addSubview(infoView)
172+
view.addSubview(infoSwipeOverlayView)
163173
smallMarbleScrollView.addSubview(smallMarbleStackView)
164174
infoView.addSubview(infoLabel)
165175
infoView.addSubview(leftDoubleChevronImageView)
@@ -258,6 +268,12 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
258268
make.bottom.equalTo(thumbImageView.snp.bottom).offset(-Layout.handMarbleImageViewBottomSpacing)
259269
make.size.equalTo(Layout.emotionMarbleImageViewSize)
260270
}
271+
272+
infoSwipeOverlayView.snp.makeConstraints { make in
273+
make.horizontalEdges.equalToSuperview()
274+
make.bottom.equalTo(emotionCollectionView.snp.bottom)
275+
make.height.equalTo(Layout.infoSwipeOverlayViewHeight)
276+
}
261277
}
262278

263279
override func bind() {
@@ -267,11 +283,21 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
267283
guard let self = self else { return }
268284

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

272298
var snapshot = NSDiffableDataSourceSnapshot<Int, Emotion>()
273299
snapshot.appendSections([0])
274-
snapshot.appendItems(tripledEmotions)
300+
snapshot.appendItems(multipliedEmotions)
275301
self.dataSource?.apply(snapshot, animatingDifferences: false)
276302

277303
self.scrollToIndex(index: originalEmotionCount)
@@ -350,8 +376,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
350376
}
351377

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

356381
handMarbleView.layer.cornerRadius = Layout.emotionMarbleImageViewSize / 2
357382
handMarbleView.layer.masksToBounds = true
@@ -377,7 +402,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
377402
}
378403
}
379404

380-
@objc private func handlePan(gesture: UIPanGestureRecognizer) {
405+
@objc private func handleEmotionCollectionViewPanning(gesture: UIPanGestureRecognizer) {
381406
guard let marbleImageViewMidY else { return }
382407

383408
switch gesture.state {
@@ -454,8 +479,22 @@ extension EmotionRegistrationViewController: UICollectionViewDelegate {
454479
}
455480

456481
extension EmotionRegistrationViewController: UIScrollViewDelegate {
482+
func scrollViewDidScroll(_ scrollView: UIScrollView) {
483+
guard
484+
let layout = emotionCollectionView.collectionViewLayout as? EmotionCollectionViewLayout,
485+
let currentIndex = layout.fetchcurrentIndex(),
486+
lastMarbleCellIndex != currentIndex
487+
else { return }
488+
489+
lastMarbleCellIndex = currentIndex
490+
hapticGenerator.selectionChanged()
491+
hapticGenerator.prepare()
492+
493+
}
494+
457495
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
458496
emotionMarbleImageView.isHidden = true
497+
hapticGenerator.prepare()
459498
}
460499

461500
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
@@ -470,7 +509,7 @@ extension EmotionRegistrationViewController: UIScrollViewDelegate {
470509
else { return }
471510

472511
let index = indexPath.row % itemCount
473-
if indexPath.row < itemCount / 3 || indexPath.row >= itemCount * 2 / 3 {
512+
if indexPath.row < itemCount / itemSetCount || indexPath.row >= itemCount * (itemSetCount / 2 + 1) / itemSetCount {
474513
scrollToIndex(index: index)
475514
}
476515
viewModel.action(input: .selectEmotion(index: index))

0 commit comments

Comments
 (0)