Skip to content

Commit

Permalink
Merge pull request #250 from teamterning/Fix/#249
Browse files Browse the repository at this point in the history
[Fix] #249 -필터링 μž¬μ„€μ •ν™”λ©΄ 3μ°¨ μŠ€ν”„λ¦°νŠΈ 1μ°¨ QA λ°˜μ˜ν–ˆμŠ΅λ‹ˆλ‹€.
  • Loading branch information
wjdalswl authored Jan 11, 2025
2 parents e64b07f + ad46ca5 commit 158ea66
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ final class CustomSegmentedControl: UISegmentedControl {
private lazy var underbar: UIView = makeUnderbar()
private var underbarInfo: UnderbarInfo
private var isFirstSettingDone = false
private var itemSpacing: CGFloat = 24
private var underline: Bool
private var items: [String]

// MARK: - Init

init(items: [Any]?, underbarInfo info: UnderbarInfo, underline: Bool = true) {
init(items: [String], underbarInfo info: UnderbarInfo, itemSpacing: CGFloat = 24, underline: Bool = true) {
self.items = items
self.underbarInfo = info
self.itemSpacing = itemSpacing
self.underline = underline

super.init(items: items)
setUI()
setAddTarget()
}

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

if !isFirstSettingDone {
isFirstSettingDone.toggle()
setupItemBackgrounds()
if underline {
setUnderbarMovableBackgroundLayer()
}
layer.masksToBounds = false
}

updateLabelColors()
updateUnderbarPosition()
}

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var xOffset: CGFloat = 0

for index in 0..<numberOfSegments {
let textWidth = calculateTextWidth(for: index)
let segmentWidth = textWidth
let segmentFrame = CGRect(x: xOffset, y: 0, width: segmentWidth, height: bounds.height)

if segmentFrame.contains(point) {
self.selectedSegmentIndex = index
sendActions(for: .valueChanged)
return self
}
xOffset += (segmentWidth + itemSpacing)
}
return nil
}
}

// MARK: - UI & Layout
Expand All @@ -60,20 +87,41 @@ private extension CustomSegmentedControl {
private func setUI() {
removeBorders()
let normalTextAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: underbarInfo.backgroundColor,
.font: UIFont.title4
]
let selectedTextAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: underbarInfo.highlightColor,
.foregroundColor: UIColor.clear,
.font: UIFont.title4
]

setTitleTextAttributes(normalTextAttributes, for: .normal)
setTitleTextAttributes(selectedTextAttributes, for: .selected)
selectedSegmentTintColor = .clear
selectedSegmentIndex = 0
}

private func setAddTarget() {
addTarget(self, action: #selector(segmentValueChanged), for: .valueChanged)
}

private func setupItemBackgrounds() {
for index in 0..<numberOfSegments {
let frame = frameForSegment(at: index)
addBackgroundView(for: frame, at: index)
}
}

private func makeUnderbar() -> UIView {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = underbarInfo.highlightColor
addSubview(view)
return view
}

private func removeBorders() {
let image = UIImage()
setBackgroundImage(image, for: .normal, barMetrics: .default)
setBackgroundImage(image, for: .selected, barMetrics: .default)
setBackgroundImage(image, for: .highlighted, barMetrics: .default)
setDividerImage(image, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
}

private func setUnderbarMovableBackgroundLayer() {
let backgroundLayer = CALayer()
backgroundLayer.frame = .init(
Expand All @@ -86,45 +134,70 @@ private extension CustomSegmentedControl {
layer.addSublayer(backgroundLayer)
}

private func makeUnderbar() -> UIView {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = underbarInfo.highlightColor
addSubview(view)
return view
}

private func updateUnderbarPosition() {
let selectedSegmentFrame = frameForSegment(at: selectedSegmentIndex)
let textWidth = calculateTextWidth(for: selectedSegmentIndex)

UIView.animate(withDuration: 0.27, delay: 0, options: .curveEaseOut, animations: {
underbar.layer.removeAllAnimations()

UIView.animate(withDuration: 0.17, delay: 0, options: .curveLinear, animations: {
self.underbar.frame = CGRect(
x: selectedSegmentFrame.origin.x + (selectedSegmentFrame.width - textWidth) / 2,
y: self.bounds.height - self.underbarInfo.height,
width: textWidth,
width: selectedSegmentFrame.width,
height: self.underbarInfo.height
)
self.layoutIfNeeded()
})
}

private func removeBorders() {
let image = UIImage()
setBackgroundImage(image, for: .normal, barMetrics: .default)
setBackgroundImage(image, for: .selected, barMetrics: .default)
setBackgroundImage(image, for: .highlighted, barMetrics: .default)
setDividerImage(image, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
private func updateLabelColors() {
for index in 0..<numberOfSegments {
if let backgroundView = subviews.first(where: { $0.tag == 999 + index }),
let label = backgroundView.subviews.first(where: { $0 is UILabel }) as? UILabel {
label.textColor = (selectedSegmentIndex == index) ? .terningMain : .grey300
}
}
}
}

// MARK: - Methods

extension CustomSegmentedControl {
private func frameForSegment(at index: Int) -> CGRect {
let segmentWidth = bounds.width / CGFloat(numberOfSegments)
return CGRect(x: CGFloat(index) * segmentWidth, y: 0, width: segmentWidth, height: bounds.height)
let xOffset = (0..<index).reduce(0) { result, idx in
result + calculateTextWidth(for: idx) + itemSpacing
}
let itemWidth = calculateTextWidth(for: index)
return CGRect(x: xOffset, y: 0, width: itemWidth, height: bounds.height)
}


private func addBackgroundView(for frame: CGRect, at index: Int) {
subviews.filter { $0.tag == 999 + index }.forEach { $0.removeFromSuperview() }

let textWidth = calculateTextWidth(for: index)
let segmentFrame = frameForSegment(at: index)

let backgroundFrame = CGRect(
x: segmentFrame.origin.x + (segmentFrame.width - textWidth) / 2,
y: 0,
width: textWidth,
height: bounds.height
)
let backgroundView = UIView(frame: backgroundFrame)
backgroundView.tag = 999 + index

let label = UILabel(frame: backgroundView.bounds)
label.text = titleForSegment(at: index)
label.textColor = (index == selectedSegmentIndex) ? .terningMain : .grey300
label.font = UIFont.title4
label.textAlignment = .center
label.tag = 1000 + index

backgroundView.addSubview(label)
insertSubview(backgroundView, at: 0)
}

private func calculateTextWidth(for index: Int) -> CGFloat {
guard let title = titleForSegment(at: index),
let font = titleTextAttributes(for: .normal)?[.font] as? UIFont else { return 0 }
Expand All @@ -133,3 +206,12 @@ extension CustomSegmentedControl {
return size.width
}
}

// MARK: - @objc func

extension CustomSegmentedControl {
@objc
private func segmentValueChanged() {
updateLabelColors()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ final class UserFilteringData {

var grade: Grade?
var workingPeriod: WorkingPeriod?
var startYear: Int? = Date().getCurrentKrYearAndMonth().year
var startMonth: Int? = Date().getCurrentKrYearAndMonth().month
var startYear: Int?
var startMonth: Int?
var jobType: JobType?

private init() {}
Expand All @@ -30,8 +30,8 @@ final class TemporaryFilteringData {

var grade: Grade?
var workingPeriod: WorkingPeriod?
var startYear: Int? = Date().getCurrentKrYearAndMonth().year
var startMonth: Int? = Date().getCurrentKrYearAndMonth().month
var startYear: Int?
var startMonth: Int?
var jobType: JobType?

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

private var titleLabel = LabelFactory.build(
text: "ν•„ν„°",
font: .title2
)

private var segmentControl: CustomSegmentedControl = {
let underbarInfo = UnderbarInfo(
height: 4,
Expand Down Expand Up @@ -84,8 +89,8 @@ final class FilteringViewController: UIViewController {
init(viewModel: FilteringViewModel, data: UserFilteringInfoModel) {
UserFilteringData.shared.grade = data.grade.flatMap { Grade(rawValue: $0) ?? Grade.fromEnglishValue($0) }
UserFilteringData.shared.workingPeriod = data.workingPeriod.flatMap { WorkingPeriod(rawValue: $0) ?? WorkingPeriod.fromEnglishValue($0) }
UserFilteringData.shared.startYear = data.startYear ?? UserFilteringData.shared.startYear
UserFilteringData.shared.startMonth = data.startMonth ?? UserFilteringData.shared.startMonth
UserFilteringData.shared.startYear = (data.startYear == 0) ? nil : data.startYear
UserFilteringData.shared.startMonth = (data.startMonth == 0) ? nil : data.startMonth
UserFilteringData.shared.jobType = data.jobType.flatMap { JobType(rawValue: $0) ?? JobType.fromEnglishValue($0) }
self.viewModel = viewModel

Expand Down Expand Up @@ -123,8 +128,9 @@ extension FilteringViewController {
private func setHierarchy() {
view.addSubviews(
notchView,
segmentControl,
titleLabel,
underLineView,
segmentControl,
saveButton
)
}
Expand All @@ -137,15 +143,20 @@ extension FilteringViewController {
$0.height.equalTo(4.adjustedH)
}

segmentControl.snp.makeConstraints {
titleLabel.snp.makeConstraints {
$0.top.equalTo(notchView.snp.bottom).offset(24.adjustedH)
$0.leading.equalToSuperview().inset(20.adjusted)
$0.height.equalTo(40.adjustedH)
$0.leading.equalToSuperview().inset(25.adjusted)
}

segmentControl.snp.makeConstraints {
$0.top.equalTo(titleLabel.snp.bottom).offset(11.adjustedH)
$0.leading.equalToSuperview().inset(25.adjusted)
$0.height.equalTo(39)
}

underLineView.snp.makeConstraints {
$0.top.equalTo(segmentControl.snp.bottom).offset(-1)
$0.horizontalEdges.equalToSuperview().inset(29.adjusted)
$0.horizontalEdges.equalToSuperview().inset(25.adjusted)
$0.height.equalTo(1)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,10 @@ extension JobFilteringViewController {

output.selectedJobType
.drive(onNext: { [weak self] selectedJob in
guard let self = self else { return }
if let selectedJob = selectedJob,
let index = JobType.allCases.firstIndex(of: selectedJob) {
let indexPath = IndexPath(item: index, section: 0)
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
} else {
self.collectionView.indexPathsForSelectedItems?.forEach {
self.collectionView.deselectItem(at: $0, animated: false)
}
}
guard let self = self, let selectedJob = selectedJob,
let index = JobType.allCases.firstIndex(of: selectedJob) else { return }
let indexPath = IndexPath(item: index, section: 0)
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
})
.disposed(by: disposeBag)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final class JobFilteringViewModel: ViewModelType {
// MARK: - Properties

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

// MARK: - Input

Expand All @@ -38,11 +38,7 @@ final class JobFilteringViewModel: ViewModelType {
.subscribe(onNext: { [weak self] indexPath in
guard let self = self else { return }
let selectedJob = JobType.allCases[indexPath.item]
if self.selectedJobTypeRelay.value == selectedJob {
self.selectedJobTypeRelay.accept(nil)
} else {
self.selectedJobTypeRelay.accept(selectedJob)
}
self.selectedJobTypeRelay.accept(selectedJob)
})
.disposed(by: disposeBag)

Expand Down
Loading

0 comments on commit 158ea66

Please sign in to comment.