프로젝트 기간: 2022.06.13 ~ 2022.07.01
팀원: Donnie, OneTool 리뷰어: 또치
View가 사라지면 자동 저장 | Swipe Action | ActionSheet / Alert |
---|---|---|
내용 업데이트 | 백그라운드 진입시 자동저장 | 날씨 아이콘 표시 |
---|---|---|
CollectionView
MVC
Keyboard
ContentInset
Padding
ScrollView
StackView
Json
NavigationBar
TimeInterval
DateFormatter
CoreData
NotificationCenter
CoreData(CRUD)
Cell Swipe Action
Activity View
Action Sheet
Alert
subscript
URL
CoreLocation
Network
API
ImageView
Migration
- 사용자의 지역에 따른 "년월일" 형식을
DateFormatter
로 유연하게 구현timeIntervalSince1970
를 활용하여 날짜계산 및 처리
Storyboard
없이 코드를 이용하여UI
구현Model
생성 후decode
를 하여,data
사용CollectionView
를 활용,ListCell
구현Navigation Controller
를 활용하여,View
및ViewController
전환UIResponder
,NotificationCenter
,keyboardWillShow
,keyboardWillHide
를 사용하여, Keyboard 구현
CoreData
를 활용하여Local
디바이스에 데이터를 CRUD(create
,read
,update
,delete
)하는 기능 구현CollectionViewListCell
를Swipe
하여Share
,Delete
하는 기능 적용 및 구현ActionSheet
와Alert
의 활용- 하나의
View
를 두개의ViewController
에서 사용하도록 로직 구현 Keyboard
의 높이만큼ScrollView
에inset
을 주어 내용을 가리지 않도록 구현Array
를 확장하고subscript
를 활용하여index out of range
오류처리 기능 구현
- 사용자에게 위치정보 활용에 대한 권한을 받는 기능 구현
URL
을 활용하여, 통신하여 위도, 경도 및 해당 지역 날씨에 해당하는 icon의 이미지를 넣어주는 기능 구현- CoreData Migration을 활용하여, Model 변경
TimeInterval
을 활용하여 날짜 계산하기
dateStyle : .full 인 경우 → 2022년 6월 17일 금요일 dateStyle : .long 인 경우 → 2021년 6월 17일 dateStyle : .medium 인 경우 → 2022. 6. 17. dateStyle : .short 인 경우 → 2022. 6. 17.
timeStyle : .full 인 경우 → 오후 4시 28분 39초 대한민국 표준시 timeStyle : .long 인 경우 → 오후 4시 29분 39초 GMT+9 timeStyle : .medium 인 경우 → 오후 4:29:39 timeStyle : .short 인 경우 → 오후 4:29
Date Formatter
로 사용자의 지역에 따른 "년월일" 형식을 다르게 구현하는 방법
- CoreData
CoreData
는 Framework
로, permanent data
를 저장하여 활용할 수 있다. 하지만 CoreData
는 Database
로 볼 수 없다. 넓은 의미로 앱의 모델 계층이고, 객체 그래프를 관리하는 Framework
이다.
- Collection View Swipe Action
Collection View
에서 Swipe Action
을 활용하기 위해서는, UICollectionLayoutListConfiguration
의 trailingSwipeActionsConfigurationProvider
를 활용하면 된다. trailingSwipeActionsConfigurationProvider
에서 UIContextualAction
을 활용하여, Action
을 설정해주면 된다.
private var listLayout: UICollectionViewCompositionalLayout {
var configure = UICollectionLayoutListConfiguration(appearance: .plain)
configure.showsSeparators = true
configure.backgroundColor = .clear
configure.trailingSwipeActionsConfigurationProvider = { [weak self] indexPath in
let share = UIContextualAction(style: .normal, title: nil) { [weak self] _, _, completion in
guard let diaryTitle = self?.diaryData[indexPath.row].title else {
return
}
let activityViewController = UIActivityViewController(
activityItems: [diaryTitle],
applicationActivities: nil
)
self?.present(activityViewController, animated: true)
completion(true)
}
share.image = UIImage(systemName: DiaryConstants.cellSwipeShareButton)
share.backgroundColor = .systemBlue
let delete = UIContextualAction(style: .destructive, title: nil) { [weak self] _, _, completion in
guard let diaryData = self?.diaryData[indexPath.row] else {
return
}
self?.persistenceManager.delete(diary: diaryData)
self?.diaryData.remove(at: indexPath.row)
self?.collectionView.deleteItems(at: [indexPath])
completion(true)
}
delete.image = UIImage(systemName: DiaryConstants.cellSwipeDeleteButton)
return UISwipeActionsConfiguration(actions: [delete, share])
}
return UICollectionViewCompositionalLayout.list(using: configure)
}
- NotificationCenter를 활용한 background Task
background
상태에 진입하고 나서, Task
를 원한다면 didEnterBackgroundNotification
에 대한 Observer
를 추가해주면 된다.
NotificationCenter.default.addObserver(
self,
selector: #selector(/*@objc method*/),
name: UIApplication.didEnterBackgroundNotification,
object: nil
)
-
ViewModel과 Controller의 관계 처리가 필요한 로직들은 대부분 ViewModel에서 처리해주고, Controller는 사용자에게 입력 받은 값만 넘겨주는 역할 이라는 것을 알았다. MVVM 패턴에 대해서 조금이나마 학습하게 되었다.
-
MVVM 구조
>Model: 어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분 View : 사용자에게 보여지는 UI View Model : View를 나타내주기 위한 Model이며, View를 나타내기 위한 데이터 처리를 하는 부분
- MVVM 동작 순서
User의 Action -> View -> View Model -> Model -> View Model -> Data Binding -> UI
- MVVM 특징
Command 패턴 + Data Binding 패턴을 사용하여 구현, View와 View Model 사이의 의존성을 없애고, View Model과 View의 관계를 1:n 관계로 만들었다. View와 View Model이 독립적이기 때문에, 모듈화가 가능하다. 하지만, View Model의 설계 난이도가 상당히 높다.
- JSON견본 파일에 있는 createdAt의 숫자의 의미를 알아내기 위해 다른 캠퍼들과 이야기를 나누었고, 어떻게 처리해야 하는가에 고민을 하였습니다.
- 그러면서 사용자가 설정하는 지역에 따라 다르게 포맷을 주어야 했어서 TimeInterval을 확장하였고, 아래와 같은 formattedDate라는 연산 프로퍼티를 만들게 되었습니다.
extension TimeInterval {
var formattedDate: String {
let dateFormatter = DateFormatter()
let localID = Locale.preferredLanguages.first
let deviceLocale = Locale(identifier: localID ?? "ko-kr").languageCode
dateFormatter.dateStyle = .long
dateFormatter.locale = Locale(identifier: deviceLocale ?? "ko-kr")
dateFormatter.timeZone = TimeZone.current
return dateFormatter.string(from: Date(timeIntervalSince1970: self))
}
}
- 캡슐화를 하기 위해서,
private
을 모두 사용해주려고 하였으나, ViewController에서 Keyboard의 높이를 조정할 때mainScrollView
와descriptionView
를 사용해야했습니다. 이러한 경우에는 캡슐화를 어떻게 해주는 게 좋을까요?
KeyboardLayoutGuide
를 사용해면 해결되겠지만, iOS 15이상이라서 지양했습니다!
- 방법이 두가지가 있다고 생각했습니다.
- Entity를 각 ViewController가 가지고 있게 만들고, 직접적으로 CoreData에 접근하는 방식
- Entity를 생성해두었던 Model이랑 Type을 맞춰주어서 직접 Entity로 접근하는 것이 아닌 Model을 통하여 접근하는 방식
직접적인 접근은 안전하지 않을 것 같아서, 2번을 선택해 주었습니다.
//
// DiaryEntity+CoreDataProperties.swift
@NSManaged public var body: String?
@NSManaged public var createdAt: Date
@NSManaged public var title: String?
@NSManaged public var id: String
//
// DiaryModel.swift
struct DiaryModel: Decodable {
let title: String?
let body: String?
let createdAt: Date
let id: String
private enum CodingKeys: String, CodingKey {
case createdAt = "created_at"
case title, body, id
}
}
- 현재
RegisterViewController
에서 diaryModel에 데이터가 있는지 없는지에 대해서, 분기처리를 해주었습니다. - 그로 인해서 icon의 데이터를 CoreData에 저장을 해준 후, 실행하게되면 데이터 유무를 체크해주는 로직에서 CoreData에 create만 진행하게 됩니다.
- 계속해서 CoreData 자원을 사용하게 됩니다. 그래서 직접적으로 CoreData의 DiaryData에 icon에 대한 정보를 저장해주었고,
RegisterViewController
에서 사용되는 diaryModel에는 데이터의 유무를 판단할 수 있는 분기처리용 로직을 유지되게 하였습니다.
private var icon: String?
private func sendDiaryViewModel() {
do {
try diaryViewModel.checkDiaryData(
title: detailView.titleField.text,
body: detailView.descriptionView.text,
icon: icon
)
} catch {
showAlert(alertMessage: error.localizedDescription)
}
}
}