Я создал форму, используя SwiftUI, в которой у меня есть раздел, который позволяет пользователю нажимать на кнопку, которая переводит их в представление MapKit. Оказавшись внутри вида карты, пользователь может нажать кнопку «+», чтобы разместить значок на карте. Это приводит их к представлению редактирования, где они могут вводить текст внутри TextField для маркировки булавки (см. Скриншот ниже). Последние несколько дней я застрял здесь, пытаясь сохранить координаты булавки или даже ввод пользователя внутри TextField, чтобы вернуть его в виде текста (в виде города, штата или страны) внутри формы.
Форма -> Вид карты -> Редактировать вид
Вот некоторые фрагменты кода.
1) Из FormView:
import SwiftUI
import MapKit
struct FormView: View {
@State private var selectedTitle = ""
@State var meuf: Meuf
@State private var meufs = [Meuf]()
@State private var show = false
@State private var singleIsPresented = false
@Environment(\.presentationMode) var presentationMode
let newMeuf : Bool
@EnvironmentObject var meufStorage : MeufStorage
@State private var showMap = false
var body: some View {
NavigationView{
Form{
//MARK: LOCATION
Section{
HStack {
Button(action: { self.showMap = true }) {
Image(systemName: "mappin.and.ellipse")
}
.sheet(isPresented: $showMap) {
LocationMap(showModal: self.$showMap)
}
Text("Pin your location")
.font(.subheadline)
}
}
// MARK: [ SAVE ENTRY ]
Section {
Button(action: {
if self.newMeuf {
self.saveData()
self.meufStorage.meufs.append(self.meuf)
} else {
for x in 0..<self.meufStorage.meufs.count {
if self.meufStorage.meufs[x].id == self.meuf.id {
self.meufStorage.meufs[x] = self.meuf
}
}
}
self.presentationMode.wrappedValue.dismiss()
}) {
HStack{
Spacer()
Text("Save")
Spacer()
}
}.disabled(meuf.title.isEmpty)
}
}.navigationBarTitle(Text(meuf.title))
}
}
//Get file directory url
func getFileDirectory() -> URL{
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
//Load data from file directory
func loadData(){
let filename = getFileDirectory().appendingPathComponent("SavedPlaces")
do {
let data = try Data(contentsOf: filename)
meufs = try JSONDecoder().decode([Meuf].self, from: data)
}catch{
debugPrint(error)
}
}
//Save data to file directory
func saveData(){
let filename = getFileDirectory().appendingPathComponent("SavedPlaces")
let data = try? JSONEncoder().encode(self.meufs)
do{
try data?.write(to: filename, options: [.atomic , .completeFileProtection])
}catch{
debugPrint(error)
}
}
}
struct FormView_Previews: PreviewProvider {
static var previews: some View {
FormView(meuf: Meuf(), newMeuf: true)
}
}
2) Из LocationMap:
import SwiftUI
import MapKit
struct LocationMap: View {
@State private var centerCoordinate = CLLocationCoordinate2D()
@State private var locations = [CodableMKPointAnnotation]()
@State private var selectedPlace: MKPointAnnotation?
@State private var showingPlaceDetails = false
@State private var showingEditScreen = false
@Binding var showModal: Bool
var body: some View {
ZStack{
MapView(centerCoordinate: $centerCoordinate, annotations: locations, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails)
.edgesIgnoringSafeArea(.all)
Circle()
.fill(Color.blue)
.opacity(0.3)
.frame(width: 32, height: 32)
VStack {
Spacer()
HStack{
Spacer()
Button(action:{
let newLocation = CodableMKPointAnnotation()
newLocation.title = ""
newLocation.coordinate = self.centerCoordinate
self.locations.append(newLocation)
self.selectedPlace = newLocation
self.showingEditScreen = true
}){
Image(systemName: "plus")
}
.padding()
.background(Color.black.opacity(0.7))
.foregroundColor(Color.white)
.clipShape(Circle())
.shadow(radius: 0.7)
.padding([.trailing , .bottom])
}
}
.padding()
}.alert(isPresented: $showingPlaceDetails) {
Alert(title: Text(selectedPlace?.title ?? "Unknown"), message: Text(selectedPlace?.subtitle ?? "Missing place information."), primaryButton: .default(Text("OK")), secondaryButton: .default(Text("Edit")) {
self.showingEditScreen = true
}
)
}
.sheet(isPresented: $showingEditScreen, onDismiss: savedData) {
if self.selectedPlace != nil {
EditView(placemark: self.selectedPlace!)
}
}
.onAppear(perform: loadData)
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func loadData() {
let filename = getDocumentsDirectory().appendingPathComponent("Saved Places")
do {
let data = try Data(contentsOf: filename)
locations = try JSONDecoder().decode([CodableMKPointAnnotation].self, from: data)
} catch {
print("Unable to load saved data.")
}
}
func savedData() {
do {
let filename = getDocumentsDirectory().appendingPathComponent("SavedPlaces")
let data = try JSONEncoder().encode(self.locations)
try data.write(to: filename, options: [.atomicWrite, .completeFileProtection])
} catch {
print("Unable to save data")
}
}
}
struct LocationMap_Previews: PreviewProvider {
static var previews: some View {
LocationMap(showModal: .constant(true))
}
}
3) Из MapView:
import MapKit
import Combine
import SwiftUI
struct MapView: UIViewRepresentable {
@Binding var centerCoordinate: CLLocationCoordinate2D
var annotations: [MKPointAnnotation]
@Binding var selectedPlace: MKPointAnnotation?
@Binding var showingPlaceDetails: Bool
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) {
if annotations.count != uiView.annotations.count{
uiView.removeAnnotations(uiView.annotations)
uiView.addAnnotations(annotations)
}
}
///Coordinator class for passing data
class Coordinator: NSObject , MKMapViewDelegate{
let parent: MapView
init(_ parent: MapView){
self.parent = parent
}
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
parent.centerCoordinate = mapView.centerCoordinate
}
//Gets called whenever the rightCalloutAccessory is tapped
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
guard let placeMark = view.annotation as? MKPointAnnotation else {return}
parent.selectedPlace = placeMark
parent.showingPlaceDetails = true
}
//Customizes the way the marker looks
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "PlaceMark"
var annotationview = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationview == nil {
annotationview = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationview?.canShowCallout = true
annotationview?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}else {
annotationview?.annotation = annotation
}
return annotationview
}
}
func makeCoordinator() -> MapView.Coordinator {
Coordinator(self)
}
}
extension MKPointAnnotation {
static var example: MKPointAnnotation {
let annotation = MKPointAnnotation()
annotation.title = "Montreal"
annotation.subtitle = "Home of French Canadians"
annotation.coordinate = CLLocationCoordinate2D(latitude: 45.5, longitude: -73.58)
return annotation
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(centerCoordinate: .constant(MKPointAnnotation.example.coordinate), annotations: [MKPointAnnotation.example], selectedPlace: .constant(MKPointAnnotation.example), showingPlaceDetails: .constant(false))
}
}
4) Из представления редактирования:
import SwiftUI
import MapKit
struct EditView: View {
@Environment(\.presentationMode) var presentationMode
@ObservedObject var placemark: MKPointAnnotation
var body: some View {
NavigationView {
Form {
Section {
TextField("Place name", text: $placemark.wrappedTitle)
TextField("Description", text: $placemark.wrappedSubtitle)
}
}
.navigationBarTitle("Edit place")
.navigationBarItems(trailing: Button("Done") {
self.presentationMode.wrappedValue.dismiss()
})
}
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView(placemark: MKPointAnnotation.example)
}
}
5) Кодируемый MKPointAnnotation
import Foundation
import MapKit
class CodableMKPointAnnotation: MKPointAnnotation , Codable {
enum codingKeys: CodingKey {
case title ,subtitle , longitude , latitude
}
override init() {
super.init()
}
public required init(from decoder: Decoder) throws{
super.init()
let container = try decoder.container(keyedBy: codingKeys.self)
title = try container.decode(String.self, forKey: .title)
subtitle = try container.decode(String.self, forKey: .subtitle)
let latitude = try container.decode(CLLocationDegrees.self, forKey: .latitude)
let longitude = try container.decode(CLLocationDegrees.self, forKey: .longitude)
coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: codingKeys.self)
try container.encode(title, forKey: .title)
try container.encode(subtitle, forKey: .subtitle)
try container.encode(coordinate.latitude, forKey: .latitude)
try container.encode(coordinate.longitude, forKey: .longitude)
}
}
6) Объект MKPointAnnotation
import MapKit
extension MKPointAnnotation: ObservableObject{
public var wrappedTitle: String{
get{
self.title ?? "No Title"
}
set{
self.title = newValue
}
}
public var wrappedSubtitle: String{
get{
self.subtitle ?? "No information on this location"
}
set{
self.subtitle = newValue
}
}
}
7) Хранение Meuf & Meuf:
import Foundation
struct Meuf: Identifiable, Encodable, Decodable {
var id = UUID()
var img = ""
var title = ""
var rating = 3.0
var seen = false
var seenDate = ""
}
class MeufStorage: ObservableObject {
@Published var meufs = [Meuf]()
}
8) Делегат сцены:
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let meufStorage = MeufStorage()
let contentView = MeufList().environment(\.managedObjectContext, context).environmentObject(meufStorage)
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidEnterBackground(_ scene: UIScene) {
(UIApplication.shared.delegate as? AppDelegate)?.saveContext()
}
}