WIP: new demo app

This commit is contained in:
John Estropia
2020-08-17 09:06:25 +09:00
parent e720504855
commit d988daa025
62 changed files with 4304 additions and 2 deletions

View File

@@ -0,0 +1,67 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Contacts
import CoreLocation
import CoreStore
// MARK: - Modern.PlacemarksDemo
extension Modern.PlacemarksDemo {
// MARK: Geocoder
final class Geocoder {
// MARK: Internal
func geocode(
place: ObjectSnapshot<Modern.PlacemarksDemo.Place>,
completion: @escaping (_ title: String?, _ subtitle: String?) -> Void
) {
self.geocoder?.cancelGeocode()
let geocoder = CLGeocoder()
self.geocoder = geocoder
geocoder.reverseGeocodeLocation(
CLLocation(latitude: place.$latitude, longitude: place.$longitude),
completionHandler: { (placemarks, error) -> Void in
defer {
self.geocoder = nil
}
guard let placemark = placemarks?.first else {
return
}
let address = CNMutablePostalAddress()
address.street = placemark.thoroughfare ?? ""
address.subLocality = placemark.subThoroughfare ?? ""
address.city = placemark.locality ?? ""
address.subAdministrativeArea = placemark.subAdministrativeArea ?? ""
address.state = placemark.administrativeArea ?? ""
address.postalCode = placemark.postalCode ?? ""
address.country = placemark.country ?? ""
address.isoCountryCode = placemark.isoCountryCode ?? ""
completion(
placemark.name,
CNPostalAddressFormatter.string(
from: address,
style: .mailingAddress
)
)
}
)
}
// MARK: Private
private var geocoder: CLGeocoder?
}
}

View File

@@ -0,0 +1,153 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreLocation
import Combine
import CoreStore
import Foundation
import MapKit
import SwiftUI
// MARK: - Modern.PlacemarksDemo
extension Modern.PlacemarksDemo {
// MARK: - Modern.PlacemarksDemo.MainView
struct MainView: View {
/**
Sample 1: Asynchronous transactions
*/
private func demoAsynchronousTransaction(coordinate: CLLocationCoordinate2D) {
Modern.PlacemarksDemo.dataStack.perform(
asynchronous: { (transaction) in
let place = self.place.asEditable(in: transaction)
place?.annotation = .init(coordinate: coordinate)
},
completion: { _ in }
)
}
/**
Sample 2: Synchronous transactions
- Important: `perform(synchronous:)` was used here for illustration purposes. In practice, `perform(asynchronous:completion:)` is the preferred transaction type as synchronous transactions are very likely to cause deadlocks.
*/
private func demoSynchronousTransaction() {
_ = try? Modern.PlacemarksDemo.dataStack.perform(
synchronous: { (transaction) in
let place = self.place.asEditable(in: transaction)
place?.setRandomLocation()
}
)
}
/**
Sample 3: Unsafe transactions
- Important: `beginUnsafe()` was used here for illustration purposes. In practice, `perform(asynchronous:completion:)` is the preferred transaction type. Use Unsafe Transactions only when you need to bypass CoreStore's serialized transactions.
*/
private func demoUnsafeTransaction(
title: String?,
subtitle: String?,
for snapshot: ObjectSnapshot<Modern.PlacemarksDemo.Place>
) {
let transaction = Modern.PlacemarksDemo.dataStack.beginUnsafe()
let place = snapshot.asEditable(in: transaction)
place?.title = title
place?.subtitle = subtitle
transaction.commit { (error) in
print("Commit failed: \(error as Any)")
}
}
// MARK: Internal
@ObservedObject
var place: ObjectPublisher<Modern.PlacemarksDemo.Place>
init() {
self.place = Modern.PlacemarksDemo.placePublisher
self.sinkCancellable = self.place.sink(
receiveCompletion: { _ in
// Deleted, do nothing
},
receiveValue: { [self] (snapshot) in
self.geocoder.geocode(place: snapshot) { (title, subtitle) in
guard self.place.snapshot == snapshot else {
return
}
self.demoUnsafeTransaction(
title: title,
subtitle: subtitle,
for: snapshot
)
}
}
)
}
// MARK: View
var body: some View {
Modern.PlacemarksDemo.MapView(
place: self.place.snapshot,
onTap: { coordinate in
self.demoAsynchronousTransaction(coordinate: coordinate)
}
)
.overlay(
InstructionsView(
("Random", "Sets random coordinate"),
("Tap", "Sets to tapped coordinate")
)
.padding(.leading, 10)
.padding(.bottom, 40),
alignment: .bottomLeading
)
.navigationBarTitle("Placemarks")
.navigationBarItems(
trailing: Button("Random") {
self.demoSynchronousTransaction()
}
)
}
// MARK: Private
private var sinkCancellable: AnyCancellable? = nil
private let geocoder = Modern.PlacemarksDemo.Geocoder()
}
}
#if DEBUG
struct _Demo_Modern_PlacemarksDemo_MainView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
Modern.PlacemarksDemo.MainView()
}
}
#endif

View File

@@ -0,0 +1,119 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreLocation
import CoreStore
import MapKit
import UIKit
import SwiftUI
// MARK: - Modern.PlacemarksDemo
extension Modern.PlacemarksDemo {
// MARK: - Modern.PlacemarksDemo.MapView
struct MapView: UIViewRepresentable {
// MARK: Internal
var place: ObjectSnapshot<Modern.PlacemarksDemo.Place>?
let onTap: (CLLocationCoordinate2D) -> Void
// MARK: UIViewRepresentable
typealias UIViewType = MKMapView
func makeUIView(context: Context) -> UIViewType {
let coordinator = context.coordinator
let mapView = MKMapView()
mapView.delegate = coordinator
mapView.addGestureRecognizer(
UITapGestureRecognizer(
target: coordinator,
action: #selector(coordinator.tapGestureRecognized(_:))
)
)
return mapView
}
func updateUIView(_ view: UIViewType, context: Context) {
let currentAnnotations = view.annotations
view.removeAnnotations(currentAnnotations)
guard let newAnnotation = self.place?.$annotation else {
return
}
view.addAnnotation(newAnnotation)
view.setCenter(newAnnotation.coordinate, animated: true)
view.selectAnnotation(newAnnotation, animated: true)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, MKMapViewDelegate {
// MARK: Internal
init(_ parent: MapView) {
self.parent = parent
}
// MARK: MKMapViewDelegate
@objc dynamic func mapView(
_ mapView: MKMapView,
viewFor annotation: MKAnnotation
) -> MKAnnotationView? {
let identifier = "MKAnnotationView"
var annotationView: MKPinAnnotationView! = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.isEnabled = true
annotationView.canShowCallout = true
annotationView.animatesDrop = true
}
else {
annotationView.annotation = annotation
}
return annotationView
}
// MARK: FilePrivate
@objc
fileprivate dynamic func tapGestureRecognized(_ gesture: UILongPressGestureRecognizer) {
guard
case let mapView as MKMapView = gesture.view,
gesture.state == .recognized
else {
return
}
let coordinate = mapView.convert(
gesture.location(in: mapView),
toCoordinateFrom: mapView
)
self.parent.onTap(coordinate)
}
// MARK: Private
private var parent: MapView
}
}
}

View File

@@ -0,0 +1,120 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import struct CoreLocation.CLLocationCoordinate2D
import protocol MapKit.MKAnnotation
// MARK: - Modern.PlacemarksDemo
extension Modern.PlacemarksDemo {
// MARK: - Modern.PlacemarksDemo.Place
final class Place: CoreStoreObject {
// MARK: Internal
@Field.Stored("latitude")
var latitude: Double = 0
@Field.Stored("longitude")
var longitude: Double = 0
@Field.Stored("title")
var title: String?
@Field.Stored("subtitle")
var subtitle: String?
@Field.Virtual(
"annotation",
customGetter: { object, field in
Annotation(
latitude: object.$latitude.value,
longitude: object.$longitude.value,
title: object.$title.value,
subtitle: object.$subtitle.value
)
},
customSetter: { object, field, newValue in
object.$latitude.value = newValue.coordinate.latitude
object.$longitude.value = newValue.coordinate.longitude
object.$title.value = "\(newValue.coordinate.latitude), \(newValue.coordinate.longitude)"
object.$subtitle.value = nil
}
)
var annotation: Annotation
func setRandomLocation() {
self.latitude = Double(arc4random_uniform(180)) - 90
self.longitude = Double(arc4random_uniform(360)) - 180
self.title = "\(self.latitude), \(self.longitude)"
self.subtitle = nil
}
// MARK: - Annotation
final class Annotation: NSObject, MKAnnotation {
// MARK: Internal
init(coordinate: CLLocationCoordinate2D) {
self.coordinate = coordinate
self.title = nil
self.subtitle = nil
}
// MARK: MKAnnotation
let coordinate: CLLocationCoordinate2D
let title: String?
let subtitle: String?
// MARK: NSObjectProtocol
override func isEqual(_ object: Any?) -> Bool {
guard case let object as Annotation = object else {
return false
}
return self.coordinate.latitude == object.coordinate.latitude
&& self.coordinate.longitude == object.coordinate.longitude
&& self.title == object.title
&& self.subtitle == object.subtitle
}
override var hash: Int {
var hasher = Hasher()
hasher.combine(self.coordinate.latitude)
hasher.combine(self.coordinate.longitude)
hasher.combine(self.title)
hasher.combine(self.subtitle)
return hasher.finalize()
}
// MARK: FilePrivate
fileprivate init(
latitude: Double,
longitude: Double,
title: String?,
subtitle: String?
) {
self.coordinate = .init(latitude: latitude, longitude: longitude)
self.title = title
self.subtitle = subtitle
}
}
}
}

View File

@@ -0,0 +1,64 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Modern
extension Modern {
// MARK: - Modern.PlacemarksDemo
/**
Sample usages for `CoreStoreObject` transactions
*/
enum PlacemarksDemo {
// MARK: Internal
static let dataStack: DataStack = {
let dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<Modern.PlacemarksDemo.Place>("Place")
],
versionLock: [
"Place": [0xa7eec849af5e8fcb, 0x638e69c040090319, 0x4e976d66ed400447, 0x18e96bc0438d07bb]
]
)
)
/**
- Important: `addStorageAndWait(_:)` and `perform(synchronous:)` methods were used here to simplify initializing the demo, but in practice the asynchronous function variants are recommended.
*/
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "Modern.PlacemarksDemo.sqlite",
localStorageOptions: .recreateStoreOnModelMismatch
)
)
return dataStack
}()
static let placePublisher: ObjectPublisher<Modern.PlacemarksDemo.Place> = {
let dataStack = Modern.PlacemarksDemo.dataStack
if let place = try! dataStack.fetchOne(From<Place>()) {
return dataStack.publishObject(place)
}
_ = try! dataStack.perform(
synchronous: { (transaction) in
let place = transaction.create(Into<Place>())
place.setRandomLocation()
}
)
let place = try! dataStack.fetchOne(From<Place>())
return dataStack.publishObject(place!)
}()
}
}