iOS MKMapViewDelegate - Getting UserLocation the second time - iphone

I create a MKMapView and associate a MKMapViewDelegate with it.
The MKMapViewDelegate gets notified correctly that DidUpdateUserLocation and other life cycle events occurred.
When I create another MKMapView later on in the app, the MKMapViewDelegate doesn't receive any notifications about MapLoaded or the like. Just the constructor is fired.
How do I get a new map instance the 2nd time to update the MKMapViewDelegate with lifecycle events?

Using .NET for iPhones (MonoTouch)
In the Map Delegate handle this event ONCE only, it gets fired a lot. This way you never need to worry about how the map caches.
public override void RegionChanged (MKMapView mapView, bool animated)
{
if (!hasSetupMapBoolean)
{
localCopymap = mapView;
DoYourSetupMap ();
}
}

Related

Hide Google Map info window when map is moved with Swift

So with my app, I'm displaying a custom info window on a GMS Marker. This info window takes up the top half of the screen and I don't want it moving with the Marker. So I want to hide the info window as soon as the map has moved. How can I do this?
You could make your UIViewController conform to GMSMapViewDelegate and then implement one of the methods listed here: https://developers.google.com/maps/documentation/ios-sdk/reference/protocol_g_m_s_map_view_delegate-p
I guess the first one (mapView:willMove:) would work for you. I would use a print statement (or breakpoint) to check, whether the method is triggered as expected. To hide the current info window, you should be able to set mapView.selectedMarker = nil.
To sum up, your code might look like this:
import UIKit
import GoogleMaps
class ViewController: UIViewController {
//declare your map, etc.
override func viewDidLoad() {
super.viewDidLoad()
// setup your map
}
}
extension ViewController: GMSMapViewDelegate {
func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
if gesture { //The map was moved by the user
//mapView.selectedMarker = nil
}
}
}

How to use applicationWillResignActive to call a function of a view controller

I am trying to call my menu view inside my view controller when the home button is pressed, or for that matter when the user gets a phone call, etc...
My goal is to call the function: toggleMenu() that is inside the QuestionViewController. Here's the code:
class QuestionViewController: UIViewController, MFMailComposeViewControllerDelegate {
///////////
func toggleMenu() {
// Show or hide menu
if menuStackView.isHidden == true {
// Show the menu
print("Showing Menu")
// Update labels
questionNumberMenuLabel.text = questionNumberLabel.text
endTimer()
menuStackView.isHidden = false
animateInMenu()
} else {
// Hide the menu
animateOutMenu()
}
}
I do believe I should utilize the following method found in the AppDelegate.swift file:
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
If I'm on the right track I need help calling the toggleMenu() function of the QuestionViewController class in this method. How do I do that?
Thanks so much!
Use the NotificationCenter and listen for UIApplicationWillResignActiveNotification. The system broadcasts that notification as well as calling your app delegate's applicationWillResignActive method (assuming you have one.)
Listen for notifications (using the method addObserver(forName:object:queue:using:) in your viewDidLoad. If you don't need to support iOS versions < 9.0, you don't need to worry about calling removeObserver - the system takes care of it for you.

watchOS 3.0 detect crown rotation in SpriteKit

So as of watchOS 3.0 you are now able to get the rotation of the digital crown. I managed to use the crownDidRotate function in an InterfaceController.
But I can't get the rotation of the crown from inside a SKScene Class.
Can anybody help me with this I'm pretty lost right now?
Thanks.
To get those crownDidRotate calls in your interface controller, you had to adopt the WKCrownDelegate protocol in your interface controller, and set your interface controller as the delegate of its crownSequencer.
To get crownDidRotate calls in some other class, adopt the WKCrownDelegate protocol in that class, and set an instance of that class as the delegate of your interface controller's crownSequencer.
Presumably you already have some code like this to set up your SpriteKit scene:
class InterfaceController: WKInterfaceController {
#IBOutlet var spriteGizmo: WKInterfaceSKScene!
override func awake(withContext context: AnyObject?) {
super.awake(withContext: context)
let scene = MyScene(fileNamed: "MyScene")
spriteGizmo.presentScene(MyScene(fileNamed: "MyScene"))
}
}
If you've declared WKCrownDelegate conformance in your MyScene class, just add a line to set it as the delegate of the interface controller's crown sequencer:
let scene = MyScene(fileNamed: "MyScene")
spriteGizmo.presentScene(MyScene(fileNamed: "MyScene"))
crownSequencer.delegate = scene
(Alternatively, you may set your WKInterfaceSKScene's scene in the Storyboard. In that case, you can still reference the WKInterfaceSKScene from your interface controller with an IBOutlet. Then in awake(withContext:), you can access the scene through that outlet and set it as the crown delegate.)
In watchOS 3 just any object object can get digital crown events by setting them as a delegate:
let crownSequencer = WKExtension.shared().rootInterfaceController!.crownSequencer
crownSequencer.delegate = self
crownSequencer.focus()
Then read back the value by implementing:
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double)
It is important to call the focus(), especially for controllers whose UI fits the screen and do not need actual scrolling.

update viewcontroller from appdelegate - best practice?

I am playing around with iBeacons implementing CoreLocation methods inside AppDelegate.swift (methods are implemented in AppDelegate to ensure App Background Capabilities)
In a SingleView application that comes with "ViewController.swift", what would be the best practice to pass data from AppDelegate to ViewController to update Views, say UIImages, UILabels or UITableViews ?
I have successfully implemented two different approaches:
1) Delegation, by firing Viewcontroller Delegation methods inside AppDelegate.swift:
protocol BeaconLocationDelegate { func minorBeaconChanged(minorValue:NSNumber) }
var locationDelegate: BeaconLocationDelegate
locationDelegate?.minorBeaconChanged(nearestBeacon.minor)
ViewController.swift:
in viewDidLoad method:
(UIApplication.sharedApplication().delegate as AppDelegate).locationDelegate = self
(I find this to look rather ugly - any better way to declare this property as delegate?)
protocol implementation:
func minorBeaconChanged(minorValue: NSNumber) {
// do fancy stuff here
}
2) By creating a reference to ViewController inside AppDelegate:
let viewController:ViewController = window!.rootViewController as ViewController
viewController.doSomethingFancy()
Both approaches work fine for me, but I think the first approach via delegation is more elegant and the better choice once you have multiple ViewControllers.
Any recommendations?
I think the second solution is much easier. It also ensures the view controller does not go out of memory (is "released") and the app delegate does not try to contact an inexistent object.
Also, I do not understand the delegate argument in the case of more than one controllers. In that case, you would have to have multiple delegates, really not an ideal situation. Or you would have to change the delegate to different view controllers from the outside - also not very elegant and quite risky.
For the multiple view controller scenario I recommend notifications via NSNotificationCenter. This is a lose coupling that ensures only those objects that need to react receive the message. View controllers should start listening to the notifications upon creation and deregister from the notification center when they disappear.
How about this?
In AppDelegate
var minorBeaconChanged: NSNumber -> () = { minor in }
And call this method when location is updated.
In ViewController's viewWillAppear
( UIApplication.sharedApplication().delegate as? AppDelegate )!.minorBeaconChanged = { minor in
// Do with minor
}
EDIT: 27/8/2015
If you wanna have multiple observers.
In AppDelegate
var minorBeaconObservers: [ NSNumber -> () ] = []
And call all the item in minorBeaconObservers with 'minor'
In ViewController's viewWillAppear
( UIApplication.sharedApplication().delegate as? AppDelegate )!.minorBeaconObservers.append(
{ minor in
// Do with minor
}
)

How to define the order of overlapping MKAnnotationViews?

I have several MKAnnotations (and their corresponding views) in my map, and it sometimes gets really crowded. Now, the annotations in my app come in two flavors: some are bound to stay where they are, while others will move as time goes on. I'd prefer to have the more stable ones visually in the background and the moving ones to always pass in front of them.
One would think, perhaps, that the annotations most recently added to the map would end up to the front (or alternatively at the very back, at least) but this just doesn't seem to be the rule. As far as I can tell, I create and add ALL the non-moving annotations first, and then add some newly-instantiated moving annotations, but many of them (although not all!) end up drawn under the perpetually stock-still ones.
Interestingly, when time goes by, and yet new moving annotations are created, they tend to gravitate more to the top than the first ones - even if all moving annotation objects were created only after the nonmoving parts were already added to the map.
Does anyone know a trick to alter this strange natural order of the annotation views on the map? I tried to search the Map Kit API, but it doesn't seem to speak of such a thing.
Ok, so for solution use method from MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
In this method you should rearrange AnnotationView after it was added to mapKit View.
So, code may looks like this:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
for (MKAnnotationView * annView in views) {
TopBottomAnnotation * ann = (TopBottomAnnotation *) [annView annotation];
if ([ann top]) {
[[annView superview] bringSubviewToFront:annView];
} else {
[[annView superview] sendSubviewToBack:annView];
}
}
}
This works for me.
Under iOS 11 the implementation of displayPriority broke all the solutions which use bringSubviewToFront or zPosition.
If you override the annotation view's CALayer, you can wrestle control of zPosition back from the OS.
class AnnotationView: MKAnnotationView {
/// Override the layer factory for this class to return a custom CALayer class
override class var layerClass: AnyClass {
return ZPositionableLayer.self
}
/// convenience accessor for setting zPosition
var stickyZPosition: CGFloat {
get {
return (self.layer as! ZPositionableLayer).stickyZPosition
}
set {
(self.layer as! ZPositionableLayer).stickyZPosition = newValue
}
}
/// force the pin to the front of the z-ordering in the map view
func bringViewToFront() {
superview?.bringSubviewToFront(toFront: self)
stickyZPosition = CGFloat(1)
}
/// force the pin to the back of the z-ordering in the map view
func setViewToDefaultZOrder() {
stickyZPosition = CGFloat(0)
}
}
/// iOS 11 automagically manages the CALayer zPosition, which breaks manual z-ordering.
/// This subclass just throws away any values which the OS sets for zPosition, and provides
/// a specialized accessor for setting the zPosition
private class ZPositionableLayer: CALayer {
/// no-op accessor for setting the zPosition
override var zPosition: CGFloat {
get {
return super.zPosition
}
set {
// do nothing
}
}
/// specialized accessor for setting the zPosition
var stickyZPosition: CGFloat {
get {
return super.zPosition
}
set {
super.zPosition = newValue
}
}
}
Try to setup annotation view layer's zPosition (annotationView.layer.zPosition) in:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views;
Swift 3:
I get pin locations from API and I was having similar issues, the pins that had to be on top weren't. I was able to solve it like this.
var count = 0 // just so we don't get the same index in bottom pins
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
for view in views {
view.layer.zPosition = CGFloat(count)
}
count += 1
if count > 500 {
count = 250 // just so we don't end up with 999999999999+ as a value for count, plus I have at least 30 pins that show at the same time and need to have lower Z-Index values than the top pins.
}
}
Hope this helps
In the delegate function, you can select the pin to force it on top:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?` {
...
if my annotation is the special one {
annotationView.isSelected = true
}
...
}
I'm finding that this reordering the annotation views causes the callout that pops up when one is clicked to no longer be on top of all the annotations. I've even tried refining it so that instead of bringSubviewToFront and sendSubviewToBack, I use insertSubview:aboveSubview and insertSubview:belowSubview: where the second argument is the first annotationView in the list. This would seem to cause much less front to back scattering, but the call outs still pop up under some annotations.
I really needed to do this, and none of the (current) answers seemed to provide a reliable implementation. They sort of worked, but panning the map, selecting annotations, or zooming in could mess up the order again.
The final, well behaved solution wasn't so trivial, so I'll just outline the steps I took here. The annotation ordering that MKMapView uses doesn't respect the added order, or even the order of an overriden annotations property. So...
Steps
• Create a CADisplayLink
• Every frame, reorder annotations using both the layer zPosition, and the view's ordering in the superview's subviews array.
• If the view is selected, promote it to the front in your ordering scheme
• Tapping on annotations still respects internal MKMapView ordering, despite the already made changes. To counter this, add an MKMapViewDelegate
• In the delegate object's mapView:didSelect: method, check if the selected annotation is what you'd like it to be
• You can figure out the correct/prioritised annotation by running hit tests on the annotations yourself, with your own ordering taken into account
• If the selected annotation is correct, great. If not, manually select the correct annotation using selectAnnotation:animated:
And there you have it. The above method seems to work well, and the performance hit from running this each frame isn't too bad. You could also look at switching to MapBox, which I believe supports annotation ordering, but this isn't always an option for various reasons.