Как получить удаленный штифт внутри MapView, чтобы сохранить координаты и представить их в виде строки, используя SwiftUI? - PullRequest
0 голосов
/ 11 февраля 2020

Я создал форму, используя 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()
    }


}

1 Ответ

0 голосов
/ 14 февраля 2020

Здравствуйте, мне удалось получить решение.

Добавит только код, который изменяется

// Made this a ObservableObject
class Meuf: Identifiable, Codable, ObservableObject {
var id = UUID()
var img = ""
var title = ""
var rating = 3.0
var seen = false
var seenDate = ""
var locations = [CodableMKPointAnnotation]() // We need this to keep the track of locations
}

FormView

struct FormView: View {
    @State private var selectedTitle = ""

    @ObservedObject var meufObject = Meuf() // This is new will help to keep track of the added locations

    @State private var meufs = [Meuf]() 
    @State private var show = false
    @State private var singleIsPresented = false
    @Environment(\.presentationMode) var presentationMode
    @EnvironmentObject var meufStorage : MeufStorage
    @State private  var showMap = false


    var body: some View {
        NavigationView{
            Form {
                List {
                    // This will list the added locations now
                    ForEach(self.meufObject.locations, id: \.self) { location in
                        LocationView(location: location)
                    }
                }

                //MARK: LOCATION
                Section{
                    HStack {
                        Button(action: { self.showMap = true }) {
                            Image(systemName: "mappin.and.ellipse")
                        }
                        .sheet(isPresented: $showMap) {
                            LocationMap(meufObject: self.meufObject, showModal: self.$showMap)
                        }
                        Text("Pin your location")
                            .font(.subheadline)
                    }
                }

                // MARK: [ SAVE ENTRY ]
                Section {
                    Button(action: {
                        // Handle save action
                    }) {
                        HStack{
                            Spacer()
                            Text("Save")
                            Spacer()
                        }
                    }
                }

            }
        }
    }

    // Rest of your code stays same .......
}

// Added this new view to render the location view
struct LocationView: View {
    var location : CodableMKPointAnnotation
    var body: some View {
        Text(location.title ?? "title" )
    }
}

LocationMap

struct LocationMap: View {
    @ObservedObject var meufObject: Meuf // This is new will help to keep track of the added locations

    @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

    @Environment(\.presentationMode) var presentationMode

    @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.meufObject.locations = self.locations // By doing this we will be able to pass it to main screen
                        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])
                    // Rest stays same as your implementation
                }
            }
            .padding()
        }
    }
    // Rest stays same as your implementation
}
...