Updating the centre point & zoom of MKMapView (iPhone) - objective-c

I have been trying to centre the map view on the users location. This should also update as and when a change in location is registered. The issue I'm having is upon loading the app I can't get hold of the current latitude and longitude to apply the centring & zooming.
These are the key bits of code I have so far...
- (void)viewDidLoad
{
self.locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
[self updateMapZoomLocation:locationManager.location];
[super viewDidLoad];
}
The idea being that it should create an instance of the location manager, then start updating the location. Finally it should run the function I wrote to update the map view accordingly...
- (void)updateMapZoomLocation:(CLLocation *)newLocation
{
MKCoordinateRegion region;
region.center.latitude = newLocation.coordinate.latitude;
region.center.longitude = newLocation.coordinate.longitude;
region.span.latitudeDelta = 0.1;
region.span.longitudeDelta = 0.1;
[map setRegion:region animated:YES];
}
However this doesn't seem to happen. The app builds and runs ok, but all that gets displayed is a black screen - it's as if the coordinates don't exist?!
I also have a delegate method that deals with any updates by calling the function above...
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
[self updateMapZoomLocation:newLocation];
}
I have tried looking at several of the other similar questions that have been asked & answered previously however I still can't seem to find the solution I'm after.
Any help on this would be really appreciated; I've spent hours and hours trawling the internet in search of help and solutions.
ps. 'map' is the mapView.

You want to use the MKMapViewDelegate callback so you only zoom in once the location is actually known:
Here's some swift code to handle that:
var alreadyZoomedIn = false
func mapView(mapView: MKMapView!, didUpdateUserLocation userLocation: MKUserLocation!) {
if(!alreadyZoomedIn) {
self.zoomInOnCurrentLocation()
alreadyZoomedIn = true
}
}
func zoomInOnCurrentLocation() {
var userCoordinate = mapView?.userLocation.coordinate
var longitudeDeltaDegrees : CLLocationDegrees = 0.03
var latitudeDeltaDegrees : CLLocationDegrees = 0.03
var userSpan = MKCoordinateSpanMake(latitudeDeltaDegrees, longitudeDeltaDegrees)
var userRegion = MKCoordinateRegionMake(userCoordinate!, userSpan)
mapView?.setRegion(userRegion, animated: true)
}

You shouldn't call updateMapZoomLocation during your viewDidLoad function because the location manager has not yet go a location. If/when it does it'll call the delegate function when it's ready. Until then your map won't know where to center. You could try zooming as far out as it goes, or remembering where it was last looking before the app was shutdown.

Related

Accelerometer cocos2d spritebuiilder cant get it to work

I am trying to use the accelerometer in my spritebuidler cocos2d project.I tried to follow https://www.makeschool.com/gamernews/371/accelerometer-with-cocos2d-30-and-ios-7 which seems pretty straight forward but it doesnt seem to work for me. I have tried downloading their git example but the project wont build.
my code is below
#import "GameOverScene.h"
#import <CoreMotion/CoreMotion.h>
#implementation GameOverScene{
CCButton *_restartButton;
CMMotionManager *_motionManager;
CCLabelTTF *_label;
}
- (id)init
{
if (self = [super init])
{
_label= [CCLabelTTF labelWithString:#"X" fontName:#"Arial" fontSize:48];
[self addChild:_label];
_motionManager = [[CMMotionManager alloc] init];
}
return self;
}
-(void)restartButtonClicked {
CCScene *scene = [CCBReader loadAsScene:#"MainScene"];
[[CCDirector sharedDirector] replaceScene:scene withTransition:[CCTransition transitionPushWithDirection:CCTransitionDirectionLeft duration:0.25f]];
}
- (void)onEnter
{
[super onEnter];
_label.position = ccp(self.contentSize.width/2, self.contentSize.height/2);
[_motionManager startAccelerometerUpdates];
}
- (void)onExit
{
[super onExit];
[_motionManager stopAccelerometerUpdates];
}
- (void)update:(CCTime)delta {
CMAccelerometerData *accelerometerData = _motionManager.accelerometerData;
CMAcceleration acceleration = accelerometerData.acceleration;
CGFloat newXPosition = _label.position.x + acceleration.y * 1000 * delta;
newXPosition = clampf(newXPosition, 0, self.contentSize.width);
_label.position = CGPointMake(newXPosition, _label.position.y);
}
#end
What happens is i see half an x on the bottom left corner when its meant to be in the middle of the screen.i tried changing the spawn position to
_label.position = ccp(500, 300);
and that changes the y axis but it is still on 0 on the x axis when i run the project
and tilting my phone does nothing.
What am i missing?
Author of the tutorial here. Sorry for the hickup, I have updated the repository with the solution on GitHub and fixed the Cocos2D related issue, we will migrate this tutorial to Cocos2D 3.3 soon. If you download the latest version you should see everything working as expected.
Regarding your specific problem, could you inspect/print the contentSize of the scene to which you are adding the label?

MKMapView Overlay (image animation)

I'm trying to animate a radar image on an MKMapView. I have separate images for different times, and I have successfully set an image as an overlay on top of the MKMapView. My problem is when I try to show these different images in a sequence one after each other. I tried a method of adding and removing overlays, but the system does not handle it well at all, with flickering of overlays, and some overlays not being removed, and overall, its not worth it.
Could anyone help me find a way to show multiple images (like an animated gif) on top of a MapView in a way that is smooth and fast?
Here's my code to overlay the image:
- (id)initWithImageData:(NSData*)imageData withLowerLeftCoordinate:(CLLocationCoordinate2D)lowerLeftCoordinate withUpperRightCoordinate:(CLLocationCoordinate2D)upperRightCoordinate {
self.radarData = imageData;
MKMapPoint lowerLeft = MKMapPointForCoordinate(lowerLeftCoordinate);
MKMapPoint upperRight = MKMapPointForCoordinate(upperRightCoordinate);
mapRect = MKMapRectMake(lowerLeft.x, upperRight.y, upperRight.x - lowerLeft.x, lowerLeft.y - upperRight.y);
return self;
}
- (CLLocationCoordinate2D)coordinate
{
return MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMidX(mapRect), MKMapRectGetMidY(mapRect)));
}
- (MKMapRect)boundingMapRect
{
return mapRect;
}
and here's how I'm setting it up on the MapView:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)ctx
{
MapOverlay *mapOverlay = (MapOverlay *)self.overlay;
image = [[UIImage alloc] initWithData:mapOverlay.radarData];
MKMapRect theMapRect = [self.overlay boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
#try {
UIGraphicsBeginImageContext(image.size);
UIGraphicsPushContext(ctx);
[image drawInRect:theRect blendMode:kCGBlendModeNormal alpha:0.5];
UIGraphicsPopContext();
UIGraphicsEndImageContext();
}
#catch (NSException *exception) {
NSLog(#"Caught an exception while drawing radar on map - %#",[exception description]);
}
#finally {
}
}
and here's where I'm adding the overlay:
- (void)mapRadar {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.mapOverlay = [[MapOverlay alloc] initWithImageData:appDelegate.image withLowerLeftCoordinate:CLLocationCoordinate2DMake(appDelegate.south, appDelegate.west) withUpperRightCoordinate:CLLocationCoordinate2DMake(appDelegate.north, appDelegate.east)];
[self.mapView addOverlay:self.mapOverlay];
[self.mapView setNeedsDisplay];
self.mapView.showsUserLocation = YES;
MKMapPoint lowerLeft2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(appDelegate.south2, appDelegate.west2) );
MKMapPoint upperRight2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(appDelegate.north2, appDelegate.east2));
MKMapRect localMapRect = MKMapRectMake(lowerLeft2.x, upperRight2.y, upperRight2.x - lowerLeft2.x, lowerLeft2.y - upperRight2.y);
[self.mapView setVisibleMapRect:localMapRect animated:YES];
}
#pragma Mark - MKOverlayDelgateMethods
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay{
if([overlay isKindOfClass:[MapOverlay class]]) {
MapOverlay *mapOverlay = overlay;
self.mapOverlayView = [[MapOverlayView alloc] initWithOverlay:mapOverlay];
}
return self.mapOverlayView;
}
Does anyone have an idea or a solution where I can show a sequence of images on an MKMapView smoothly? At this point, I'm desperate for solutions and can't find it anywhere else, so anything would help me, thanks!
Using Lefteris' suggestion, you can just use UIImageView with animationImages. The animation should be smooth provided your overlay images are properly sized for the screen.
To pass touches through to the map view below, you can create a new UIView subclass with a (weak) reference to the map view. Override the touch event methods (touchesBegan:withEvent:, touchesMoved:withEvent:, etc) to forward to the map view, like so:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.mapView touchesBegan:touches withEvent:event];
}
This way the user can still interact with the view below.
You may run into problems when the user tries to zoom, as it will be difficult if not impossible to scale all your animation frames in real-time. You should look into using CATiledLayer instead.

setRegion:animated: does not make region change

I'm trying to get user location and display it in mapView with following code in iOS6:
- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
if (locations.count>0) {
for (CLLocation *location in locations) {
[locationList addObject:location];
if (location != nil) {
// Zoom to the current user location.
MKCoordinateRegion userLocation =[mapView regionThatFits: MKCoordinateRegionMakeWithDistance(location.coordinate, 150.0, 150.0)];
[mapView setRegion:userLocation animated:YES];
NSLog(#"%f,%f", mapView.centerCoordinate.latitude,mapView.centerCoordinate.longitude);
NSLog(#"%f,%f", mapView.region.span.latitudeDelta,mapView.region.span.longitudeDelta);
}
}
}
}
but after location updated,[mapView setRegion:userLocation animated:YES] does not make any change to region of mapView, but the user location can be display as a blue dot. the NSLog prints like this:
nan,nan
0.000000,360.000000
Which means nothing changed. I checked most questions related map, they just call this method and get location display in the center of map.
I want to know why how can I put user location to the map center.
You could just turn on the MKMapView tracking mode
[mapView setTrackingMode:MKMapViewTrackingModeFollowsUser];
You can try this:
mMapView.showsUserLocation = YES;

UIWebView without Copy/Paste when displaying PDF files

I have tried to disable Copy/Paste in UIWebView by using a category and overriding canPerformAction and returning NO for copy, cut and paste selectors.
It worked as expected when I loaded a webpage or all other document formats (e.g. docx, pptx, rtf, txt) but not when I loaded a PDF document into the UIWebView.
It seems like there is some different mechanism that handles PDF documents in UIWebView which handles/responds to Copy selector, and therefore I can not block it.
I also tried to disable user interaction for all the subviews of the UIWebView's UIScrollView, which worked fine for other document formats except PDF.
Can anyone help figuring out how to disable Copy in UIWebView for PDF documents as well?
OK, so I've been experiencing the same problem myself and seem to find a solution, even if it's partial.
What I do is use a UILongPressGestureRecognizer to disable long press gestures that can lead to copy/paste.
The code:
UILongPressGestureRecognizer* longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)]; // allocating the UILongPressGestureRecognizer
longPress.allowableMovement=100; // Making sure the allowable movement isn't too narrow
longPress.minimumPressDuration=0.3; // This is important - the duration must be long enough to allow taps but not longer than the period in which the scroll view opens the magnifying glass
longPress.delegate=self; // initialization stuff
longPress.delaysTouchesBegan=YES;
longPress.delaysTouchesEnded=YES;
longPress.cancelsTouchesInView=YES; // That's when we tell the gesture recognizer to block the gestures we want
[webView addGestureRecognizer:longPress]; // Add the gesture recognizer to the view and scroll view then release
[[webView scrollView] addGestureRecognizer:longPress];
[longPress release];
This solution worked for me:
METHOD 1 - Detect Custom Long Presses
A) Create a subclass of UILongPressGestureRecogniser.
B) Include the canBePreventedByGestureRecognizer: method in your subclass, like this:
Header:
#import <UIKit/UIKit.h>
#interface CustomLongPress : UILongPressGestureRecognizer
#end
Implementation:
#import "CustomLongPress.h"
#implementation CustomLongPress
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer*)preventedGestureRecognizer {
return NO;
}
#end
That's the only code you need in the subclass.
C) Open up the view containing your uiwebview/pdf reader. Include your subclass: #import "CustomLongPress.h" and then add the custom UILongPressGestureRecogniser to your UIWebView, like this:
- (void)viewDidLoad
{
[super viewDidLoad];
//Load UIWebView etc
//Add your custom gesture recogniser
CustomLongPress * longPress = [[CustomLongPress alloc] initWithTarget:self action:#selector(longPressDetected)];
[pdfWebView addGestureRecognizer:longPress];
}
D) Detect the long press and switch your UIWebView's userInteraction Off then back On:
-(void)longPressDetected {
NSLog(#"long press detected");
[pdfWebView setUserInteractionEnabled:NO];
[pdfWebView setUserInteractionEnabled:YES];
}
Apparently the reason this works is because the UIWebView captures long presses with its own gesture recognisers, to the exclusion of any additional gesture recongisers you've added. But subclassing your gesture recognisers and preventing their exclusion by returning "NO" to the canBePreventedByGestureRecognizer: method overrides the default behaviour.
Once you can detect the long presses on PDFs, switching the userInteraction Off then On again prevents the UIWebView from actioning its default behaviour, i.e. launching a "Copy/Define" UIMenu or, if long pressing over a link, launching a pop-up actionsheet with "Copy" and "Open" actions.
METHOD 2 - Catch UIMenu NSNotification
Alternatively, if you just want to block the "Copy/Define" UIMenu, (but not affect long presses), you can add this line (listening for UIMenuControllerDidShowMenuNotification) to your ViewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(menuShown) name:UIMenuControllerDidShowMenuNotification object:nil];
and then add this method, using the same userInteraction Off/On method as above:
-(void)menuShown {
NSLog(#"menu shown");
[pdfWebView setUserInteractionEnabled:NO];
[pdfWebView setUserInteractionEnabled:YES];
}
First method taken from: https://devforums.apple.com/thread/72521?start=25&tstart=0, and second method from somewhere on Stack, sorry forgotten where. Please include if you know.
Great answer Zubaba. I’m using a webView to display colored and bolded text and I had the same problem. I put your solution into a method and call it just after I initialize the webView. I don’t seem to need the delegate.
self.textView = [[UIWebView alloc] initWithFrame:textFrame];
[self longPress:self.textView];
- (void)longPress:(UIView *)webView {
UILongPressGestureRecognizer* longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress)]; // allocating the UILongPressGestureRecognizer
longPress.allowableMovement=100; // Making sure the allowable movement isn't too narrow
longPress.minimumPressDuration=0.3; // This is important - the duration must be long enough to allow taps but not longer than the period in which the scroll view opens the magnifying glass
longPress.delaysTouchesBegan=YES;
longPress.delaysTouchesEnded=YES;
longPress.cancelsTouchesInView=YES; // That's when we tell the gesture recognizer to block the gestures we want
[webView addGestureRecognizer:longPress]; // Add the gesture recognizer to the view and scroll view then release
[webView addGestureRecognizer:longPress];
}
- (void)handleLongPress {
}
In case someone needs Zubaba's answer in Swift 3;
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
longPress.allowableMovement = 100
longPress.minimumPressDuration = 0.3
longPress.delegate = self
longPress.delaysTouchesBegan = true
longPress.delaysTouchesEnded = true
longPress.cancelsTouchesInView = true
yourWebView.addGestureRecognizer(longPress)
yourWebView.scrollView.addGestureRecognizer(longPress)
func handleLongPress() {
// Show some alert to inform user or do nothing.
}
Here is a modification to Zubaba's answer in Swift 3 that ended up working for me to eliminate warning. I changed assignment longPress.delegate = self to longPress.delegate = self as? UIGestureRecognizerDelegate.
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
longPress.allowableMovement = 100
longPress.minimumPressDuration = 0.3
longPress.delegate = self as? UIGestureRecognizerDelegate
longPress.delaysTouchesBegan = true
longPress.delaysTouchesEnded = true
longPress.cancelsTouchesInView = true
webView.addGestureRecognizer(longPress)
webView.scrollView.addGestureRecognizer(longPress)
webView.loadRequest(request)
The UILongPressGestureRecognizer is located in the UIPDFPageView. To get access to this view look at the view hierarchy in the Debug menu, currently you can access this view like so once you load the pdf to the web view:
let pdfPageView = myWebView.scrollview?.subviews[0]?.subviews[0]
Then to remove the Long Press use this method while passing in the pdfPageView:
func removeLongPressFromView(view: UIView){
if let gestures = view.gestureRecognizers{
for gesture in gestures{
if gesture is UILongPressGestureRecognzier{
view.removeGestureRecognizer(gesture)
}
}
}
}
Looking for a Xamarin.iOS solution.
var longPressGestureRecognizer = new CustomLongPressGestureRecognizer ((UILongPressGestureRecognizer obj) =>
{
Console.WriteLine ("CustomLongPressGestureRecognizer action");
});
webView.AddGestureRecognizer (longPressGestureRecognizer);
The approach given by Zubaba might look like this:
public class ZubabaLongPressGestureRecognizer : UIKit.UILongPressGestureRecognizer
{
public ZubabaLongPressGestureRecognizer (Action<UILongPressGestureRecognizer> action)
: base (action)
{
AllowableMovement = 100;
MinimumPressDuration = 0.3;
DelaysTouchesBegan = true;
DelaysTouchesEnded = true;
CancelsTouchesInView = true;
}
}
The open/copy/cancel menu still shows the first time a link is long held per PDF page. After that first long press, however, it properly does not show up for that page. That this is PDF page dependent does not give me confidence that there is a solution available.
Johnny Rockex's solutions might look like this:
public class RockexLongPressGestureRecognizer : UIKit.UILongPressGestureRecognizer
{
public RockexLongPressGestureRecognizer(UIKit.UIWebView webView, Action<UILongPressGestureRecognizer> action) :
base(UserInteractionAction(webView) + action)
{
}
private static Action<UILongPressGestureRecognizer> UserInteractionAction(UIKit.UIWebView webView)
{
return (UILongPressGestureRecognizer obj) =>
{
webView.UserInteractionEnabled = false;
webView.UserInteractionEnabled = true;
};
}
public override bool CanPreventGestureRecognizer(UIGestureRecognizer preventedGestureRecognizer)
{
return false;
}
}
and
notificationToken1 = UIMenuController.Notifications.ObserveMenuFrameDidChange (Callback);
notificationToken2 = NSNotificationCenter.DefaultCenter.AddObserver(UIMenuController.DidShowMenuNotification, Callback);
I was not able to get either to do anything. Helpfully someone else has done better with a Xamarin.iOS fix
1.ios11 iphone6 Object-C Without Copy/Paste/lookUp/share
2.
viewDidLoad{
.......
[self setupExclude];
}
- (void)setupExclude{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPG)];
longPress.minimumPressDuration = 0.2;
[self.webview addGestureRecognizer:longPress];
UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:nil];
singleTapGesture.numberOfTapsRequired = 1;
singleTapGesture.numberOfTouchesRequired = 1;
[self.webview addGestureRecognizer:singleTapGesture];
UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(longPG)];
doubleTapGesture.numberOfTapsRequired = 2;
doubleTapGesture.numberOfTouchesRequired = 1;
[self.webview addGestureRecognizer:doubleTapGesture];
[singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
BOOL res = [super canPerformAction:action withSender:sender];
UIMenuController.sharedMenuController.menuVisible = NO;
self.webview.userInteractionEnabled = NO;
self.webview.userInteractionEnabled = YES;
return res;
}
- (void)longPG{
UIMenuController.sharedMenuController.menuVisible = NO;
self.webview.userInteractionEnabled = NO;
self.webview.userInteractionEnabled = YES;
}
3. Done!

Passing variable, pushViewControll

I'm trying to build a simple Navigation Controller with a map function. I'm trying to take two variables from the Root view to the Detail view. One have the label and have the longitude.
I get the values from the Root to the Detail'd view but in the log it says this:
2012-04-12 14:38:41.331 Map[80073:11603] long: 0.000000
2012-04-1214:38:41.331 Map [80073:11603] Label
2012-04-12 14:38:41.331 Map[80073:11603] long: 62.375702
Where long is the longitude and label the variable which should be the label. I'm only passing one 'long' variable but always get the 0.00000 first anyway which makes the map show a wrong location and the label won't show anything at all..
I've been trying to figure out what's wrong for hours but can't seem to find anything in the code or on the web so any help is very much appreciated.
RootViewController.m:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *term = #"Label";
float longi1 = 100.102321;
DetailViewController *svc = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:[NSBundle mainBundle]];
//send properties to your view controller
svc.term = term;
svc.longi = longi1;
//push it to the navigationController
[self.navigationController pushViewController:svc animated:YES];
[svc release];
svc = nil;
}
DetailViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(term);
NSLog(#"long: %f", longi);
coor.text = term;
float lati = 65.12414;
CLLocationCoordinate2D coord = {.latitude = lati, .longitude = longi};
MKCoordinateSpan span = {.latitudeDelta = 0.5, .longitudeDelta= 0.5};
MKCoordinateRegion region = {coord, span};
[mapView setRegion:region];
[self.view addSubview:mapView];
}
This happens because -initWithNibName:bundle: loads the view in to memory and causes -viewDidLoad to be invoked before you assign the longitude.
Try moving the code in -viewDidLoad to -viewWillAppear