Skip to content

Commit 9f273f8

Browse files
committed
🧩 :: 관심분야 cell 추가
1 parent 7b27218 commit 9f273f8

File tree

3 files changed

+142
-67
lines changed

3 files changed

+142
-67
lines changed

Projects/Presentation/Sources/InterestField/Components/Cell/MajorCollectionViewCell.swift

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import Domain
33
import DesignSystem
44
import SnapKit
55
import Then
6+
import RxSwift
7+
import RxCocoa
68

7-
final class MajorCollectionViewCell: BaseCollectionViewCell<InterestsEntity> {
9+
final class MajorCollectionViewCell: BaseCollectionViewCell<CodeEntity> {
810
static let identifier = "MajorCollectionViewCell"
911

1012
public var isCheck: Bool = false {
@@ -18,41 +20,56 @@ final class MajorCollectionViewCell: BaseCollectionViewCell<InterestsEntity> {
1820
}
1921
}
2022

21-
private let majorLabel = UILabel().then {
22-
$0.textAlignment = .center
23-
$0.font = UIFont.jobisFont(.body)
24-
$0.numberOfLines = 1
25-
$0.adjustsFontSizeToFitWidth = true
26-
$0.minimumScaleFactor = 0.8
27-
$0.setContentHuggingPriority(.required, for: .horizontal)
28-
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
29-
}
23+
private var disposeBag = DisposeBag()
24+
private let majorLabel = UILabel()
3025

3126
override func addView() {
3227
self.contentView.addSubview(majorLabel)
3328
}
3429

3530
override func setLayout() {
3631
majorLabel.snp.makeConstraints {
37-
$0.top.bottom.equalToSuperview().inset(6)
38-
$0.center.equalToSuperview()
39-
$0.leading.greaterThanOrEqualToSuperview().offset(16)
40-
$0.trailing.lessThanOrEqualToSuperview().offset(-16)
32+
$0.centerY.equalToSuperview()
33+
$0.leading.trailing.equalToSuperview().inset(16)
34+
$0.height.equalTo(31)
4135
}
4236
}
4337

4438
override func configureView() {
4539
self.backgroundColor = .GrayScale.gray30
46-
self.layer.cornerRadius = 18
40+
self.layer.cornerRadius = 15.5
4741
self.layer.masksToBounds = true
42+
majorLabel.textAlignment = .center
43+
majorLabel.font = UIFont.jobisFont(.body)
44+
majorLabel.numberOfLines = 1
45+
majorLabel.lineBreakMode = .byClipping
46+
majorLabel.adjustsFontSizeToFitWidth = false
4847
}
4948

50-
override func adapt(model: InterestsEntity) {
49+
override func adapt(model: CodeEntity) {
5150
super.adapt(model: model)
52-
majorLabel.setJobisText(
53-
model.keyword,
54-
font: .body,
55-
color: .Primary.blue40
51+
majorLabel.setJobisText(model.keyword, font: .body, color: .Primary.blue40)
52+
invalidateIntrinsicContentSize()
53+
}
54+
55+
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
56+
let targetSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: 31)
57+
let fittingSize = contentView.systemLayoutSizeFitting(
58+
targetSize,
59+
withHorizontalFittingPriority: .fittingSizeLevel,
60+
verticalFittingPriority: .required
5661
)
62+
63+
let minWidth: CGFloat = 60
64+
let width = max(fittingSize.width, minWidth)
65+
66+
layoutAttributes.frame.size = CGSize(width: width, height: 31)
67+
return layoutAttributes
68+
}
69+
70+
override var intrinsicContentSize: CGSize {
71+
let labelSize = majorLabel.intrinsicContentSize
72+
let width = labelSize.width + 32
73+
return CGSize(width: width, height: 31)
5774
}
5875
}

Projects/Presentation/Sources/InterestField/InterestFieldViewController.swift

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import DesignSystem
88
import Domain
99

1010
public final class InterestFieldViewController: BaseViewController<InterestFieldViewModel> {
11-
private let viewAppearRelay = PublishRelay<Void>()
11+
private let selectedIndexesRelay = BehaviorRelay<Set<IndexPath>>(value: [])
12+
private let interestsRelay = BehaviorRelay<[CodeEntity]>(value: [])
13+
private let selectedInterestsRelay = BehaviorRelay<[CodeEntity]>(value: [])
14+
1215
private let interestFieldTitleLabel = UILabel().then {
1316
$0.setJobisText(
1417
"님의\n관심사를 선택해주세요",
@@ -26,7 +29,25 @@ public final class InterestFieldViewController: BaseViewController<InterestField
2629
)
2730
}
2831

29-
private let majorCollectionView = InterestFieldCollectionView()
32+
private let layout = AlignedCollectionViewFlowLayout().then {
33+
$0.minimumLineSpacing = 8
34+
$0.minimumInteritemSpacing = 8
35+
$0.scrollDirection = .vertical
36+
$0.estimatedItemSize = CGSize(width: 100, height: 31)
37+
$0.sectionInset = .zero
38+
}
39+
40+
private lazy var majorCollectionView = UICollectionView(
41+
frame: .zero,
42+
collectionViewLayout: layout
43+
).then {
44+
$0.backgroundColor = .clear
45+
$0.isScrollEnabled = false
46+
$0.register(
47+
MajorCollectionViewCell.self,
48+
forCellWithReuseIdentifier: MajorCollectionViewCell.identifier
49+
)
50+
}
3051

3152
private let selectButton = JobisButton(style: .main).then {
3253
$0.setText("관심 분야를 선택해 주세요!")
@@ -57,45 +78,56 @@ public final class InterestFieldViewController: BaseViewController<InterestField
5778
$0.bottom.equalTo(selectButton.snp.top).offset(-24)
5879
}
5980
selectButton.snp.makeConstraints {
60-
$0.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom).inset(12)
81+
$0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).inset(12)
6182
$0.leading.trailing.equalToSuperview().inset(24)
6283
}
6384
}
6485

65-
public override func configureNavigation() {
66-
setSmallTitle(title: "관심사 설정")
67-
self.navigationItem.largeTitleDisplayMode = .never
68-
self.hideTabbar()
69-
}
70-
7186
public override func bind() {
7287
let input = InterestFieldViewModel.Input(
73-
viewAppear: viewAppearRelay,
88+
viewAppear: viewWillAppearPublisher,
7489
selectButtonDidTap: selectButton.rx.tap.asSignal(),
75-
selectedInterests: majorCollectionView.selectedInterests
90+
selectedInterests: selectedInterestsRelay.asObservable()
7691
)
7792

7893
let output = viewModel.transform(input)
7994

80-
rx.methodInvoked(#selector(UIViewController.viewWillAppear(_:)))
81-
.map { _ in }
82-
.bind(to: viewAppearRelay)
95+
output.availableInterests
96+
.bind(to: interestsRelay)
8397
.disposed(by: disposeBag)
8498

85-
output.availableInterests
86-
.subscribe(onNext: { [weak self] list in
87-
self?.majorCollectionView.updateInterests(list)
88-
})
99+
interestsRelay
100+
.bind(to: majorCollectionView.rx.items(
101+
cellIdentifier: MajorCollectionViewCell.identifier,
102+
cellType: MajorCollectionViewCell.self
103+
)) { [weak self] index, codeEntity, cell in
104+
let indexPath = IndexPath(item: index, section: 0)
105+
let isSelected = self?.selectedIndexesRelay.value.contains(indexPath) ?? false
106+
107+
cell.adapt(model: codeEntity)
108+
cell.isCheck = isSelected
109+
}
89110
.disposed(by: disposeBag)
90111

91-
Observable.combineLatest(
92-
output.availableInterests,
93-
output.userSavedInterests
94-
)
95-
.subscribe(onNext: { [weak self] (availableInterests, userInterests) in
96-
self?.majorCollectionView.preSelectInterests(userInterests)
97-
})
98-
.disposed(by: disposeBag)
112+
majorCollectionView.rx.itemSelected
113+
.withUnretained(self)
114+
.bind { owner, indexPath in
115+
var currentSelected = owner.selectedIndexesRelay.value
116+
117+
if currentSelected.contains(indexPath) {
118+
currentSelected.remove(indexPath)
119+
} else {
120+
currentSelected.insert(indexPath)
121+
}
122+
123+
owner.selectedIndexesRelay.accept(currentSelected)
124+
owner.updateSelectedInterests()
125+
126+
DispatchQueue.main.async {
127+
owner.majorCollectionView.reloadItems(at: [indexPath])
128+
}
129+
}
130+
.disposed(by: disposeBag)
99131

100132
output.selectedInterests
101133
.map { $0.count }
@@ -110,5 +142,28 @@ public final class InterestFieldViewController: BaseViewController<InterestField
110142
}
111143
})
112144
.disposed(by: disposeBag)
145+
146+
output.studentName
147+
.drive(onNext: { [weak self] name in
148+
self?.interestFieldTitleLabel.setJobisText(
149+
"\(name)님의\n관심사를 선택해주세요",
150+
font: .smallBody,
151+
color: .GrayScale.gray90
152+
)
153+
})
154+
.disposed(by: disposeBag)
155+
}
156+
157+
public override func configureNavigation() {
158+
setSmallTitle(title: "관심 분야 선택")
159+
navigationItem.largeTitleDisplayMode = .never
160+
hideTabbar()
161+
}
162+
163+
private func updateSelectedInterests() {
164+
let currentInterests = interestsRelay.value
165+
let currentSelectedIndexes = selectedIndexesRelay.value
166+
let selectedInterests = currentSelectedIndexes.map { currentInterests[$0.item] }
167+
selectedInterestsRelay.accept(selectedInterests)
113168
}
114169
}

Projects/Presentation/Sources/InterestField/InterestFieldViewModel.swift

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ public final class InterestFieldViewModel: BaseViewModel, Stepper {
1010
private let disposeBag = DisposeBag()
1111
private let fetchCodeListUseCase: FetchCodeListUseCase
1212
private let changeInterestsUseCase: ChangeInterestsUseCase
13-
private let isLoading = PublishRelay<Bool>()
13+
private let fetchStudentInfoUseCase: FetchStudentInfoUseCase
14+
private let studentNameRelay = BehaviorRelay<String>(value: "")
1415

15-
init(
16+
public init(
1617
fetchCodeListUseCase: FetchCodeListUseCase,
17-
changeInterestsUseCase: ChangeInterestsUseCase
18+
changeInterestsUseCase: ChangeInterestsUseCase,
19+
fetchStudentInfoUseCase: FetchStudentInfoUseCase
1820
) {
1921
self.fetchCodeListUseCase = fetchCodeListUseCase
2022
self.changeInterestsUseCase = changeInterestsUseCase
23+
self.fetchStudentInfoUseCase = fetchStudentInfoUseCase
24+
fetchStudentInfo()
2125
}
2226

2327
public struct Input {
@@ -29,7 +33,7 @@ public final class InterestFieldViewModel: BaseViewModel, Stepper {
2933
public struct Output {
3034
let availableInterests: BehaviorRelay<[CodeEntity]>
3135
let selectedInterests: Driver<[CodeEntity]>
32-
let isLoading: Driver<Bool>
36+
let studentName: Driver<String>
3337
}
3438

3539
public func transform(_ input: Input) -> Output {
@@ -50,28 +54,15 @@ public final class InterestFieldViewModel: BaseViewModel, Stepper {
5054
.withLatestFrom(selectedInterests)
5155
.emit(onNext: { [weak self] interests in
5256
guard let self = self else { return }
53-
print("선택된 관심분야: \(interests.map { $0.keyword })")
54-
self.isLoading.accept(true)
5557

56-
let interestsEntities = interests.map { codeEntity in
57-
InterestsEntity(
58-
studentName: "",
59-
interestID: 0,
60-
studentID: 0,
61-
code: codeEntity.code,
62-
keyword: codeEntity.keyword
63-
)
64-
}
58+
let codeIDs = interests.map { $0.code }
6559

66-
self.changeInterestsUseCase.execute(interests: interestsEntities)
60+
self.changeInterestsUseCase.execute(codeIDs: codeIDs)
6761
.subscribe(
6862
onCompleted: { [weak self] in
69-
self?.isLoading.accept(false)
7063
self?.steps.accept(InterestFieldStep.interestFieldCheckIsRequired)
7164
},
72-
onError: { [weak self] error in
73-
self?.isLoading.accept(false)
74-
print("관심분야 업데이트 실패: \(error.localizedDescription)")
65+
onError: { _ in
7566
}
7667
)
7768
.disposed(by: self.disposeBag)
@@ -81,7 +72,19 @@ public final class InterestFieldViewModel: BaseViewModel, Stepper {
8172
return Output(
8273
availableInterests: availableInterests,
8374
selectedInterests: selectedInterests,
84-
isLoading: isLoading.asDriver(onErrorJustReturn: false)
75+
studentName: studentNameRelay.asDriver()
8576
)
8677
}
78+
79+
private func fetchStudentInfo() {
80+
fetchStudentInfoUseCase.execute()
81+
.subscribe(
82+
onSuccess: { [weak self] studentInfo in
83+
self?.studentNameRelay.accept(studentInfo.studentName)
84+
},
85+
onFailure: { _ in
86+
}
87+
)
88+
.disposed(by: disposeBag)
89+
}
8790
}

0 commit comments

Comments
 (0)