프로젝트 기간: 2023.8.28 ~ 2023.9.15
일기를 생성, 수정, 삭제할 수 있는 앱입니다. 현재 위치에 따라 오늘의 날씨 이모티콘이 함께 들어갑니다.
Max | hamg |
---|---|
Github Profile | Github Profile |
날짜 | 내용 |
---|---|
2023.08.28 | SwiftLint 라이브러리 추가 |
2023.08.29 | SwiftLint 조건 변경 |
2023.08.30 | DiaryEntity CreateDiaryViewController 생성keyboard NotificationCenter 생성 및 구현 |
2023.09.01 | CoreData : Create 구현 |
2023.09.05 | CoreData : UpDate , Delete 구현 Swipe share , delete 구현 AlertController 생성 |
2023.09.06 | CoreData : fetchDiary 구현 |
2023.09.07 | 개인 학습 및 README 작성 |
2023.09.10 | CoreDataError 생성, 예외처리 추가AlertVC 로직수정, Namespace 생성 |
2023.09.13 | WeatherAPI 통신 진행WeatherIcon Cache 구현 CoreLocation 생성 Migration-DiaryV2 구현 |
├── Diary
│ ├── Protocol
│ │ ├── AlertDisplayble.swift
│ │ └── ShareDisplayable.swift
│ ├── Extension
│ │ └── DateFormatter+.swift
│ ├── Error
│ │ ├── APIError.swift
│ │ ├── CoreDataError.swift
│ │ └── DecodingError.swift
│ ├── Model
│ │ ├── CoreData
│ │ │ ├── CoreDataManager.swift
│ │ │ ├── Diary+CoreDataClass.swift
│ │ │ └── Diary+CoreDataProperties.swift
│ │ ├── DTO
│ │ │ ├── DecodingManager.swift
│ │ │ └── WeatherResult.swift
│ │ ├── ImageCache
│ │ │ └── ImageCachingManager.swift
│ │ └── Namespace
│ │ ├── AlertNamespace.swift
│ │ └── ButtonNamespace.swift
│ ├── Network
│ │ ├── NetworkConfiguration.swift
│ │ └── NetworkManager.swift
│ ├── Controller
│ │ ├── DiaryDetailViewController.swift
│ │ └── DiaryListViewController.swift
│ ├── View
│ │ └── DiaryListTableViewCell.swift
│ ├── App
│ │ ├── AppDelegate.swift
│ │ └── SceneDelegate.swift
│ ├── Assets.xcassets
│ ├── Info.plist
│ └── Diary.xcdatamodeld
├── Diary.xcodeproj
└── README.md
새 일기 작성 | 일기 수정 | 일기 공유 | 일기 삭제 |
---|---|---|---|
일기 데이터를 위한 저장소로 CoreData를 활용하였습니다.
CoreData의 버전 정보를 추가하고 이를 MappingModel로 연결하여 DB 변경사항에 대한 Migration을 진행하였습니다.
데이터 처리를 위한 로직 전반을 Singleton 패턴으로 구현하여 앱 전역에서 활용 가능하도록 하였습니다.
키보드 활성화 여부에 따라 뷰의 크기를 변경하여 커서 위치가 가려지지 않도록 NotificationCenter를 활용하였습니다.
상황에 따라 ViewController에서 다른 데이터를 표시해야 하는 경우에 대비해 생성자를 활용하였습니다.
Alert, Swipe 등 별개의 작업으로 분리할 수 있는 내용들은 Protocol과 Extension을 통해 분리하였습니다.
하나의 NetworkManager 타입을 구현하여 날씨 API 데이터 통신과 아이콘 이미지 관련 통신에 모두 활용하였습니다.
🔒 문제점
일기 리스트 화면과 새로운 일기를 생성하는 화면에서 모두 아래와 같이 날짜 포매팅을 사용해야 하는 것을 알 수 있었습니다.
private let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ko_kr")
formatter.dateFormat = "yyyy년MM월dd일"
return formatter
}()
동일한 코드가 두 개의 ViewController
에서 반복되어 사용하고 있었으며
반복되는 것을 막기 위해 해당 코드를 분리하고자 했습니다.
🔑 해결방법
저장 프로퍼티가 아닌 메서드로 사용하여 재사용성을 높히게 되었습니다.
extension DateFormatter {
func formatToString(from date: Date, with format: String) -> String {
self.dateFormat = format
return self.string(from: date)
}
}
DateFormatter().formatToString(from: entity.createdAt, with: "YYYY년 MM월 dd일")
🔒 문제점
요구사항에 따르면 사용자가 화면을 벗어날 때마다 자동 저장을 진행해야 했습니다. 이를 구현하기 위해 처음에는 CreateViewController
의 viewWillDisappear
메서드에서 저장처리를 진행할 수 있도록 작업했습니다.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
saveDiary()
}
하지만 이렇게 하니 일기 삭제 처리를 한 뒤 뷰컨트롤러를 pop할 때에도 저장처리를 거치게 되어 오류가 발생하였습니다.
🔑 해결방법
TextView
가 수정될 때마다 뷰컨트롤러가 가지고 있는 일기 객체의 내용을 바꿔주고, 저장이 필요한 순간에 saveContext
처리만 진행할 수 있도록 아래와 같이 구현하였습니다.
func textViewDidChange(_ textView: UITextView) {
let contents = textView.text.split(separator: "\n")
guard !contents.isEmpty,
let title = contents.first else { return }
let body = contents.dropFirst().joined(separator: "\n")
diary.title = "\(title)"
diary.body = body
}
🔒 문제점(1)
처음에는 키보드가 비활성화되면 무조건 내용을 저장하도록 구현을 하였습니다. 하지만 이렇게 하니, 신규 생성 버튼(+)을 누른 뒤 아무런 내용도 입력하지 않고 뒤로 가기 처리를 하면 제목과 내용이 모두 비어있는 일기가 생성이 되었습니다.
func textViewDidEndEditing(_ textView: UITextView) {
CoreDataManager.shared.saveContext()
}
빈일기 생성 화면 |
---|
🔑 해결방법(1)
빈 일기가 생성되는것을 막기 위해 title 이 없을 경우 저장 되지 않게 진행하였습니다.
func textViewDidEndEditing(_ textView: UITextView) {
let contents = textView.text.split(separator: "\n")
guard !contents.isEmpty else { return }
CoreDataManager.shared.saveContext()
}
🔒 문제점(2)
위의 처리를 통해 더 이상 데이터베이스에 빈 일기가 저장되지는 않았지만, saveContext 되지 않은 객체가 여전히 context 내부에 남아 일시적으로 빈 일기가 리스트에 보이는 현상이 생겼습니다.
func readCoreData() {
do {
diaryList = try container.viewContext.fetch(Diary.fetchRequest())
tableView.reloadData()
} catch {
....
}
}
🔑 해결방법(2)
fetch해 온 일기들 중에 title이 비어있는 건은 걸러낼 수 있도록 filter 처리를 추가하였습니다.
private func readCoreData() {
do {
let fetchedDiaries = try CoreDataManager.shared.fetchDiary()
diaryList = fetchedDiaries.filter { $0.title != nil }
tableView.reloadData()
} catch {
.....
}
}
🔒 문제점
일기장 앱은 모든 셀이 서버통신을 통해 아이콘을 가지고 오도록 구현되어 있습니다. 하지만 날씨 아이콘은 몇 개의 정해진 아이콘을 반복하여 활용합니다. 따라서 동일한 이미지를 매번 통신을 통해 가져오는 것은 비효율적이라고 생각되었습니다.
🔑 해결방법
한 번 활용된 이미지는 NSCache
를 통해 캐싱 처리하여 바로 보여줄 수 있도록 구현하였습니다.
class ImageCachingManager {
static let shared = NSCache<NSString, UIImage>()
...
}
guard let image = UIImage(data: data) else { return }
DispatchQueue.main.async {
ImageCachingManager.shared.setObject(image, forKey: NSString(string: icon))
self?.weatherIconImageView.image = image
}
🔒 문제점 (1) - CoreLocation을 통해 정보를 받아오는 위치
실질적으로 Location 정보가 필요한 것은 DiaryDetailViewController
에서 날씨 API를 호출할 때입니다. 때문에 처음에는 DiaryDetailViewController
에서 활용 동의를 받고 위치 정보를 업데이트하도록 구현하려 하였습니다. 하지만 이렇게 하면 앱을 실행한 뒤 일기장 생성 화면에 넘어가서야 위치정보 활용 동의 창이 활성화되어 흐름상 어색해지고, 또 위치 정보가 제때 업데이트되지 않아 API 호출이 이루어지지 않는 등 다양한 문제가 발생했습니다.
🔑 해결방법 (1)
위치 정보 업데이트 자체는 첫 화면인 DiaryListViewController
에서 진행하고, DiaryDetailViewController
에서는 API 통신에 필요한 위도, 경도 데이터만 넘겨받을 수 있도록 구현하였습니다.
let createDiaryView = DiaryDetailViewController(latitude: self.latitude, longitude: self.longitude)
self.navigationController?.pushViewController(createDiaryView, animated: true)
또한 위치정보 활용에 동의하지 않은 경우에도 일기 자체는 작성 가능하도록 구현하기 위해(날씨 이모티콘만 제외) 위도, 경도 데이터는 nil로도 전달될 수 있도록 하였습니다.
init(latitude: Double?, longitude: Double?) {
self.diary = CoreDataManager.shared.createDiary()
self.isNew = true
self.latitude = latitude
self.longitude = longitude
super.init(nibName: nil, bundle: nil)
fetchWeather()
}
🔒 문제점 (2) - 시뮬레이터의 위치 정보 설정
시뮬레이터로 CoreLocation
기능을 테스트하면 시뮬레이터 자체에 설정된 Location 정보에 따라 위치를 표시하게 됩니다. 따라서 이 설정이 None으로 되어있을 경우에는 위치가 정상적으로 불러와지지 않습니다. 이 사실을 간과하여 테스트 과정에서 많은 시행착오를 거쳤습니다.
🔑 해결방법 (2)
Custom Location을 활용하여 정상적으로 테스트를 진행할 수 있었습니다.
- Apple Docs: Adaptivity and Layout
- Apple Docs: DateFormatter
- Apple Docs: UITextView
- Apple Docs: Core Data
- Apple Docs: Making Apps with Core Data
- Apple Docs: NSFetchedResultsController
- Apple Docs: UITextViewDelegate
- Apple Docs: UISwipeActionsConfiguration
- Apple Docs: CoreLocation
- Apple Docs: Migrating your data model automatically
- Apple Docs: NSCache
- Open Weather API