How to show multiple items from Core Data on an MKAnnotation - swift

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?

Related

Remove annotation when slider moves - 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()
}
}
}

Swift fetch custom annotation information

I'm trying to present a view controller with an object within annotation didSelect function. But it's returning nil when I try to print the post.id within the newly presented viewcontroller. If I print post.id within the addAnnotation function it returns fine.
func addAnnotations(post: Post, title: String?, locationName: String?, coords: [CLLocation]) {
for coord in coords {
let CLLCoordType = CLLocationCoordinate2D(latitude: coord.coordinate.latitude,
longitude: coord.coordinate.longitude);
let anno = MKPointAnnotation()
anno.coordinate = CLLCoordType
if title == "" {
anno.title = locationName
} else {
anno.title = title
}
mapView.addAnnotation(anno)
}
}
let activityPreview = ActivityPreviewLauncher()
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
//Returns an error
guard let post = view.annotation?.post else { return }
activityPreview.showActivityPreview()
activityPreview.homeController = self
activityPreview.post = post
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation{
return nil
} else{
let pinIdent = "Pin"
var pinView: MKPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: pinIdent) as? MKPinAnnotationView {
dequeuedView.annotation = annotation
pinView = dequeuedView
} else {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: pinIdent)
}
return pinView
}
}
Value of type 'MKAnnotation' has no member 'post'. You have to subclass MKPointAnnotation
class MyAnno: MKPointAnnotation {
var post: Post? = nil
}
in func addAnnotations override your code to
let CLLCoordType = CLLocationCoordinate2D(latitude: coord.coordinate.latitude,
longitude: coord.coordinate.longitude);
let anno = MyAnno()
anno.coordinate = CLLCoordType
anno.post = post
if title == "" {
anno.title = locationName
} else {
anno.title = title
}
mapView.addAnnotation(anno)
then in didSelect check type of your annotation
guard
let anno = view.annotation as? MyAnno,
let post = anno.post
else { return }
smth like this

How to add image for annotation (mapView) using Firebase?

I have this func for populate pins on mapView:
func obervePins(){
let magRef = Database.database().reference().child("map")
magRef.observe(.value) { (snapshot) in
//var tempCoord = [CoordinatesOfMagazine]()
for child in snapshot.children {
if let chidSnapshot = child as? DataSnapshot,
let dictMag = chidSnapshot.value as? [String: Any],
let title = dictMag["Title"] as? String,
let latitude = dictMag["Latitude"] as? String,
let longitude = dictMag["Longitude"] as? String {
// let imageOfMagazine = dictMag["imageOfMagazine"] as? String,
// let url = URL(string: imageOfMagazine) {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: Double(latitude)!, longitude: Double(longitude)!)
annotation.title = title
print(annotation)
self.mapView.addAnnotations([annotation])
// let coordinates = CoordinatesOfMagazine(imageOfMagazine: url, Title: title)
// tempCoord.append(coordinates)
}
}
//self.coordinates = tempCoord
}
}
My data in Firebase looks like:
Pins in mapView is correct.
I don't know, how to display images for magazine in mapView. Help plz
Create a custom MKAnnotation class.
class ImageAnnotation : NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
var imageOfMagazine: String?
override init() {
self.coordinate = CLLocationCoordinate2D()
self.title = nil
self.subtitle = nil
self.imageOfMagazine = nil
}
}
Set data and add the annotation to your mapView.
let annotation = ImageAnnotation()
annotation.coordinate = coordinate1
annotation.title = "title"
annotation.subtitle = "subtitle"
annotation.imageOfMagazine = imageOfMagazine
self.mapView.addAnnotation(annotation)
Implement mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) delegate method.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let annotation = annotation as? ImageAnnotation else {
return nil
}
let reuseId = "Pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if pinView == nil {
pinView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.canShowCallout = true
let data = NSData(contentsOf: URL(string: annotation.imageOfMagazine!)!)
pinView?.image = UIImage(data: data! as Data)
}
else {
pinView?.annotation = annotation
}
return pinView
}
// here take marker as global varible
var marker : GMSMarker?
self.ref.observe(.value) { snapshot in
let dict = snapshot.value as! NSDictionary
self.marker?.map?.clear()
if let lat = dict["latitude"] as? CLLocationDegrees ,let long = dict["longitude"] as? CLLocationDegrees {
var camera = GMSCameraPosition.camera(withLatitude: lat, longitude: longt, zoom: 12)
var position: CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat, longt)
self.marker = GMSMarker(position: position)
self.marker?.icon = UIImage(named: imageName) // set your image here
self.marker?.map = self.mapview
self.mapview?.animate(to: camera)
}
}

Firebase Geofire centre map on location from database

So I have multiple locations stored in my database, using Geofire. I want to centre the map on a specific one.
I want to know if it is possible to access the firebase database to only retrieve the coordinates saved by GeoFire as a variable so that I can create a CLLocation and centre the map on this one location. Here is my code.
I call getJobLocation in my viewDidLoad. This is where I want to grab the job coordinates from Firebase and then call the other methods i.e. showJobsOnMap using these coordinates
func getJobLocation(jobID: String) {
...
}
func centreMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, 2000, 2000)
mapView.setRegion(coordinateRegion, animated: true)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annoIdentifier = "job"
var annotationView: MKAnnotationView?
if annotation.isKind(of: MKUserLocation.self) {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "User")
annotationView?.image = UIImage(named:"currentLocationPin")
} else if let deqAnno = mapView.dequeueReusableAnnotationView(withIdentifier: "job") {
annotationView = deqAnno
annotationView?.annotation = annotation
} else {
let MKAV = MKAnnotationView(annotation: annotation, reuseIdentifier: annoIdentifier)
MKAV.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView = MKAV
}
if let annotationView = annotationView, let _ = annotation as? JobAnnotation {
let postsRef = self.dataBaseRef.child("jobs")
postsRef.observe(.value, with: { (posts) in
for _ in posts.children {
annotationView.canShowCallout = true
annotationView.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView.image = UIImage(named: "jobPin")
}
})
}
return annotationView
}
func showJobsOnMap(location: CLLocation) {
let circleQuery = geoFire!.query(at: location, withRadius: 2.5)
_ = circleQuery?.observe(GFEventType.keyEntered, with: { (key, location) in
if let key = key, let location = location {
if key == self.job.postID {
let postsRef = self.dataBaseRef.child("jobs").child(key)
postsRef.observe(.value, with: { (posts) in
for _ in posts.children {
let post = Post(snapshot: posts )
let jobDate = post.postDateTime
let price = post.price
let anno = JobAnnotation(coordinate: location.coordinate, jobID: key, jobDate: jobDate, title: "£\(price) - \(jobDate)")
self.mapView.addAnnotation(anno)
}
})
}
}
})
}

Swift Custom Map Icon Using MKMapViewAnnotation SIGABRT error

I am trying to add custom images to my mapView but am not having any luck so far. I keep getting a SIGABRT error when the map is loaded. here is my code:
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.isKindOfClass(NiteOwlAnnotations) {
let reuseId = "test"
var annoView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if annoView == nil {
annoView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
annoView?.image = UIImage(named: "bar-map-icon")
annoView?.canShowCallout = true
} else {
annoView?.annotation = annotation
}
return annoView
/* let annoView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "Default")
// change color of pins.
annoView.pinTintColor = UIColor.blackColor()
annoView.animatesDrop = false
annoView.image = UIImage(named: "bar-map-icon")
annoView.backgroundColor = UIColor.clearColor()
annoView.canShowCallout = true
return annoView
*/
} else if annotation.isKindOfClass(MKUserLocation) {
return nil
}
return nil
}// end func
//--------------------------------------------------------------
func createAnnotationForLocation(location: CLLocation) {
let niteOwl = NiteOwlAnnotations(coordinate: location.coordinate)
map.addAnnotation(niteOwl)
}
I have looked for more information on this and still no luck yet:
Change pin image on MKMapView in Swift