Skip to content
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 26 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9fe307e
[refactor] CircleCheckView 색상 κ°’ λ³€κ²½
wickedRun Nov 22, 2022
22bb854
[refactor] CircleCheckView stateλ₯Ό CalendarCell둜 λ³€κ²½
wickedRun Nov 22, 2022
20bef3c
[refactor] CalendarCell autolayout μˆ˜μ • 및 final ν‚€μ›Œλ“œ μΆ”κ°€
wickedRun Nov 22, 2022
e64140c
[feat] CalendarView 파일 μΆ”κ°€
wickedRun Nov 22, 2022
dfc20b2
[feat] CalendarViewμ—μ„œ ν•„μš”ν•œ ν•˜μœ„ λ·° 및 λ³€μˆ˜ μΆ”κ°€
wickedRun Nov 22, 2022
64ece8f
[feat] CalendarView ν•˜μœ„ collectionView의 DataSource κ΅¬ν˜„
wickedRun Nov 22, 2022
1252573
[feat] CalendarView의 ν•˜μœ„ collectionView delegate κ΅¬ν˜„
wickedRun Nov 22, 2022
b513a31
[feat] CalendarViewModel 파일 μΆ”κ°€
wickedRun Nov 22, 2022
fc73557
Merge branch 'develop' into feature/CalendarView
wickedRun Nov 23, 2022
a09823c
Merge branch 'develop' into feature/CalendarView
wickedRun Nov 28, 2022
72f084d
[feat] Date 계산 extension μΆ”κ°€
wickedRun Nov 28, 2022
264962b
[feat] calendar property μ‚­μ œ 및 weekdayLabels UI λ³€κ²½
wickedRun Nov 28, 2022
f63f408
[feat] yearMonthLabel 쒌우 insetκ°’ λ³€κ²½
wickedRun Nov 28, 2022
26f3ee0
[feat] κ·Έλ €μ§ˆλ•Œ ν˜„μž¬ section을 2번째 section으둜 λ³€κ²½
wickedRun Nov 28, 2022
80750fc
[feat] nextMonth λ©”μ†Œλ“œμ˜ 둜직과 이름 λ³€κ²½
wickedRun Nov 28, 2022
83f6409
[feat] λ³€κ²½λœ nextMonth둜 인해 setupMonths도 λ³€κ²½
wickedRun Nov 28, 2022
3c75826
[feat] CalendarCell.State νƒ€μž…μ„ Hashable ν”„λ‘œν† μ½œμ„ conformν•˜λ„λ‘ λ³€κ²½
wickedRun Nov 29, 2022
58e4a47
[feat] dayλΌλŠ” 속성을 Date둜 λ³€κ²½ 및 Hashable ν”„λ‘œν† μ½œ conform
wickedRun Nov 29, 2022
c17a737
[feat] DiffableDataSourceλ₯Ό 가지도둝 λ³€κ²½
wickedRun Nov 29, 2022
44fbeef
[feat] scrollDidEndDecelerating ν•¨μˆ˜μ˜ 둜직 λ³€κ²½
wickedRun Nov 29, 2022
5816610
[feat] weekday view의 locale λ³€κ²½
wickedRun Nov 29, 2022
6a67e03
[refactor] CalendarViewModel 이름 λ³€κ²½
wickedRun Nov 29, 2022
7d14c6d
Merge branch 'develop' into feature/CalendarView
wickedRun Dec 4, 2022
30679a6
Merge branch 'develop' into feature/CalendarView
wickedRun Dec 5, 2022
7d47788
[refactor] MyCalendarViewModel 이름 λ³€κ²½
wickedRun Dec 5, 2022
0df45f0
[chore] CalendarViewModel 주석 λ³€κ²½
wickedRun Dec 5, 2022
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
8 changes: 8 additions & 0 deletions DailyQuest/DailyQuest.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@
A5AC96E629223F06003B7637 /* QuestsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AC96E529223F06003B7637 /* QuestsStorage.swift */; };
A5AC96E829223F27003B7637 /* RealmQuestsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AC96E729223F27003B7637 /* RealmQuestsStorage.swift */; };
B50078D629222F3F0070AFC4 /* CircleCheckView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50078D529222F3F0070AFC4 /* CircleCheckView.swift */; };
B5115453292CD07100FDBD22 /* CalendarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5115452292CD07100FDBD22 /* CalendarViewModel.swift */; };
B5833F732924C08900503E0D /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5833F722924C08900503E0D /* CalendarView.swift */; };
B58DFC0A29227DA800C68A4B /* CalendarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58DFC0929227DA800C68A4B /* CalendarCell.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -291,6 +293,8 @@
A5AC96E529223F06003B7637 /* QuestsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestsStorage.swift; sourceTree = "<group>"; };
A5AC96E729223F27003B7637 /* RealmQuestsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmQuestsStorage.swift; sourceTree = "<group>"; };
B50078D529222F3F0070AFC4 /* CircleCheckView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleCheckView.swift; sourceTree = "<group>"; };
B5115452292CD07100FDBD22 /* CalendarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarViewModel.swift; sourceTree = "<group>"; };
B5833F722924C08900503E0D /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
B58DFC0929227DA800C68A4B /* CalendarCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarCell.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -530,6 +534,7 @@
B50078D529222F3F0070AFC4 /* CircleCheckView.swift */,
3499551F29234637007AB99E /* CustomProgressBar.swift */,
34113BEC2934BD3D00AB4919 /* TextFieldForm.swift */,
B5833F722924C08900503E0D /* CalendarView.swift */,
);
path = Common;
sourceTree = "<group>";
Expand Down Expand Up @@ -685,6 +690,7 @@
isa = PBXGroup;
children = (
34EE6EB82924CAA1005AF583 /* QuestViewModel.swift */,
B5115452292CD07100FDBD22 /* CalendarViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
Expand Down Expand Up @@ -1156,6 +1162,7 @@
34874AA229250C43000570DF /* UIButton+.swift in Sources */,
A50F9A3F292679BC005C00FE /* NetworkConfigure.swift in Sources */,
34A529E7292481E1001BAD34 /* BrowseCoordinator.swift in Sources */,
B5115453292CD07100FDBD22 /* CalendarViewModel.swift in Sources */,
34A529D329247903001BAD34 /* TabCoordinator.swift in Sources */,
3417B1462935DA9D00900454 /* DefaultBrowseUseCase.swift in Sources */,
347D258D292C6E220038FCA2 /* MessageBubble.swift in Sources */,
Expand All @@ -1167,6 +1174,7 @@
3449AD5D2922197000B87619 /* User.swift in Sources */,
9BD8CCF52935C38300E6EA2F /* UserDTO+Mapping.swift in Sources */,
A51F01CD29233ABB0031ECA2 /* RealmUserInfoStorage.swift in Sources */,
B5833F732924C08900503E0D /* CalendarView.swift in Sources */,
A50F9A3429266F45005C00FE /* NetworkService.swift in Sources */,
A51F01DA292345990031ECA2 /* BrowseQuest.swift in Sources */,
A5AC96E629223F06003B7637 /* QuestsStorage.swift in Sources */,
Expand Down
227 changes: 227 additions & 0 deletions DailyQuest/DailyQuest/Presentation/Common/CalendarView.swift
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 = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

privateν‚€μ›Œλ“œλ₯Ό μ»΄ν¬λ„ŒνŠΈλ§ˆλ‹€ 달아주면 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€. final ν‚€μ›Œλ“œμ²˜λŸΌ, μ„±λŠ₯ν–₯상에 도움이 λœλ‹€κ³ ν•΄μš”!
final ν‚€μ›Œλ“œμ™€ κ΄€λ ¨λœ 뢀뢄은 μ—¬κΈ°μ˜ μ‹€ν—˜λΆ€λΆ„μ„ ν™•μΈν•΄μ£Όμ„Έμš”!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

였우.. final μ‘°μ°¨ μ•ˆλ‹¨κ±Έ κΉœλΉ‘ν–ˆλ„€μš” γ… 
λ‹€μŒ pr에선 μˆ˜μ •ν•΄μ„œ μ˜¬λ¦¬κ² μŠ΅λ‹ˆλ‹€.

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
}
}
39 changes: 33 additions & 6 deletions DailyQuest/DailyQuest/Presentation/Common/Cells/CalendarCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import UIKit
import SnapKit

class CalendarCell: UICollectionViewCell {
final class CalendarCell: UICollectionViewCell {

/// μž¬μ‚¬μš© μ‹λ³„μž
static let reuseIdentifier = "CalendarCell"

// MARK: - Sub Views

Expand Down Expand Up @@ -45,13 +48,16 @@ class CalendarCell: UICollectionViewCell {

private func setupContstraints() {
circleCheckView.snp.makeConstraints { make in
make.top.horizontalEdges.equalToSuperview()
make.height.equalTo(circleCheckView.snp.width)
make.top.equalToSuperview().inset(5)
make.centerX.equalToSuperview().priority(.high)
make.width.equalTo(self.snp.width).multipliedBy(0.9).inset(5)
make.height.equalTo(circleCheckView.snp.width).priority(.required)
}

dayLabel.snp.makeConstraints { make in
make.top.equalTo(circleCheckView.snp.bottom).offset(4)
make.bottom.horizontalEdges.equalToSuperview()
make.horizontalEdges.equalToSuperview()
make.bottom.lessThanOrEqualToSuperview().priority(.high)
}
}

Expand All @@ -61,8 +67,29 @@ class CalendarCell: UICollectionViewCell {
/// - parameters:
/// - state : CircleCheckView.State
/// - day : Int
func configure(state: CircleCheckView.State, day: Int) {
func configure(state: CalendarCell.State, day: Int) {
self.isHidden = false

switch state {
case .none:
self.isHidden = true
case .normal:
self.circleCheckView.setNormal()
case .display(let number):
self.circleCheckView.setNumber(to: number)
case .done:
self.circleCheckView.setDone()
}
dayLabel.text = "\(day)"
circleCheckView.updateState(state)
}
}

extension CalendarCell {

enum State: Hashable {
case none
case normal
case display(Int)
case done
}
}
Loading