How to customize multiple annotations? Parse-Swift - swift

I have been struggling with this for days but I still can't find a way to do it. Basically I have this map view and I am getting the annotations data (location,title,subtitle) from Parse, all I want to do is to replace the default pins with a custom one, I managed to customize one when I only use one annotation but for multiple it is not working, this is what I have for adding the annotations:
func setAnnotations(){
//Run query and get objects from parse, then add annotations
var query = PFQuery(className: "Countries")
query.orderByDescending("Location")
query.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]?,error: NSError?)-> Void in
if error == nil{
let myObjects = objects as! [PFObject]
for object in myObjects{
//data for annotation
var annotation = MKPointAnnotation()
let place = object["Location"] as? PFGeoPoint
let placeName = object["nameEnglish"] as? String
annotation.title = placeName
annotation.subtitle = placeName
annotation.coordinate = CLLocationCoordinate2DMake(place!.latitude,place!.longitude)
//add annotations
self.mapView.addAnnotation(annotation)
}
}else{println(error)}
}
}
How would I apply the "dequeueReusableAnnotationViewWithIdentifier" here?

You will want to use the viewForAnnotation delegate method similar to this answer. Make sure you also implement the MKMapViewDelegate protocol and set your controller to be the delegate.
Other than that, all you need to do is update your query to create and add your custom annotations like you are right now.
Cheers,
Russell

Related

Problem with callout button and firestore in swift

I have 5 collections on Firestore. With the data of the documents of those collections, I create annotations of different types and display them on a map view.
The problem is that I want to pass all the information that is stored in each annotation and display it on another view controller, that appears when you press the callout button.
I can't figure out a way of referencing the annotation I'm pressing and then pass the data to the other screen.
This is my first time using databases and I don't have many experience, so I would appreciate some help.
Thanks!
It's difficult to give a specific answer since I don't know the full scope of your features. But this is how you generally do it.
First when you create the MKAnnotation subclass, you define a property to hold an object that you can reference later. For example, say I'm showing restaurants and supermarkets in a map.
class RestaurantAnnotation: NSObject, MKAnnotation {
let restaurant: Restaurant
var title: String? {
return restaurant.name
}
var coordinate: CLLocationCoordinate2D {
return restaurant.coordinate
}
init(restaurant: Restaurant) {
self.restaurant = restaurant
super.init()
}
}
struct Restaurant {
let name: String
let coordinate: CLLocationCoordinate2D
}
Same goes for the supermarkets.
Then when you create the annotation, you pass the Restaurant object to it.
let restaurant = Restaurant(name: "McDonald's", coordinate: CLLocationCoordinate2D(latitude: 27.2831, longitude: -127.831))
let restaurantAnnotation = RestaurantAnnotation(restaurant: restaurant)
mapView.addAnnotation(restaurantAnnotation)
You implement the mapView(_:annotationView:calloutAccessoryControlTapped:) delegate method to be notified when the user taps on the callout button in annotations. In it, you can easily reference the object you passed to it earlier.
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if let annotation = view.annotation as? RestaurantAnnotation {
print(annotation.restaurant)
} else if let annotation = view.annotation as? SupermarketAnnotation {
print(annotation.supermarket)
}
}
And you can use that data to do whatever you want after that. In your case, pass it to a new screen.

Swift error: Cannot assign to property: 'coordinate' is immutable

The problem is when I write this line of code on which I try to convert the user location to another coordinate, I get this error: Cannot assign to property: 'coordinate' is immutable
The code is:
var location = sender.location(in: self.mapView)
let locCoord = self.mapView.convert(location, toCoordinateFrom: self.mapView)
self.mapView.userLocation.coordinate = locCoord *// Cannot assign to property:'coordinate' is immutable*
Can you help please?
As the error says the coordibate property is immutable
var coordinate: CLLocationCoordinate2D { get }
var userLocation: CLLocation { get }
you can't alter it in addition to userLocation , if you need a different location go directly with
If your want to assign new value then use var instead of let , let is used to non changed values

Calling a Core Data attribute through a relationship

I have two Entities as depicted in the image below:
Food and Restaurant.
I know the naming is a bit off for now, but basically, I'm building up a list of Food items. A user will add in a new entry with the name of the food and the name of the restaurant. I'm at the very early stages of development.
So in the AddViewController, and in the save method, I have:
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
foodEntry = FoodManagedObject(context: appDelegate.persistentContainer.viewContext)
foodEntry.nameOfFood = foodNameTextField.text
foodEntry.restaurantName?.nameOfRestaurant = restaurantNameTextField.text
With a variable declared:
var foodEntry:FoodManagedObject!
In the TimelineView, using NSFetchedResultsController, I'm fetching for the FoodManagedObject and able to display the name of the food in the label. However, the name of the restaurant doesn't display.
So, I'm fetching appropriately:
let fetchRequest: NSFetchRequest<FoodManagedObject> = FoodManagedObject.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "nameOfFood", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
let context = appDelegate.persistentContainer.viewContext
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
if let fetchedObjects = fetchedResultsController.fetchedObjects {
foods = fetchedObjects
}
} catch {
print(error)
}
}
and in the cellForRow:
cell.foodNameLabel.text = foods[indexPath.row].nameOfFood
cell.restaurantLabel.text = foods[indexPath.row].restaurantName?.nameOfRestaurant
I get no errors, but the name of the restaurant never displays.
Foods is:
var foods:[FoodManagedObject] = []
So I've tried adding in an attribute called theRestaurant into the Food Entity and that works, but calling through a relationship never seems to work.
Am I missing something obvious here?
You're creating relations between the objects, not of their values
It means, that you must assign already existing restaurant entity object or create the new one when you're saving the new food object.
You can't just assign object values without an initialisation of the restaurant object.
E.g.
foodEntry = FoodManagedObject(context: appDelegate.persistentContainer.viewContext)
foodEntry.nameOfFood = foodNameTextField.text
// Here you must to load existing Restaurant entity object from database or create the new one
let restaurant = RestaurantManagedObject(context: appDelegate.persistentContainer.viewContext)
restaurant.nameOfRestaurant = restaurantNameTextField.text
foodEntry.restaurantName = restaurant // Object instead of value
Or if you already have some list of restaurants, than just add the new food object to one of them

Core Data - Change NSManagedObject array into array of Strings using valueForKey -OSX

So iv using an NSTokenField to allow data entry, the TokenField will suggest thing when the user starts typing. I want it to suggest things that are already inside core data.
To do this i have this function being called when the cell moves to superview (This is all happening inside a custom table view cell)
var subjectInformation = [NSManagedObject]()
let appDel = NSApplication.sharedApplication().delegate as! AppDelegate
let context = appDel.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "SubjectInformation")
do {
let results = try context.executeFetchRequest(fetchRequest)
subjectInformation = results as! [NSManagedObject]
} catch {
}
this returns an array of NSManagedObjects, now i want for every object in managed object get get the valueForKey("subjectName") as insert it into a array of string so that i can return that inside this token field Function
func tokenField(tokenField: NSTokenField, completionsForSubstring substring: String, indexOfToken tokenIndex: Int, indexOfSelectedItem selectedIndex: UnsafeMutablePointer<Int>) -> [AnyObject]? {
return subjectInformation //this is where is should return an array eg; ["English","Maths","Science"]
How would i do this? Thanks :)
If you properly subclassed your NSManagedObject you can use expressive Swift style filters and maps. You would cast your results array to [SubjectInformation] and
let subjectList = subjectInformation.map { $0.subjectName }
Try this:
(subjectInformation as! NSArray).valueForKeyPath("#unionOfObjects.subjectName")
This should return an array of the subjectNames of all the subjectInformation items.

How do I change a switch in CoreData using Dynamic Tables (Swift)

I have an app that I am converting from objective-c to Swift and am also changing it to use dynamic (rather than static) tables. I can load the cells with entity rows but I have been unable to figure out how to reference the UISwitch value in the #IBAction function in order to save it to CoreData.
Can anyone point me to simple example of how to do this?
In storyboard, link the UISwitch in your prototype cell to the #IBAction handler in the view controller. In the handler, determine the core data object and manipulate as desired.
Assuming you have a fetched results controller (recommended):
#IBAction didFlipSwitch(sender: UISwitch) {
let point = sender.convertPoint(CGPointZero, toView:tableView)
let indexPath = tableView.indexPathForRowAtPoint(point)
let object = fetchedResultsController.objectAtIndexPath(indexPath) as! Thing
thing.flag = sender.on
}
Addition:
Pursuant to your question: If you are not using a fetched results controller (though this is not recommended), you are presumably using an Array like [Thing]. You would replace the one line with something like:
let object = dataArray[indexPath.row] // no need to cast to Thing
Second, if you need access to other values in the cell, you can get to the cell and its elements with
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomCell
let textToRetrieve = cell.textField?.text
However, this is not a good method, because you are getting data from your UI elements. Instead you should always store the data in your model, not in a table view row!
Thus, the proper text attribute of your Thing should already have been set by the UITextFieldDelegate implementation. (You can get the indexPath and thence the desired object in pretty much the same way as above, with convertPoint and indexPathForRowAtPoint.)
Consequently, when you flip the switch and retrieve the Core Data object as shown above, the text attribute be readily available (though you will probably not need it).
Ultimately I resolved this by using both cellForRowAtIndexPath to load the cells from my data model in my custom UITableViewController class and the following code in my custom UITableViewCell class. (Probably this is what Mundi was suggesting but if so I did not understand him.)
class CREWTableViewCell: UITableViewCell {
#IBOutlet weak var myTextView: UITextView!
#IBOutlet weak var mySwitch: UISwitch!
#IBAction func changedSwitch(sender: UISwitch) {
var newDescription = self.myTextView.text // value from cell
var newSwitchValue = self.mySwitch.on // value from cell
var fetchRequest = NSFetchRequest(entityName: "Switch")
var pred1 = NSPredicate(format: "(viewName = %#)",viewName)
var pred2 = NSPredicate(format: "(switchDescription = %#)",newDescription)
fetchRequest.predicate = NSCompoundPredicate(type: NSCompoundPredicateType.AndPredicateType, subpredicates: [pred1, pred2])
// Execute the fetch request
var error: NSError? = nil
if let fetchResults = context.executeFetchRequest(fetchRequest, error: &error) as? [Switch]
{
var recordCount = 0
recordCount = fetchResults.count
if recordCount == 1 {
var appConfig = fetchResults [0]
appConfig.switchValue = newSwitchValue
if !managedObjectContext!.save(nil) {
NSLog("Unresolved error ")
abort()
}
}
}
}