Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
import UIKit

final class EmotionCollectionViewLayout: UICollectionViewFlowLayout {
private let itemWidthRatio: CGFloat = 0.5
private let lineSpacing: CGFloat = 16
private enum Layout {
static let itemWidthRatio: CGFloat = 0.5
static let lineSpacing: CGFloat = 16
static let bottomSpacing: CGFloat = 100
}

private var baseDistance: CGFloat { itemSize.width + minimumLineSpacing }

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

scrollDirection = .horizontal
minimumLineSpacing = lineSpacing
minimumLineSpacing = Layout.lineSpacing

let itemWidth = floor(collectionView.bounds.width * itemWidthRatio)
let itemHeight = collectionView.bounds.height
let itemWidth = floor(collectionView.bounds.width * Layout.itemWidthRatio)
let itemHeight = collectionView.bounds.height - Layout.bottomSpacing
itemSize = CGSize(width: itemWidth, height: itemHeight)

let insetX = (collectionView.bounds.width - itemWidth) / 2
sectionInset = UIEdgeInsets(top: 0, left: insetX, bottom: 0, right: insetX)
sectionInset = UIEdgeInsets(
top: 0,
left: insetX,
bottom: Layout.bottomSpacing,
right: insetX)

collectionView.decelerationRate = .fast
}
Expand Down Expand Up @@ -75,4 +83,14 @@ final class EmotionCollectionViewLayout: UICollectionViewFlowLayout {
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}

func fetchcurrentIndex() -> Int? {
guard let collectionView else { return nil }

let centerX = collectionView.contentOffset.x + (collectionView.bounds.width / 2)
let insetX = (collectionView.bounds.width - itemSize.width) / 2

let relativeX = centerX - insetX
return Int(round(relativeX / baseDistance))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
static let smallMarbleImageSize: CGFloat = 40
static let emotionLabelWidth: CGFloat = 92
static let emotionLabelHeight: CGFloat = 36
static let emotionCollectionViewHeight: CGFloat = 191
static let emotionCollectionViewHeight: CGFloat = 291
static let emotionMarbleImageViewSize: CGFloat = 140
static let emotionMarbleImageViewTopSpacing: CGFloat = 13
static let speechImageTopSpacing: CGFloat = 26
Expand All @@ -33,11 +33,12 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
static let foromThumbLeadingSpacing: CGFloat = 71
static let handMarbleImageViewBottomSpacing: CGFloat = 7
static let handMarbleImageViewTopSpacing: CGFloat = 50
static let infoLabelTopSpacing: CGFloat = 30
static let infoLabelTopSpacing: CGFloat = -70
static let doubleChevronIconSize: CGFloat = 24
static let doubleChevronIconHorizontalSpacing: CGFloat = 26
static let doubleChevronIconTopSpacing: CGFloat = 10
static let infoViewSize: CGFloat = 0
static let infoSwipeOverlayViewHeight = 100
}

private let smallMarbleScrollView = UIScrollView()
Expand All @@ -55,14 +56,19 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
private let leftDoubleChevronImageView = UIImageView()
private let rightDoubleChevronImageView = UIImageView()
private let downDoubleChevronImageView = UIImageView()
private let infoSwipeOverlayView = UIView()
private let hapticGenerator = UISelectionFeedbackGenerator()
private let itemSetCount = 7
private var isMarbleHeld = false
private var marbleImageViewMidY: CGFloat?
private var marbleImageViewPanGesture: UIPanGestureRecognizer?
private var emotion: Emotion?
private var dataSource: UICollectionViewDiffableDataSource<Int, Emotion>?
private var cancellables: Set<AnyCancellable>
private var lastMarbleCellIndex: Int = 0

var itemCount: Int {
return (dataSource?.snapshot().numberOfItems ?? 0) / 3
return (dataSource?.snapshot().numberOfItems ?? 0) / itemSetCount
}


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

infoSwipeOverlayView.backgroundColor = .clear
infoSwipeOverlayView.isUserInteractionEnabled = false

infoLabel.numberOfLines = 0
infoLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font
infoLabel.textColor = BitnagilColor.gray50
Expand Down Expand Up @@ -160,6 +169,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
view.addSubview(handMarbleView)
view.addSubview(thumbImageView)
view.addSubview(infoView)
view.addSubview(infoSwipeOverlayView)
smallMarbleScrollView.addSubview(smallMarbleStackView)
infoView.addSubview(infoLabel)
infoView.addSubview(leftDoubleChevronImageView)
Expand Down Expand Up @@ -258,6 +268,12 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
make.bottom.equalTo(thumbImageView.snp.bottom).offset(-Layout.handMarbleImageViewBottomSpacing)
make.size.equalTo(Layout.emotionMarbleImageViewSize)
}

infoSwipeOverlayView.snp.makeConstraints { make in
make.horizontalEdges.equalToSuperview()
make.bottom.equalTo(emotionCollectionView.snp.bottom)
make.height.equalTo(Layout.infoSwipeOverlayViewHeight)
}
}

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

let originalEmotionCount = emotions.count
let tripledEmotions = emotions.map({ $0.copy() }) + emotions + emotions.map({ $0.copy() })
let centerIndex = itemSetCount / 2 + 1
let multipliedEmotions = (0..<itemSetCount).flatMap { i in
i == centerIndex ? emotions : emotions.map { $0.copy() }
}

guard
let layout = emotionCollectionView.collectionViewLayout as? EmotionCollectionViewLayout,
let currentIndex = layout.fetchcurrentIndex()
else { return }

lastMarbleCellIndex = currentIndex

var snapshot = NSDiffableDataSourceSnapshot<Int, Emotion>()
snapshot.appendSections([0])
snapshot.appendItems(tripledEmotions)
snapshot.appendItems(multipliedEmotions)
self.dataSource?.apply(snapshot, animatingDifferences: false)

self.scrollToIndex(index: originalEmotionCount)
Expand Down Expand Up @@ -350,8 +376,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
}

private func configureMarbleImageView() {
marbleImageViewPanGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(gesture:)))
marbleImageViewPanGesture?.cancelsTouchesInView = false
marbleImageViewPanGesture = UIPanGestureRecognizer(target: self, action: #selector(handleEmotionCollectionViewPanning(gesture:)))

handMarbleView.layer.cornerRadius = Layout.emotionMarbleImageViewSize / 2
handMarbleView.layer.masksToBounds = true
Expand All @@ -377,7 +402,7 @@ final class EmotionRegistrationViewController: BaseViewController<EmotionRegiste
}
}

@objc private func handlePan(gesture: UIPanGestureRecognizer) {
@objc private func handleEmotionCollectionViewPanning(gesture: UIPanGestureRecognizer) {
guard let marbleImageViewMidY else { return }

switch gesture.state {
Expand Down Expand Up @@ -454,8 +479,22 @@ extension EmotionRegistrationViewController: UICollectionViewDelegate {
}

extension EmotionRegistrationViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard
let layout = emotionCollectionView.collectionViewLayout as? EmotionCollectionViewLayout,
let currentIndex = layout.fetchcurrentIndex(),
lastMarbleCellIndex != currentIndex
else { return }

lastMarbleCellIndex = currentIndex
hapticGenerator.selectionChanged()
hapticGenerator.prepare()

}

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
emotionMarbleImageView.isHidden = true
hapticGenerator.prepare()
}

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

let index = indexPath.row % itemCount
if indexPath.row < itemCount / 3 || indexPath.row >= itemCount * 2 / 3 {
if indexPath.row < itemCount / itemSetCount || indexPath.row >= itemCount * (itemSetCount / 2 + 1) / itemSetCount {
scrollToIndex(index: index)
}
Comment on lines +512 to 514
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

재센터 조건이 대부분의 인덱스에서 항상 참이 됩니다.
itemCount / itemSetCount 기준은 임계값이 너무 작아 대부분의 스크롤에서 scrollToIndex가 호출될 수 있습니다. 총 아이템 수 기준으로 첫/마지막 세트에서만 재센터하도록 조정하는 편이 안전합니다.

🛠️ 수정 제안
-        if indexPath.row < itemCount / itemSetCount || indexPath.row >= itemCount * (itemSetCount / 2 + 1) / itemSetCount {
+        let totalItemCount = itemCount * itemSetCount
+        if indexPath.row < itemCount || indexPath.row >= totalItemCount - itemCount {
             scrollToIndex(index: index)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if indexPath.row < itemCount / itemSetCount || indexPath.row >= itemCount * (itemSetCount / 2 + 1) / itemSetCount {
scrollToIndex(index: index)
}
let totalItemCount = itemCount * itemSetCount
if indexPath.row < itemCount || indexPath.row >= totalItemCount - itemCount {
scrollToIndex(index: index)
}
🤖 Prompt for AI Agents
In
`@Projects/Presentation/Sources/EmotionRegister/View/EmotionRegistrationViewController.swift`
around lines 515 - 517, The current recenter condition wrongly uses itemCount /
itemSetCount and triggers for most indices; update the check in
EmotionRegistrationViewController so recentering only happens when the
tapped/visible index is in the very first or very last repeated set: use if
indexPath.row < itemCount || indexPath.row >= itemCount * (itemSetCount - 1) {
scrollToIndex(index: index) } (use the existing index variable and the itemCount
and itemSetCount symbols) so only the first set (indices < itemCount) or the
last set (indices >= itemCount * (itemSetCount - 1)) cause scrollToIndex to be
called.

viewModel.action(input: .selectEmotion(index: index))
Expand Down