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
@@ -1,10 +1,3 @@
//
// AdminBottomSheetReactor.swift
// Poppool
//
// Created by 김기현 on 1/13/25.
//

import Foundation
import ReactorKit

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import UIKit

import DesignSystem
import Infrastructure

import ReactorKit
import RxCocoa
import RxSwift
import SnapKit
import UIKit

final class AdminBottomSheetView: UIView {

Expand Down Expand Up @@ -39,7 +37,6 @@ final class AdminBottomSheetView: UIView {
let closeButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named: "icon_xmark"), for: .normal)

button.tintColor = .black
return button
}()
Expand Down Expand Up @@ -78,16 +75,12 @@ final class AdminBottomSheetView: UIView {
trailing: 20
)
section.interGroupSpacing = 16

return section
}

let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: layout
)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = .clear
collectionView.isScrollEnabled = false
// Register cell here if needed, e.g. collectionView.register(TagSectionCell.self, forCellWithReuseIdentifier: TagSectionCell.identifiers)
return collectionView
}()

Expand All @@ -102,10 +95,7 @@ final class AdminBottomSheetView: UIView {
)
button.isEnabled = false
button.contentEdgeInsets = UIEdgeInsets(
top: 9,
left: 16,
bottom: 9,
right: 12
top: 9, left: 16, bottom: 9, right: 12
)
return button
}()
Expand All @@ -120,10 +110,7 @@ final class AdminBottomSheetView: UIView {
)
button.isEnabled = false
button.contentEdgeInsets = UIEdgeInsets(
top: 9,
left: 16,
bottom: 9,
right: 12
top: 9, left: 16, bottom: 9, right: 12
)
return button
}()
Expand Down Expand Up @@ -168,7 +155,7 @@ final class AdminBottomSheetView: UIView {
private func setupConstraints() {
containerView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(headerView.snp.top)
make.top.equalToSuperview()
}

headerView.snp.makeConstraints { make in
Expand Down Expand Up @@ -212,18 +199,40 @@ final class AdminBottomSheetView: UIView {
}

// MARK: - Public Methods
func updateContentVisibility(isCategorySelected: Bool) {
Logger.log("높이 변경 시작: \(isCategorySelected ? "카테고리" : "상태값")", category: .debug)

let newHeight: CGFloat = isCategorySelected ? 200 : 160

// 애니메이션 없이 바로 적용
contentHeightConstraint?.update(offset: newHeight)
contentCollectionView.invalidateIntrinsicContentSize()

setNeedsLayout()
layoutIfNeeded()
func calculateCollectionViewHeight(for items: [String]) -> CGFloat {
let sectionInsets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
let itemSpacing: CGFloat = 12
let lineSpacing: CGFloat = 16

let collectionViewWidth = UIScreen.main.bounds.width
let availableWidth = collectionViewWidth - sectionInsets.left - sectionInsets.right

var currentRowWidth: CGFloat = 0
var numberOfRows = 1

for (index, item) in items.enumerated() {
let text = item as NSString
let textSize = text.size(withAttributes: [
.font: UIFont.korFont(style: .medium, size: 13)
])
let itemWidth = textSize.width + 32 // padding: 16 left/right each

if index == 0 {
currentRowWidth = itemWidth
} else {
let widthWithSpacing = currentRowWidth + itemSpacing + itemWidth
if widthWithSpacing > availableWidth {
numberOfRows += 1
currentRowWidth = itemWidth
} else {
currentRowWidth = widthWithSpacing
}
}
}

Logger.log("높이 변경 완료", category: .debug)
let itemHeight: CGFloat = 36
return sectionInsets.top + sectionInsets.bottom +
(CGFloat(numberOfRows) * itemHeight) +
(CGFloat(max(0, numberOfRows - 1)) * lineSpacing)
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import UIKit

import DesignSystem
import Infrastructure

import ReactorKit
import RxCocoa
import RxSwift
import SnapKit
import UIKit

final class AdminBottomSheetViewController: BaseViewController, View {

typealias Reactor = AdminBottomSheetReactor

// MARK: - Properties
private let mainView = AdminBottomSheetView()
private let dimmedView = UIView()
private lazy var dimmedView: UIView = {
let view = UIView()
view.backgroundColor = .black.withAlphaComponent(0.4)
view.alpha = 0
return view
}()
var disposeBag = DisposeBag()
private var containerViewBottomConstraint: Constraint?
private var containerHeightConstraint: Constraint?
private var tagSection: TagSection?

var onSave: (([String]) -> Void)?
Expand All @@ -43,37 +47,30 @@ final class AdminBottomSheetViewController: BaseViewController, View {
private func setupViews() {
view.backgroundColor = .clear

Logger.log("초기 뷰 계층:", category: .debug)
view.addSubview(dimmedView)
dimmedView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}

view.addSubview(mainView)
mainView.isUserInteractionEnabled = true
mainView.containerView.isUserInteractionEnabled = true
mainView.closeButton.isUserInteractionEnabled = true
mainView.segmentedControl.isUserInteractionEnabled = true
mainView.headerView.isUserInteractionEnabled = true

mainView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.height.equalTo(view.bounds.height * 0.45)
containerHeightConstraint = make.height.greaterThanOrEqualTo(400).constraint
containerViewBottomConstraint = make.bottom.equalTo(view.snp.bottom).constraint
}

Logger.log("mainView 추가 후 계층:", category: .debug)

dimmedView.backgroundColor = .black.withAlphaComponent(0.4)
dimmedView.alpha = 0
dimmedView.isUserInteractionEnabled = false
setupGestures()
}

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dimmedViewTapped))
private func setupGestures() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapDimmedView))
tapGesture.delegate = self
dimmedView.addGestureRecognizer(tapGesture)
tapGesture.cancelsTouchesInView = true // 터치 이벤트가 다른 뷰로 전달되도록 설정
view.insertSubview(dimmedView, belowSubview: mainView)

dimmedView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
dimmedView.isUserInteractionEnabled = true
}

Logger.log("최종 뷰 계층:", category: .debug)
@objc private func handleTapDimmedView() {
hideBottomSheet()
}

private func setupCollectionView() {
Expand All @@ -86,58 +83,58 @@ final class AdminBottomSheetViewController: BaseViewController, View {
// MARK: - Binding
func bind(reactor: Reactor) {
mainView.segmentedControl.rx.selectedSegmentIndex
.do(onNext: { _ in
})
.map { Reactor.Action.segmentChanged($0) }
.bind(to: reactor.action)
.disposed(by: disposeBag)
.map { Reactor.Action.segmentChanged($0) }
.bind(to: reactor.action)
.disposed(by: disposeBag)

mainView.resetButton.rx.tap
.map { Reactor.Action.resetFilters }
.bind(to: reactor.action)
.disposed(by: disposeBag)

mainView.contentCollectionView.rx.itemSelected
.withLatestFrom(reactor.state) { indexPath, state -> Reactor.Action in
let title = state.activeSegment == 0 ?
state.statusOptions[indexPath.item] :
state.categoryOptions[indexPath.item]

return state.activeSegment == 0 ?
.toggleStatusOption(title) :
.toggleCategoryOption(title)
}
.bind(to: reactor.action)
.disposed(by: disposeBag)

reactor.state.map { state in
let items = state.activeSegment == 0 ?
state.statusOptions :
state.categoryOptions
let selectedItems = state.activeSegment == 0 ?
state.selectedStatusOptions :
state.selectedCategoryOptions

return items.map {
TagSectionCell.Input(
title: $0,
isSelected: selectedItems.contains($0),
id: nil
)
.withLatestFrom(reactor.state) { indexPath, state -> Reactor.Action in
let title = state.activeSegment == 0 ?
state.statusOptions[indexPath.item] :
state.categoryOptions[indexPath.item]

return state.activeSegment == 0 ?
.toggleStatusOption(title) :
.toggleCategoryOption(title)
}
}
.bind(to: mainView.contentCollectionView.rx.items(
cellIdentifier: TagSectionCell.identifiers,
cellType: TagSectionCell.self
)) { _, item, cell in
cell.injection(with: item)
}
.disposed(by: disposeBag)
.bind(to: reactor.action)
.disposed(by: disposeBag)

reactor.state
.map { state in
let items = state.activeSegment == 0 ?
state.statusOptions :
state.categoryOptions
let selectedItems = state.activeSegment == 0 ?
state.selectedStatusOptions :
state.selectedCategoryOptions

return items.map {
TagSectionCell.Input(
title: $0,
isSelected: selectedItems.contains($0),
id: nil
)
}
}
.bind(to: mainView.contentCollectionView.rx.items(
cellIdentifier: TagSectionCell.identifiers,
cellType: TagSectionCell.self
)) { _, item, cell in
cell.injection(with: item)
}
.disposed(by: disposeBag)

// 세그먼트 변경 시 전체 시트 높이 업데이트
reactor.state.map { $0.activeSegment }
.distinctUntilChanged()
.bind { [weak self] index in
self?.mainView.updateContentVisibility(isCategorySelected: index == 1)
.bind { [weak self] _ in
self?.updateContainerHeight()
}
.disposed(by: disposeBag)

Expand Down Expand Up @@ -167,7 +164,6 @@ final class AdminBottomSheetViewController: BaseViewController, View {
}
.disposed(by: disposeBag)

// View Events
mainView.closeButton.rx.tap
.bind { [weak self] in
self?.hideBottomSheet()
Expand All @@ -189,13 +185,30 @@ final class AdminBottomSheetViewController: BaseViewController, View {
.disposed(by: disposeBag)
}

// MARK: - Actions
@objc private func dimmedViewTapped() {
hideBottomSheet()
// MARK: - Height Management
private func updateContainerHeight() {
guard let reactor = reactor else { return }

let items = reactor.currentState.activeSegment == 0 ?
reactor.currentState.statusOptions :
reactor.currentState.categoryOptions

let collectionViewHeight = mainView.calculateCollectionViewHeight(for: items)

let totalHeight = 60 + 50 + collectionViewHeight + 80 + 52 + 100

let finalHeight = min(max(totalHeight, 400), UIScreen.main.bounds.height * 0.8)

containerHeightConstraint?.update(offset: finalHeight)

self.view.layoutIfNeeded()
}

// MARK: - Show/Hide
func showBottomSheet() {
// 초기 높이 설정
updateContainerHeight()

UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) {
self.dimmedView.alpha = 1
self.containerViewBottomConstraint?.update(offset: 0)
Expand All @@ -218,3 +231,13 @@ final class AdminBottomSheetViewController: BaseViewController, View {
Logger.log("BottomSheet deinit", category: .debug)
}
}

extension AdminBottomSheetViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if gestureRecognizer.view == dimmedView {
let touchPoint = touch.location(in: view)
return !mainView.containerView.frame.contains(touchPoint)
}
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ final class AdminView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupLayout()
tableView.backgroundView = UIView()
tableView.backgroundView?.isUserInteractionEnabled = true
Comment on lines +100 to +101
Copy link
Member

Choose a reason for hiding this comment

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

이거 바텀시트 뒤에 탭할때 사라지게하는 관련 코드인거같은데

맞다면 디자인시스템에 PPModal(UIViewController+.swift에 위치) 참고해서 적용해보시면 좋을것같아요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updateContainerHeight로 탭이 바뀔때마다 탭별로 컨테이너 영역의 높이가 동적으로 계산되게끔 작성했던것으로 기억ㅎ는데
PPModal 같은경우에는 modal의 높이를 한번만 주입하기때문에 UI 상단영역이 깨져서 적용했던것으로 기억합니다 😂

Copy link
Member

Choose a reason for hiding this comment

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

아마 PPModal도 값을 주입하지 않으면 동적으로 modal의 높이를 계산하도록 내부적으로 구현되어있을텐데 한번 확인해보시면 좋을거같아요.

@zzangzzangguy 내일까지 어려우시다면 일단 관리자페이지는 사용자 경험과는 크게 문제가 없으니 이대로 넘어가시고 추후 작업으로 이슈 빼두시면 좋을것 같습니다 ☺️

}

required init?(coder: NSCoder) {
Expand Down
Loading