*# ๐ ํ๋ก์ ํธ ๋งค๋์
## ๐ ๋ชฉ์ฐจ
- [์๊ฐ](#-์๊ฐ)
- [ํ์๋ผ์ธ](#-ํ์๋ผ์ธ)
- [ํ์ผ๊ตฌ์กฐ](#-ํ์ผ๊ตฌ์กฐ)
- [์คํํ๋ฉด](#-์คํํ๋ฉด)
- [ํธ๋ฌ๋ธ ์ํ
](#-ํธ๋ฌ๋ธ-์ํ
)
- [์ฐธ๊ณ ๋งํฌ](#์ฐธ๊ณ -๋งํฌ)
## ๐ฃ ์๊ฐ
### ํ๋ก์ ํธ ์๊ฐ
#### ํ๋ก์ ํธ ๊ธฐ๊ฐ : 23.05.15 ~ 23.06.02
- ToDo๋ฅผ ์์ฑํ๊ณ ๊ด๋ฆฌํ๋ ํ๋ก๊ทธ๋จ
### ํ ์๊ฐ
- ํ ๊ตฌ์ฑ์(2์ธ)
| vetto | ์ก์ค |
| :--------: | :--------: |
| <img src="https://cdn.discordapp.com/attachments/535779947118329866/1055718870951940146/1671110054020-0.jpg" width="200" height="250"/> | <img src="https://user-images.githubusercontent.com/88870642/210026753-591175fe-27c1-4335-a2cb-f883bfeb2784.png" width="200" height="200"/>|
|[Github](https://github.com/gzzjk159)|[Github](https://github.com/kimseongj)|
## โฑ ํ์๋ผ์ธ
|๋ ์ง|ํ๋|
|---|---|
|2023.05.16|- ๊ธฐ์ ์ ์ |
|2023.05.17|- CompositionalLayout๊ณผ DiffableDataSource๋ฅผ ์ฌ์ฉํ์ฌ CollectionView ๊ตฌํ|
|2023.05.18|- CollectionView์ TableView์ ๋ํ ๋น๊ต ๋ฐ ๊ตฌํ|
|2023.05.19|- CollectionView Swipe ๊ตฌํ|
|2023.05.22|- ModalView ๊ตฌํ </br> - DateFormmatter ์ค์ |
|2023.05.23|- ViewModel ๊ตฌํ </br> - MVVM Bind ๊ตฌํ|
|2023.05.24|- ViewModel ๋ฆฌํฉํ ๋ง </br> - DatePicker ๊ตฌํ|
|2023.05.25|- LongPressGesture ๊ตฌํ </br> Popover ๊ตฌํ|
|2023.05.26|- ์ฝ๋ ๋ฆฌํฉํ ๋ง|
|2023.05.29|- Combine์ ํตํ Binding|
|2023.05.30|- Constant๊ฐ ์์ ์ ์ธ ๋ฐ firebase ์คํ|
|2023.05.31|- ViewModel ๋ฐ์ดํฐ๋ฅผ ScheduleManager๋ก ๋ถ๋ฆฌ|
|2023.06.01|- MVVM ๋ฆฌํฉํ ๋ง|
|2023.06.02|- Readme ์์ฑ|
## ๐ ํ์ผ๊ตฌ์กฐ
```swift
.
โโโ Resource
โ โโโ Assets.xcassets
โ โ โโโ AccentColor.colorset
โ โ โ โโโ Contents.json
โ โ โโโ AppIcon.appiconset
โ โ โ โโโ Contents.json
โ โ โโโ Contents.json
โ โโโ Base.lproj
โ โ โโโ LaunchScreen.storyboard
โ โโโ GoogleService-Info.plist
โ โโโ Info.plist
โโโ Source
โโโ Application
โ โโโ AppDelegate.swift
โ โโโ SceneDelegate.swift
โโโ Model
โ โโโ ModalType.swift
โ โโโ Schedule.swift
โ โโโ ScheduleManager.swift
โ โโโ ScheduleType.swift
โโโ Protocol
โ โโโ IdentifierType.swift
โโโ Utility
โ โโโ DateFormatterManager.swift
โ โโโ FirebaseManager.swift
โ โโโ Namespace.swift
โ โโโ Section.swift
โโโ View
โ โโโ DoList
โ โ โโโ DoListViewController.swift
โ โ โโโ ScheduleCell.swift
โ โ โโโ TodoHeaderView.swift
โ โโโ Main
โ โ โโโ MainViewController.swift
โ โโโ Modal
โ โโโ ModalViewController.swift
โโโ ViewModel
โโโ DoListViewModel.swift
โโโ ModalViewModel.swift
```
## ๐ป ์คํํ๋ฉด
| schedule ์์ฑ | Schedule ์ด๋|
| :--------: | :--------: |
| | |
| schedule ์ญ์ | schedule ์์ |
| :---: | :---: |
|||
## ๐ฅ ํธ๋ฌ๋ธ ์ํ
### 1๏ธโฃCombine @Published์ willset

> `Published`์ ๊ฒฝ์ฐ ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ ๊ฒฐ๊ณผ, subscriber๋ ์ค์ ๋ก ์์ฑ์ ์ค์ ๋๊ธฐ ์ ์ ์ ๊ฐ์ ๋ฐ์ต๋๋ค. ์ด ์ ์ ๊ฐ๊ณผํ๊ณ , ์ค์ ์์ฑ์ ๊ฐ์ ๊ฐ์ ธ์ค๋ ๋ฐ๋์ ํ๋ฐ์ ๋ฆ๊ฒ ์๋๋๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์๋๋ ๊ทธ์ ๋ํ ์์์
๋๋ค. ์ฆ, `@Published`๋ก ๊ฐ์ธ์ ธ์๋ ํ๋กํผํฐ๋ `sink`ํ ๊ฒฝ์ฐ `willset`์ด ์๋๋์ด, `weather.temperature`๋ฅผ `sink`์ `escaping closure`์ ๋ถ๋ฌ์ฌ ๊ฒฝ์ฐ ๋ณํ์ง ์์ ์ํ๋ก ๊ฐ์ด ๋ถ๋ฌ์ต๋๋ค. `sink`์ `escaping closure` ๋ด๋ถ์ ์๋ ๋งค๊ฐ๋ณ์($0)์ ๋ณํ๋ ๊ฐ์ด ์ ์ฅ๋๋ฉฐ, ์ด ๊ฐ์ ํตํด ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
- ๋ณ๊ฒฝ๋ ๊ฐ์ subscriber๊ฐ ๋ฐ์ ์์ ์
```Swift
class Weather {
@Published var temperature: [Double]
init(temperature: [Double]) {
self.temperature = temperature
}
}
var cancellables: Set<AnyCancellable> = []
let weather = Weather(temperature: [20])
weather.$temperature
.sink() {
print ("Temperature now: \(weather.temperature)")
}.store(in: &cancellables)
weather.temperature = [25] // [20]์ ํธ์ถํ๋ค.
```
- ์ค์ ์์ฑ ๊ฐ์ ๋ฐ์์ ์ฒ๋ฆฌ๋ฅผ ํ์์ ์
```Swift
class Weather {
@Published var temperature: [Double]
init(temperature: [Double]) {
self.temperature = temperature
}
}
var cancellables: Set<AnyCancellable> = []
let weather = Weather(temperature: [20])
weather.$temperature
.sink() {
print ("Temperature now: \($0)")
}.store(in: &cancellables)
weather.temperature = [25] // [20, 25]๋ฅผ printํ๋ค.
```
### 2๏ธโฃcell identifier
cell์ collectionView์ ๋ฑ๋กํ ๋, "cell"์ด๋ผ๋ ๋ค์ ๋ชจํธํ identifier๋ฅผ ์์ฑํ์์ต๋๋ค. ๋ชจํธ์ฑ์ ์์ ๊ธฐ ์ํด `identifierType`์ด๋ผ๋ ํ๋กํ ์ฝ์ ๊ตฌํํ์ฌ, `UIViewController`์ `UICollectionViewCell`์ ์ฑํ์ ํ์์ต๋๋ค.
- ๋ณ๊ฒฝ ์
```swift
self.collectionView.register(ScheduleCell.self, forCellWithReuseIdentifier: "cell")
```
- ๋ณ๊ฒฝ ํ
```swift
public protocol IdentifierType {
static var identifier: String { get }
}
extension IdentifierType {
public static var identifier: String {
return String(describing: self)
}
}
extension UICollectionViewCell: IdentifierType {}
extension UIViewController: IdentifierType {}
collectionView.register(ScheduleCell.self, forCellWithReuseIdentifier: ScheduleCell.identifier)
```
### 3๏ธโฃ๋งค์ง ๋๋ฒ
๊ธฐ์กด์๋ constant๊ฐ์ด CGFloat๊ฐ ๋ค์ด๊ฐ๋ ์๋ฆฌ์ 20, 25๋ฑ์ ์ง์ ์ ์ด์ฃผ๋ฉฐ ์ฌ์ฉํ์์ต๋๋ค. ํ์ง๋ง ์ด๋ ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๊ฒ ๋๋ฉด 20, 25๊ฐ์ ๋งค์ง ๋๋ฒ๊ฐ ์๊ฒจ๋ฒ๋ฆฐ๋ค. ๋งค์ง ๋๋ฒ๋ฅผ ํด๊ฒฐํด์ฃผ๊ธฐ ์ํด ํ์ผ ๋ด๋ถ์ private let ์ผ๋ก ๋ณ์๋ฅผ ์ ์ธํ๊ณ ์ ์ธํ ๋ณ์๋ฅผ ๋ฃ์ด์ฃผ๋ ์์ผ๋ก ๋ฆฌํฉํ ๋ง์ ํ์์ต๋๋ค.
- ์์ ์
```swift
private func configureUI() {
contentView.backgroundColor = .white
contentView.addSubview(stackView)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(contentLabel)
stackView.addArrangedSubview(expirationLabel)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15),
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 15),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -15)
])
}
```
- ์์ ํ
```swift
private let cellPadding: CGFloat = 15
...
private func configureUI() {
contentView.backgroundColor = .white
contentView.addSubview(stackView)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(contentLabel)
stackView.addArrangedSubview(expirationLabel)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: cellPadding),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -cellPadding),
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: cellPadding),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -cellPadding)
])
}
```
### 4๏ธโฃMVVM ๋ฆฌํฉํ ๋ง
MVVM์ ๋ํ ์ ํํ ์ดํด์ ์ฌ์ฉ์ ์ํด ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ์ต๋๋ค.
MVVM์ ๊ฒฝ์ฐ MVC์์ ViewController์ ์ญํ ์ด ๋น๋ํด์ง์ ๋ฐฉ์งํ๊ณ , ๋
๋ฆฝ์ ์ธ ํ
์คํธ๊ฐ ๊ฐ๋ฅํ๊ฒ ํ๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค. ํ์ง๋ง ViewModel์ด ์ฌ๋ฌ ViewController์ ์ฌ์ฉ๋๊ณ , ๊ธฐ๋ฅ์ด ๋ง์์ง๋ค๋ฉด, ViewModel ๋ํ MVC์ ViewController์ฒ๋ผ ๋น๋ํด์ง ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.
์ด์ ๋ด๋ฆฐ ๊ฒฐ๋ก ์ Viewcontroller ํ๋๋น ํ๋์ ViewModel์ ๊ฐ์ง๊ณ ์์ด์ผ ํ๋ฉฐ, ViewModel์ ์์ ์ด ํ์ํ ๋ฐ์ดํฐ๋ง Model์์ ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ต๋๋ค.
- Model
```swift
class ScheduleManager {
static let shared = ScheduleManager()
@Published var todoSchedules: [Schedule] = []
@Published var doingSchedules: [Schedule] = []
@Published var doneSchedules: [Schedule] = []
func addTodoSchedule( schedule: Schedule) {
todoSchedules.append(schedule)
}
func sendSchedule(scheduleType: ScheduleType) -> AnyPublisher<[Schedule], Never> {
switch scheduleType {
case .todo:
return $todoSchedules.eraseToAnyPublisher()
case .doing:
return $doingSchedules.eraseToAnyPublisher()
case .done:
return $doneSchedules.eraseToAnyPublisher()
}
}
...
}
```
- ViewModel
```swift
final class DoListViewModel {
let scheduleManager = ScheduleManager.shared
@Published var scheduleList: [Schedule] = []
private var cancelBag: Set<AnyCancellable> = []
init(scheduleType: ScheduleType) {
fetchSchedule(scheduleType: scheduleType)
}
private func fetchSchedule(scheduleType: ScheduleType) {
scheduleManager.sendSchedule(scheduleType: scheduleType).assign(to: \.scheduleList, on: self).store(in: &cancelBag)
}
func deleteSchedule(scheduleType: ScheduleType, index: Int) {
scheduleManager.deleteSchedule(scheduleType: scheduleType, index: index)
}
func move(fromIndex: Int, from: ScheduleType, to: ScheduleType) {
scheduleManager.move(fromIndex: fromIndex, from: from, to: to)
}
}
```
## ์ฐธ๊ณ ๋งํฌ
- [Swift Tutorial: An Introduction to the MVVM Design Pattern](https://adevait.com/ios/swift-tutorial-mvvm-design-pattern)
- [Swift Document UICollectionViewCompositionalLayout](https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout)
- [Swift Document Published](https://developer.apple.com/documentation/combine/published)
- [Swift Document DatePicker](https://developer.apple.com/documentation/swiftui/datepicker)
- [Swift Document Combine](https://developer.apple.com/documentation/combine)