-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/calendar view #78
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
9fe307e
[refactor] CircleCheckView μμ κ° λ³κ²½
wickedRun 22bb854
[refactor] CircleCheckView stateλ₯Ό CalendarCellλ‘ λ³κ²½
wickedRun 20bef3c
[refactor] CalendarCell autolayout μμ λ° final ν€μλ μΆκ°
wickedRun e64140c
[feat] CalendarView νμΌ μΆκ°
wickedRun dfc20b2
[feat] CalendarViewμμ νμν νμ λ·° λ° λ³μ μΆκ°
wickedRun 64ece8f
[feat] CalendarView νμ collectionViewμ DataSource ꡬν
wickedRun 1252573
[feat] CalendarViewμ νμ collectionView delegate ꡬν
wickedRun b513a31
[feat] CalendarViewModel νμΌ μΆκ°
wickedRun fc73557
Merge branch 'develop' into feature/CalendarView
wickedRun a09823c
Merge branch 'develop' into feature/CalendarView
wickedRun 72f084d
[feat] Date κ³μ° extension μΆκ°
wickedRun 264962b
[feat] calendar property μμ λ° weekdayLabels UI λ³κ²½
wickedRun f63f408
[feat] yearMonthLabel μ’μ° insetκ° λ³κ²½
wickedRun 26f3ee0
[feat] κ·Έλ €μ§λ νμ¬ sectionμ 2λ²μ§Έ sectionμΌλ‘ λ³κ²½
wickedRun 80750fc
[feat] nextMonth λ©μλμ λ‘μ§κ³Ό μ΄λ¦ λ³κ²½
wickedRun 83f6409
[feat] λ³κ²½λ nextMonthλ‘ μΈν΄ setupMonthsλ λ³κ²½
wickedRun 3c75826
[feat] CalendarCell.State νμ
μ Hashable νλ‘ν μ½μ conformνλλ‘ λ³κ²½
wickedRun 58e4a47
[feat] dayλΌλ μμ±μ Dateλ‘ λ³κ²½ λ° Hashable νλ‘ν μ½ conform
wickedRun c17a737
[feat] DiffableDataSourceλ₯Ό κ°μ§λλ‘ λ³κ²½
wickedRun 44fbeef
[feat] scrollDidEndDecelerating ν¨μμ λ‘μ§ λ³κ²½
wickedRun 5816610
[feat] weekday viewμ locale λ³κ²½
wickedRun 6a67e03
[refactor] CalendarViewModel μ΄λ¦ λ³κ²½
wickedRun 7d14c6d
Merge branch 'develop' into feature/CalendarView
wickedRun 30679a6
Merge branch 'develop' into feature/CalendarView
wickedRun 7d47788
[refactor] MyCalendarViewModel μ΄λ¦ λ³κ²½
wickedRun 0df45f0
[chore] CalendarViewModel μ£Όμ λ³κ²½
wickedRun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
227 changes: 227 additions & 0 deletions
227
DailyQuest/DailyQuest/Presentation/Common/CalendarView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
// | ||
// CalendarView.swift | ||
// DailyQuest | ||
// | ||
// Created by wickedRun on 2022/11/15. | ||
// | ||
|
||
import UIKit | ||
import SnapKit | ||
|
||
class CalendarView: UIView { | ||
|
||
lazy var yearMonthLabel: UILabel = { | ||
let view = UILabel() | ||
view.adjustsFontSizeToFitWidth = true | ||
view.font = .systemFont(ofSize: 32, weight: .bold) | ||
view.text = dateFormatter.string(from: currentDay) | ||
return view | ||
}() | ||
|
||
lazy var weekdayLabels: UIStackView = { | ||
let view = UIStackView() | ||
view.axis = .horizontal | ||
view.distribution = .fillEqually | ||
var calendar = Calendar.current | ||
calendar.locale = .init(identifier: "ko_KR") | ||
|
||
calendar.shortWeekdaySymbols.forEach { | ||
let label = UILabel() | ||
label.adjustsFontSizeToFitWidth = true | ||
label.text = $0 | ||
label.textAlignment = .center | ||
view.addArrangedSubview(label) | ||
} | ||
|
||
return view | ||
}() | ||
|
||
lazy var monthCollectionView: UICollectionView = { | ||
let layout = setupCollectionViewLayout() | ||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) | ||
collectionView.showsHorizontalScrollIndicator = false | ||
collectionView.showsVerticalScrollIndicator = false | ||
collectionView.bounces = false | ||
collectionView.isPagingEnabled = true | ||
collectionView.register(CalendarCell.self, forCellWithReuseIdentifier: CalendarCell.reuseIdentifier) | ||
collectionView.delegate = self | ||
return collectionView | ||
}() | ||
|
||
private let dateFormatter = { | ||
let formatter = DateFormatter() | ||
formatter.dateFormat = "yyyyλ MMμ" | ||
return formatter | ||
}() | ||
|
||
private var currentDay: Date { | ||
didSet { | ||
yearMonthLabel.text = dateFormatter.string(from: currentDay) | ||
} | ||
} | ||
|
||
var dataSource: UICollectionViewDiffableDataSource<Int, DisplayDate>! | ||
|
||
var itemsBySection: [[CalendarView.DisplayDate]] = [[], [], []] | ||
|
||
override init(frame: CGRect = .zero) { | ||
self.currentDay = Date.now | ||
|
||
super.init(frame: frame) | ||
self.itemsBySection = self.setupMonths() | ||
|
||
addSubviews() | ||
setupConstraints() | ||
setupDataSource() | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
override func draw(_ rect: CGRect) { | ||
super.draw(rect) | ||
|
||
monthCollectionView.scrollToItem(at: IndexPath(item: 0, section: 1), at: .centeredHorizontally, animated: false) | ||
} | ||
|
||
private func addSubviews() { | ||
addSubview(yearMonthLabel) | ||
addSubview(weekdayLabels) | ||
addSubview(monthCollectionView) | ||
} | ||
|
||
private func setupConstraints() { | ||
yearMonthLabel.snp.makeConstraints { make in | ||
make.top.equalToSuperview().inset(5) | ||
make.horizontalEdges.equalToSuperview().inset(20) | ||
} | ||
|
||
weekdayLabels.snp.makeConstraints { make in | ||
make.top.equalTo(yearMonthLabel.snp.bottom).offset(10) | ||
make.horizontalEdges.equalToSuperview().inset(5) | ||
} | ||
|
||
monthCollectionView.snp.makeConstraints { make in | ||
make.top.equalTo(weekdayLabels.snp.bottom).offset(10) | ||
make.bottom.horizontalEdges.equalToSuperview().inset(5) | ||
} | ||
} | ||
|
||
private func setupCollectionViewLayout() -> UICollectionViewLayout { | ||
let itemWidth: CGFloat = 1 / 7 | ||
let groupHeight: CGFloat = 1 / 6 | ||
|
||
let itemSize = NSCollectionLayoutSize( | ||
widthDimension: .fractionalWidth(itemWidth), | ||
heightDimension: .fractionalHeight(1) | ||
) | ||
let item = NSCollectionLayoutItem(layoutSize: itemSize) | ||
|
||
let groupSize = NSCollectionLayoutSize( | ||
widthDimension: .fractionalWidth(1), | ||
heightDimension: .fractionalHeight(groupHeight) | ||
) | ||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, repeatingSubitem: item, count: 7) | ||
|
||
let section = NSCollectionLayoutSection(group: group) | ||
section.orthogonalScrollingBehavior = .groupPaging | ||
|
||
let configuration = UICollectionViewCompositionalLayoutConfiguration() | ||
configuration.scrollDirection = .horizontal | ||
|
||
let layout = UICollectionViewCompositionalLayout(section: section, configuration: configuration) | ||
|
||
return layout | ||
} | ||
|
||
private func setupDataSource() { | ||
self.dataSource = UICollectionViewDiffableDataSource(collectionView: monthCollectionView) { collectionView, indexPath, item in | ||
guard | ||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarCell.reuseIdentifier, for: indexPath) as? CalendarCell | ||
else { | ||
preconditionFailure() | ||
} | ||
|
||
cell.configure(state: item.state, day: item.date.day) | ||
|
||
return cell | ||
} | ||
|
||
var snapshot = NSDiffableDataSourceSnapshot<Int, DisplayDate>() | ||
itemsBySection.indices.forEach { index in | ||
snapshot.appendSections([index]) | ||
snapshot.appendItems(itemsBySection[index], toSection: index) | ||
} | ||
dataSource.apply(snapshot) | ||
} | ||
|
||
private func applySnapshot() { | ||
var snapshot = NSDiffableDataSourceSnapshot<Int, DisplayDate>() | ||
let allSectionIndex = itemsBySection.indices.map { Int($0) } | ||
snapshot.appendSections(allSectionIndex) | ||
allSectionIndex.forEach { index in | ||
snapshot.appendItems(itemsBySection[index], toSection: index) | ||
} | ||
dataSource.apply(snapshot, animatingDifferences: false) | ||
} | ||
} | ||
|
||
extension CalendarView: UICollectionViewDelegate { | ||
|
||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { | ||
guard let indexPath = monthCollectionView.indexPathsForVisibleItems.first else { | ||
return | ||
} | ||
|
||
if indexPath.section > 1 { | ||
nextMonth() | ||
} else if indexPath.section < 1 { | ||
lastMonth() | ||
} else { | ||
return | ||
} | ||
|
||
applySnapshot() | ||
monthCollectionView.scrollToItem(at: IndexPath(item: 0, section: 1), at: .centeredHorizontally, animated: false) | ||
} | ||
|
||
private func fetchDisplayDaysOfMonth(for date: Date?) -> [DisplayDate] { | ||
guard let date else { return [] } | ||
|
||
return date.rangeFromStartWeekdayOfLastMonthToEndDayOfCurrentMonth.map { DisplayDate(date: $0, state: .none) } + date.rangeDaysOfMonth.map { DisplayDate(date: $0, state: .normal) } | ||
} | ||
|
||
private func setupMonths() -> [[DisplayDate]] { | ||
let startDayOfPrevMonth = currentDay.startDayOfLastMonth | ||
let startDayOfNextMonth = currentDay.startDayOfNextMonth | ||
|
||
return [startDayOfPrevMonth, currentDay, startDayOfNextMonth].map(fetchDisplayDaysOfMonth(for:)) | ||
} | ||
|
||
private func nextMonth() { | ||
currentDay = currentDay.nextMonthOfCurrentDay! | ||
let monthAfterNext = currentDay.nextMonthOfCurrentDay! | ||
let monthAfterNextDisplayDays = fetchDisplayDaysOfMonth(for: monthAfterNext) | ||
|
||
self.itemsBySection.removeFirst() | ||
self.itemsBySection.append(monthAfterNextDisplayDays) | ||
} | ||
|
||
private func lastMonth() { | ||
currentDay = currentDay.lastMonthOfCurrentDay! | ||
let monthBeforeLast = currentDay.lastMonthOfCurrentDay! | ||
let monthBeforeLastDisplayDays = fetchDisplayDaysOfMonth(for: monthBeforeLast) | ||
|
||
self.itemsBySection.removeLast() | ||
self.itemsBySection.insert(monthBeforeLastDisplayDays, at: 0) | ||
} | ||
} | ||
|
||
extension CalendarView { | ||
|
||
struct DisplayDate: Hashable { | ||
let date: Date | ||
let state: CalendarCell.State | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private
ν€μλλ₯Ό μ»΄ν¬λνΈλ§λ€ λ¬μμ£Όλ©΄ μ’μ κ² κ°μ΅λλ€.final
ν€μλμ²λΌ, μ±λ₯ν₯μμ λμμ΄ λλ€κ³ ν΄μ!final
ν€μλμ κ΄λ ¨λ λΆλΆμ μ¬κΈ°μ μ€νλΆλΆμ νμΈν΄μ£ΌμΈμ!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
μ€μ°.. final μ‘°μ°¨ μλ¨κ±Έ κΉλΉ‘νλ€μ γ
λ€μ prμμ μμ ν΄μ μ¬λ¦¬κ² μ΅λλ€.