-
Notifications
You must be signed in to change notification settings - Fork 80
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
[Team-08][iOS] Calendar Model 생성 / CollectionView HeaderCell 추가 작업 #75
Changes from all commits
8319f7b
7ac4484
13d1bff
9ad9f5f
909f524
ef96abf
a8a6252
94f404f
2da81a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// | ||
// SearchDayDTO.swift | ||
// Airbnb | ||
// | ||
// Created by 백상휘 on 2022/05/26. | ||
// | ||
|
||
import Foundation | ||
|
||
struct SearchDayDTO { | ||
let date: Date | ||
let components: DateComponents | ||
let day: String | ||
// 캘린더에서 표시하지 않는 빈칸일 경우 true 입니다. | ||
let isOccupied: Bool | ||
|
||
let isSelected: Bool = false | ||
// 선택되었을 시 왼쪽부터 서서히 채워나가는 이펙트를 줌 | ||
var fadeLeft: Bool = false | ||
// 선택되었을 시 오른쪽부터 서서히 채워나가는 이펙트를 줌 | ||
var fadeRight: Bool = false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// | ||
// SearchCalendarModel.swift | ||
// Airbnb | ||
// | ||
// Created by 백상휘 on 2022/05/26. | ||
// | ||
|
||
import Foundation | ||
|
||
class SearchCalendarModel { | ||
|
||
private(set) var localCalendar: Calendar = { | ||
var calendar = Calendar(identifier: .gregorian) | ||
calendar.locale = Locale(identifier: "ko_kr") | ||
return calendar | ||
}() | ||
|
||
/// 전달되는 기준 날짜를 이용하여 날짜 DTO를 가져온다. | ||
func getMonthDays(from baseDate: Date) -> CalendarResult { | ||
generateMonth(from: baseDate, isPrevious: nil) | ||
} | ||
|
||
/// 전달되는 기준 날짜를 이용하여 다음월 날짜 DTO를 가져온다. | ||
func getNextMonthDays(from baseDate: Date) -> CalendarResult { | ||
generateMonth(from: baseDate, isPrevious: false) | ||
} | ||
|
||
/// 전달되는 기준 날짜를 이용하여 전월 날짜 DTO를 가져온다. | ||
func getPreviousMonthDays(from baseDate: Date) -> CalendarResult { | ||
generateMonth(from: baseDate, isPrevious: true) | ||
} | ||
|
||
private func generateMonth(from baseDate: Date, isPrevious: Bool?) -> CalendarResult { | ||
|
||
var moveNum = 0 | ||
|
||
if let previous = isPrevious { | ||
moveNum = previous ? -1 : 1 | ||
} | ||
|
||
guard let dateMonthMoved = localCalendar.date( | ||
byAdding: .month, | ||
value: moveNum, | ||
to: baseDate) | ||
else { | ||
return CalendarResult(date: baseDate, result: []) | ||
} | ||
|
||
var dateComponent = localCalendar.dateComponents([.year,.month,.day,.hour], from: dateMonthMoved) | ||
dateComponent.day = 2 | ||
dateComponent.hour = 0 | ||
|
||
guard | ||
let dateFirstInMonth = localCalendar.date(from: dateComponent), | ||
let numberOfDays = localCalendar.range( | ||
of: .day, | ||
in: .month, | ||
for: dateFirstInMonth)?.count | ||
else { | ||
return CalendarResult(date: baseDate, result: []) | ||
} | ||
|
||
// Swift는 요일을 0부터 6까지로 매기고, weekday는 1부터 7까지로 매깁니다. | ||
// 미국 날짜와 한국 날짜는 하루 차이가 납니다. | ||
// 위와 아래의 사실에 의거하여 2를 뺍니다. | ||
let weekdayTemp = localCalendar.component(.weekday, from: dateFirstInMonth) - 2 | ||
let weekday = weekdayTemp < 0 ? 6 : weekdayTemp | ||
|
||
guard var date = localCalendar.date( | ||
byAdding: .day, | ||
value: weekday * -1, | ||
to: dateFirstInMonth) | ||
else { | ||
return CalendarResult(date: baseDate, result: []) | ||
} | ||
|
||
var result = [SearchDayDTO]() | ||
|
||
for day in 1...weekday+numberOfDays { | ||
result.append( | ||
SearchDayDTO( | ||
date: date, | ||
components: localCalendar.dateComponents([.year,.month,.day], from: date), | ||
day: (weekday >= day ? "" : "\(day-weekday)"), | ||
isOccupied: (weekday >= day) | ||
) | ||
) | ||
|
||
if let targetDate = localCalendar.date(byAdding: .day, value: 1, to: date) { | ||
date = targetDate | ||
} | ||
} | ||
|
||
return CalendarResult(date: dateFirstInMonth, result: result) | ||
} | ||
} | ||
|
||
struct CalendarResult { | ||
let date: Date | ||
let result: [SearchDayDTO] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,12 +6,18 @@ | |
// | ||
|
||
import UIKit | ||
import SnapKit | ||
|
||
class LocationViewController: BackgroundViewController, CommonViewControllerProtocol { | ||
|
||
|
||
private let locations: [String] = [ | ||
"서울", "광주", "의정부시", "수원시", "대구", "울산", "대전", "부천시" | ||
] | ||
|
||
private let searchController: UISearchController = { | ||
let searchController = UISearchController(searchResultsController: nil) | ||
let searchBar = searchController.searchBar | ||
searchBar.searchBarStyle = .minimal | ||
searchBar.placeholder = "어디로 여행가세요?" | ||
searchBar.searchTextField.clearButtonMode = .never | ||
searchBar.searchTextField.returnKeyType = .go | ||
|
@@ -20,40 +26,70 @@ class LocationViewController: BackgroundViewController, CommonViewControllerProt | |
return searchController | ||
}() | ||
|
||
private var nearbyLoactionView: UICollectionView = { | ||
let layout = UICollectionViewFlowLayout() | ||
layout.scrollDirection = .vertical | ||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) | ||
collectionView.register(LocationCollectionViewCell.self, forCellWithReuseIdentifier: LocationCollectionViewCell.reuseIdentifier) | ||
collectionView.register(HeaderReusableView.self, | ||
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HeaderReusableView.ID) | ||
return collectionView | ||
}() | ||
|
||
lazy var removeButton: UIBarButtonItem = { | ||
lazy var removeSearchTextField: UIBarButtonItem = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 버튼은 네이밍이 메소드스럽네요ㅎㅎ |
||
let barButton = UIBarButtonItem(title: "지우기", style: .done, | ||
target: self, action: #selector(self.clearSearchField(_:))) | ||
barButton.tintColor = .gray | ||
return barButton | ||
}() | ||
|
||
func attribute() { | ||
|
||
setUpDelegates() | ||
navigationItem.searchController = searchController | ||
navigationItem.title = "숙소 찾기" | ||
super.setUpNavigationAppearance() | ||
view.backgroundColor = .systemBackground | ||
} | ||
|
||
func layout() { | ||
let sideMargin: CGFloat = 16 | ||
view.addSubview(nearbyLoactionView) | ||
|
||
nearbyLoactionView.snp.makeConstraints { | ||
$0.top.bottom.equalTo(view) | ||
$0.leading.equalTo(view).offset(sideMargin) | ||
$0.trailing.equalTo(view).inset(sideMargin) | ||
} | ||
} | ||
|
||
func bind() { | ||
|
||
} | ||
|
||
private func setUpSearchController() { | ||
self.navigationItem.searchController = searchController | ||
private func setUpDelegates() { | ||
nearbyLoactionView.delegate = self | ||
nearbyLoactionView.dataSource = self | ||
searchController.searchBar.delegate = self | ||
searchController.searchResultsUpdater = self | ||
definesPresentationContext = true | ||
} | ||
|
||
override func viewDidLoad() { | ||
super.viewDidLoad() | ||
super.setUpNavigationAppearance() | ||
setUpSearchController() | ||
attribute() | ||
layout() | ||
bind() | ||
} | ||
|
||
} | ||
|
||
extension LocationViewController: UISearchBarDelegate, UITextFieldDelegate { | ||
extension LocationViewController: UISearchBarDelegate, UITextFieldDelegate, UISearchResultsUpdating | ||
{ | ||
func updateSearchResults(for searchController: UISearchController) { | ||
guard let query = searchController.searchBar.text else { return } | ||
dump(query) | ||
} | ||
|
||
|
||
@objc func clearSearchField(_ sender: Any) { | ||
searchController.searchBar.text = nil | ||
|
@@ -71,7 +107,46 @@ extension LocationViewController: UISearchBarDelegate, UITextFieldDelegate { | |
|
||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { | ||
!searchText.isEmpty | ||
? (navigationItem.rightBarButtonItem = removeButton) | ||
? (navigationItem.rightBarButtonItem = removeSearchTextField) | ||
: (navigationItem.rightBarButtonItem = nil) | ||
Comment on lines
109
to
111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 참고로 이 부분은 아래와 같이 쓸 수도 있습니다 :) navigationItem.rightBarButtonItem = !searchText.isEmpty ? removeSearchTextField : nil |
||
} | ||
} | ||
|
||
extension LocationViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource { | ||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { | ||
locations.count | ||
} | ||
|
||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | ||
guard let cell = nearbyLoactionView.dequeueReusableCell(withReuseIdentifier: LocationCollectionViewCell.reuseIdentifier, for: indexPath) as? LocationCollectionViewCell else { | ||
return UICollectionViewCell() | ||
} | ||
cell.cityName.text = locations[indexPath.row] | ||
cell.spendingTime.text = locations[indexPath.row] | ||
Comment on lines
+124
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. locations.count가 곧 셀 개수가 되긴 하지만, index를 통해 정보를 빼 내올 때엔 늘 안전장치를 마련해두는 것이 좋습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그리고 locations를 뷰 컨트롤러에서 가지고 있을 때의 장단점에 대해서도 생각해보면 좋겠네요ㅎㅎ |
||
return cell | ||
} | ||
|
||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { | ||
return CGSize(width: collectionView.frame.width, height: 64) | ||
} | ||
|
||
|
||
|
||
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> | ||
UICollectionReusableView { | ||
switch kind { | ||
case UICollectionView.elementKindSectionHeader: | ||
let headerView = nearbyLoactionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: HeaderReusableView.ID, for: indexPath) | ||
return headerView | ||
default: | ||
assert(false, "header, footer, withReuseIdentifier 를 확인하세요.") | ||
} | ||
} | ||
|
||
|
||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { | ||
let width: CGFloat = nearbyLoactionView.frame.width | ||
let headerViewHeight: CGFloat = 78 | ||
return CGSize(width: width, height: headerViewHeight) | ||
} | ||
} |
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.
fadeLeft와 fadeRight가 동시에 true일 경우도 있는 걸까요?👀
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.
말씀 듣고 보니 디자인에는 둘 다 true 일 수 없군요!
fade 라는 것 자체를 enum 타입으로 변경하는 것이 의미 상 맞는 것 같습니다!!