Setup location tracking capabilities in the LocationCollectionView
This commit is contained in:
parent
e470e8ad40
commit
c59b394f73
|
@ -43,7 +43,7 @@
|
|||
F39A41C023A40C3800F3B91D /* WikipediaAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39A41BF23A40C3800F3B91D /* WikipediaAPIService.swift */; };
|
||||
F39A41C323A4179400F3B91D /* CypherPoetNetStack in Frameworks */ = {isa = PBXBuildFile; productRef = F39A41C223A4179400F3B91D /* CypherPoetNetStack */; };
|
||||
F39A41C523A41B5200F3B91D /* Endpoint+WikipediaAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39A41C423A41B5200F3B91D /* Endpoint+WikipediaAPI.swift */; };
|
||||
F3BF5BE1239BA2E900B9F8D8 /* LocationCollectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BF5BE0239BA2E900B9F8D8 /* LocationCollectionViewModel.swift */; };
|
||||
F3BF5BE1239BA2E900B9F8D8 /* LocationCollectionView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BF5BE0239BA2E900B9F8D8 /* LocationCollectionView+ViewModel.swift */; };
|
||||
F3BF5BE7239CF84D00B9F8D8 /* LocationCollection+Computeds.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BF5BE6239CF84D00B9F8D8 /* LocationCollection+Computeds.swift */; };
|
||||
F3BF5BE9239CF89B00B9F8D8 /* Location+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BF5BE8239CF89B00B9F8D8 /* Location+Comparable.swift */; };
|
||||
F3BF5C0E239D332400B9F8D8 /* Location+LocationAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BF5C0D239D332400B9F8D8 /* Location+LocationAnnotation.swift */; };
|
||||
|
@ -96,7 +96,7 @@
|
|||
F39A41BC23A3F53E00F3B91D /* EditLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLocationView.swift; sourceTree = "<group>"; };
|
||||
F39A41BF23A40C3800F3B91D /* WikipediaAPIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WikipediaAPIService.swift; sourceTree = "<group>"; };
|
||||
F39A41C423A41B5200F3B91D /* Endpoint+WikipediaAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Endpoint+WikipediaAPI.swift"; sourceTree = "<group>"; };
|
||||
F3BF5BE0239BA2E900B9F8D8 /* LocationCollectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCollectionViewModel.swift; sourceTree = "<group>"; };
|
||||
F3BF5BE0239BA2E900B9F8D8 /* LocationCollectionView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocationCollectionView+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
F3BF5BE6239CF84D00B9F8D8 /* LocationCollection+Computeds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocationCollection+Computeds.swift"; sourceTree = "<group>"; };
|
||||
F3BF5BE8239CF89B00B9F8D8 /* Location+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Location+Comparable.swift"; sourceTree = "<group>"; };
|
||||
F3BF5C0D239D332400B9F8D8 /* Location+LocationAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Location+LocationAnnotation.swift"; sourceTree = "<group>"; };
|
||||
|
@ -260,7 +260,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F374DC09239A431E0016CC65 /* LocationCollectionView.swift */,
|
||||
F3BF5BE0239BA2E900B9F8D8 /* LocationCollectionViewModel.swift */,
|
||||
F3BF5BE0239BA2E900B9F8D8 /* LocationCollectionView+ViewModel.swift */,
|
||||
F39A41BB23A3F52600F3B91D /* Edit Location */,
|
||||
);
|
||||
path = "Location Collection";
|
||||
|
@ -461,7 +461,7 @@
|
|||
F374DC14239A4B2C0016CC65 /* Location+CoreDataClass.swift in Sources */,
|
||||
F374DC15239A4B2C0016CC65 /* Location+CoreDataProperties.swift in Sources */,
|
||||
F3BF5C1A239D467F00B9F8D8 /* Location+FetchHelpers.swift in Sources */,
|
||||
F3BF5BE1239BA2E900B9F8D8 /* LocationCollectionViewModel.swift in Sources */,
|
||||
F3BF5BE1239BA2E900B9F8D8 /* LocationCollectionView+ViewModel.swift in Sources */,
|
||||
F3C8F65423A82E1C008B66A7 /* WikiPagesState.swift in Sources */,
|
||||
F36D295F239A071F00095B66 /* CoreDataManager.swift in Sources */,
|
||||
F3C8F65623A837A7008B66A7 /* EditLocationViewModel.swift in Sources */,
|
||||
|
|
|
@ -16,5 +16,4 @@ extension LocationCollection {
|
|||
|
||||
return locations.sorted()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>This application would like permission to read your current location. This will be used to enable current location pinning in a location collection map.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>This application would like permission to read and load images from your Photos Album. These can be used to as custom images for a location. </string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
|
|
|
@ -15,12 +15,11 @@ extension SampleData {
|
|||
|
||||
enum LocationCollections {
|
||||
static let `default`: LocationCollection = {
|
||||
let context = CurrentApp.coreDataManager.mainContext
|
||||
let collection = LocationCollection(context: context)
|
||||
|
||||
let collection = LocationCollection()
|
||||
|
||||
collection.title = "Default Collection"
|
||||
collection.addToLocations(SampleData.Locations.santorini)
|
||||
|
||||
|
||||
return collection
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@ extension SampleData {
|
|||
enum Locations {
|
||||
|
||||
static let santorini: Location = {
|
||||
let location = Location(context: CurrentApp.coreDataManager.mainContext)
|
||||
|
||||
let location = Location()
|
||||
|
||||
location.title = "Santorini"
|
||||
location.subtitle = "An an island in the southern Aegean Sea, speculated to be the inspiration for the city of Atlantis."
|
||||
|
||||
|
||||
location.latitude = 36.416667
|
||||
location.longitude = 25.433333
|
||||
|
||||
|
||||
return location
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -15,16 +15,16 @@ import MapKit
|
|||
extension LocationCollectionMapView {
|
||||
|
||||
class Coordinator: NSObject {
|
||||
@Binding var centerCoordinate: CLLocationCoordinate2D
|
||||
let onSelectLocation: ((Location) -> Void)?
|
||||
let onCenterChanged: ((CLLocationCoordinate2D) -> Void)?
|
||||
|
||||
|
||||
init(
|
||||
centerCoordinate: Binding<CLLocationCoordinate2D>,
|
||||
onSelectLocation: ((Location) -> Void)? = nil
|
||||
onSelectLocation: ((Location) -> Void)? = nil,
|
||||
onCenterChanged: ((CLLocationCoordinate2D) -> Void)? = nil
|
||||
) {
|
||||
self._centerCoordinate = centerCoordinate
|
||||
self.onSelectLocation = onSelectLocation
|
||||
self.onCenterChanged = onCenterChanged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ extension LocationCollectionMapView.Coordinator {
|
|||
extension LocationCollectionMapView.Coordinator: MKMapViewDelegate {
|
||||
|
||||
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
|
||||
centerCoordinate = mapView.centerCoordinate
|
||||
onCenterChanged?(mapView.centerCoordinate)
|
||||
}
|
||||
|
||||
|
||||
|
@ -69,7 +69,6 @@ extension LocationCollectionMapView.Coordinator: MKMapViewDelegate {
|
|||
) as? MKPinAnnotationView
|
||||
?? MKPinAnnotationView(annotation: annotation, reuseIdentifier: ReuseIdentifier.pinAnnotation)
|
||||
|
||||
|
||||
configure(annotationView)
|
||||
|
||||
return annotationView
|
||||
|
|
|
@ -13,10 +13,10 @@ import MapKit
|
|||
|
||||
struct LocationCollectionMapView {
|
||||
var annotations: [LocationAnnotation] = []
|
||||
|
||||
@Binding var centerCoordinate: CLLocationCoordinate2D
|
||||
var startingCenterCoordinate: CLLocationCoordinate2D?
|
||||
|
||||
let onSelectLocation: ((Location) -> Void)?
|
||||
let onCenterChanged: ((CLLocationCoordinate2D) -> Void)?
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,8 +25,8 @@ extension LocationCollectionMapView: UIViewRepresentable {
|
|||
|
||||
func makeCoordinator() -> LocationCollectionMapView.Coordinator {
|
||||
Self.Coordinator(
|
||||
centerCoordinate: $centerCoordinate,
|
||||
onSelectLocation: onSelectLocation
|
||||
onSelectLocation: onSelectLocation,
|
||||
onCenterChanged: onCenterChanged
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,10 @@ extension LocationCollectionMapView: UIViewRepresentable {
|
|||
mapView.removeAnnotations(mapView.annotations)
|
||||
mapView.addAnnotations(annotations)
|
||||
}
|
||||
|
||||
if let startingCenterCoordinate = startingCenterCoordinate {
|
||||
mapView.setCenter(startingCenterCoordinate, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
//
|
||||
// LocationCollectionView.ViewModel.swif {
|
||||
// PlaceCase
|
||||
//
|
||||
// Created by CypherPoet on 12/7/19.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreLocation
|
||||
|
||||
|
||||
extension LocationCollectionView {
|
||||
|
||||
final class ViewModel: NSObject, ObservableObject {
|
||||
private var subscriptions = Set<AnyCancellable>()
|
||||
private let locationManager = CLLocationManager()
|
||||
|
||||
enum AlertState {
|
||||
case noAlert
|
||||
case currentLocationDisabled
|
||||
case locationSelected
|
||||
case currentLocationReadingFailed
|
||||
}
|
||||
|
||||
|
||||
@ObservedObject var collection: LocationCollection
|
||||
|
||||
|
||||
// MARK: - Published Outputs
|
||||
@Published var locations: [Location] = []
|
||||
@Published var selectedLocation: Location? = nil
|
||||
|
||||
// ⚠️ Trying to pass this down to another view as a binding causes
|
||||
// that binding to get separated when this viewModel re-initializes.
|
||||
// @Published var centerCoordinate = CLLocationCoordinate2D()
|
||||
|
||||
@Published var canReadCurrentLocation = false
|
||||
@Published var userLocationCoordinate: CLLocationCoordinate2D? = nil
|
||||
@Published var isShowingAlert = false
|
||||
@Published var isShowingEditView = false
|
||||
@Published var currentAlertState: AlertState = .noAlert
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
init(collection: LocationCollection) {
|
||||
self.collection = collection
|
||||
|
||||
super.init()
|
||||
|
||||
setupSubscribers()
|
||||
|
||||
self.fetchedResultsController.delegate = self
|
||||
fetchLocations()
|
||||
}
|
||||
|
||||
|
||||
lazy var fetchRequest: NSFetchRequest<Location> = {
|
||||
Location.fetchRequest(for: collection)
|
||||
}()
|
||||
|
||||
|
||||
lazy var fetchedResultsController: NSFetchedResultsController<Location> = {
|
||||
.init(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: CurrentApp.coreDataManager.mainContext,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Publishers
|
||||
extension LocationCollectionView.ViewModel {
|
||||
|
||||
private var isShowingAlertPublisher: AnyPublisher<Bool, Never> {
|
||||
$currentAlertState
|
||||
.map { alertState in alertState != .noAlert }
|
||||
.print("isShowingAlertPublisher")
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension LocationCollectionView.ViewModel {
|
||||
var locationAuthStatus: CLAuthorizationStatus { CLLocationManager.authorizationStatus() }
|
||||
|
||||
var selectedLocationAlertTitle: String { selectedLocation?.title ?? "Undisclosed Location" }
|
||||
|
||||
var selectedLocationAlertMessage: String {
|
||||
selectedLocation?.longDescription ?? "No description has been provided yet."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Public Methods
|
||||
extension LocationCollectionView.ViewModel {
|
||||
|
||||
func fetchLocations() {
|
||||
// TODO: Better error handling here?
|
||||
try? fetchedResultsController.performFetch()
|
||||
setLocations(from: fetchedResultsController)
|
||||
}
|
||||
|
||||
|
||||
// TODO: Could this functionality live in a separate redux state struct
|
||||
// (e.g. UserLocationState)?
|
||||
func requestLocationTrackingAuthorization() {
|
||||
locationManager.requestWhenInUseAuthorization()
|
||||
}
|
||||
|
||||
|
||||
func activateLocationManager() {
|
||||
switch locationAuthStatus {
|
||||
case .notDetermined:
|
||||
requestLocationTrackingAuthorization()
|
||||
case .denied, .restricted:
|
||||
self.currentAlertState = .currentLocationDisabled
|
||||
case .authorizedAlways, .authorizedWhenInUse:
|
||||
startUpdatingLocation()
|
||||
@unknown default:
|
||||
requestLocationTrackingAuthorization()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
extension LocationCollectionView.ViewModel: NSFetchedResultsControllerDelegate {
|
||||
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
guard let controller = controller as? NSFetchedResultsController<Location> else { return }
|
||||
|
||||
print("controllerDidChangeContent")
|
||||
setLocations(from: controller)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CLLocationManagerDelegate
|
||||
extension LocationCollectionView.ViewModel: CLLocationManagerDelegate {
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
guard let currentLocationCoordinate = locations.last?.coordinate else { return }
|
||||
|
||||
userLocationCoordinate = currentLocationCoordinate
|
||||
}
|
||||
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
// TODO: Better handling here
|
||||
print("locationManager::didFailWithError: \(error.localizedDescription)")
|
||||
|
||||
currentAlertState = .currentLocationReadingFailed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private extension LocationCollectionView.ViewModel {
|
||||
|
||||
func setLocations(from fetchedResultsController: NSFetchedResultsController<Location>) {
|
||||
guard
|
||||
let section = fetchedResultsController.sections?.first,
|
||||
let fetchedLocations = section.objects as? [Location]
|
||||
else {
|
||||
locations = []
|
||||
return
|
||||
}
|
||||
print("setLocations || Count: \(fetchedLocations.count)")
|
||||
locations = fetchedLocations
|
||||
}
|
||||
|
||||
|
||||
func startUpdatingLocation() {
|
||||
locationManager.delegate = self
|
||||
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||
|
||||
|
||||
locationManager.requestLocation()
|
||||
}
|
||||
|
||||
|
||||
func setupSubscribers() {
|
||||
isShowingAlertPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.isShowingAlert, on: self)
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,13 +12,9 @@ import MapKit
|
|||
|
||||
struct LocationCollectionView: View {
|
||||
@EnvironmentObject var store: AppStore
|
||||
|
||||
@ObservedObject var viewModel: LocationCollectionViewModel
|
||||
@ObservedObject var viewModel: ViewModel
|
||||
|
||||
@State private var centerCoordinate = CLLocationCoordinate2D()
|
||||
@State var selectedLocation: Location? = nil
|
||||
@State private var isShowingEditView = false
|
||||
@State private var isShowingSelectedLocationAlert = false
|
||||
}
|
||||
|
||||
|
||||
|
@ -28,25 +24,24 @@ extension LocationCollectionView {
|
|||
var body: some View {
|
||||
ZStack {
|
||||
mapUnderlay
|
||||
|
||||
centerIndicator
|
||||
|
||||
addLocationButton
|
||||
mapControls
|
||||
}
|
||||
.alert(isPresented: $viewModel.isShowingAlert, content: { self.currentAlert })
|
||||
.sheet(
|
||||
isPresented: $isShowingEditView,
|
||||
isPresented: $viewModel.isShowingEditView,
|
||||
onDismiss: {
|
||||
guard let context = self.selectedLocation?.managedObjectContext else {
|
||||
guard let context = self.viewModel.selectedLocation?.managedObjectContext else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
CurrentApp.coreDataManager.save(context)
|
||||
}
|
||||
) {
|
||||
if self.selectedLocation != nil {
|
||||
if self.viewModel.selectedLocation != nil {
|
||||
EditLocationView(
|
||||
viewModel: EditLocationViewModel(
|
||||
location: self.selectedLocation!,
|
||||
location: self.viewModel.selectedLocation!,
|
||||
wikiPagesState: self.store.state.wikiPagesState
|
||||
)
|
||||
)
|
||||
|
@ -55,23 +50,41 @@ extension LocationCollectionView {
|
|||
Text("No Location found for editing")
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $isShowingSelectedLocationAlert) {
|
||||
Alert(
|
||||
title: Text(selectedLocation?.title ?? "Undisclosed Location"),
|
||||
message: Text(selectedLocation?.longDescription ?? "No description has been provided yet."),
|
||||
primaryButton: .default(
|
||||
Text("Edit"),
|
||||
action: { self.isShowingEditView = true }
|
||||
),
|
||||
secondaryButton: .cancel(Text("OK"))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension LocationCollectionView {
|
||||
|
||||
var currentAlert: Alert {
|
||||
switch viewModel.currentAlertState {
|
||||
case .currentLocationDisabled:
|
||||
return Alert(
|
||||
title: Text("Location Services are Disabled"),
|
||||
message: Text("Location authorization settings can be changed from within the Settings app."),
|
||||
dismissButton: .default(Text("👌 Got It"))
|
||||
)
|
||||
case .currentLocationReadingFailed:
|
||||
return Alert(
|
||||
title: Text("Error while attempting to read location."),
|
||||
message: Text("Your current location could not be determined."),
|
||||
dismissButton: .default(Text("OK"))
|
||||
)
|
||||
case .locationSelected:
|
||||
return Alert(
|
||||
title: Text(viewModel.selectedLocationAlertTitle),
|
||||
message: Text(viewModel.selectedLocationAlertMessage),
|
||||
primaryButton: .default(
|
||||
Text("Edit"),
|
||||
action: { self.viewModel.isShowingEditView = true }
|
||||
),
|
||||
secondaryButton: .cancel(Text("OK"))
|
||||
)
|
||||
default:
|
||||
preconditionFailure("No alert should be set")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -80,9 +93,10 @@ extension LocationCollectionView {
|
|||
|
||||
private var mapUnderlay: some View {
|
||||
LocationCollectionMapView(
|
||||
annotations: viewModel.collection.locationsArray,
|
||||
centerCoordinate: $centerCoordinate,
|
||||
onSelectLocation: locationSelected(_:)
|
||||
annotations: viewModel.locations,
|
||||
startingCenterCoordinate: viewModel.userLocationCoordinate,
|
||||
onSelectLocation: locationSelected(_:),
|
||||
onCenterChanged: centerCoordinateChanged(_:)
|
||||
)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
|
@ -96,27 +110,50 @@ extension LocationCollectionView {
|
|||
}
|
||||
|
||||
|
||||
private var addLocationButton: some View {
|
||||
private var mapControls: some View {
|
||||
VStack(alignment: .trailing) {
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .bottom) {
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
self.createNewLocation()
|
||||
}) {
|
||||
Image(systemName: "plus.rectangle.fill")
|
||||
.font(.title)
|
||||
.padding(24)
|
||||
.background(Color.accentColor.opacity(0.8))
|
||||
.foregroundColor(.white)
|
||||
VStack {
|
||||
snapToCurrentLocationButton
|
||||
addLocationButton
|
||||
}
|
||||
.clipShape(Circle())
|
||||
.padding(.trailing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var addLocationButton: some View {
|
||||
Button(action: {
|
||||
self.createNewLocation()
|
||||
}) {
|
||||
Image(systemName: "plus.rectangle.fill")
|
||||
.font(.title)
|
||||
.padding(24)
|
||||
.background(Color.accentColor.opacity(0.8))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
|
||||
private var snapToCurrentLocationButton: some View {
|
||||
Button(action: {
|
||||
self.viewModel.activateLocationManager()
|
||||
}) {
|
||||
Image(systemName: "location.fill")
|
||||
.font(.headline)
|
||||
.padding(18)
|
||||
.background(Color.accentColor.opacity(0.8))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -139,10 +176,13 @@ private extension LocationCollectionView {
|
|||
|
||||
|
||||
func locationSelected(_ location: Location) {
|
||||
// viewModel.selectedLocation = location
|
||||
// viewModel.isShowingSelectedLocationAlert = true
|
||||
self.selectedLocation = location
|
||||
self.isShowingSelectedLocationAlert = true
|
||||
viewModel.selectedLocation = location
|
||||
viewModel.currentAlertState = .locationSelected
|
||||
}
|
||||
|
||||
|
||||
func centerCoordinateChanged(_ newCoordinate: CLLocationCoordinate2D) {
|
||||
centerCoordinate = newCoordinate
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,8 +193,9 @@ struct LocationCollectionView_Previews: PreviewProvider {
|
|||
|
||||
static var previews: some View {
|
||||
LocationCollectionView(
|
||||
viewModel: LocationCollectionViewModel(collection: SampleData.LocationCollections.default)
|
||||
viewModel: .init(collection: SampleData.LocationCollections.default)
|
||||
)
|
||||
.environment(\.managedObjectContext, CurrentApp.coreDataManager.mainContext)
|
||||
.environmentObject(SampleData.SampleAppStore.default)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
//
|
||||
// LocationCollectionViewModel.swift
|
||||
// PlaceCase
|
||||
//
|
||||
// Created by CypherPoet on 12/7/19.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import CoreData
|
||||
|
||||
|
||||
final class LocationCollectionViewModel: NSObject, ObservableObject {
|
||||
private var subscriptions = Set<AnyCancellable>()
|
||||
|
||||
@ObservedObject var collection: LocationCollection
|
||||
|
||||
|
||||
// MARK: - Published Outputs
|
||||
@Published var locations: [Location] = []
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
init(collection: LocationCollection) {
|
||||
self.collection = collection
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
||||
lazy var fetchRequest: NSFetchRequest<Location> = {
|
||||
Location.fetchRequest(for: collection)
|
||||
}()
|
||||
|
||||
|
||||
lazy var fetchedResultsController: NSFetchedResultsController<Location> = {
|
||||
.init(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: CurrentApp.coreDataManager.mainContext,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Publishers
|
||||
extension LocationCollectionViewModel {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension LocationCollectionViewModel {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Public Methods
|
||||
extension LocationCollectionViewModel {
|
||||
|
||||
func fetchLocations() {
|
||||
// TODO: Better error handling here?
|
||||
try? fetchedResultsController.performFetch()
|
||||
setLocations(from: fetchedResultsController)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
extension LocationCollectionViewModel: NSFetchedResultsControllerDelegate {
|
||||
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
guard let controller = controller as? NSFetchedResultsController<Location> else { return }
|
||||
|
||||
setLocations(from: controller)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private extension LocationCollectionViewModel {
|
||||
|
||||
func setLocations(from fetchedResultsController: NSFetchedResultsController<Location>) {
|
||||
guard
|
||||
let section = fetchedResultsController.sections?.first,
|
||||
let fetchedLocations = section.objects as? [Location]
|
||||
else {
|
||||
locations = []
|
||||
return
|
||||
}
|
||||
|
||||
locations = fetchedLocations
|
||||
}
|
||||
}
|
|
@ -83,17 +83,6 @@ extension LocationCollectionsContainerView {
|
|||
}
|
||||
|
||||
|
||||
private extension LocationCollectionsContainerView {
|
||||
|
||||
func destination(for locationCollection: LocationCollection) -> LocationCollectionView {
|
||||
let viewModel = LocationCollectionViewModel(collection: locationCollection)
|
||||
|
||||
return LocationCollectionView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Preview
|
||||
struct LocationCollectionsContainerView_Previews: PreviewProvider {
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ final class LocationCollectionsContainerViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Public Methods
|
||||
extension LocationCollectionsContainerViewModel {
|
||||
|
||||
|
@ -67,9 +66,7 @@ extension LocationCollectionsContainerViewModel {
|
|||
|
||||
|
||||
static func destination(for locationCollection: LocationCollection) -> LocationCollectionView {
|
||||
let viewModel = LocationCollectionViewModel(collection: locationCollection)
|
||||
|
||||
return .init(viewModel: viewModel)
|
||||
.init(viewModel: .init(collection: locationCollection))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue