Skip to content

Commit 158ea66

Browse files
authored
Merge pull request #250 from teamterning/Fix/#249
[Fix] #249 -ํ•„ํ„ฐ๋ง ์žฌ์„ค์ •ํ™”๋ฉด 3์ฐจ ์Šคํ”„๋ฆฐํŠธ 1์ฐจ QA ๋ฐ˜์˜ํ–ˆ์Šต๋‹ˆ๋‹ค.
2 parents e64b07f + ad46ca5 commit 158ea66

7 files changed

Lines changed: 257 additions & 167 deletions

File tree

โ€ŽTerning-iOS/Terning-iOS/Resource/UIComponents/CustomSegmentedControl.swiftโ€Ž

Lines changed: 109 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,21 @@ final class CustomSegmentedControl: UISegmentedControl {
2323
private lazy var underbar: UIView = makeUnderbar()
2424
private var underbarInfo: UnderbarInfo
2525
private var isFirstSettingDone = false
26+
private var itemSpacing: CGFloat = 24
2627
private var underline: Bool
28+
private var items: [String]
2729

2830
// MARK: - Init
2931

30-
init(items: [Any]?, underbarInfo info: UnderbarInfo, underline: Bool = true) {
32+
init(items: [String], underbarInfo info: UnderbarInfo, itemSpacing: CGFloat = 24, underline: Bool = true) {
33+
self.items = items
3134
self.underbarInfo = info
35+
self.itemSpacing = itemSpacing
3236
self.underline = underline
37+
3338
super.init(items: items)
3439
setUI()
40+
setAddTarget()
3541
}
3642

3743
required init?(coder: NSCoder) {
@@ -45,13 +51,34 @@ final class CustomSegmentedControl: UISegmentedControl {
4551

4652
if !isFirstSettingDone {
4753
isFirstSettingDone.toggle()
54+
setupItemBackgrounds()
4855
if underline {
4956
setUnderbarMovableBackgroundLayer()
5057
}
5158
layer.masksToBounds = false
5259
}
60+
61+
updateLabelColors()
5362
updateUnderbarPosition()
5463
}
64+
65+
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
66+
var xOffset: CGFloat = 0
67+
68+
for index in 0..<numberOfSegments {
69+
let textWidth = calculateTextWidth(for: index)
70+
let segmentWidth = textWidth
71+
let segmentFrame = CGRect(x: xOffset, y: 0, width: segmentWidth, height: bounds.height)
72+
73+
if segmentFrame.contains(point) {
74+
self.selectedSegmentIndex = index
75+
sendActions(for: .valueChanged)
76+
return self
77+
}
78+
xOffset += (segmentWidth + itemSpacing)
79+
}
80+
return nil
81+
}
5582
}
5683

5784
// MARK: - UI & Layout
@@ -60,20 +87,41 @@ private extension CustomSegmentedControl {
6087
private func setUI() {
6188
removeBorders()
6289
let normalTextAttributes: [NSAttributedString.Key: Any] = [
63-
.foregroundColor: underbarInfo.backgroundColor,
64-
.font: UIFont.title4
65-
]
66-
let selectedTextAttributes: [NSAttributedString.Key: Any] = [
67-
.foregroundColor: underbarInfo.highlightColor,
90+
.foregroundColor: UIColor.clear,
6891
.font: UIFont.title4
6992
]
70-
7193
setTitleTextAttributes(normalTextAttributes, for: .normal)
72-
setTitleTextAttributes(selectedTextAttributes, for: .selected)
7394
selectedSegmentTintColor = .clear
7495
selectedSegmentIndex = 0
7596
}
7697

98+
private func setAddTarget() {
99+
addTarget(self, action: #selector(segmentValueChanged), for: .valueChanged)
100+
}
101+
102+
private func setupItemBackgrounds() {
103+
for index in 0..<numberOfSegments {
104+
let frame = frameForSegment(at: index)
105+
addBackgroundView(for: frame, at: index)
106+
}
107+
}
108+
109+
private func makeUnderbar() -> UIView {
110+
let view = UIView(frame: .zero)
111+
view.translatesAutoresizingMaskIntoConstraints = false
112+
view.backgroundColor = underbarInfo.highlightColor
113+
addSubview(view)
114+
return view
115+
}
116+
117+
private func removeBorders() {
118+
let image = UIImage()
119+
setBackgroundImage(image, for: .normal, barMetrics: .default)
120+
setBackgroundImage(image, for: .selected, barMetrics: .default)
121+
setBackgroundImage(image, for: .highlighted, barMetrics: .default)
122+
setDividerImage(image, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
123+
}
124+
77125
private func setUnderbarMovableBackgroundLayer() {
78126
let backgroundLayer = CALayer()
79127
backgroundLayer.frame = .init(
@@ -86,45 +134,70 @@ private extension CustomSegmentedControl {
86134
layer.addSublayer(backgroundLayer)
87135
}
88136

89-
private func makeUnderbar() -> UIView {
90-
let view = UIView(frame: .zero)
91-
view.translatesAutoresizingMaskIntoConstraints = false
92-
view.backgroundColor = underbarInfo.highlightColor
93-
addSubview(view)
94-
return view
95-
}
96-
97137
private func updateUnderbarPosition() {
98138
let selectedSegmentFrame = frameForSegment(at: selectedSegmentIndex)
99139
let textWidth = calculateTextWidth(for: selectedSegmentIndex)
100140

101-
UIView.animate(withDuration: 0.27, delay: 0, options: .curveEaseOut, animations: {
141+
underbar.layer.removeAllAnimations()
142+
143+
UIView.animate(withDuration: 0.17, delay: 0, options: .curveLinear, animations: {
102144
self.underbar.frame = CGRect(
103145
x: selectedSegmentFrame.origin.x + (selectedSegmentFrame.width - textWidth) / 2,
104146
y: self.bounds.height - self.underbarInfo.height,
105-
width: textWidth,
147+
width: selectedSegmentFrame.width,
106148
height: self.underbarInfo.height
107149
)
150+
self.layoutIfNeeded()
108151
})
109152
}
110153

111-
private func removeBorders() {
112-
let image = UIImage()
113-
setBackgroundImage(image, for: .normal, barMetrics: .default)
114-
setBackgroundImage(image, for: .selected, barMetrics: .default)
115-
setBackgroundImage(image, for: .highlighted, barMetrics: .default)
116-
setDividerImage(image, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
154+
private func updateLabelColors() {
155+
for index in 0..<numberOfSegments {
156+
if let backgroundView = subviews.first(where: { $0.tag == 999 + index }),
157+
let label = backgroundView.subviews.first(where: { $0 is UILabel }) as? UILabel {
158+
label.textColor = (selectedSegmentIndex == index) ? .terningMain : .grey300
159+
}
160+
}
117161
}
118162
}
119163

120164
// MARK: - Methods
121165

122166
extension CustomSegmentedControl {
123167
private func frameForSegment(at index: Int) -> CGRect {
124-
let segmentWidth = bounds.width / CGFloat(numberOfSegments)
125-
return CGRect(x: CGFloat(index) * segmentWidth, y: 0, width: segmentWidth, height: bounds.height)
168+
let xOffset = (0..<index).reduce(0) { result, idx in
169+
result + calculateTextWidth(for: idx) + itemSpacing
170+
}
171+
let itemWidth = calculateTextWidth(for: index)
172+
return CGRect(x: xOffset, y: 0, width: itemWidth, height: bounds.height)
126173
}
127-
174+
175+
private func addBackgroundView(for frame: CGRect, at index: Int) {
176+
subviews.filter { $0.tag == 999 + index }.forEach { $0.removeFromSuperview() }
177+
178+
let textWidth = calculateTextWidth(for: index)
179+
let segmentFrame = frameForSegment(at: index)
180+
181+
let backgroundFrame = CGRect(
182+
x: segmentFrame.origin.x + (segmentFrame.width - textWidth) / 2,
183+
y: 0,
184+
width: textWidth,
185+
height: bounds.height
186+
)
187+
let backgroundView = UIView(frame: backgroundFrame)
188+
backgroundView.tag = 999 + index
189+
190+
let label = UILabel(frame: backgroundView.bounds)
191+
label.text = titleForSegment(at: index)
192+
label.textColor = (index == selectedSegmentIndex) ? .terningMain : .grey300
193+
label.font = UIFont.title4
194+
label.textAlignment = .center
195+
label.tag = 1000 + index
196+
197+
backgroundView.addSubview(label)
198+
insertSubview(backgroundView, at: 0)
199+
}
200+
128201
private func calculateTextWidth(for index: Int) -> CGFloat {
129202
guard let title = titleForSegment(at: index),
130203
let font = titleTextAttributes(for: .normal)?[.font] as? UIFont else { return 0 }
@@ -133,3 +206,12 @@ extension CustomSegmentedControl {
133206
return size.width
134207
}
135208
}
209+
210+
// MARK: - @objc func
211+
212+
extension CustomSegmentedControl {
213+
@objc
214+
private func segmentValueChanged() {
215+
updateLabelColors()
216+
}
217+
}

โ€ŽTerning-iOS/Terning-iOS/Source/Presentation/FilteringSetting/FilteringView/ViewController/FilteringViewController.swiftโ€Ž

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ final class UserFilteringData {
1818

1919
var grade: Grade?
2020
var workingPeriod: WorkingPeriod?
21-
var startYear: Int? = Date().getCurrentKrYearAndMonth().year
22-
var startMonth: Int? = Date().getCurrentKrYearAndMonth().month
21+
var startYear: Int?
22+
var startMonth: Int?
2323
var jobType: JobType?
2424

2525
private init() {}
@@ -30,8 +30,8 @@ final class TemporaryFilteringData {
3030

3131
var grade: Grade?
3232
var workingPeriod: WorkingPeriod?
33-
var startYear: Int? = Date().getCurrentKrYearAndMonth().year
34-
var startMonth: Int? = Date().getCurrentKrYearAndMonth().month
33+
var startYear: Int?
34+
var startMonth: Int?
3535
var jobType: JobType?
3636

3737
private init() {}
@@ -54,6 +54,11 @@ final class FilteringViewController: UIViewController {
5454
$0.isUserInteractionEnabled = true
5555
}
5656

57+
private var titleLabel = LabelFactory.build(
58+
text: "ํ•„ํ„ฐ",
59+
font: .title2
60+
)
61+
5762
private var segmentControl: CustomSegmentedControl = {
5863
let underbarInfo = UnderbarInfo(
5964
height: 4,
@@ -84,8 +89,8 @@ final class FilteringViewController: UIViewController {
8489
init(viewModel: FilteringViewModel, data: UserFilteringInfoModel) {
8590
UserFilteringData.shared.grade = data.grade.flatMap { Grade(rawValue: $0) ?? Grade.fromEnglishValue($0) }
8691
UserFilteringData.shared.workingPeriod = data.workingPeriod.flatMap { WorkingPeriod(rawValue: $0) ?? WorkingPeriod.fromEnglishValue($0) }
87-
UserFilteringData.shared.startYear = data.startYear ?? UserFilteringData.shared.startYear
88-
UserFilteringData.shared.startMonth = data.startMonth ?? UserFilteringData.shared.startMonth
92+
UserFilteringData.shared.startYear = (data.startYear == 0) ? nil : data.startYear
93+
UserFilteringData.shared.startMonth = (data.startMonth == 0) ? nil : data.startMonth
8994
UserFilteringData.shared.jobType = data.jobType.flatMap { JobType(rawValue: $0) ?? JobType.fromEnglishValue($0) }
9095
self.viewModel = viewModel
9196

@@ -123,8 +128,9 @@ extension FilteringViewController {
123128
private func setHierarchy() {
124129
view.addSubviews(
125130
notchView,
126-
segmentControl,
131+
titleLabel,
127132
underLineView,
133+
segmentControl,
128134
saveButton
129135
)
130136
}
@@ -137,15 +143,20 @@ extension FilteringViewController {
137143
$0.height.equalTo(4.adjustedH)
138144
}
139145

140-
segmentControl.snp.makeConstraints {
146+
titleLabel.snp.makeConstraints {
141147
$0.top.equalTo(notchView.snp.bottom).offset(24.adjustedH)
142-
$0.leading.equalToSuperview().inset(20.adjusted)
143-
$0.height.equalTo(40.adjustedH)
148+
$0.leading.equalToSuperview().inset(25.adjusted)
149+
}
150+
151+
segmentControl.snp.makeConstraints {
152+
$0.top.equalTo(titleLabel.snp.bottom).offset(11.adjustedH)
153+
$0.leading.equalToSuperview().inset(25.adjusted)
154+
$0.height.equalTo(39)
144155
}
145156

146157
underLineView.snp.makeConstraints {
147158
$0.top.equalTo(segmentControl.snp.bottom).offset(-1)
148-
$0.horizontalEdges.equalToSuperview().inset(29.adjusted)
159+
$0.horizontalEdges.equalToSuperview().inset(25.adjusted)
149160
$0.height.equalTo(1)
150161
}
151162

โ€ŽTerning-iOS/Terning-iOS/Source/Presentation/FilteringSetting/JobFiltering/ViewController/JobFilteringViewController.swiftโ€Ž

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,10 @@ extension JobFilteringViewController {
9090

9191
output.selectedJobType
9292
.drive(onNext: { [weak self] selectedJob in
93-
guard let self = self else { return }
94-
if let selectedJob = selectedJob,
95-
let index = JobType.allCases.firstIndex(of: selectedJob) {
96-
let indexPath = IndexPath(item: index, section: 0)
97-
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
98-
} else {
99-
self.collectionView.indexPathsForSelectedItems?.forEach {
100-
self.collectionView.deselectItem(at: $0, animated: false)
101-
}
102-
}
93+
guard let self = self, let selectedJob = selectedJob,
94+
let index = JobType.allCases.firstIndex(of: selectedJob) else { return }
95+
let indexPath = IndexPath(item: index, section: 0)
96+
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
10397
})
10498
.disposed(by: disposeBag)
10599
}

โ€ŽTerning-iOS/Terning-iOS/Source/Presentation/FilteringSetting/JobFiltering/ViewModel/JobFilteringViewModel.swiftโ€Ž

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ final class JobFilteringViewModel: ViewModelType {
1515
// MARK: - Properties
1616

1717
private let jobTypesRelay = BehaviorRelay<[JobType]>(value: JobType.allCases)
18-
private let selectedJobTypeRelay = BehaviorRelay<JobType?>(value: UserFilteringData.shared.jobType)
18+
private let selectedJobTypeRelay = BehaviorRelay<JobType?>(value: UserFilteringData.shared.jobType ?? JobType.allCases.last)
1919

2020
// MARK: - Input
2121

@@ -38,11 +38,7 @@ final class JobFilteringViewModel: ViewModelType {
3838
.subscribe(onNext: { [weak self] indexPath in
3939
guard let self = self else { return }
4040
let selectedJob = JobType.allCases[indexPath.item]
41-
if self.selectedJobTypeRelay.value == selectedJob {
42-
self.selectedJobTypeRelay.accept(nil)
43-
} else {
44-
self.selectedJobTypeRelay.accept(selectedJob)
45-
}
41+
self.selectedJobTypeRelay.accept(selectedJob)
4642
})
4743
.disposed(by: disposeBag)
4844

0 commit comments

Comments
ย (0)