Why would MKMapView not add new annotations from Firestore data? - swift

I am using an MKMapView wrapped in UIVIewRepresentable under SwiftUI to display various locations on the map. The data is fetched from a Google Firestore collection and annotations added to the map view when the app loads - no problem there. Locations are also loaded into a lazy grid for display - also no problems.
The issue arises if a new location is added to the Firestore set no corresponding annotation(s) will be added to the map view. They will be aded to the lazy grid however.
Locations in Firestore can be deleted and the annotations on the map will be removed, data can be edited (like text values for display) and these will update on the map.
I'm not sure where to go from here to solve the issue ...
the updateUIView code > 'siteData' is a simple array of site structs and populated from Firestore.
func updateUIView(_ uiView: MKMapView, context: Context) {
if siteData.count != uiView.annotations.count {
uiView.removeAnnotations(uiView.annotations)
for points in siteData {
let annotation = SiteAnnotation(title: points.name, subtitle: points.shortDescription, site: points, coordinate: points.locationCoordinate)
uiView.addAnnotation(annotation)
}
}
uiView.showsUserLocation = true
}
}
What am I missing in this?

Came back to this this morning and realized that the uiView.annotations.count is including the pin for the user's location marked on the map. So when if siteData.count != uiView.annotations.count {, the siteData.count and uiView.annotations.count are actually equal.
Guess it's the little things that can really trip us up.

Related

WidgetKit - Intents - widget user setting lost after core data update

Working on an app that contains widget settings. The user can select different options like "alpha" or "beta" as shown in the image. The list data are provided by core data and can be modified in the main app. the entries are stored with an uuid so i can easy identifiy the list items.
the problem. let say i select "Alpha" in the widget's setting, the widget loads the settings from "Alpha". All good.
If i now change the entrie "Alpha" in the main app and do an update to core data, the widget pages looses the previeous selected state "Alpha".
It looks like, as soons as an core update update was performed, the settings screen loose the reference.
class IntentHandler: INExtension, SmallWidgetConfigurationIntentHandling, MediumWidgetConfigurationIntentHandling, LargeWidgetConfigurationIntentHandling {
let userData = UserData()
func provideFlapOptionsCollection(for intent: SmallWidgetConfigurationIntent, searchTerm: String?, with completion: #escaping (INObjectCollection<FlapObject>?, Error?) -> Void) {
let data = userData.getUserDataWithPredicate(_type: "small")
var symbols: [FlapObject] = []
for item in data {
let ob1 = FlapObject(identifier: item.id, display: item.title)
ob1.message = item.message
ob1.flapid = item.id
symbols.append(ob1)
}
let collection = INObjectCollection(items: symbols)
completion(collection, nil)
}
}
Is there a way to keep the widget settings persist even i perform an core data update on that entrie? i mean the identifier and title dont even change.
widget settings page 1 selected settings before core data update 2 setting after core data update - lost reference 3
Solved. Did a new database entry just for the widget list. If there is some changes in the db, that list is not updated, therefore the widget user-selection is not changed.

iOS Swift Mapkit annotations order

I need to keep an order for the annotations. I use custom annotationViews.
Each annotations are related to a point of interest, from 1 to n, so I need to open POI 1 when the map open the first time and then, when I click next or previous, I need to open POI 2 etc..
I keep an index to show in the detail callout the Point 1. I know that annotations are a Set, not an array.
Thanks
tour.poi?.forEach {
let coord = CLLocationCoordinate2DMake(CLLocationDegrees($0.lat), CLLocationDegrees($0.long))
let poiAnnotation = PoiAnnotation(coord: coord, poi: $0, index: index)
map.addAnnotation(poiAnnotation)
index = index + 1
}
My ugly solution :
let annotation = map.annotations.first { $0.subtitle == String(poiIndex) }
UIView.animate(withDuration: 0.2) {
self.map.setCenter(annotation!.coordinate, animated: false)
}completion: { (_) in
self.map.selectAnnotation(annotation!, animated: true)
}
I've added some animation to always center the pin before opening the callout.
You said:
I know that annotations are a Set, not an array.
That is incorrect.
The map view annotations property is an array, not a set. To quote the docs:
annotations
The complete list of annotations associated with the receiver.
var annotations: [MKAnnotation] { get }
Note the type of annotations: [MKAnnotation]. That's an array.
You should be able to keep an instance variable with the index of the annotation you have displayed most recently, and increment it when you display the map again.
If you're opening and closing a map view over and over with the same set of annotations you might want to keep your array of annotations rather than rebuilding it each time. For that matter you might want to hide the map view rather than closing it, and then simply show it again. Map views use a lot of network data to display.

Looping through entities which describe what action should be taken on screen after keypresses

Please forgive me if I don't describe this question too well, I am new to programming MacOS apps using Swift. I know the way I'm going about this is probably wrong and I just need someone to tell me the right way.
My main app screen
I have a Core Data application that stores an ordered list of entities called Items. These Items are intended to describe a single step in an activity that describes what should happen on screen. If you know the Mac application QLab each Item is like a single cue in QLab.
I have created an Activity class that is designed to read through each Item to determine the Item type and it's related information. Once the Item type has been determined the Activity class needs to present a View with information related to that particular Item and then wait until the user presses the right arrow key to then proceed to the next Item in the Core Data store where the process repeats until all Items have been read. Each time a new Item is read in the loop, the information on the screen should change after the user presses the right arrow each time.
The problem is that I don't know exactly how the best way of going about this should be programatically speaking. I have the code that retrieves the array of Items as an NSFetchRequest:
let moc = (NSApplication.shared.mainWindow?.contentViewController?.representedObject as! NSPersistentDocument).managedObjectContext!
let fetchRequest : NSFetchRequest = Item.fetchRequest()
do {
let items = try moc.fetch(fetchRequest)
print("Found " + String(items.count) + " items to use in the activity.")
for item in items {
print(item.itemType)
// How do I pause this loop for a user keypress after using data from this Item to display?
}
} catch {
print("Error retrieving Items")
}
I can retrieve the keydown event using NSEvent.addLocalMonitorForEvents(matching: .keyDown) and I'm also able to create View Controllers to display the information on a second screen. I just don't know how I should create the 'main loop', so to speak, so that information is displayed and then the app waits until the user presses a key to proceed...
I can share my project code if more information is needed and many thanks to anyone who can enlighten me... :)
You could try using a NSPageController. In your NSPageController you add a ContainerView which will display the ViewControllers that display information for each item. Each ViewController will need a storyboard identifier, e.g. ViewControllerItem1.
Your NSPageController class must conform to the NSPageControllerDelegate protocol and contains an array of ViewControllers to display.
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
arrangedObjects = ["ViewControllerItem1", "ViewControllerItem2", "...","ViewControllerItemN" ]
}
Note about arrangedObjects from the NSPageController documentation: An array containing the objects displayed in the page controller’s view.
Then you implement NSPageControllers viewControllerForIdentifier to return the ViewController that you currently want to display in the ContainerView.
func pageController(_ pageController: NSPageController, viewControllerForIdentifier identifier: String) -> NSViewController {
switch identifier {
case "ViewControllerItem1":
return mainStoryboard().instantiateController(withIdentifier:"ViewControllerItem1") as? ViewControllerItem1
case "...":
default:
}
}
In your action handler for the key down event you implement.
self.navigateForward(sender) or self.navigateBack(sender)
I also implemented this method but I don't remember whether it was required.
func pageControllerDidEndLiveTransition(_ pageController: NSPageController) {
self.completeTransition()
}

How to setup printing in cocoa, swift?

I have made printing functionality for custom NSView of NSPopover by the assigning the following action to button for this NSView in mainController:
#IBOutlet var plasmidMapIBOutlet: PlasmidMapView!
#IBAction func actionPrintfMap(sender: AnyObject)
{
plasmidMapIBOutlet.print(sender)
}
It is working, but the print window has no option for Paper Size and Orientation, see screenshot below.
What should I do to get these options in the print window?
And, how to make the NSView fitting to the printable area? Now it is not fitting.
I have figured out some moments, but not completely. So, I can setup the printing by the following code
#IBAction func actionPrintMap(sender: AnyObject)
{
let printInfo = NSPrintInfo.sharedPrintInfo()
let operation: NSPrintOperation = NSPrintOperation(view: plasmidMapIBOutlet, printInfo: printInfo)
operation.printPanel.options = NSPrintPanelOptions.ShowsPaperSize
operation.printPanel.options = NSPrintPanelOptions.ShowsOrientation
operation.runOperation()
//plasmidMapIBOutlet.print(sender)
}
But, I still have problem. From the code above I can get only orientation (the last, ShowsOrientation), but not both PaperSize and Orientation. How can I manage both ShowsPaperSize and ShowsOrientation?
Finally I have found the answer which is simple to write but it is not really obvious from apple documentation.
operation.printPanel.options.insert(NSPrintPanelOptions.showsPaperSize)
operation.printPanel.options.insert(NSPrintPanelOptions.showsOrientation)
The problem in the code originally posted is that options is being assigned twice, so the first value assigned, ShowsPaperSize is overwritten by the value ShowsOrientation. That's why you only see the ShowsOrientation option in the dialog.
By using multiple insert operations, you are adding options rather than overwriting each time. You can also do it this way which I think reads better:
operation.printPanel.options.insert([.showsPaperSize, .showsOrientation])
And finally, it also works to "set" the options, and by supplying the existing options as the first array value, you achieve the affect of appending:
operation.printPanel.options = [
operation.printPanel.options,
.showsPaperSize,
.showsOrientation
]
(The first array element operation.printPanel.options means that the old options are supplied in the list of new options.)

How to prevent overlapping pushpins at the same geolocation with Bing Maps?

If you have 2 pushpins on 'London' at the same geolocation, is there anything in the API to move them apart so they are both visible?
I can only find documentation on their old map points API which had PreventIconCollisions, this is what I want but can't see any reference to this in the new API.
I am using the JavaScript API.
So if I understand correctly, you have similar information on the same location, it this correct?
In order to display both information, you will have two options:
Merge information in the textbox using an appropriate way to present the information inside this ui element (using your own tabbed infobox for example)
Decluster the point manually when you're at a certain level of zoom
There is no default property to set this and it would really messy to do this on many pushpins, but in the main idea, you would have to: detect viewchangeend event, if you're at a certain level of zoom (or higher zoom level) then you're declustering them (I call it decluter nearby pushpins).
// Bind pushpin mouseover.
Microsoft.Maps.Events.addHandler(pin, 'mouseover', function (e) {
var currentPin = e.target;
currentPin.setOptions({ visible: false });
var currentLocation = currentPin.getLocation().clone();
var currentPoint = bmGlobals.geo.map.tryLocationToPixel(currentLocation);
if (currentPin.associatedCluster.length == 2) {
// Display the first pushpin
var pinA = createPin(currentPin.associatedCluster[0]);
var locA = bmGlobals.geo.map.tryPixelToLocation(new Microsoft.Maps.Point(currentPoint.x - pinA.getWidth(), currentPoint.y));
pinA.setLocation(locA);
bmGlobals.geo.layerClusteredPin.push(pinA);
// Display the second pushpin
var pinB = createPin(currentPin.associatedCluster[1]);
var locB = bmGlobals.geo.map.tryPixelToLocation(new Microsoft.Maps.Point(currentPoint.x + pinB.getWidth(), currentPoint.y));
pinB.setLocation(locB);
bmGlobals.geo.layerClusteredPin.push(pinB);
}
});
I will try to write a bing maps module about this, but in the fact, you'll have to get your clustered pushpins (or your own pushpin that has two associated data object) and then you will have to set their position based on the rendering on the client side.
I know this question is really old, but if someone is looking for something similar (clustering the pins) here is a good start: http://rtsinani.github.io/PinClusterer/