This PR contains the work done to add a location at a time and updates the locations in the list of locations screen right after. To give further details about the work done: - [x] implemented the `LocationProvider` provider in the **Persistence** library; - [x] implemented the `SaveLocalLocationUseCase` use case; - [x] defined the properties and functions of the `LocationsAddViewModeling` protocol to support the clean, updating and saving of locations; - [x] implemented the `LocationsAddViewModel` view model; - [x] implemented the `LocationsAddViewController` view controller; - [x] implemented the dismissal of the `LocationsAddCoordinator` coordinator. Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Reviewed-on: rock-n-code/deep-linking-assignment#11
150 lines
4.6 KiB
Swift
150 lines
4.6 KiB
Swift
//
|
|
// LocationProvider.swift
|
|
// Persistence
|
|
//
|
|
// Created by Javier Cicchelli on 12/04/2023.
|
|
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
|
//
|
|
|
|
import Combine
|
|
import CoreData
|
|
|
|
public class LocationProvider: NSObject {
|
|
|
|
// MARK: Properties
|
|
|
|
private let fetchedResultsController: NSFetchedResultsController<Location>
|
|
|
|
/// The publisher that emits the changes detected to the Location entities in a given object context.
|
|
public let didChangePublisher = PassthroughSubject<[Change], Never>()
|
|
|
|
private var inProgressChanges: [Change] = []
|
|
|
|
/// The number of sections in the data.
|
|
public var numberOfSections: Int { fetchedResultsController.sections?.count ?? 0 }
|
|
|
|
// MARK: Initialisers
|
|
|
|
/// Initialise this provider with the managed object context that would be used.
|
|
/// - Parameter managedContext: A `NSManagedObjectContext` object context instance that will be used to provide entities.
|
|
public init(managedContext: NSManagedObjectContext) {
|
|
self.fetchedResultsController = .init(
|
|
fetchRequest: .allLocations(),
|
|
managedObjectContext: managedContext,
|
|
sectionNameKeyPath: nil,
|
|
cacheName: nil
|
|
)
|
|
|
|
super.init()
|
|
|
|
self.fetchedResultsController.delegate = self
|
|
}
|
|
|
|
// MARK: Functions
|
|
|
|
/// Perform the fetching.
|
|
public func fetch() throws {
|
|
try fetchedResultsController.performFetch()
|
|
}
|
|
|
|
/// Retrieve the number of locations inside a given section number.
|
|
/// - Parameter section: The section number to inquiry about.
|
|
/// - Returns: A number of locations inside the given section number.
|
|
public func numberOfLocationsInSection(_ section: Int) -> Int {
|
|
guard
|
|
let sections = fetchedResultsController.sections,
|
|
sections.endIndex > section
|
|
else {
|
|
return 0
|
|
}
|
|
|
|
return sections[section].numberOfObjects
|
|
}
|
|
|
|
/// Retrieve a location entity out of a given index path.
|
|
/// - Parameter indexPath: The index path to which retrieve a location entity.
|
|
/// - Returns: A `Location` entity positioned in the given index path.
|
|
public func location(at indexPath: IndexPath) -> Location {
|
|
return fetchedResultsController.object(at: indexPath)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - NSFetchedResultsControllerDelegate
|
|
|
|
extension LocationProvider: NSFetchedResultsControllerDelegate {
|
|
|
|
// MARK: Functions
|
|
|
|
public func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
|
inProgressChanges.removeAll()
|
|
}
|
|
|
|
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
|
didChangePublisher.send(inProgressChanges)
|
|
}
|
|
|
|
public func controller(
|
|
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
|
didChange sectionInfo: NSFetchedResultsSectionInfo,
|
|
atSectionIndex sectionIndex: Int,
|
|
for type: NSFetchedResultsChangeType
|
|
) {
|
|
if type == .insert {
|
|
inProgressChanges.append(.section(.inserted(sectionIndex)))
|
|
} else if type == .delete {
|
|
inProgressChanges.append(.section(.deleted(sectionIndex)))
|
|
}
|
|
}
|
|
|
|
public func controller(
|
|
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
|
didChange anObject: Any,
|
|
at indexPath: IndexPath?,
|
|
for type: NSFetchedResultsChangeType,
|
|
newIndexPath: IndexPath?
|
|
) {
|
|
switch type {
|
|
case .insert:
|
|
guard let newIndexPath else { return }
|
|
|
|
inProgressChanges.append(.object(.inserted(at: newIndexPath)))
|
|
case .delete:
|
|
guard let indexPath else { return }
|
|
|
|
inProgressChanges.append(.object(.deleted(from: indexPath)))
|
|
case .move:
|
|
guard let indexPath, let newIndexPath else { return }
|
|
|
|
inProgressChanges.append(.object(.moved(from: indexPath, to: newIndexPath)))
|
|
case .update:
|
|
guard let indexPath else { return }
|
|
|
|
inProgressChanges.append(.object(.updated(at: indexPath)))
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Enumerations
|
|
|
|
public enum Change: Hashable {
|
|
public enum SectionUpdate: Hashable {
|
|
case inserted(Int)
|
|
case deleted(Int)
|
|
}
|
|
|
|
public enum ObjectUpdate: Hashable {
|
|
case inserted(at: IndexPath)
|
|
case deleted(from: IndexPath)
|
|
case updated(at: IndexPath)
|
|
case moved(from: IndexPath, to: IndexPath)
|
|
}
|
|
|
|
case section(SectionUpdate)
|
|
case object(ObjectUpdate)
|
|
}
|
|
|