How to limit map bounds in mapbox MGLMapView? - ios

I'm using mapbox-iOS-SDK 3.2.3 and can't find any properties or smth like this to control the map bounds. I need to limit the visible area for the user. Is it possible in current SDK version?

There's nothing built in to the current version of Mapbox that looks like it would do what you want. You might be able to get something like it by
Implementing mapViewRegionIsChanging: or mapView:regionDidChangeAnimated: in the map view delegate
In those methods, check the current region. If it's outside the desired area, reset the map view to something inside the desired region. That is, as soon as the map view starts to move outside the region, make it go back.
This would probably work but it might make the view "stutter" if the user tries to scroll outside the target region. I don't know what your app does, but it might be worth considering (a) whether this is actually necessary and (b) whether there might be a better way to avoid whatever problem you expect than restricting map view scrolling.

I took the idea from #Tom Harrington and implemented the delegate with the goal of seeing how much stutter or animation jankiness there was while keeping a user within a known Mapbox bound. I upvoted his answer, but also wanted to share this example.
Here is a Swift delegate that fills out the delegate for mapViewRegionIsChanging
the main goal is to test if the experience is still pleasing to the customer (yes)
Can the delegate properly keep the user within bounds without calling the delegate too often? (still needs to be verified)
Uses Balboa Park, as a square Mapbox MGLCoordinateBounds
Only checks if the customer scrolls too far north
The concept of too far is checked by some tolerance, epsilon
Checking out of bounds on four sides should be straightforward
func mapViewRegionIsChanging (mapView: MGLMapView) {
let viewBounds = mapView.visibleCoordinateBounds
// Set the map's bounds to Balboa Park, San Diego
let boundsBalboaPark = MGLCoordinateBounds(
sw: CLLocationCoordinate2D(latitude: 32.71942, longitude: -117.15914),
ne: CLLocationCoordinate2D(latitude: 32.74093, longitude: -117.13374))
let deltaNorth = viewBounds.ne.latitude - boundsBalboaPark.ne.latitude
let epsilon = 0.025 // Magic number for tolerance of how far 'north' we allow (in degrees); Q.E.D meters
if( deltaNorth > epsilon) {
mapView.setVisibleCoordinateBounds(boundsBalboaPark, animated: true)
}
}
This animation shows moving the mapView too far north of Balboa Park. The stutter you see is real, but acceptable.

Related

MKMapCamera doesn't zoom to correct altitude

When I'm setting an MKMapCamera with a specific altitude on my map view (MapKit) it doesn't zoom to the correct altitude sometimes. I think it has something to do with the map not being fully loaded so it stops higher (950m or so) instead of the altitude I set (about 280m).
Initially I noticed the issue when I first loaded the map but it seems more related to the lower altitudes. Higher altitudes seems to work OK.
Here's a video demonstrating the problem: https://streamable.com/644l1
In the video I'm setting the same camera twice.
The code for setting the camera:
let distance = currentHole!.teeCoordinate.distance(to: currentHole!.greenCoordinate)
let altitude = Double(distance) * 2.5
let camera = MKMapCamera(
lookingAtCenter: currentHole!.centerCoordinate(),
fromDistance: altitude,
pitch: 0.0,
heading: currentHole!.teeCoordinate.bearing(to: currentHole!.greenCoordinate) - 20
)
mapView.setCamera(camera, animated: true)
I also tried to use something like:
UIView.animate(withDuration: 1.0, animations: { () -> Void in
self.mapView.camera = camera
}, completion: { (done) -> Void in
print("Animation complete")
})
to do the animation instead. It works better (not perfect) when setting the duration to something very high, like 10 seconds or so.
Any ideas on what might be the issue here?
UPDATE:
It seems to only happen with "Satellite Flyover" maps. Satellite is fine.
I don't know for certain why this is happening but I have a theory. When you're using the flyover map types the minimum altitude of the camera is restricted by the tallest structure in the centre of the map.
If you go to the Maps app, set it to 3D Satellite view and go directly above a tall building (say the Empire State Building in New York) you can only pinch to zoom to a little above the height of the building. If you pan the camera away from the tall structure you can pinch to zoom in further. The map won't let you zoom through or inside the structure. If you zoom in to the entrance of a tall building and try to pan towards the building, the map will adjust the altitude upwards without you pinching to zoom out to prevent you passing through the building.
So before the map is fully loaded, it doesn't know what the tallest structure at the centre is going to be. To prevent you zooming inside a tall structure, the map limits the minimum height. After the map is fully loaded and it knows that there are no tall structures it lets you zoom in closer.
When you set a long duration on the animation, it's giving the map a chance to load before it gets to the lower altitude. The map knows that there are no tall structures and allows further zooming in. I would say that if you tried a longer duration animation but throttled the network bandwidth it would stop working again.
Note that the Satellite mode allows you to pass through tall structures.
As a workaround, try using mapViewDidFinishLoadingMap: or mapViewDidFinishRenderingMap:fullyRendered: to know when to zoom in more.

Circular UI Slider with Image and Color

I am working on an app, with UISlider and need to make it circular.
Is it possible via some native code, or does a 3rd party library have to be integrated?
I want to fill color as user will slider over slider.
You can find 3rd party libraries for circular slider.
Take a look into these:
https://www.cocoacontrols.com/controls/uicircularslider
https://github.com/thomasfinch/Circular-UISlider
https://github.com/milianoo/CurvySlider
I came across this question in a seperate thread with multiple links and didn't like any of the answers offered so I thought I would add my own in what seemed to be the most relevant thread, on the off chance someone else finds their way here and likes the option I provide.
it is not elegant or simple but it is easy and I thought it would be a nice starting point for others to develop their own.
in this example the circular uiScroller is placed on a view (or HUD) that is presented over the top of an SKView. I then use a hitTest to transfer any touches from the gameScene to the UIView and use _touchesBegan and _touchesMoved to update a series of buttons that are hidden like so:
the arrows are all hidden but we use the frames of each box to register where the touch is
I made the arrows UIButtons so it was easier to use the storyboard but I think an imageView works fine as we are only checking the frame of the arrow inside _touchesBegan and then sending it to the function rather than collection a touch event from the arrow itself.
if upBtn.frame.contains(touch) {
upBtnPress() //if you made this a touch func like me a call to (self) as the sender may be required
}
the black circle itself remains visible so it looks like this is where your taps are going.
then finally you create multiple images like this:
any circular pattern for each location possible is appropriate
(I had a total of 16 points for mine)
and then under any press function you just change the image of the black circle to show the correct image based on where the touch location is and you're done!
circleDpad.image = UIImage(named: "up")
calculating the angle:
is quite very simple, there are 16 points on my dial so I need to convert this into a possible direction in 1...360
func angleChanged(angle: CGFloat) {
let valueReached = (angle / 16) * 360 // this gives you the angle in degrees, converting any point that's x/16 into x/360
let turret = checkBaseSelect() //this is my personal code
turret?.tAngle = CGFloat(valueReached) * (CGFloat.pi / 180) //this converts degrees to radians and applies it where I want it.
}
despite only having 16 points I found that even without applying animation it looks quite smooth. thanks for reading.
this is a gif of my circular slider in action
This is my first ever guide, if it needs more information (or should be somewhere else) please let me know.

MKMapView show extra zoomed region. How?

I need to show very small area (30x30 meters) on MKMapView. Setting appropriate region or visibleMapRect doesn't work. MapView shows much bigger region.
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([centerLocation coordinate], 30, 30);
[mapView setRegion:region];
It seems with extra small regions MapView corrects with regionThatFits method before update map.
Manually zoom allows displaying such region.
MapKit is not really designed for such high-zoom indoor uses. You may want to check out alternatives such as the open source MapBox iOS SDK, which has been used for indoor applications. In particular iOS 7's iBeacons technology as well may be useful to you for indoor triangulation and higher accuracy than something like GPS, which was neither designed for indoor nor high-zoom use.
According to Apple docs:
When setting a new region, the map may adjust the value in the region
parameter so that it fits the visible area of the map precisely. This
is normal and is done to ensure that the value in the region property
always reflects the visible portion of the map. However, it does mean
that if you get the value of that property right after calling this
method, the returned value may not match the value you set. (You can
use the regionThatFits: method to determine the region that will
actually be set by the map.)
So, when you apply distance, it creates the region which is best fit for your request. It will not be exactly same as what you have requested.
Also, 30*30 meters is very very high zoom level which might not be supported. Hope it will help.

Simple algorithm for annotations request from server on map

What is a good enough method of knowing when to go out to the server and request for annotations?
i.e. knowing when the area on the screen was not yet exposed by the user?
If I have a LAT1,LON1 LAT2,LON2 specifying the screen boundaries, or maybe the screen's center as LAT,LON, how can I know that the surface the user has moved to has never been exposed or even just a part of it?
Weird, but I can't find ideas online, Any methods would be welcome!
Thanks!
store a set of MKMapRect objects that the map showed in a NSMutableSet maybe -- you then have all the areas that were visible previously. (combine rects when it makes sense to keep the set reasonable)
when you get a new MKMapRect (after a scroll or zoom of the map view -- the delegate is informed here) see if the new visibleMapRect lies within an old one or just intersects or is not inside the rect at all
an MKMapRect can be treated almost like a CGRect :)

MKMapView zooms itself out

This is a small method of mine which takes the location manager's current location and focuses the map just a little bit above it to make room for some other subviews that I've added to the top of the map. If the span's lat/long deltas get too big (more than 1.0), every time the region is set, the span gets bigger and bigger until the map is zoomed out all the way, even without actually touching the map. I think it has something to do with reusing the map view's span, but I don't know exactly what's going on.
- (void)setRegion {
CLLocationCoordinate2D coord = locationManager.location.coordinate;
MKCoordinateSpan span = mapView.region.span;
coord.latitude += 0.002 * (span.latitudeDelta / 0.012523);
MKCoordinateRegion region = MKCoordinateRegionMake(coord, span);
[mapViewGlobal setRegion:region animated:animated];
}
From Apple's docs "If you want to change the center coordinate without changing the zoom level, use the setCenterCoordinate:animated: instead." MKMapView
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated
MKMapView will pick a new region that nicely fits the to a redefined zoom level while still displaying your entire target region and each time yo move north the span changes and things spiral out of control.
Also, your way of working out how much to change the latitude by is a bit odd. Aside from doing a multiple and divide when one would do the trick, it'll only work at a certain latitude (they get stretched away from the equator) and a certain zoom level. You'd be better off working out how many pixels you need the map scrolled by (presumably half the height of your subviews) and then using this function to work out where you need to center the map.
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view
Another way would be to put the subviews outside of the MKMapView, but that'll affect your aesthetics.

Resources