I'm looking to make a setting view in my app where I could define 3 map parameters :
- Map center initial position
- Map initial zoom
- Map initial type
Then show an example map and save in into the @AppStorage
My initial position is determined from an array of data which include :
- Place name
- Place Latitude
- Place Longitude
Latitude and Longitude is find via a function which return a Double? :
func getApLat(ApName: String) -> Double?{
guard let foundAirport = FR_airportsDB.first(where: {$0.Icao == ApName}),
let lat = Double(foundAirport.Latitude) else { return nil }
return lat
}
func getApLong(ApName: String) -> Double?{
guard let foundAirport = FR_airportsDB.first(where: {$0.Icao == ApName}),
let longit = Double(foundAirport.Longitude) else { return nil }
return longit
}
Now my settingMapView is defined as follow :
struct MapOptionView: View {
@State private var showingAlert = false
@State private var latDouble = getApLat(ApName: UserDefaults.standard.string(forKey: "MAP_CenterInit") ?? "LFLI")
@State private var longDouble = getApLong(ApName: UserDefaults.standard.string(forKey: "MAP_CenterInit") ?? "LFLI")
@State private var typeExemple: MKMapType = getTypeFromUD()
@State private var exampleZoom: Int = 5
@AppStorage("MAP_CenterInit") private var MapCenterAirport = ""
@AppStorage("MAP_ZoomInit") private var MapZoom = 2
@AppStorage("MAP_TypeInit") private var MapType = 1
var body: some View {
List{
Section(header: Text("Initial location")){
HStack{
TextField("ICAO", text: $MapCenterAirport)
.padding()
.background(.white)
.cornerRadius(20.0)
.keyboardType(.default)
.textCase(.uppercase)
.onReceive(Just(MapCenterAirport)) { inputValue in
if inputValue.count > 4 {
self.MapCenterAirport.removeLast()
}
}
.disableAutocorrection(true)
.textCase(.uppercase)
if MapCenterAirport != "" {
Button {
//Here I update the center data after get it from the function
latDouble = getApLat(ApName: MapCenterAirport)
longDouble = getApLong(ApName: MapCenterAirport)
} label: {
Text("Check")
}
}
}
}
// Section where Zoom is defined
Section(header: Text("Initial zoom")){
HStack {
if MapZoom == 1{
Text("Zoom : Low")
}else if MapZoom == 2{
Text("Zoom : Medium")
}else if MapZoom == 3{
Text("Zoom : Large")
}
Spacer()
Stepper("", value: $MapZoom, in: 1...3)
}.padding(.vertical)
}
// Section where Type is defined
Section(header: Text("Initial type")){
HStack {
if MapType == 1{
Text("Type : Standard")
}else if MapType == 2{
Text("Type : Satellite")
}else if MapType == 3{
Text("Type : Satellite-flyover")
}else if MapType == 4{
Text("Type : Hybrid")
}else if MapType == 5{
Text("Type : Hybrid-flyover")
}
Spacer()
Stepper("", value: $MapType, in: 1...5)
.frame(width: 60)
.padding(.horizontal, 20)
}.padding(.vertical)
}
// Section where Exemple is Show but need to clickButton to update map ...
Section(header: Text("Example")){
Button {
//Here is zoom adaptation regarding stepper choice from $MapZoom
if MapZoom == 1{
exampleZoom = 60000
}else if MapZoom == 2{
exampleZoom = 110000
}else if MapZoom == 3{
exampleZoom = 160000
}
//Here is type adaptation regarding stepper choice from $MapType
if MapType == 1{
typeExemple = .standard
}else if MapType == 2{
typeExemple = .satellite
}else if MapType == 3{
typeExemple = .satelliteFlyover
}else if MapType == 4{
typeExemple = .hybrid
}else if MapType == 5{
typeExemple = .hybridFlyover
}
} label: {
HStack {
Text("Update example")
}
}
//Here is my Map definition
MapViewSetting(mapType: $typeExemple,
funcLat: $latDouble,
funcLong: $longDouble,
funcZoom: $exampleZoom
)
.edgesIgnoringSafeArea(.all)
.frame(height: 300)
}
}.navigationTitle("Map options")
.navigationBarItems(trailing: Button(action: {
showingAlert = true
//Here we save the 3 value into the AppStorage
MapCenterAirport = MapCenterAirport.uppercased()
UserDefaults.standard.set(self.MapCenterAirport, forKey: "MAP_CenterInit")
UserDefaults.standard.set(self.MapZoom, forKey: "MAP_ZoomInit")
UserDefaults.standard.set(self.MapType, forKey: "MAP_TypeInit")
}, label: {
HStack {
Text("Save")
}
})).alert("Informations saved !\nMust have to restart the app to apply it.", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
}
}
And finally I've my map struct defined as follow after following some post find on this forum.
struct MapViewSetting: UIViewRepresentable {
@Binding var mapType: MKMapType
@Binding var funcLat: Double
@Binding var funcLong: Double
@Binding var funcZoom: Int
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView(frame: .zero)
let center = CLLocationCoordinate2D(latitude: funcLat, longitude: funcLong)
let region = MKCoordinateRegion(center: center,
latitudinalMeters: CLLocationDistance(funcZoom),
longitudinalMeters: CLLocationDistance(funcZoom)
)
mapView.setRegion(region, animated: true)
mapView.mapType = mapType
mapView.showsScale = true
mapView.showsTraffic = false
mapView.showsCompass = true
mapView.showsUserLocation = false
mapView.showsBuildings = false
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
view.mapType = self.mapType
}
}
But Two big mistake with my code :
First I got an error here :
MapViewSetting(mapType: $typeExemple,
/*regionFunc: $region,*/
funcLat: $latDouble ?? 46.192001,
funcLong: $longDouble ?? 6.26839,
funcZoom: $exampleZoom
)
On funcLat : and funcLong which say : Cannot convert value of type 'Binding<Double?>' to expected argument type 'Binding<Double>'
And If I define manually the Lat and Long in my code to test the two other parameters I could only change the type but not the Zoom ...
Hope to be as clear as I can
Thanks