Difficulty In Subclassing MKTileOverlay - ios

I am trying to subclass MKTileOverlay, but am having issues with it not finding property canReplaceMap on object. What am I doing wrong? I go to New, create new class, Subclass of MKTileOverlay and add in the methods the tutorials all say to add, but these simple properties aren't getting found!

Here is the custom class extension for MKTileOverlay that I've been using to overlay the map in MapKit:
class CustomTileOverlay : MKTileOverlay
{
var mapLocation: MKMapPoint
var mapSize: MKMapSize
init(urlTemplate: String, location: MKMapPoint, size: MKMapSize)
{
mapLocation = location
mapSize = size
super.init(urlTemplate: urlTemplate)
}
override var boundingMapRect: MKMapRect {
get {
return MKMapRect(origin: mapLocation, size: mapSize)
}
}
}
The reason for doing an extension is to be able to adjust the boundingMapRect since that's read only in the base class (so if you don't need to adjust it, don't sub-class MKTileOverlay).
Here's the setup for using the Custom Class. I'm pulling the values from a CoreData record I set up for the tile set, but you could hardwire those or get them from wherever fits your app. Since I have polylines overlaying the tiles, I need the last line to be sure the tiles are under the lines, so if you don't have both you won't need that line.
[Declaration...]
private var tileLayer: CustomTileOverlay?
[Later in the code...]
let rectangle = overlayMap.getMapRectangle() // Why I need to sub-class
let mapURL = "file://" + overlayMap.getMapPath() + "/{z}/{x}/{y}.png"
tileLayer = CustomTileOverlay(urlTemplate: mapURL, location: rectangle.origin, size: rectangle.size)
tileLayer?.minimumZ = overlayMap.getMinimumZoom()
tileLayer?.maximumZ = overlayMap.getMaximumZoom()
tileLayer?.canReplaceMapContent = true
tileLayer?.tileSize = overlayMap.getTileSize()
self.mapView.add(tileLayer!)
self.mapView.insert(tileLayer!, at: 0) // Set to lowest z-level to ensure polylines are above map tiles

Related

Adding custom sprite object to a view

I'm making a game and built a custom NSObject sprite class for it. What I want to do is when my view loads add a visual representation of my object. I don't think it should be that hard but this is my first time trying to get a custom class to render on the screen. Also I want to do this without using Sprite Kit. Here is a stripped down version of what my sprite class looks like.
class sprite: NSObject {
var img: UIImage = UIImage(named: "koalio_stand")!
var width = 10
var height = 10
var x, y: Int!
var velocity: Int!
}

GMSMarker icon in the top left corner of the view (iOS)

I am trying to create a UITableViewCell containing a GMSMapView with a GMSMarker at the center of the current Position.
The problem is that the marker always appears at the top left corner of the current position and I don't know how to solve the problem.
I tried to follow these steps: Implementing a Google Map with UItableviewCell
here is my code from cellForRowAt:
let locationCell = tableView.dequeueReusableCell(withIdentifier: "activityLocationCell") as! ActivityLocationCell
let latitude = CLLocationDegrees(activity.coordinates![0])
let longitude = CLLocationDegrees(activity.coordinates![1])
let position = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
locationCell.googleMapView.camera = GMSCameraPosition.camera(withTarget: position, zoom: 15)
let marker = GMSMarker(position: position)
marker.groundAnchor = CGPoint(x: 0.5, y: 0.5)
marker.map = locationCell.googleMapView
return locationCell
Here is a screenshot of my problem:
marker is at the top left corner of the map
I had a pretty similar issue. I resolved it by changing the moment I configure the map in the view lifecycle.
In my case, I was using a child view controller. I was configuring the map before viewWillAppear was called which caused the map to not center properly (the marker was on the top left corner). I moved my call to after the viewWillAppear and it fixed it. A good place would be viewDidAppear.
If you are using a cell, you will probably need to investigate with the view lifecycle instead of the controller lifecycle.
This is not written anywhere on the Google documentation.
you have to draw map in
func viewDidLayoutSubviews()
Try creating Marker when map is ready completely. for eg: use the delegate.
var ifMapReady: Bool = false
...
...
func mapViewSnapshotReady(_ mapView: GMSMapView) {
ifMapReady = true
}
//Call this method from where ever you want to load map
func updateMap() {
if ifMapReady {
//Load Map
}
}
This delegate will be called multiple times(eg: map is swiped or moved etc) whenever the Map tiles are ready. So we can use a boolean value for understanding that map loaded successfully. Based on that value we can load the Map properly when initiating.
I want to add one more thing. #Gabriel Cartier's answer worked for me with one additional change in my code.
[self->mapView_ animateToCameraPosition:camera];
And I replaced with
[self->mapView_ setCamera:camera];

Adding custom view to ARKit

I just started looking at ARKitExample from apple and I am still studying. I need to do like interactive guide. For example, we can detect something (like QRCode), in that area, can I show with 1 label ?
Is it possible to add custom view (like may be UIVIew, UIlabel) to surface?
Edit
I saw some example to add line. I will need to find how to add additional view or image.
let mat = SCNMatrix4FromMat4(currentFrame.camera.transform)
let dir = SCNVector3(-1 * mat.m31, -1 * mat.m32, -1 * mat.m33)
let currentPosition = pointOfView.position + (dir * 0.1)
if button!.isHighlighted {
if let previousPoint = previousPoint {
let line = lineFrom(vector: previousPoint, toVector: currentPosition)
let lineNode = SCNNode(geometry: line)
lineNode.geometry?.firstMaterial?.diffuse.contents = lineColor
sceneView.scene.rootNode.addChildNode(lineNode)
}
}
I think this code should be able to add custom image. But I need to find the whole sample.
func updateRenderer(_ frame: ARFrame){
drawCameraImage(withPixelBuffer:frame.capturedImage)
let viewMatrix = simd_inverse(frame.came.transform)
let prijectionMatrix = frame.camera.prijectionMatrix
updateCamera(viewMatrix, projectionMatrix)
updateLighting(frame.lightEstimate?.ambientIntensity)
drawGeometry(forAnchors: frame.anchors)
}
ARKit isn't a rendering engine — it doesn't display any content for you. ARKit provides information about real-world spaces for use by rendering engines such as SceneKit, Unity, and any custom engine you build (with Metal, etc), so that they can display content that appears to inhabit real-world space. Thus, any "how do I show" question for ARKit is actually a question for whichever rendering engine you use with ARKit.
SceneKit is the easy out-of-the-box, no-additional-software-required way to display 3D content with ARKit, so I presume you're asking about that.
SceneKit can't render a UIView as part of a 3D scene. But it can render planes, cubes, or other shapes, and texture-map 2D content onto them. If you want to draw a text label on a plane detected by ARKit, that's the direction to investigate — follow the example's, um, example to create SCNPlane objects corresponding to detected ARPlaneAnchors, get yourself an image of some text, and set that image as the plane geometry's diffuse contents.
Yes you can add custom view in ARKit Scene.
Just make image of your view and add it wherever you want.
You can use following code to get image for UIView
func image(with view: UIView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
view.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}

Converting two UIViews to the same coordinate system when UIViews are in hierarchy

I have 2 UIImageViews in a ViewController and I'm trying to work out when they intersect.
imageViewA: is in my storyboard view, with constraints, and is in a hierarchy of views as follows:
- Background
-- Images
--- imageViewA
imageViewB: is created dynamically and is dragged around the screen using a UIPanGestureRecognizer.
When the drag ends, I want to check if imageViewB intersects with imageViewB. I used the intersects function, but don't get the results I expect, I suppose because imageViewA is in a hierachy of views, which means it's in a different coordinate system. So I want to convert both views to the same coordinate system. How I can do this?
I've tried the following:
let frameA = imageViewA.convert(imageViewA.frame, to: self.view)
let frameB = imageViewB.convert(imageViewB.frame, to: self.view)
But it doesn't give me the results I expect, which frameB having a much larger Y coordinate.
Do I need to do something like this:
let frameA = imageViewA.superview?.superview?.convert(imageViewA.superview?.superview?.frame, to: self.view)
There are other questions covering conversion to coordinate systems, but they don't seem to tackle what to do when the view is in a hierarchy.
Your problem is that imageViewA.frame is in the geometry (coordinate system) of imageViewA.superview, but UIView.convert(_ rect: to view:) expects rect to be in the geometry of imageViewA.
UPDATE
The simplest solution is to convert imageViewA.bounds (which is in imageViewA's geometry) directly to imageViewB's geometry, and then see if it intersects imageViewB.bounds, which is also in imageViewB's geometry:
let aInB = imageViewA.convert(imageViewA.bounds, to: imageViewB)
if aInB.intersects(imageViewB.bounds) {
...
ORIGINAL
The simplest solution is to convert imageViewA.bounds, which is in imageViewA's own geometry:
let frameA = imageViewA.convert(imageViewA.bounds, to: self.view)
let frameB = imageViewB.convert(imageViewB.bounds, to: self.view)
I misunderstood the convert function. Just in case it's useful for anyone else, the solution looks like this:
let convertedFrameB = frameA.superview?.convert(frameA.frame, from self.view)
if(convertedFrameB.intersects(frameA.frame) {...
...
}
Here's an extension that might be useful:
extension UIView {
func intersectsIgnoringCoordinateSpace(_ view2: UIView) -> Bool {
let frameOne = self.convert(self.bounds, to: self.topLevelView)
let frameTwo = view2.convert(self.bounds, to: self.topLevelView)
return frameOne.intersects(frameTwo)
}
var topLevelView: UIView? {
get {
var topView = self.superview
while(topView?.superview != nil) {
topView = topView?.superview
}
return topView
}
}

Detect click on MKPolygon overlay

I have been trying to work out if a tap gesture is in an overlay polygon, to no avail.
I am trying to make a map of country overlays - on clicking on an overlay I want to be able to tell what country the overlay is of.
First I found this: Detecting touches on MKOverlay in iOS7 (MKOverlayRenderer) and this: detect if a point is inside a MKPolygon overlay which suggest either:
make a tiny rectangle around your touch point and see if it intersects any overlays.
```
//point clicked
let point = MKMapPointForCoordinate(newCoordinates)
//make a rectangle around this click
let mapRect = MKMapRectMake(point.x, point.y, 0,0);
//loop through the polygons on the map and
for polygon in worldMap.overlays as! [MKPolygon] {
if polygon.intersectsMapRect(mapRect) {
print("found intersection")
}
}
```
Using viewForOverlay with a promising sounding function CGPathContainsPoint, however viewForOverlay is now deprecated.
This led me to find Detecting a point in a MKPolygon broke with iOS7 (CGPathContainsPoint) which suggests the following method:
Make a mutable polygon from the points of each overlay(instead of using the deprecated viewForOverlay) and then use CGPathContainsPoint to return if the clicked point is in the overlay.
However I am unable to make this code work.
```
func overlaySelected (gestureRecognizer: UIGestureRecognizer) {
let pointTapped = gestureRecognizer.locationInView(worldMap)
let newCoordinates = worldMap.convertPoint(pointTapped, toCoordinateFromView: worldMap)
let mapPointAsCGP = CGPointMake(CGFloat(newCoordinates.latitude), CGFloat(newCoordinates.longitude));
print(mapPointAsCGP.x, mapPointAsCGP.y)
for overlay: MKOverlay in worldMap.overlays {
if (overlay is MKPolygon) {
let polygon: MKPolygon = (overlay as! MKPolygon)
let mpr: CGMutablePathRef = CGPathCreateMutable()
for p in 0..<polygon.pointCount {
let mp = polygon.points()[p]
print(polygon.coordinate)
if p == 0 {
CGPathMoveToPoint(mpr, nil, CGFloat(mp.x), CGFloat(mp.y))
}
else {
CGPathAddLineToPoint(mpr, nil, CGFloat(mp.x), CGFloat(mp.y))
}
}
if CGPathContainsPoint(mpr, nil, mapPointAsCGP, false) {
print("------ is inside! ------")
}
}
}
}
```
The first method works but no matter how small I try and make the height and width of the rectangle around the click point let mapRect = MKMapRectMake(point.x, point.y, 0.00000000001,0.00000000001); the accuracy of the tap is not reliable and so you can end up clicking on several polygons at once.
Currently I am working on deciding on which county is nearer to the tap by using the 'MKPolygon' property coordinate - which gives the central point of the polygon. With this one can then measure the distance from this polygon to the tapped point to find the closest one. But this is not ideal as the user may never be able to tap on the country that they intend.
So, to sum up my questions:
Is there something that I am not implementing correctly in the second method above (one using CGPathContainsPoint)?
Is there a more accurate way to register an on click event with the rectangle method?
Any other suggestions or pointers on how to achieve my goal of clicking the map and seeing if the click is on an overlay.

Resources