Remove annotation when slider moves - swift - swift

i have a problem with annotations that i can't resolve. When you click on a UIButton, the #IBAction pressPlay function starts, which causes the slider on my map to start moving. The slider has the max value 0 and min -31, and the initial value is 0 and it starts to move only if the thumb is in position! = From 0 and moves every 1 second. This works correctly moves the slider.
#IBAction func pressPlay(_ sender: Any)
{
let calendar2 = Calendar.current
let today = Date()
var cnt = Int(sliderTime.value)
let play = UIImage(named: "play")
let pause = UIImage(named: "pause")
let format = DateFormatter()
playButton.setImage(play, for: .normal)
if control == true && Int(sliderTime.value) < 0
{ //mette in play
control = false
playButton.setImage(pause, for: .normal)
//removeSeismometers = true
if Int(sliderTime.value) < 0
{
timer = Timer.scheduledTimer(withTimeInterval: 1,repeats: true)
{ [self]t in //ogni secondo questo timer cambia il valore dell'alpha del pin che sta vibrando
if cnt < 0
{
cnt = Int(self.sliderTime.value)
self.sliderTime.value += 1
let newDate2 = calendar2.date(byAdding: .day, value: Int(self.sliderTime.value), to:today)! //sottraggo alla data attuale il vlaore dello slider per tornare indietro nel tempo
format.dateStyle = .medium // "MM/GG/AAAA"
self.labelTime.text = "\(format.string(from: newDate2))"
appo += 1
for i in mapEqAnnotation{
let str: String = i.eq.eventTime
let index = str.index(str.endIndex, offsetBy: -9)
let mySubstring = str[..<index]
nuovaData = calendario.date(byAdding: .day, value: Int(sliderTime.value), to:dataCorrente)!
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
let dataCntr = "\(format.string(from: nuovaData))"
if mySubstring == dataCntr{
printQuake(quake: i)
}else{
removeQuake(quake:i)
}
}
//printQuake(sliderValue: appo)
}else if cnt == 0{
//removeSeismometers = false
playButton.setImage(play, for: .normal)
timer!.invalidate()
}
}
}
}else if control == false && Int(sliderTime.value) < 0 {
playButton.setImage(play, for: .normal)
control = true
timer!.invalidate()
}
}
My problem is that every second the slider has to move when you click on the UIButton, and every second has to add an annotation to the map and remove it as soon as you move the slider again.
Everything works, except that when the slider scrolls, the annotations of the previous move do not disappear, but remain on the map
func printQuake(quake: MapEarthquakeAnnotation){
let q = MapEarthquakeAnnotation(eq:quake.eq)
mapView.addAnnotation(q)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if annotation is MapEarthquakeAnnotation{
annotazioni.append(annotation)
let EQAnnotation = annotation as! MapEarthquakeAnnotation
var view: MKPinAnnotationView
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: EQAnnotation.identifier)
view.canShowCallout = true
view.pinTintColor = UIColor.brown
return view
}else if (annotation is MapSeismometerAnnotation) {
if let annotation = annotation as? MapSeismometerAnnotation
{
var view: MKPinAnnotationView
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
view.canShowCallout = true
view.pinTintColor = UIColor.green
view.image = UIImage(named: "pin-verde")
return view
}
return nil
}
return nil
}
Can you give me some advice?

It would be helpful to see the code you use in removeQuake but after a quick view of your code a very likely candidate is the code
func printQuake(quake: MapEarthquakeAnnotation){
let q = MapEarthquakeAnnotation(eq:quake.eq)
mapView.addAnnotation(q)
}
Here you create a new annotation each time you call this method. And this annotation is not preserved anywhere. So when you call removeQuake(quake:i) you can not expect that i is any of the annotations you have added using printQuake.
I am not sure why this code was build the it was but it is possible all you need to do is change this method to
func printQuake(quake: MapEarthquakeAnnotation){
mapView.addAnnotation(quake)
}
In general your code is a bit hard to read. You should look into more modern approaches using Swift and you should try to split parts of code so that they are easier to read and maintain. Non-English languages are not very helpful either.
I tried to tear down and reconstruct your code. Not everything is there and I am not sure it does what you want it to do. But still please inspect it. Maybe it will help you fix the issue you have.
import UIKit
import MapKit
class ViewController: UIViewController {
#IBOutlet private var mapView: MKMapView!
#IBOutlet private var sliderTime: UISlider!
#IBOutlet private var playButton: UIButton!
#IBOutlet private var labelTime: UILabel!
private var timer: Timer?
private var earthquakeAnnotations: [MapEarthquakeAnnotation] = []
private var dayOffset: Int = -30 {
didSet {
refresh()
}
}
private var isPlaying: Bool = false {
didSet {
playButton.setImage(isPlaying ? UIImage(named: "play") : UIImage(named: "pause"), for: .normal)
}
}
private func refresh() {
let beginningOfToday: Date = Calendar.autoupdatingCurrent.date(from: Calendar.autoupdatingCurrent.dateComponents([.year, .month, .day], from: Date()))!
let presentedDay = Calendar.autoupdatingCurrent.date(byAdding: .day, value: dayOffset, to: beginningOfToday)!
refreshSlider()
refreshTimeLabel(date: presentedDay)
refreshAnnotations(date: presentedDay)
}
private func refreshSlider() {
sliderTime.value = Float(dayOffset)
}
private func refreshTimeLabel(date: Date) {
labelTime.text = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter.string(from: date)
}()
}
private func refreshAnnotations(date: Date) {
earthquakeAnnotations.forEach { annotation in
if annotation.eq.date == date {
if !mapView.annotations.contains(where: { $0 === annotation }) {
mapView.addAnnotation(annotation)
}
} else {
if mapView.annotations.contains(where: { $0 === annotation }) {
mapView.removeAnnotation(annotation)
}
}
}
}
private func stopPlaying() {
timer?.invalidate()
timer = nil
isPlaying = false
}
private func startPlaying() {
if dayOffset < 0 {
isPlaying = true
timer?.invalidate() // Just in case
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
if self.dayOffset < 0 {
self.dayOffset += 1
} else {
self.stopPlaying()
}
})
} else {
// Already at the end. Should restart?
}
}
#IBAction func pressPausePlay(_ sender: Any) {
if isPlaying {
stopPlaying()
} else {
startPlaying()
}
}
}
// MARK: - MKMapViewDelegate
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? MapEarthquakeAnnotation {
let view: MKPinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
view.canShowCallout = true
view.pinTintColor = UIColor.brown
return view
} else if let annotation = annotation as? MapSeismometerAnnotation {
let view: MKPinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
view.canShowCallout = true
view.pinTintColor = UIColor.green
view.image = UIImage(named: "pin-verde")
return view
} else {
return nil
}
}
}
// MARK: - MapEarthquakeAnnotation
private extension ViewController {
class MapEarthquakeAnnotation: NSObject, MKAnnotation {
var identifier: String { "MapEarthquakeAnnotationID" }
let coordinate: CLLocationCoordinate2D
let eq: Earthquake
init(earthquake: Earthquake, coordinate: CLLocationCoordinate2D) { self.eq = earthquake; self.coordinate = coordinate }
}
}
// MARK: - MapSeismometerAnnotation
private extension ViewController {
class MapSeismometerAnnotation: NSObject, MKAnnotation {
var identifier: String { "MapSeismometerAnnotationID" }
let coordinate: CLLocationCoordinate2D
init(coordinate: CLLocationCoordinate2D) { self.coordinate = coordinate }
}
}
// MARK: - Earthquake
private extension ViewController {
struct Earthquake {
let eventTime: String
var date: Date {
let index = eventTime.index(eventTime.endIndex, offsetBy: -9)
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
return format.date(from: String(eventTime[..<index])) ?? Date()
}
}
}

Related

Mapbox Maps SDK for iOS - Maps won't display anymore (or gets cut off)

Maps (from MapBox API) were working just fine in our app, but about a month they stopped displaying on a swiftui view. BUT the tricky thing is that the maps in some phones still work (or works partially but being cut off), and they still show the annotations/pins (see screenshot).
We have the MapBox Maps SDK for iOS installed as cocoapods into Xcode.
It's a simple app, it just detects a vehicle trip and add event points to the map (eg. point A as trip start, and point B as trip end).
Versions:
Xcode: 13.2.1
pod 'Mapbox-iOS-SDK', '~> 6.4.1'
pod 'MapboxNavigation', '~> 1.4.2'
Basically, the following is our main code to show the map.
Note: It also adds speeding, hard brake and rapid accelaration annotations to the map.
Does someone have any idea why the map is not showing properly?
Thanks much in advance.
import SwiftUI
import Mapbox
import MapboxDirections
import Polyline
struct TestMapDriveView: View {
var selectedTrip: VehicleTripData // The selected trip that needs to be shown
#Binding var speedingCount: Int? // the number of speeding events in the selected trip
#Binding var showCallouts: Bool // Whether to show callout when a point annotation is selected
#Binding var showInfoSheet: Bool // whether show the event details view or not (show the details when the user tap on a specific marker on the map)
#Binding var isFullScreen: Bool // whether show full screen map or not
#Binding var selectedEncodedPolyline: String // The encoded polyline for the trip
#State var alreadyClean: Bool = false
#State var changed: Bool = false
#State var showSpecificPoint = false // Whether focues on a specific trip point or not
let action: () -> () // Used to toggle the full screen state variable
var body: some View {
VStack {
NavigationLink(destination: EmptyView(), label: {EmptyView()})
NavigationLink(destination: EventDetails(tripPoint: selectedTripPoint, selectedTrip: selectedTrip, typeInfo: .constant(selectedTypeInfo))
, isActive: $showInfoSheet, label: {EmptyView()}).navigationTitle("").navigationBarTitle("").navigationBarHidden(true)
MapboxMap(selectedTrip: selectedTrip, speedingCount: $speedingCount, showCallouts: $showCallouts, showInfoSheet: $showInfoSheet, isFullScreen: $isFullScreen, selectedEncodedPolyline: $selectedEncodedPolyline, alreadyClean: $alreadyClean, changed: $changed, showSpecificPoint: $showSpecificPoint)
.onAppear {
showDetails = false
// The reason for adding a timer here is that since showDetails is a global variable,
// and it is not a state variable, we canot trigger its value
// We need a timer so that we can check the value of the showDetails every 0.1 second
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
showInfoSheet = showDetails
if showInfoSheet {
isFullScreen = false
findTripPoint()
timer.invalidate()
}
}
}
}
}
}
// MARK: - New Map Provided by MapBox
// MARK: - Since MapBox is designed to be implemented with the UIView, for SwiftUI, we need to transfer the UIView to View
struct MapboxMap: UIViewRepresentable {
var selectedTrip: VehicleTripData
#Binding var speedingCount: Int?
#Binding var showCallouts: Bool
#Binding var showInfoSheet: Bool
#Binding var isFullScreen: Bool
#Binding var selectedEncodedPolyline: String
#Binding var alreadyClean: Bool
#Binding var changed: Bool
#Binding var showSpecificPoint: Bool
private let mapView: MGLMapView = MGLMapView(frame: .zero, styleURL: MGLStyle.streetsStyleURL)
class Coordinator: NSObject, MGLMapViewDelegate {
var showCallouts: Bool
init(showcallouts: Bool) {
self.showCallouts = showcallouts
}
func mapView(_ mapView: MGLMapView, shapeAnnotationIsEnabled annotation: MGLShape) -> Bool {
return annotation.title != nil && annotation.title != "Speeding"
}
// When you tap on a point on the map, whether show the callout or not
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
if let title = annotation.title {
return (!(title == "Speeding") && showCallouts)
}
else {
return false
}
}
// MARK: - This function is used to replace the default marker icon with our icons for specific events
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
guard annotation is MGLPointAnnotation else {
return nil
}
guard annotation.title != nil else {return nil}
guard annotation.title! != nil else {return nil}
if annotation.title != ""{
let identifier = annotation.title!!
print("error \(annotation.title)")
var image = UIImage()
if identifier.hasPrefix("Speeding") {
image = UIImage(named: "Speeding Marker")!
} else if identifier.hasPrefix("Hard"){
image = UIImage(named: "Hard Brake Pin")!
} else if identifier.hasPrefix("Rapid") {
image = UIImage(named: "Rapid Accel Pin")!
} else {
image = UIImage(named: identifier)!
}
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MGLAnnotationView(annotation: annotation, reuseIdentifier: identifier as! String)
let imageView = UIImageView(image: image)
annotationView!.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
annotationView?.addSubview(imageView)
annotationView?.centerOffset = CGVector(dx: 0, dy: -image.size.height / 2.0)
}
return annotationView
}
return nil
}
// MARK: - This function is executed when the user taps on "View Details"
// In fact, we should set showInfoSheet to be true in order to show the EventDetailView
// However, since we are using a coordinator here, we cannnot change showInfoSheet directly
// Therefore, we change the global variable "showDetails" to be true
#objc func labelAction(_ sender: UITapGestureRecognizer) {
showDetails = true
}
// MARK: - Add the "View Details" Label in the Callout View
// MARK: - We should use UIButtion, however, if we use UIButtion, it will never be shown. Need further tests
func mapView(_ mapView: MGLMapView, leftCalloutAccessoryViewFor annotation: MGLAnnotation) -> UIView? {
guard let title = annotation.title else {return nil}
if title != nil {
if title!.hasPrefix("Speeding:") {
selectedTypeInfo = "speeding"
} else if title!.hasPrefix("Hard") {
selectedTypeInfo = "hardBrake"
} else if title!.hasPrefix("Rapid") {
selectedTypeInfo = "rapidAccel"
} else {
return nil
}
let labelTap = UITapGestureRecognizer(target: self, action: #selector(labelAction(_:)))
labelTap.numberOfTapsRequired = 1
// Callout height is fixed; width expands to fit its content.
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 60, height: 50))
label.textAlignment = .center
label.textColor = UIColor(named: "Blue")
label.text = "View Details"
label.numberOfLines = 2
label.font = UIFont(name: "NotoSans-Regular", size: 12)
label.isUserInteractionEnabled = true
label.addGestureRecognizer(labelTap)
selectedCoor = annotation.coordinate
return label
}
return nil
}
// MARK: - This function is used to change the color of the polyline based on the event name
func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor {
guard let title = annotation.title else {return UIColor(named: "Blue")!}
if title.hasPrefix("Speeding") {
return UIColor(named: "MapRed")!
} else {
return UIColor(named: "Blue")!
}
}
}
func makeCoordinator() -> MapboxMap.Coordinator {
return Coordinator(showcallouts: showCallouts)
}
// MARK: - Initialize the MapView
func makeUIView(context: Context) -> MGLMapView {
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.setCenter(CLLocationCoordinate2D(latitude: selectedTrip.startedLatitude!, longitude: selectedTrip.startedLongitude!), animated: true)
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MGLMapView, context: Context) {
if (uiView.annotations ?? []).count == 0 {
mapViewDidFinishLoadingMap(uiView, didFinishLoading: MGLStyle())
}
}
func mapViewDidFinishLoadingMap(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
mapView.isRotateEnabled = false
// MARK: - Get the result from the encoded polyline string
let result = Polyline.init(encodedPolyline: selectedEncodedPolyline, encodedLevels: .none)
// MARK: - Get the coordinates in the polyline for the trip
let coordinates = result.coordinates
guard let tripPoints = coordinates else {return}
// MARK: - Get the start and end point
let startPoint: CLLocationCoordinate2D = coordinates?.first ?? CLLocationCoordinate2D(latitude: 0, longitude: 0)
let endPoint: CLLocationCoordinate2D = coordinates?.last ?? CLLocationCoordinate2D(latitude: 0, longitude: 0)
var startTripPoint = MGLPointAnnotation()
var endTripPoint = MGLPointAnnotation()
startTripPoint.title = "Starting Point"
startTripPoint.coordinate = startPoint
endTripPoint.title = "End Point"
endTripPoint.coordinate = endPoint
mapView.addAnnotation(startTripPoint)
mapView.addAnnotation(endTripPoint)
let tryLine = MGLPolyline(coordinates: coordinates!, count: UInt(coordinates!.count))
mapView.addAnnotation(tryLine)
var speedingArray = [[TripPoint]]()
DispatchQueue.global(qos: .background).async {
// MARK: - Deal with the speeding events in the trip
// MARK: - The speeding icon will be placed at the point which has the maximum speed for each speeding polyline
var ctr = 0
while ctr < selectedTrip.speedingPoints!.count{
var speedPath = [TripPoint]()
speedPath.append(selectedTrip.speedingPoints![ctr])
while ctr + 1 < selectedTrip.speedingPoints!.count && selectedTrip.speedingPoints![ctr + 1].timeStamp!.timeIntervalSince1970 - selectedTrip.speedingPoints![ctr].timeStamp!.timeIntervalSince1970 < 10{
speedPath.append(selectedTrip.speedingPoints![ctr+1])
ctr += 1
}
speedingArray.append(speedPath)
ctr += 1
}
for speedingLine in speedingArray {
var maxDelta = 0.0
var maxDeltaPoint:TripPoint? = nil
var path: [CLLocationCoordinate2D] = []
for speedingPoint in speedingLine{
path.append(CLLocationCoordinate2D(latitude: speedingPoint.latitude!, longitude: speedingPoint.longitude!))
if speedingPoint.speed_limit_delta! > maxDelta{
maxDelta = speedingPoint.speed_limit_delta!
maxDeltaPoint = speedingPoint
}
}
if let markerPoint = maxDeltaPoint{
tripPointsArray.append(markerPoint)
var speedMarker = MGLPointAnnotation()
speedMarker.coordinate = CLLocationCoordinate2D(latitude: markerPoint.latitude!, longitude: markerPoint.longitude!)
speedMarker.title = "Speeding: \(Int(markerPoint.speed!)) " + markerPoint.speedUnit!
let speedLimitBR = Double(markerPoint.speed!) - markerPoint.speed_limit_delta! //before rounding
var speedLimit = 10 * Int((speedLimitBR / 10.0).rounded())//round to nearest 10
speedMarker.subtitle = "Speed Limit: ~ \(speedLimit) " + markerPoint.speedUnit!
DispatchQueue.main.async {
mapView.addAnnotation(speedMarker)
}
}
let speedingPolyline = MGLPolyline(coordinates: path, count: UInt(path.count))
speedingPolyline.title = "Speeding"
DispatchQueue.main.async {
mapView.addAnnotation(speedingPolyline)
}
}
speedingCount = speedingArray.count
// MARK: - Deal with hard brakes in the trip
for hardBrakePoint in selectedTrip.hardBrakePoints!{
tripPointsArray.append(hardBrakePoint)
let hardBrakeMarker = MGLPointAnnotation()
hardBrakeMarker.coordinate = CLLocationCoordinate2D(latitude: hardBrakePoint.latitude!, longitude: hardBrakePoint.longitude!)
hardBrakeMarker.title = "Hard Brake"
hardBrakeMarker.subtitle = "Acceleration: \(String(format: "%.1f", hardBrakePoint.acceleration! * 3.6)) " + "km/h/s"
DispatchQueue.main.async {
mapView.addAnnotation(hardBrakeMarker)
}
}
// MARK: - Deal with rapid accel in the trip
for rapidAccelPoint in selectedTrip.rapidAccelPoints!{
tripPointsArray.append(rapidAccelPoint)
let rapidAccelMarker = MGLPointAnnotation()
rapidAccelMarker.coordinate = CLLocationCoordinate2D(latitude: rapidAccelPoint.latitude!, longitude: rapidAccelPoint.longitude!)
rapidAccelMarker.title = "Rapid Accel"
rapidAccelMarker.subtitle = "Acceleration: \(String(format: "%.1f", rapidAccelPoint.acceleration! * 3.6)) " + "km/h/s"
DispatchQueue.main.async {
mapView.addAnnotation(rapidAccelMarker)
}
}
}
// MARK: - If we are not in EventDetailView, then the mapView.showAnnotations will help us to zoom the map to the proper level so that all the markers and annotations in the map will be shown
if !showSpecificPoint {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { timer in
guard let annotations = mapView.annotations else {return}
mapView.showAnnotations(annotations, edgePadding: .init(top: 60, left: 40, bottom: 10, right: 40), animated: true, completionHandler: nil)
}
} else {
// MARK: - If we need to zoom into a specific point, we need to find which point it is among all the trip points and zoom into that specific point
var alreadyAdded = false
var point = MGLPointAnnotation()
point.coordinate = CLLocationCoordinate2D(latitude: selectedTripPoint.latitude ?? 0, longitude: selectedTripPoint.longitude ?? 0)
for hardBrakePoint in selectedTrip.hardBrakePoints! {
if hardBrakePoint.latitude == selectedTripPoint.latitude && hardBrakePoint.longitude == selectedTripPoint.longitude {
point.title = "Hard Brake"
point.subtitle = "Acceleration: \(String(format: "%.1f", hardBrakePoint.acceleration! * 3.6)) " + "km/h/s"
mapView.addAnnotation(point)
alreadyAdded = true
break
}
}
if !alreadyAdded {
for rapidAccelPoint in selectedTrip.rapidAccelPoints!{
if rapidAccelPoint.latitude == selectedTripPoint.latitude && rapidAccelPoint.longitude == selectedTripPoint.longitude {
point.title = "Rapid Accel"
point.title! += "Acceleration: \(String(format: "%.1f", rapidAccelPoint.acceleration! * 3.6)) " + "km/h/s"
mapView.addAnnotation(point)
alreadyAdded = true
break
}
}
}
if !alreadyAdded {
var presentPolyLine: MGLPolyline = MGLPolyline()
for speedingLine in speedingArray{
var maxDelta = 0.0
var maxDeltaPoint:TripPoint? = nil
var path: [CLLocationCoordinate2D] = []
for speedingPoint in speedingLine{
path.append(CLLocationCoordinate2D(latitude: speedingPoint.latitude!, longitude: speedingPoint.longitude!))
if speedingPoint.speed_limit_delta! > maxDelta{
maxDelta = speedingPoint.speed_limit_delta!
maxDeltaPoint = speedingPoint
}
}
if let markerPoint = maxDeltaPoint{
if selectedTripPoint.latitude == markerPoint.latitude && selectedTripPoint.longitude == markerPoint.longitude {
let specificMarker = MGLPointAnnotation()
specificMarker.coordinate = CLLocationCoordinate2D(latitude: markerPoint.latitude ?? 0, longitude: markerPoint.longitude ?? 0)
var speedMarker = MGLPointAnnotation()
speedMarker.coordinate = CLLocationCoordinate2D(latitude: markerPoint.latitude!, longitude: markerPoint.longitude!)
speedMarker.title = "Speeding: \(Int(markerPoint.speed!)) " + markerPoint.speedUnit!
let speedLimitBR = Double(markerPoint.speed!) - markerPoint.speed_limit_delta! //before rounding
var speedLimit = 10 * Int((speedLimitBR / 10.0).rounded())//round to nearest 10
speedMarker.subtitle = "Speed Limit: ~ \(speedLimit) " + markerPoint.speedUnit!
point = speedMarker
DispatchQueue.main.async {
let speedingPolyline = MGLPolyline(coordinates: path, count: UInt(path.count))
speedingPolyline.title = "Speeding"
presentPolyLine = speedingPolyline
}
break
} else {
path.removeAll()
}
}
}
mapView.showAnnotations([point], edgePadding: .init(top: 60, left: 40, bottom: 10, right: 40), animated: true, completionHandler: nil)
} else {
mapView.showAnnotations([point], edgePadding: .init(top: -10, left: -10, bottom: -10, right: -10), animated: true, completionHandler: nil)
}
}
}
}
I tried updating the pods from 6.3 to 6.4.1 but it didn't work.
I know MapBox has a new framework as of v6.4.1 called v10, but I haven't installed yet cause that would change the whole code.
I am expecting to see the map as it was displaying before this new bug. eg. see screenshot

macOS menu bar text with icon

As you can see in the image I would like to be able to do a similar one, to make a way that instead of showing only the icon of the sun, also showing a text.
As seen in the image below, an icon followed by a text.
But I only managed to do this:
The problem I would like to put the icon on the left or right of the text, not above it, can you give me a hand?
P.s.
The text must change accordingly, how can I make the StatusBarController receive the text changes.
import AppKit
import SwiftUI
class StatusBarController {
#ObservedObject var userPreferences = UserPreferences.instance
private var statusBar: NSStatusBar
private var statusItem: NSStatusItem
private var popover: NSPopover
init(_ popover: NSPopover) {
self.popover = popover
statusBar = NSStatusBar.init()
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let statusBarButton = statusItem.button {
if let _ = userPreferences.$inDownload {
statusItem.button?.title = userPreferences.$percentualDownload
}
statusBarButton.image = #imageLiteral(resourceName: "Weather")
statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
statusBarButton.image?.isTemplate = true
statusBarButton.action = #selector(togglePopover(sender:))
statusBarButton.target = self
statusBarButton.imagePosition = NSControl.ImagePosition.imageLeft
}
}
#objc func togglePopover(sender: AnyObject) {
if(popover.isShown) {
hidePopover(sender)
}
else {
showPopover(sender)
}
}
func showPopover(_ sender: AnyObject) {
if let statusBarButton = statusItem.button {
popover.show(relativeTo: statusBarButton.bounds, of: statusBarButton, preferredEdge: NSRectEdge.maxY)
}
}
func hidePopover(_ sender: AnyObject) {
popover.performClose(sender)
}
}
I'm thinking of using something like that:
import EventKit
import ServiceManagement
private struct PreferencesKeys {
static let backgroundIsTransparent = "backgroundIsTransparent"
static let inDownload = "inDownload"
static let percentualDownload = "percentualDownload"
}
class UserPreferences: ObservableObject {
static let instance = UserPreferences()
private init() {
// This prevents others from using the default '()' initializer for this class.
}
private static let defaults = UserDefaults.standard
#Published var backgroundIsTransparent: Bool = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.backgroundIsTransparent) != nil else {
return true
}
return UserDefaults.standard.bool(forKey: PreferencesKeys.backgroundIsTransparent)
}() {
didSet {
UserPreferences.defaults.set(backgroundIsTransparent, forKey: PreferencesKeys.backgroundIsTransparent)
}
}
#Published var inDownload: Bool = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.inDownload) != nil else {
return true
}
return UserDefaults.standard.bool(forKey: PreferencesKeys.inDownload)
}() {
didSet {
UserPreferences.defaults.set(inDownload, forKey: PreferencesKeys.inDownload)
}
}
#Published var percentualDownload: String = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.percentualDownload) != nil else {
return "0%"
}
return UserDefaults.standard.string(forKey: PreferencesKeys.percentualDownload)!
}() {
didSet {
UserPreferences.defaults.set(percentualDownload, forKey: PreferencesKeys.percentualDownload)
}
}
}
but I get the following error:
Edit:
First problem solved I used:
statusBarButton.imagePosition = NSControl.ImagePosition.imageLeft
statusBarButton.imagePosition = NSControl.ImagePosition.imageRight
For the update text problem, what can I do?

MKUserTrackingButton doesn't appear because of Firebase?

I added in code the MapView, MKUserTrackingButton button, localManager, DispatchGroup:
let mapView: MKMapView = {
let mapView = MKMapView()
mapView.translatesAutoresizingMaskIntoConstraints = false
return mapView
}()
private var userTrackingButton: MKUserTrackingButton!
private let locationManager = CLLocationManager()
let myGroup = DispatchGroup()
var array = [Car]()
In ViewDidLoad I set:
myGroup.enter()
Get data from Firebase:
observeCars()
Wait until I get all the data from Firebase:
myGroup.notify(queue: DispatchQueue.main) {
self.view.addSubview(self.mapView)
//Here code to set the mapView in the view
self.setupUserTrackingButton()
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
Here I set my button in the view:
private func setupUserTrackingButton() {
mapView.showsUserLocation = true
userTrackingButton = MKUserTrackingButton(mapView: mapView)
userTrackingButton.layer.backgroundColor = UIColor(white: 0.5, alpha: 1).cgColor
userTrackingButton.layer.borderColor = UIColor.white.cgColor
userTrackingButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(userTrackingButton)
///Code to set in the view the button
}
Class where I set connection to Firebase to get data:
class APIService: NSObject {
class func observeCars(completion: ((_ car: Car) -> Void)?) {
let ref = Database.database().reference()
ref.child("Cars").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: Any] {
let car = Car(dictionary: dictionary)
completion?(car)
}
}, withCancel: nil)
}
}
This is the function (written in the MainViewController) where I get the data from Firebase and add it to the array:
internal func observeCars() {
/////I think this is the part that makes It doesn't appear because
if I delete this part, it works like I aspect
APIService.observeCars {
(car) in
self.array.append(car)
print(self.array.count)
if self.array.count == totalCars {
self.myGroup.leave()
}
}
//////////
}
Any hints? Thanks
I added the MKUserTrackingButton in the mapView:
mapView.addSubview(userLocationButton)
It works to me.

How to show multiple items from Core Data on an MKAnnotation

I am attempting to show multiple objects on my map annotations from Care Data. Currently, I can get title and subtitle to show info with this function
func getData() -> [MKAnnotation]? {
do {
storedLocations = try context.fetch(Fish.fetchRequest())
var annotations = [MKAnnotation]()
for storedLocation in storedLocations {
let newAnnotation = MyAnnotation(coordinate: CLLocationCoordinate2D(latitude: storedLocation.latitude, longitude: storedLocation.longitude))
newAnnotation.coordinate.latitude = storedLocation.latitude
newAnnotation.coordinate.longitude = storedLocation.longitude
newAnnotation.title = storedLocation.species
newAnnotation.subtitle = storedLocation.length
newAnnotation.newTime = storedLocation.time
newAnnotation.newBait = storedLocation.bait
newAnnotation.newNotes = storedLocation.notes
newAnnotation.newWaterTempDepth = storedLocation.water
newAnnotation.newWeatherCond = storedLocation.weather
// print(storedLocation.length)
let newLength = storedLocation.length
let newTime = newAnnotation.newTime
// let newBait = storedLocation.bait
// let newNotes = storedLocation.notes
// let newWaterTempDepth = storedLocation.water
// let newWeatherCond = weatherCond
myLength = newLength!
myTime = newTime!
//
annotations.append(newAnnotation)
}
return annotations
}
catch {
print("Fetching Failed")
}
return nil
}
I am trying to show all of the storedLocation data on the annotation.
By using this extension I found here I am able to add multiple lines to the annotation.
However it is not from the info stored in core data. This is how I set up the func for the extension
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else {
return nil
}
let reuseIdentifier = "pin"
var annotationView = map.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
annotationView?.canShowCallout = true
annotationView?.loadCustomLines(customLines: [myLength, myTime])
} else {
annotationView?.annotation = annotation
}
annotationView?.image = UIImage(named: "fish")
return annotationView
Here is the extension.
extension MKAnnotationView {
func loadCustomLines(customLines: [String]) {
let stackView = self.stackView()
for line in customLines {
let label = UILabel()
label.text = line
stackView.addArrangedSubview(label)
}
self.detailCalloutAccessoryView = stackView
}
private func stackView() -> UIStackView {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .fill
return stackView
}
}
here is my annotation
import UIKit
import MapKit
class MyAnnotation: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
var newLength: String?
var newTime: String?
var newBait: String?
var newNotes: String?
var newWaterTempDepth: String?
var newWeatherCond: String?
var detailCalloutAccessoryView: UIStackView?
init(coordinate: CLLocationCoordinate2D){
self.coordinate = coordinate
}
}
When I run I get
this
I am very new to programming and would greatly appreciate any help, and also an explanation of what I am doing wrong Thank you in advance
'!' means Force Unwrapping
myLength = newLength! <- CRASH
As 'newLength' is nil here, you will get a crash on the line where you force unwrap it.
e.g. You can fix the error
myLength = newLength ?? 0
myTime = newTime ?? 0
See also What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?

How to update annotations when in mapview on zoom?

I'm trying to make an app, where you get buses positions from web API. When I got the data, I put annotations on mapview for all of the coordinates i got from API. Then, when annotation is tapped on, I try to hide all the annotations which aren't on the same route as the one clicked. It works fine, but when I zoom out the annotations that weren't in the original region aren't hidden. Is there a way to refresh what is displayed when user zooms on map view?
import UIKit
import MapKit
import MBProgressHUD
import Alamofire
class MapViewController: UIViewController {
//MARK: Properties and Outlets
private var timer: NSTimer?
private lazy var dateFormatter = NSDateFormatter()
private var vehicles = [String: Vehicle]()
private var points = [CLLocationCoordinate2D]()
private let locationManager = CLLocationManager()
private var currentLocation: CLLocation?
#IBOutlet weak var mapView: MKMapView!
//MARK: Actions
#IBAction func getMyLocation(sender: UIBarButtonItem) {
getLocation()
}
//MARK: Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
dateFormatter.locale = NSLocale.currentLocale()
dateFormatter.dateFormat = "HH:mm:ss"
timer = NSTimer.scheduledTimerWithTimeInterval(30, target: self, selector: "fetchVehiclesLocations", userInfo: nil, repeats: true)
fetchVehiclesLocations()
getLocation()
}
//MARK: Get Location
private func getLocation() {
let authStatus = CLLocationManager.authorizationStatus()
if authStatus == .NotDetermined {
locationManager.requestWhenInUseAuthorization()
return
}
if authStatus == .Denied || authStatus == .Restricted {
showLocationServicesDeniedAlert()
return
}
startLocationManager()
}
//MARK: Helper methods
private func showLocationServicesDeniedAlert() {
let alert = UIAlertController(title: "Location Services Disabled", message: "Please enable location services for this app in Settings.", preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alert.addAction(okAction)
presentViewController(alert, animated: true, completion: nil)
}
private func startLocationManager () {
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
showLoadingHUD()
}
}
private func stopLocationManager() {
locationManager.delegate = nil
locationManager.stopUpdatingLocation()
hideLoadingHUD()
}
private func rotateBus() {
UIView.animateWithDuration(1.0, delay: 0, options: .CurveLinear, animations: {
for (id, _) in self.vehicles {
if self.vehicles[id]!.lastPosition != nil {
let annotationView = self.mapView.viewForAnnotation(self.vehicles[id]!.annotation!)
let currentLocationPoint = self.mapView.convertCoordinate(self.vehicles[id]!.lastPosition!, toPointToView: self.mapView)
let destinationPoint = self.mapView.convertCoordinate(self.vehicles[id]!.coordinates, toPointToView: self.mapView)
let yDiff = currentLocationPoint.y - destinationPoint.y
let xDiff = destinationPoint.x - currentLocationPoint.x
let arcTan = atan(yDiff / xDiff)
var angle = CGFloat(M_PI / 2) - arcTan
if xDiff < 0 {
angle = angle + CGFloat(M_PI)
}
if angle.isNaN || angle == 0.0 {
continue
}
else {
annotationView?.transform = CGAffineTransformMakeRotation(CGFloat(angle))
}
}
}
}, completion: nil)
moveBus()
}
private func moveBus() {
UIView.animateWithDuration(28.0, delay: 0, options: .CurveLinear, animations: {
for(id, _) in self.vehicles {
if self.vehicles[id]!.lastPosition != nil {
self.vehicles[id]!.annotation?.coordinate = self.vehicles[id]!.coordinates
}
}
}, completion: nil)
}
private func createVehicles() {
print(vehicles.count)
for (id, _) in vehicles {
if vehicles[id]?.annotation == nil {
let annotation = BusAnnotation()
annotation.imageName = "bus"
annotation.coordinate = vehicles[id]!.coordinates
annotation.title = id
vehicles[id]?.annotation = annotation
mapView.addAnnotation(annotation)
}
}
print(mapView.annotations.count)
rotateBus()
}
private func drawPolyline() {
let myPolyline = MKPolyline(coordinates: &points, count: points.count)
mapView.addOverlay(myPolyline)
}
//MARK: HUD display methods
private func showLoadingHUD() {
let hud = MBProgressHUD.showHUDAddedTo(mapView, animated: true)
hud.labelText = "Getting Your Location..."
}
private func hideLoadingHUD() {
MBProgressHUD.hideAllHUDsForView(mapView, animated: true)
}
//MARK: Networking
func fetchVehiclesLocations() {
let URL = urlVehiclesLocations + apiKey
if !vehicles.isEmpty {
for (id , _) in vehicles {
vehicles[id]?.lastPosition = vehicles[id]?.coordinates
}
}
Alamofire.request(.GET, URL)
.validate()
.responseJSON {
(request, response, result) in
guard result.isSuccess else {
print("Error while fetching \(result.error!)")
return
}
let value = result.value as? [String: AnyObject]
let responseDict = value?["response"] as? [String: AnyObject]
let array = responseDict?["entity"] as? [[String: AnyObject]]
for var i = 0; i < array?.count; i++ {
let item = array?[i]["vehicle"] as? [String: AnyObject]
let position = item?["position"] as? [String: AnyObject]
let trip = item?["trip"] as? [String: AnyObject]
let vehicle = Vehicle()
vehicle.latitude = position?["latitude"] as! Double
vehicle.longitude = position?["longitude"] as! Double
vehicle.tripId = trip?["trip_id"] as! String
let startTime = trip?["start_time"] as! String
vehicle.startTime = self.dateFormatter.dateFromString(startTime)
let vehicleId = item?["vehicle"] as? [String: AnyObject]
let id = vehicleId?["id"] as! String
if self.vehicles[id] == nil {
self.vehicles[id] = vehicle
}
else {
self.vehicles[id]?.latitude = vehicle.latitude
self.vehicles[id]?.longitude = vehicle.longitude
}
}
self.createVehicles()
}
}
func fetchPointsForTripPolyline() {
let URL = urlTripPolyline + apiKey
Alamofire.request(.GET, URL)
.validate()
.responseJSON {
(request, response, result) in
guard result.isSuccess else {
print("Error while fetching \(result.error!)")
return
}
let value = result.value as! [String: AnyObject]
let responseArray = value["response"] as! [[String: AnyObject]]
for var i = 0; i < responseArray.count; i++ {
let longitude = responseArray[i]["shape_pt_lon"] as! CLLocationDegrees
let latitude = responseArray[i]["shape_pt_lat"] as! CLLocationDegrees
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
self.points.append(coordinate)
}
self.drawPolyline()
}
}
}
extension MapViewController: CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let newLocation = locations.last!
if newLocation.timestamp.timeIntervalSinceNow < -5 {
return
}
if newLocation.horizontalAccuracy < 0 {
return
}
if newLocation.horizontalAccuracy <= locationManager.desiredAccuracy {
stopLocationManager()
currentLocation = newLocation
let annotation = MKPointAnnotation()
annotation.coordinate = newLocation.coordinate
mapView.addAnnotation(annotation)
}
let center = CLLocationCoordinate2D(latitude: newLocation.coordinate.latitude, longitude: newLocation.coordinate.longitude)
let region = MKCoordinateRegionMakeWithDistance(center, 1000, 1000)
self.mapView.setRegion(region, animated: true)
}
}
extension MapViewController: MKMapViewDelegate {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is BusAnnotation) {
return nil
}
let reuseId = annotation.title!
var busAnnotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId!)
if busAnnotationView == nil {
busAnnotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
busAnnotationView?.canShowCallout = true
}
else {
busAnnotationView?.annotation = annotation
}
let busAnnotation = annotation as! BusAnnotation
busAnnotationView?.image = UIImage(named: busAnnotation.imageName)
return busAnnotationView
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolyline {
let lineView = MKPolylineRenderer(overlay: overlay)
lineView.lineWidth = 3.0
lineView.strokeColor = UIColor.greenColor()
return lineView
}
return MKOverlayRenderer()
}
func hideBuses(tripId: String) {
for (_, vehicle) in vehicles {
if vehicle.tripId != tripId {
let annotationView = mapView.viewForAnnotation(vehicle.annotation!)
annotationView?.hidden = true
}
}
}
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
let vehicleId = view.annotation?.title!
let tripId = vehicles[vehicleId!]?.tripId
hideBuses(tripId!)
}
}