Building Netflix App in Swift 5 and UIKit - Episode 14 - CoreData and stuff
import Foundation
import CoreData
import Combine
class DatabaseManager {
enum DatabaseError: LocalizedError {
case failedToSaveData
case failedToFetchData
case failedToDeleteData
}
private var container: NSPersistentContainer?
private var context: NSManagedObjectContext {
guard let context = container?.viewContext else { fatalError() }
return context
}
static let shared = DatabaseManager()
private init() {
}
func setUp(with modelName: String) {
container = NSPersistentContainer(name: modelName)
container?.loadPersistentStores(completionHandler: { description, error in
guard error == nil else { return }
print("Successfully Loaded CoreData")
})
}
...
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
DatabaseManager.shared.setUp(with: "NetflixContentModel")
return true
}
setUp
이후 데이터베이스 클래스 사용 가능func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? {
guard let indexPath = indexPaths.first else { return nil }
let config = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in
let downloadAction = UIAction(title: "Donwload", subtitle: nil, image: nil, identifier: nil, discoverabilityTitle: nil, state: .off) { _ in
self?.downloadContentAt(indexPath: indexPath)
}
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: [downloadAction])
}
return config
}
downloadButton
.tapPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.viewModel.saveData()
}
.store(in: &cancellables)
func save(with model: ContentModel) -> AnyPublisher<Bool, Error> {
let item = ContentItem(context: context)
item.original_title = model.original_title
item.id = Int64(model.id)
item.title = model.title
item.media_type = model.media_type
item.poster_path = model.poster_path
item.popularity = model.popularity
item.release_date = model.release_date
item.vote_count = Int64(model.vote_count)
item.vote_average = model.vote_average
item.overview = model.overview
item.adult = model.adult
return Future { [weak self] promise in
do {
try self?.context.save()
promise(.success(true))
} catch {
promise(.failure(DatabaseError.failedToSaveData))
}
}
.eraseToAnyPublisher()
}
import Foundation
import Combine
class DownloadViewModel {
let downloadedContentsModel: CurrentValueSubject<[ContentModel], Never> = .init([])
private var cancellables = Set<AnyCancellable>()
private let database = DatabaseManager.shared
func deleteData(model: ContentModel) {
database
.delete(with: model)
.sink { completion in
switch completion {
case .failure(let error):
print(error.localizedDescription)
case .finished: break
}
} receiveValue: { [weak self] success in
self?.fetchData()
}
.store(in: &cancellables)
}
func fetchData() {
database
.fetch()
.sink { completion in
switch completion {
case .failure(let error): print(error.localizedDescription)
case .finished: break
}
} receiveValue: { [weak self] models in
self?.downloadedContentsModel.send(models)
}
.store(in: &cancellables)
}
}
viewWillAppear
단에서 자동으로 fetch
가 이루어지기 때문에 데이터 업데이트 가능extension DownloadViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let model = viewModel.downloadedContentsModel.value[indexPath.row]
viewModel.deleteData(model: model)
}
}
}
실제 넷플릭스의 모든 인터렉션을 담기에는 UI를 그리는 디테일한 능력도, 데이터 API 역시 부족했지만, 컬렉션 뷰의 컨텍스트 메뉴, 검색 바 커스텀 등 다양한 종류로 뷰를 그릴 수 있다는 데 자신감을 얻었다! 사실 강의의 내용과는 매우 달라진 결과물을 얻게 되었는데, 생각보다 매우 재미있었다.