๐ SOPT 27th APPJAM ๐
HOUSING iOS
- 2020.12.26 ~ 2021.01.16
๋ผ์ด๋ธ๋ฌ๋ฆฌ | ๋ชฉ์ | |
---|---|---|
RxSwift | ๋น๋๊ธฐ ์ฒ๋ฆฌ | SPM |
Kingfisher | ์ด๋ฏธ์ง ์บ์ค | SPM |
SnapKit | ์คํ ๋ ์ด์์ | SPM |
Alamofire | ์๋ฒ ํต์ | SPM |
Then | ์ปดํฌ๋ํธ ์ฝ๋ ์์ฑ์ ์ฉ์ด๋ฅผ ์ํด | SPM |
FSCalendar | ์บ๋ฆฐ๋ ์ฌ์ฉ | SPM |
SwiftKeychainWrapper | ์ ์ฅ์ ์ํธํ | SPM |
YPImagePicker | ์ฌ์ง์ฒฉ ์ฌ์ฉ | SPM |
RxKeyboard | ํค๋ณด๋ ๋์ ์ฌ์ฉ | SPM |
Moya | ์๋ฒ ํต์ | SPM |
Lottie | ์ ๋๋ฉ์ด์ ์ฌ์ฉ | SPM |
SegementSlide | ํญ๋ฐ ์ฌ์ฉ | CocoaPod |
- ์ธ์ ์
๊ธฐ๋ฅ | ์์ธ ๊ธฐ๋ฅ | ๋ด๋น์ | ๊ตฌํ ์ฌ๋ถ | ํต์ ๊ตฌํ ์ฌ๋ถ |
---|---|---|---|---|
์คํ๋์ | ์คํ๋์ | ์คํ | โ | โ |
๋ก๊ทธ์ธ | ๋ก๊ทธ์ธ | ๋ฏผ์ | โ | โ |
ํ์๊ฐ์ | ์ด๋ ์ธ์ฆ | ๋ฏผ์ | โ | โ |
ํ์๊ฐ์ | ๋ฏผ์ | โ | โ | |
์ํตํ๊ธฐ | ์ํตํ๊ธฐ | ์ฃผ์ | โ | โ |
์ํตํ๊ธฐ ์์ธ | ํ์ | โ | โ | |
๋ฌธ์ ์์ฑ | ํํ | โ | โ | |
์บ๋ฆฐ๋ | ์บ๋ฆฐ๋ | ์คํ | โ | โ |
๋น์ผ ๋ฌธ์/๊ณต์ง์ฌํญ ๋ณด๊ธฐ | ์คํ | โ | โ | |
์ฐ๋ฆฌ์ง ์์ | ์๋์ธ ํ๋กํ | ๋ฏผ์ | โ | โ |
๊ณต์ง์ฌํญ | ๋ฏผ์ | โ | โ |
- ์ง์ฃผ์ธ
๊ธฐ๋ฅ | ์์ธ๊ธฐ๋ฅ | ๋ด๋น์ | ๊ตฌํ ์ฌ๋ถ | ํต์ ๊ตฌํ ์ฌ๋ถ |
---|---|---|---|---|
์คํ๋์ | ์คํ๋์ | ์คํ | โ | โ |
๋ก๊ทธ์ธ | ๋ก๊ทธ์ธ | ๋ฏผ์ | โ | โ |
ํ์๊ฐ์ | ํ์๊ฐ์ | ๋ฏผ์ | โ | โ |
์ํตํ๊ธฐ | ์ํตํ๊ธฐ | ์ฃผ์ | โ | โ |
์ํตํ๊ธฐ ์์ธ | ํ์ | โ | โ | |
๋ฌธ์ ํ์ธ | ํ์ | โ | โ | |
์บ๋ฆฐ๋ | ์บ๋ฆฐ๋ | ์คํ | โ | โ |
๋น์ผ ๋ฌธ์/๊ณต์ง์ฌํญ ๋ณด๊ธฐ | ์คํ | โ | โ | |
์ฐ๋ฆฌ์ง ์์ | ๋ด ํ๋กํ | ๋ฏผ์ | โ | โ |
๊ณต์ง์ฌํญ | ๋ฏผ์ | โ | โ | |
๊ณต์ง์ฌํญ ์์ฑ | ํํ | โ | โ | |
์ด๋ ๋ฒํธ ์์ฑ | ํํ | โ | โ |
-
์บ๋ฆฐ๋
FSCalendar ๋ฅผ ์ด์ฉํด ๊ฐ๋ฐ์ ์งํํ์ต๋๋ค.
๊ตฌํ ์ค ๊ฐ์ฅ ์ค์ํ๋ค ์๊ฐํ๋ ๋ถ๋ถ์ ์บ๋ฆฐ๋ ๋ด ์ ๋ณด ๊ด๋ฆฌ๋ถ๋ถ์ธ๋ฐ์.
์๋ฒ๋ก๋ถํฐ ๋ ์ง ์ ๋ณด๋ฅผ ๋ฐ์์ Dictionaryํํ๋ก ๋ง๋ค์ด ์ ์ฅ์ ํด๋๊ณ ([String : [CalendarModel]])
์ฌ์ฉ์์๊ฒ ํด๋นํ๋ ๋ ์ง์ ์ ๋ณด๊ฐ ์๋๊ฒฝ์ฐ ๋ฐ๋ณต๋ฌธ์ ๋๋ฆฌ๋๊ฒ๋ณด๋ค ํจ์จ์ ์ผ๋ก ์ ๋ณด ํธ์ถ์ ํ ์๊ฐ ์์์ต๋๋ค.
// ์บ๋ฆฐ๋ ์ ๋ณด ์ ์ฅ์ ์ํ ๋ณ์ var calendarDictionary: [String : [FSCalendarModel]] = [:] guard let promise: [FSCalendarModel] = calendarDictionary[day] else { return UICollectionViewCell() } if promise[indexPath.row].isNotice == 0 { let cell: CalendarCollectionViewCell = collectionView.dequeueCell(forIndexPath: indexPath) cell.calendar = promise[indexPath.row] cell.fetchCalendar() cell.fetchCategory() cell.fetchTime() return cell } else { let cell: NoticeCollectionViewCell = collectionView.dequeueCell(forIndexPath: indexPath) cell.calendar = promise[indexPath.row] cell.fetchCalendar() cell.fetchTime() return cell }
-
ํ์ฐ์ง ์ชฝ์ง
SegementSlide ๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฒด ๋ทฐ๋ฅผ ๊ตฌ์ฑํ์ต๋๋ค.
header ๋ถ๋ถ์๋ ์ ๋ชฉ๊ณผ ๋ฌธ์ ๋ด์ฉ์ด ๋ค์ด๊ฐ๊ณ ์๋ ๋ ๊ฐ์ ํญ์๋ ๊ฐ๊ฐ ์์ธ ์ ๋ณด์ ํ์ฐ์ง ์ชฝ์ง๊ฐ ๋ค์ด๊ฐ๋๋ค.
ํ์ฐ์ง ์ชฝ์ง๊ฐ ์ง์ฃผ์ธ๊ณผ ์์ทจ์์ ์ํต ํ๋ฆ์ ๋ณผ ์ ์๋ ํต์ฌ ๊ธฐ๋ฅ์ธ๋ฐ์.
MessageViewController ๋ด์ ํ ์ด๋ธ ๋ทฐ๋ฅผ ๋ฃ๊ณ ๊ทธ ์ ์์ ๋ค์ ํ ์ด๋ธ ๋ทฐ๋ฅผ ๋ฃ๋ ๋ฐฉ์์ผ๋ก ์งํํ์ต๋๋ค.
๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ฒด์์ ๋ด์ฅ ํจ์๋ก ํญ ์์ ๋ทฐ๊ฐ ํ ์ด๋ธ๋ทฐ๋ก ๊ทธ๋ ค์ง๊ธฐ ๋๋ฌธ์ ๊ทธ ์ฒซ ๋ฒ ์งธ ์ ์ ๋ค์ ํ ์ด๋ธ๋ทฐ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ์์ ์ ํํ๊ฒ ๋์์ต๋๋ค.
์ ๋ง๋ค ์ด๋ค ๋ทฐ๋ฅผ ๋ฃ์ด์ฃผ๊ณ ๊ทธ ์ ์์ ๋ฒํผ์ ์ด๋ค ํจ์๋ฅผ ๋ฃ๋์ง๊ฐ ๊ฐ์ฅ ์ค์ํ ๊ตฌํ์ฌํญ์ด์๋๋ฐ์.
Datasource๋ฅผ ์ต์คํ ์ ์ผ๋ก ์ ์ธํ์ฌ ๊ทธ ์์ ์ ๋ง๋ค์ ๋ฐ์ดํฐ๋ฅผ ์ ํด์ค ์ ์๋ cellForRowAt ์ด ํฌํจ๋ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ฅผ ๋ฐ๊ฟ์ฃผ๊ณ ๋ฒํผ์ด ๋๋ ธ์ ๋ selector๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์์์ต๋๋ค.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: MessageDetailTableViewCell = tableView.dequeueCell(forIndexPath: indexPath) if self.userOrOwner == 0 { if self.status[indexPath.row] == 0 { cell.titleLabel.text = "๋ฌธ์์ฌํญ์ด ๋ฑ๋ก๋์์ด์!" cell.contextLabel.attributedText = self.makeAttributed( context: "์๋์ ๋ฒํผ์ ๋๋ฌ\n์ฝ์์๊ฐ์ ์ ํด๋ณด์ธ์." ) cell.transitionButton.addTarget(self, action: #selector(didTapConfirmButton(_:)), for: .touchUpInside ) cell.transitionButton.setTitle("์ฝ์ ํ์ ํ๊ธฐ", for: .normal) } else if self.status[indexPath.row] == 1 { cell.titleLabel.text = "์ฝ์์ด ํ์ ๋์์ด์!" var confirmedPromise = "\(self.confirmedPromiseOption)์์ ์ด์์\n ์บ๋ฆฐ๋์์ ์ผ์ ์ ํ์ธํด๋ณด์ธ์." cell.contextLabel.attributedText = self.makeAttributed(context: confirmedPromise) cell.transitionButton.addTarget(self, action: #selector(didTapCalendarButton(_:)), for: .touchUpInside) cell.transitionButton.setTitle("์บ๋ฆฐ๋ ๋ณด๊ธฐ", for: .normal) } else if self.status[indexPath.row] == 2 { cell.titleLabel.text = "์ฝ์ ์์ ์์ฒญ์ ๋ณด๋์ด์!" cell.contextLabel.attributedText = self.makeAttributed( context: "์์ผ๋ก๋ ํ์ฐ์ง๊ณผ ํจ๊ป\n์์ทจ์๊ณผ ์ํตํด๋ณด์ธ์!" ) cell.transitionButton.snp.makeConstraints { $0.height.equalTo(0) } } ...
Extension | ๋ชฉ์ |
---|---|
UICollectionView+ | ์ฝ๋ ์ ๋ทฐ ๊ด๋ฆฌ |
UICollectionViewCell+ | ์ฝ๋ ์ ๋ทฐ ์ ๊ด๋ฆฌ |
UICollectionReusableView+ | ์ฝ๋ ์ ๋ทฐ, ํค๋ ํธํฐ ๋ทฐ ๊ด๋ฆฌ |
UITableView+ | ํ ์ด๋ธ ๋ทฐ ๊ด๋ฆฌ |
UITableViewCell+ | ํ ์ด๋ธ ๋ทฐ ์ ๊ด๋ฆฌ |
UIColor+ | color ์ฝ์ |
UIView+ | ๊ทธ๋ฆผ์ ์์ฑ ๋ทฐ, ์ปดํฌ๋ํธ ์ฝ์ , ๋ฑ๋ฑ... |
UIViewController+ | ํ ์คํฐ ์์ฑ / ๋ทฐ ์ปจํธ๋กค๋ฌ ๊ด๋ฆฌ |
CALayer+ | ๊ทธ๋ฆผ์ ์์ฑ |
UIImage+ | ์ด๋ฏธ์ง ํฌ๊ธฐ ์กฐ์ |
UIDatePicker+ | DatePicker๋ก ํ ์คํธ ์ปฌ๋ฌ ๋ฃ๊ธฐ |
UIImageView+ | URL๋ก ์ด๋ฏธ์ง ๋ฃ๊ธฐ |
๊ณฝ๋ฏผ์
extension CALayer { func applyShadow( color: UIColor = .black, alpha: Float = 0.1, x: CGFloat = 0, y: CGFloat = 0, blur: CGFloat = 8 ) { shadowColor = color.cgColor shadowOpacity = alpha shadowOffset = CGSize(width: x, height: y) shadowRadius = blur / 1.0 } } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell else { return UICollectionViewCell() } // collectionViewCell์ uiView outlet์ ์ถ๊ฐํ์ต๋๋ค. cell.containerView.layer.applyShadow() cell.backgroundColor = .white cell.contentView.backgroundColor = UIColor.white return cell }
๊น์ฃผ์
extension CommunicationViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.row == 0 { if tableViewData[indexPath.section].opened == true { tableViewData[indexPath.section].opened = false communicationTableView.backgroundColor = .primaryGray let sections = IndexSet(integer: indexPath.section) tableView.reloadSections(sections, with: .none) } else { tableViewData[indexPath.section].opened = true communicationTableView.backgroundColor = .primaryGray let sections = IndexSet(integer: indexPath.section) tableView.reloadSections(sections, with: .none) } communicationTableView.reloadData() } else { let viewController = DetailViewController() viewController.requestId = >tableViewData[indexPath.section].sectionData[indexPath.row-1].id navigationController?.pushViewController(viewController, animated: true) } } } }
๋ ธํ์
detailProvider.rx.request(.homeDetail(id: requestId)) .asObservable() .subscribe(onNext: { response in do{ let json = JSON(response.data) let decoder = JSONDecoder() let data = try decoder.decode(ResponseType<Detail>.self, from: response.data) let result = data.data self.statusModel.append(DetailStatus( ownerStatus: json["data"]["Replies"][0]["owner_status"].arrayValue.map{$0.intValue}, userStatus: json["data"]["Replies"][0]["user_status"].arrayValue.map{$0.intValue}, id: json["data"]["Replies"][0]["id"].intValue ) ) self.detailDataBind(result!) let viewController = ContentViewController() viewController.model = self.model let statusViewController = MessageViewController() self.idValue.id = data.data?.id ?? 11 statusViewController.model = self.model statusViewController.statusModel = self.statusModel //viewController.tableView.reloadData() statusViewController.tableView.reloadData() } catch { print(error) } }, onError: { error in print(error.localizedDescription) }, onCompleted: { self.headerViewLayout() self.detailHeaderView.snp.makeConstraints{ $0.height.equalTo(130+self.contextHeight()*22) } self.detailHeaderView.reloadInputViews() }).disposed(by: disposeBag)
๊นํํ
private var nextStep = UIButton().then{ $0.backgroundColor = .gray01 $0.setTitle("๋ค์ ๋จ๊ณ", for: .normal) $0.titleLabel?.font = UIFont(name: "AppleSDGothicNeo-Bold", size: 16) $0.isEnabled = false $0.setRounded(radius: 25) $0.addTarget(self, action: #selector(nextButtonDidTapped), for: .touchUpInside) } nextStep.snp.makeConstraints{ $0.top.equalTo(questionDescription.snp.bottom).offset(72) $0.centerX.equalTo(view) $0.width.equalTo(widthConstraintAmount(value: 255)) $0.height.equalTo(48) }
func resetTableViewHeight() { self.timeStampTableView.snp.updateConstraints{ $0.height.equalTo(CGFloat(70 * self.requestData.availableTimeList.count)) } self.underGrayView.snp.updateConstraints{ $0.height.equalTo(CGFloat(70 * requestData.availableTimeList.count) + 300) } } func addTimeStamp(sender : UIButton) { resetPickerLayout() resetTableViewHeight() let isTableViewEmpty = requestData.availableTimeList.isEmpty registerButton.isEnabled = isTableViewEmpty ? false : true registerButton.backgroundColor = isTableViewEmpty ? .gray : .primaryOrange tableViewBind() timeStampTableView.reloadData() }
์ค์คํ
๊ธฐ์กด์ ํ๋ก์ ํธ์์ ์ฌ์ฉ ํ ์ผ์ด ์์ด ์ฌ์ฉํ์ง ์์์ง๋ง ์ด๋ฒ ํ๋ก์ ํธ์์ ์บ๋ฆฐ๋๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด์ Dictionary ํ์ ์ ๋ํด ๋ค๋ฃจ์ด ๋ณด๋ ๊ธฐํ๋ฅผ ๊ฐ์ง๊ฒ ๋์์ต๋๋ค. ๊ตฌ์กฐ์ฒด๋ฅผ ๋ ์ง String Key๊ฐ์ ๋ง์ถฐ์ฃผ๋ ์ฝ๋๋ฅผ ์์ฑํ์์ต๋๋ค
for notice in data.notice {
let when = "\(notice.year).\(notice.month).\(notice.day)"
let model = FSCalendarModel(isNotice: notice.isNotice,
id: notice.id,
category: notice.category,
solutionMethod: notice.solutionMethod,
time: notice.time,
title: notice.title,
contents: notice.contents)ใ
calendarDictionary["\(when)"] = [model]
}
for promise in data.issue {
let when = "\(promise.year).\(promise.month).\(promise.day)"
let model = FSCalendarModel(isNotice: promise.isNotice,
id: promise.id,
category: promise.category,
solutionMethod: promise.solutionMethod,
time: promise.time,
title: promise.title,
contents: promise.contents)
if calendarDictionary[when]?.count == 0 {
calendarDictionary["\(when)"] = [model]
} else {
calendarDictionary[when]?.append(model)
}
}
์ค์คํ | ๋ ธํ์ | ๊ณฝ๋ฏผ์ | ๊น์ฃผ์ | ๊นํํ |