How to update UIViewRepresentable map through Binding and ObservedObject - binding

I have this simple MapView and I have an ObservedObject datasource to update but is not work.
This is the content view:
struct ContentView: View {
#ObservedObject var trackingOnMapViewModel = TrackingOnMapViewModel()
var body: some View {
ZStack {
MapView(selectedRegion: $trackingOnMapViewModel.selectedRegion)
.edgesIgnoringSafeArea(.vertical)
VStack {
Spacer()
Button(action: {
self.trackingOnMapViewModel.selectNextRegion()
}) {
Text("Next")
.padding()
}
}
}
}
}
This is a simple mapview:
struct MapView: UIViewRepresentable {
#Binding var selectedRegion: MKCoordinateRegion
func makeUIView(context: Context) -> MKMapView {
print("MapView - makeUIView")
let map = MKMapView()
return map
}
func updateUIView(_ mapView: MKMapView, context: Context) {
print("MapView - updateUIView")
mapView.setRegion(selectedRegion, animated: true)
}
}
And this is the datasource:
class TrackingOnMapViewModel: ObservableObject {
var regions: [MKCoordinateRegion] = [
MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 10.0, longitude: 10.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)),
MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 20.0, longitude: 20.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)),
MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 30.0, longitude: 30.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)),
MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 40.0, longitude: 40.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0))
]
var selectedRegion: MKCoordinateRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0))
var currentIndex = 0
func selectNextRegion() {
print("TrackingOnMapViewModel - selectNextLandmark")
currentIndex = currentIndex < regions.count-1 ? currentIndex + 1 : 0
self.selectedRegion = regions[currentIndex]
print("selectedRegion - \(selectedRegion)")
}
}
In this case the map is not updated.
If I put the logic to the ContentView without the ObservedObject like this:
struct ContentView: View {
// #ObservedObject var trackingOnMapViewModel = TrackingOnMapViewModel()
#State var regions: [MKCoordinateRegion] = [
MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 10.0, longitude: 10.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)),
MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 20.0, longitude: 20.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)),
MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 30.0, longitude: 30.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)),
MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 40.0, longitude: 40.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0))
]
#State var selectedRegion: MKCoordinateRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0), span: MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0))
#State var currentIndex = 0
var body: some View {
ZStack {
MapView(selectedRegion: $selectedRegion)
.edgesIgnoringSafeArea(.vertical)
VStack {
Spacer()
Button(action: {
self.selectNextRegion()
}) {
Text("Next")
.padding()
}
}
}
}
private func selectNextRegion() {
print("ContentView - selectNextLandmark")
currentIndex = currentIndex < regions.count-1 ? currentIndex + 1 : 0
self.selectedRegion = regions[currentIndex]
}
}
then the map is updating.
Could you help me with this?

At the moment when I posted the question I found the problem.
I've forgot to mark the selectedRegion variable as #Published in the TrackingOnMapViewModel.
#Published var selectedRegion: MKCoordinateRegion = MKCoordinateRegion

Related

SwiftUI Custom picker with Arrow Shape

I am trying to create SwiftUI Custom picker with Arrow Shape like
What I have done so far is :
struct SItem: Identifiable, Hashable {
var text: String
var id = UUID()
}
struct BreadCumView: View {
var items: [SItem] = [SItem(text: "Item 1"), SItem(text: "Item 2"), SItem(text: "Item 3")]
#State var selectedIndex: Int = 0
var body: some View {
HStack {
ForEach(self.items.indices) { index in
let tab = self.items[index]
Button(tab.text) {
selectedIndex = index
}.buttonStyle(CustomSegmentButtonStyle())
}
}
}
}
struct CustomSegmentButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration
.label
.padding(8)
.background(
Color(red: 0.808, green: 0.831, blue: 0.855, opacity: 0.9)
)
}
}
How to create Shape style for Background?
With Selected Stats.
Here's an arrow Shape using #Asperi's comment:
struct ArrowShape: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
let chunk = rect.height * 0.5
path.move(to: .zero)
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.addLine(to: CGPoint(x: max(rect.width - chunk, rect.width / 2), y: 0))
path.addLine(to: CGPoint(x: rect.width), y: chunk)
path.addLine(to: CGPoint(x: max(rect.width - chunk, rect.width / 2), y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
}
}
}

SwiftUI: How to generate a Map Route with transparent background?

I'm trying to create a map that will show the route of a workout, but I can't find anywhere in MapKit documentation as to how to customize the background, i.e. here I want the map itself to be transparent so that only the route (annotations) are visible. How can I do this?
struct MapOverlay: View {
#ObservedObject var workoutDetailViewModel: WorkoutDetailViewModel
var body: some View {
if let unwrappedWorkoutLocations = workoutDetailViewModel.fullyLoadedWorkout?.workoutLocations {
Map(
coordinateRegion: .constant(
MKCoordinateRegion(
center: unwrappedWorkoutLocations.map {$0.coordinate}.center(), // Use the midpoint of the workout as the centre of the map.
span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
)
),
annotationItems: unwrappedWorkoutLocations.map {$0.coordinate}
) { routeLocation in
MapAnnotation(coordinate: routeLocation) {
Circle().fill(TrackerConstants.AppleFitnessOrange)
}
}
.cornerRadius(10)
}
}
}
struct MapOverlay_Previews: PreviewProvider {
static var previews: some View {
MapOverlay(workoutDetailViewModel: WorkoutDetailViewModel())
}
}
Don't use MKMapView. You want to take the coordinates and make a UIBezierPath from them, and render that into your own view, or a UIImage. Something like this playground:
import CoreLocation
import MapKit
let myCoords: [CLLocationCoordinate2D] = [
.init(latitude: 42.42, longitude: 42.42),
.init(latitude: 42.43, longitude: 42.425),
.init(latitude: 42.425, longitude: 42.427),
.init(latitude: 42.422, longitude: 42.426),
]
let r = MKPolylineRenderer(polyline: .init(coordinates: myCoords, count: myCoords.count))
let path = r.path!
let bezier = UIBezierPath(cgPath: path)
bezier.apply(.init(scaleX: 0.05, y: 0.05))
let renderer = UIGraphicsImageRenderer(bounds: .init(x: 0, y: 0, width: 640, height: 480))
let image = renderer
.image { context in
let size = renderer.format.bounds.size
UIColor.darkGray.setFill()
context.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
UIColor.black.setStroke()
bezier.lineWidth = 5
bezier.stroke()
}

How I create Octagon shape in google map?

I would like to draw a Octagonal shape.
I don't know how to achieve this. Here is my code -
func drawOctagonalShape(){
let camera = GMSCameraPosition.camera(withLatitude: 37.4, longitude:-122.0, zoom: 10)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true;
self.view = mapView
// Creates a marker in the center of the map.
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: 37.36, longitude: -122.0)
marker.title = "India"
//marker.snippet = "Malaysia"
marker.map = mapView
let rect = GMSMutablePath()
rect.add(CLLocationCoordinate2D(latitude: 37.36, longitude: -122.0))
rect.add(CLLocationCoordinate2D(latitude: 37.45, longitude: -122.0))
rect.add(CLLocationCoordinate2D(latitude: 37.45, longitude: -122.2))
rect.add(CLLocationCoordinate2D(latitude: 37.36, longitude: -122.2))
// Create the polygon, and assign it to the map.
let polygon = GMSPolygon(path: rect)
polygon.fillColor = UIColor(red: 0.25, green: 0, blue: 0, alpha: 0.05);
polygon.strokeColor = UIColor.init(hue: 210, saturation: 88, brightness: 84, alpha: 1)
//polygon.strokeColor = .black
polygon.strokeWidth = 2
polygon.map = mapView
}
Octagon is basically 8 points distributed equally on a circle. You can create a method like the following:
func generateCircle(around center: CLLocationCoordinate2D, radius: Double, pointCount: Int = 8, rotation: Double = 0) -> [CLLocationCoordinate2D] {
(0..<count).map { index in
let angle = rotation + (Double(index)/Double(pointCount))*Double.pi*2.0
return .init(latitude: center.latitude + cos(angle)*radius, longitude: center.longitude + sin(angle)*radius)
}
}
I hope the code speaks for itself. You would use it in your code like
let rect = GMSMutablePath()
generateCircle(around center: myCenter, radius: someSmallRadius, pointCount: 8, rotation: 0).forEach { coordinate in
rect.add(coordinate)
}
I would expect this to work in most cases. There are 2 potential problems however:
I am unsure how this would behave around edges of both longitude and latitude.
Radius expressed in degrees may be a bit incorrect at some points
Would it not be easier to simply generate an image and place that on your map?

How to determine if MKCoordinateRegion inside another MKCoordinateRegion

I am trying to compare a saved region with another region, whether it's inside of that region or not. The region is going to be changed each time the user zoom in on the map; when the function below is called:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated:
Bool) {
You can easily define your own function for checking if an MKCoordinateRegion contains another one. One way to do this is to calculate the min/max latitude and longitudes of the region, then compare all 4 extremities between the 2 regions you want to compare.
extension MKCoordinateRegion {
var maxLongitude: CLLocationDegrees {
center.longitude + span.longitudeDelta / 2
}
var minLongitude: CLLocationDegrees {
center.longitude - span.longitudeDelta / 2
}
var maxLatitude: CLLocationDegrees {
center.latitude + span.latitudeDelta / 2
}
var minLatitude: CLLocationDegrees {
center.latitude - span.latitudeDelta / 2
}
func contains(_ other: MKCoordinateRegion) -> Bool {
maxLongitude >= other.maxLongitude && minLongitude <= other.minLongitude && maxLatitude >= other.maxLatitude && minLatitude <= other.minLatitude
}
}
let largerRegion = MKCoordinateRegion(MKMapRect(x: 50, y: 50, width: 100, height: 100))
let smallerRegion = MKCoordinateRegion(MKMapRect(x: 70, y: 50, width: 30, height: 80))
let otherRegion = MKCoordinateRegion(MKMapRect(x: -100, y: 0, width: 100, height: 80))
largerRegion.contains(smallerRegion) // true
largerRegion.contains(otherRegion) // false
smallerRegion.contains(otherRegion) // false

Google maps draws circle at wrong position

I'm showing marker and drawing circle using the same coordinates. Don't know why but my marker is offset a little bit to the top. How do you think what is the problem?
override func viewDidLoad() {
super.viewDidLoad()
let camera = GMSCameraPosition.camera(withLatitude: 37.36, longitude: -122.0, zoom: 13.0)
setupMapWith(cameraPosition: camera)
showMarker(position: camera.target)
circleWith(position: camera.target)
}
func showMarker(position: CLLocationCoordinate2D) {
let marker = GMSMarker()
marker.position = position
marker.title = "Palo Alto"
marker.snippet = "San Francisco"
let markerView = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
markerView.backgroundColor = .red
marker.iconView = markerView
marker.map = mapView
}
func circleWith(position: CLLocationCoordinate2D) {
let circ = GMSCircle(position: position, radius: 1000)
circ.fillColor = UIColor.MapAreaFilter.areaFilterColor
circ.map = mapView
}
Try setting your marker's groundAnchor property:
marker.groundAnchor = CGPoint(x: 0.5, y: 0.5)

Resources