MKPolyline draws strange relics when zooming - ios

I draw MKPolyline on map. Sometimes when I zoom a couple of times, something like this is left on the map:
Is it something specific for iOS maps that I cannot improve?
Or maybe with my drawing method something is wrong?
my MKPolyline class:
class MulticolorPolylineSegment: MKPolyline {
var color: UIColor?
class func colorSegments(forLocations locations: [Location]) -> [MulticolorPolylineSegment] {
var colorSegments = [MulticolorPolylineSegment]()
let red = (r: 254.0/255.0, g: 200.0 / 255.0, b: 20.0/255.0)
// RGB for Yellow (middle)
let yellow = (r: 254.0/255.0, g: 200.0 / 255.0, b: 20.0/255.0)
// RGB for Green (fastest)
let green = (r: 254.0/255.0, g: 200.0 / 255.0, b: 20.0/255.0)
let (speeds, minSpeed, maxSpeed) = allSpeeds(forLocations: locations)
// now knowing the slowest+fastest, we can get mean too
let meanSpeed = (minSpeed + maxSpeed)/2
for i in 1..<locations.count {
let l1 = locations[i-1]
let l2 = locations[i]
print("dsd")
var coords = [CLLocationCoordinate2D]()
coords.append(CLLocationCoordinate2D(latitude: l1.latitude, longitude: l1.longitude))
coords.append(CLLocationCoordinate2D(latitude: l2.latitude, longitude: l2.longitude))
let speed = speeds[i-1]
var color = UIColor.black
if speed < minSpeed { // Between Red & Yellow
let ratio = (speed - minSpeed) / (meanSpeed - minSpeed)
let r = CGFloat(red.r + ratio * (yellow.r - red.r))
let g = CGFloat(red.g + ratio * (yellow.g - red.g))
let b = CGFloat(red.r + ratio * (yellow.r - red.r))
color = UIColor(red: r, green: g, blue: b, alpha: 1)
}
else { // Between Yellow & Green
let ratio = (speed - meanSpeed) / (maxSpeed - meanSpeed)
let r = CGFloat(yellow.r + ratio * (green.r - yellow.r))
let g = CGFloat(yellow.g + ratio * (green.g - yellow.g))
let b = CGFloat(yellow.b + ratio * (green.b - yellow.b))
color = UIColor(red: r, green: g, blue: b, alpha: 1)
}
let segment = MulticolorPolylineSegment(coordinates: &coords, count: coords.count)
segment.color = color
colorSegments.append(segment)
}
return colorSegments
}
fileprivate class func allSpeeds(forLocations locations: [Location]) -> (speeds: [Double], minSpeed: Double, maxSpeed: Double) {
// Make Array of all speeds. Find slowest and fastest
var speeds = [Double]()
var minSpeed = DBL_MAX
var maxSpeed = 0.0
for i in 1..<locations.count {
let l1 = locations[i-1]
let l2 = locations[i]
let cl1 = CLLocation(latitude: l1.latitude, longitude: l1.longitude)
let cl2 = CLLocation(latitude: l2.latitude, longitude: l2.longitude)
let distance = cl2.distance(from: cl1)
let time = l2.timestamp.timeIntervalSince(l1.timestamp as Date)
let speed = distance/time
minSpeed = min(minSpeed, speed)
maxSpeed = max(maxSpeed, speed)
speeds.append(speed)
}
return (speeds, minSpeed, maxSpeed)
}
}
View Controller methods implementation
#objc(mapView:rendererForOverlay:) func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let polyline = overlay as! MulticolorPolylineSegment
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = polyline.color
renderer.lineWidth = 10
return renderer
}
func polyline() -> MKPolyline {
var coords = [CLLocationCoordinate2D]()
let locations = self.locations
for location in locations {
coords.append(CLLocationCoordinate2D(latitude: location.latitude,
longitude: location.longitude))
print("dodalo")
}
return MKPolyline(coordinates: &coords, count: run.locations.count)
}

Related

Smoothy move markers on Google Map not working in Swift 3

I placed car marker on google map for smoothy move to destination location. Here I can rotate the markers but it's not getting move to the destination. Can anyone identify what's code with the below code?
override func viewDidLoad() {
super.viewDidLoad()
let centerCoordinate = CLLocationCoordinate2DMake((self.delegate?.userLocation?.coordinate.latitude)!, (self.delegate?.userLocation?.coordinate.longitude)!)
self.setUpDriverMarkerWithNewLocation(location: centerCoordinate)
}
func setUpDriverMarkerWithNewLocation(location : CLLocationCoordinate2D) {
self.sourceMarker.icon = UIImage(named: "ARCar")
let centerCoordinate = CLLocationCoordinate2DMake(13.02025636601701, 80.26621352881193)
self.sourceMarker.groundAnchor = CGPoint(x: CGFloat(0.5), y: CGFloat(0.5))
self.sourceMarker.rotation = CLLocationDegrees(getHeadingForDirection(fromCoordinate: centerCoordinate, toCoordinate: location))
self.sourceMarker.position = location
self.sourceMarker.map = self.rideMapView
CATransaction.begin()
CATransaction.setValue(Int(2.0), forKey: kCATransactionAnimationDuration)
CATransaction.setCompletionBlock({() -> Void in
self.sourceMarker.groundAnchor = CGPoint(x: CGFloat(0.5), y: CGFloat(0.5))
self.sourceMarker.rotation = self.rotation
})
self.sourceMarker.position = centerCoordinate
//this can be new position after car moved from old position to new position with animation
self.sourceMarker.map = self.rideMapView
self.sourceMarker.groundAnchor = CGPoint(x: CGFloat(0.5), y: CGFloat(0.5))
self.sourceMarker.rotation = CLLocationDegrees(getHeadingForDirection(fromCoordinate: centerCoordinate, toCoordinate: location))
CATransaction.commit()
}
func getHeadingForDirection(fromCoordinate fromLoc: CLLocationCoordinate2D, toCoordinate toLoc: CLLocationCoordinate2D) -> Float {
let fLat: Float = Float((fromLoc.latitude).degreesToRadians)
let fLng: Float = Float((fromLoc.longitude).degreesToRadians)
let tLat: Float = Float((toLoc.latitude).degreesToRadians)
let tLng: Float = Float((toLoc.longitude).degreesToRadians)
let degree: Float = (atan2(sin(tLng - fLng) * cos(tLat), cos(fLat) * sin(tLat) - sin(fLat) * cos(tLat) * cos(tLng - fLng))).radiansToDegrees
if degree >= 0 {
return degree - 180.0
}
else {
return 360 + degree - 180
}
}

SceneKit view is rendered backwards

I'm attempting my first SceneKit app. My goal is to simulate a view from the surface of the Earth and being able to point the device's camera in any direction and overlay information over the camera view.
To start, I'm simply trying to get the SceneKit camera view to match the device's orientation. To verify that it is working as desired, I am adding a bunch of spheres at specific latitude and longitude coordinates.
Everything is working except for one important issue. The view is mirrored left/right (east/west) from what it should be showing. I've spent hours trying different permutations of adjusting the camera.
Below is my complete test app view controller. I can't figure out the right combination of changes to get the scene to render properly. The sphere at the North Pole is correct. The line of spheres stretching from the North Pole to the equator at my own current longitude appears overhead as expected. It's the other lines of spheres that are incorrect. They are mirrored east/west from what they should be as if the mirror is at my own longitude.
If you want to test this code, create a new Game project with SceneKit. Replace the template GameViewController.swift file with the one below. You also need to add the "Privacy - Location When In Use Description" key to the Info.plist. I also suggest adjusting the for lon in ... line so the numbers either start or end with your own longitude. Then you can see whether the spheres are being drawn on the correct portion of the display. This might also require a slight adjustment to the UIColor(hue: CGFloat(lon + 104) * 2 / 255.0 color.
import UIKit
import QuartzCore
import SceneKit
import CoreLocation
import CoreMotion
let EARTH_RADIUS = 6378137.0
class GameViewController: UIViewController, CLLocationManagerDelegate {
var motionManager: CMMotionManager!
var scnCameraArm: SCNNode!
var scnCamera: SCNNode!
var locationManager: CLLocationManager!
var pitchAdjust = 1.0
var rollAdjust = -1.0
var yawAdjust = 0.0
func radians(_ degrees: Double) -> Double {
return degrees * Double.pi / 180
}
func degrees(_ radians: Double) -> Double {
return radians * 180 / Double.pi
}
func setCameraPosition(lat: Double, lon: Double, alt: Double) {
let yaw = lon
let pitch = lat
scnCameraArm.eulerAngles.y = Float(radians(yaw))
scnCameraArm.eulerAngles.x = Float(radians(pitch))
scnCameraArm.eulerAngles.z = 0
scnCamera.position = SCNVector3(x: 0.0, y: 0.0, z: Float(alt + EARTH_RADIUS))
}
func setCameraPosition(loc: CLLocation) {
setCameraPosition(lat: loc.coordinate.latitude, lon: loc.coordinate.longitude, alt: loc.altitude)
}
// MARK: - UIViewController methods
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene()
let scnCamera = SCNNode()
let camera = SCNCamera()
camera.zFar = 2.5 * EARTH_RADIUS
scnCamera.camera = camera
scnCamera.position = SCNVector3(x: 0.0, y: 0.0, z: Float(EARTH_RADIUS))
self.scnCamera = scnCamera
let scnCameraArm = SCNNode()
scnCameraArm.position = SCNVector3(x: 0, y: 0, z: 0)
scnCameraArm.addChildNode(scnCamera)
self.scnCameraArm = scnCameraArm
scene.rootNode.addChildNode(scnCameraArm)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
//scnView.pointOfView = scnCamera
// Draw spheres over part of the western hemisphere
for lon in stride(from: 0, through: -105, by: -15) {
for lat in stride(from: 0, through: 90, by: 15) {
let mat4 = SCNMaterial()
if lat == 90 {
mat4.diffuse.contents = UIColor.yellow
} else if lat == -90 {
mat4.diffuse.contents = UIColor.orange
} else {
//mat4.diffuse.contents = UIColor(red: CGFloat(lat + 90) / 255.0, green: CGFloat(lon + 104) * 4 / 255.0, blue: 1, alpha: 1)
mat4.diffuse.contents = UIColor(hue: CGFloat(lon + 104) * 2 / 255.0, saturation: 1, brightness: CGFloat(255 - lat * 2) / 255.0, alpha: 1)
}
let ball = SCNSphere(radius: 100000)
ball.firstMaterial = mat4
let ballNode = SCNNode(geometry: ball)
ballNode.position = SCNVector3(x: 0.0, y: 0.0, z: Float(100000 + EARTH_RADIUS))
let ballArm = SCNNode()
ballArm.position = SCNVector3(x: 0, y: 0, z: 0)
ballArm.addChildNode(ballNode)
scene.rootNode.addChildNode(ballArm)
ballArm.eulerAngles.y = Float(radians(Double(lon)))
ballArm.eulerAngles.x = Float(radians(Double(lat)))
}
}
// configure the view
scnView.backgroundColor = UIColor(red: 0, green: 191/255, blue: 255/255, alpha: 1) // sky blue
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
let auth = CLLocationManager.authorizationStatus()
switch auth {
case .authorizedWhenInUse:
locationManager.startUpdatingLocation()
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
default:
break
}
motionManager = CMMotionManager()
motionManager.deviceMotionUpdateInterval = 1 / 30
motionManager.startDeviceMotionUpdates(using: .xTrueNorthZVertical, to: OperationQueue.main) { (motion, error) in
if error == nil {
if let motion = motion {
//print("pitch: \(self.degrees(motion.attitude.roll * self.pitchAdjust)), roll: \(self.degrees(motion.attitude.pitch * self.rollAdjust)), yaw: \(self.degrees(-motion.attitude.yaw))")
self.scnCamera.eulerAngles.z = Float(motion.attitude.yaw + self.yawAdjust)
self.scnCamera.eulerAngles.x = Float(motion.attitude.roll * self.pitchAdjust)
self.scnCamera.eulerAngles.y = Float(motion.attitude.pitch * self.rollAdjust)
}
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if UIApplication.shared.statusBarOrientation == .landscapeRight {
pitchAdjust = -1.0
rollAdjust = 1.0
yawAdjust = Double.pi
} else {
pitchAdjust = 1.0
rollAdjust = -1.0
yawAdjust = 0.0
}
}
override var shouldAutorotate: Bool {
return false
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
// MARK: - CLLocationManagerDelegate methods
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
manager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for loc in locations {
print(loc)
if loc.horizontalAccuracy > 0 && loc.horizontalAccuracy <= 100 {
setCameraPosition(loc: loc)
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
}
The changes probably need to be made in some combination of the setCameraPosition method and/or the motionManager.startDeviceMotionUpdates closure at the end of viewDidLoad.
I don't understand precisely what's wrong, but I believe that the way SceneKit uses pitch and yaw in eulerAngles doesn't match the way you're picturing it. I was unable to find chapter/verse that specifies which direction positive pitch, roll, and yaw are.
I was able to get it working (at least I think I've done what you were trying for) by changing the pitch/yaw setup to
ballArm.eulerAngles.x = -1 * Float(radians(Double(lat)))
I also added some debugging colors and labeling, and archived the scene to .SCN format to load it into the Xcode Scene Editor (I think Xcode 9 will do this for you in the debugger). Putting a name on each node lets you see what's what in the editor. Revised code:
// Draw spheres over part of the western hemisphere
for lon in stride(from: 0, through: -105, by: -15) {
for lat in stride(from: 0, through: 90, by: 15) {
let mat4 = SCNMaterial()
if lon == 0 {
mat4.diffuse.contents = UIColor.black
} else if lon == -105 {
mat4.diffuse.contents = UIColor.green
} else if lat == 90 {
mat4.diffuse.contents = UIColor.yellow
} else if lat == 0 {
mat4.diffuse.contents = UIColor.orange
} else {
mat4.diffuse.contents = UIColor(red: CGFloat(lat + 90) / 255.0, green: CGFloat(lon + 104) * 4 / 255.0, blue: 1, alpha: 1)
//mat4.diffuse.contents = UIColor(hue: CGFloat(lon + 104) * 2 / 255.0, saturation: 1, brightness: CGFloat(255 - lat * 2) / 255.0, alpha: 1)
//mat4.diffuse.contents = UIColor.green
}
let ball = SCNSphere(radius: 100000)
ball.firstMaterial = mat4
let ballNode = SCNNode(geometry: ball)
ballNode.position = SCNVector3(x: 0.0, y: 0.0, z: Float(100000 + EARTH_RADIUS))
let ballArm = SCNNode()
ballArm.position = SCNVector3(x: 0, y: 0, z: 0)
// debugging label
ballArm.name = "\(lat) \(lon)"
ballArm.addChildNode(ballNode)
scene.rootNode.addChildNode(ballArm)
ballArm.eulerAngles.y = Float(radians(Double(lon)))
ballArm.eulerAngles.x = -1 * Float(radians(Double(lat)))
}
}
// configure the view
scnView.backgroundColor = UIColor.cyan
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let docsFolderURL = urls[urls.count - 1]
let archiveURL = docsFolderURL.appendingPathComponent("rmaddy.scn")
let archivePath = archiveURL.path
// for copy/paste from Simulator's file system
print (archivePath)
let archiveResult = NSKeyedArchiver.archiveRootObject(scene, toFile: archivePath)
print(archiveResult)
And here's a screenshot of the scene in the Scene Editor. Note the faint orange circle around the sphere for the lat 45/lon -90 node, selected in the nodes listing.
Thanks to Hal for his very helpful suggestion for creating an "scn" file from my scene. After viewing that scene in the scene editor, I realized that everything was reversed left/right because the yaw Euler angle (Y-axis) rotation was reverse of what I thought. So when I set the node's eulerAngles.y to the desired longitude, I was rotating in the opposite direction of what I wanted. But since the camera had the same error, the spheres at my location were in the correct location but everything else was reversed left-to-right.
The solution was to negate the longitude value for both the spheres and the camera location.
scnCameraArm.eulerAngles.y = Float(radians(yaw))
needs to be:
scnCameraArm.eulerAngles.y = -Float(radians(yaw))
And:
ballArm.eulerAngles.y = Float(radians(Double(lon)))
needs to be:
ballArm.eulerAngles.y = -Float(radians(Double(lon)))

swift: how to draw an arc on map via MapKit?

I have to draw an arc having two angles, center point and radius as input. I use UIBezierPath, but overlay isn't added to the map. Here's my code:
func calculateByArc()
{
let startingAzimuth = Double(upperLimitTextBox.text!)
let endingAzimuth = Double(upperLimitUomTextBox.text!)
let radius = Double(lowerLimitTextBox.text!)
let latitude = Double(lowerLimitUomTextBox.text!)
let longitude = Double(textField5.text!)
let clockwise = clockwiseSwitch.isOn ? true : false
let qwe = CLLocationCoordinate2DMake(latitude!,longitude!)
let asd = Map.convert(qwe, toPointTo: Map)
let test = Map.convert(asd, toCoordinateFrom: Map)
print("test lat: " + String(test.latitude))
print("test lon: " + String(test.longitude))
let path = UIBezierPath(arcCenter: Map.convert (qwe, toPointTo: mapController.Map), radius: CGFloat(radius!), startAngle: CGFloat(Helper.toRad(startingAzimuth!)), endAngle: CGFloat(Helper.toRad(endingAzimuth!)) , clockwise: clockwise)
let arc = MKCircle(center: CLLocationCoordinate2DMake(latitude!, longitude!), radius: radius!)
arc.accessibilityPath = path
Map.add(arc)
}
and my mapView:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer
{
if overlay is MKCircle
{
print("overlay latitude: "+String(overlay.coordinate.latitude))
print("overlay longitude: "+String(overlay.coordinate.longitude))
let circleOverlay = overlay as! MKCircle
if(circleOverlay.accessibilityPath != nil)
{
let arcRenderer = MKOverlayPathRenderer()
arcRenderer.path = circleOverlay.accessibilityPath?.cgPath
arcRenderer.strokeColor = UIColor.red
arcRenderer.lineWidth = 10
arcRenderer.alpha = 0.3
return arcRenderer
}
let circle = MKCircleRenderer(overlay: overlay)
circle.strokeColor = UIColor.black
circle.fillColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0.1)
circle.lineWidth = 1
circle.alpha = 0.3
return circle
}
}
I guess it's because i don't properly convert CLLocationCoordinate2D to CGPoint, because a couple of weeks ago i managed to draw an arc, but with wrong coordinates(can't remember how i did that).
Is this what you are looking for http://nshipster.com/mkgeodesicpolyline/ ?

How to set zoom level or radius in map view?

Good day. I would like to know how to set like a radius of 1km on a current location on a MapView with
mapView.showsUserLocation = true
Is that possible? Thank you.
You can set "zoom level" by MKCoordinateSpanMake.
Try with this:
mapView.setRegion(MKCoordinateRegion(center: CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude), span: MKCoordinateSpanMake(0.05, 0.05)), animated: true)
Not my code. Maybe it can help you
extension MKMapView {
private var MERCATOR_OFFSET: Double {
get {
return 268435456
}
}
private var MERCATOR_RADIUS: Double {
get {
return 85445659.44705395
}
}
private var MAX_ZOOM_LEVEL: Double {
get {
return 19
}
}
// MARK: - Private functions
private func longitudeToPixelSpaceX (longitude: Double) -> Double {
return round(MERCATOR_OFFSET + MERCATOR_RADIUS * longitude * M_PI / 180.0)
}
private func latitudeToPixelSpaceY (latitude: Double) -> Double {
let a = 1 + sinf(Float(latitude * M_PI) / 180.0)
let b = 1.0 - sinf(Float(latitude * M_PI / 180.0)) / 2.0
return round(MERCATOR_OFFSET - MERCATOR_RADIUS * Double(logf(a / b)))
}
private func pixelSpaceXToLongitude (pixelX: Double) -> Double {
return ((round(pixelX) - MERCATOR_OFFSET) / MERCATOR_RADIUS) * 180.0 / M_PI
}
private func pixelSpaceYToLatitude (pixelY: Double) -> Double {
return (M_PI / 2.0 - 2.0 * atan(exp((round(pixelY) - MERCATOR_OFFSET) / MERCATOR_RADIUS))) * 180.0 / M_PI
}
private func coordinateSpanWithMapView(mapView: MKMapView, centerCoordinate: CLLocationCoordinate2D, andZoomLevel zoomLevel:Int) -> MKCoordinateSpan {
// convert center coordiate to pixel space
let centerPixelX = self.longitudeToPixelSpaceX(centerCoordinate.longitude)
let centerPixelY = self.latitudeToPixelSpaceY(centerCoordinate.latitude)
// determine the scale value from the zoom level
let zoomExponent = 20 - zoomLevel
let zoomScale = CGFloat(pow(Double(2), Double(zoomExponent)))
// scale the map’s size in pixel space
let mapSizeInPixels = mapView.bounds.size
let scaledMapWidth = mapSizeInPixels.width * zoomScale
let scaledMapHeight = mapSizeInPixels.height * zoomScale
// figure out the position of the top-left pixel
let topLeftPixelX = CGFloat(centerPixelX) - (scaledMapWidth / 2)
let topLeftPixelY = CGFloat(centerPixelY) - (scaledMapHeight / 2)
// find delta between left and right longitudes
let minLng: CLLocationDegrees = self.pixelSpaceXToLongitude(Double(topLeftPixelX))
let maxLng: CLLocationDegrees = self.pixelSpaceXToLongitude(Double(topLeftPixelX + scaledMapWidth))
let longitudeDelta: CLLocationDegrees = maxLng - minLng
// find delta between top and bottom latitudes
let minLat: CLLocationDegrees = self.pixelSpaceYToLatitude(Double(topLeftPixelY))
let maxLat: CLLocationDegrees = self.pixelSpaceYToLatitude(Double(topLeftPixelY + scaledMapHeight))
let latitudeDelta: CLLocationDegrees = -1 * (maxLat - minLat)
// create and return the lat/lng span
let span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta)
return span
}
// MARK: - Public Functions
func setCenterCoordinate(centerCoordinate: CLLocationCoordinate2D, zoomLevel: Int, animated: Bool) {
// clamp large numbers to 28
let zoom = min(zoomLevel, 28)
// use the zoom level to compute the region
let span = self.coordinateSpanWithMapView(self, centerCoordinate:centerCoordinate, andZoomLevel:zoom)
let region = MKCoordinateRegionMake(centerCoordinate, span)
// set the region like normal
self.setRegion(region, animated:animated)
}
func getZoomLevel() -> Int {
let longitudeDelta = self.region.span.longitudeDelta
let mapWidthInPixels = self.bounds.size.width*2 //2 is for retina display
let zoomScale = longitudeDelta * MERCATOR_RADIUS * M_PI / Double((180.0 * mapWidthInPixels))
var zoomer = MAX_ZOOM_LEVEL - log2(zoomScale)
if zoomer < 0 {
zoomer = 0
}
zoomer = round(zoomer)
return Int(zoomer)
}
}
Use like
mapView.setCenterCoordinate(mapView.centerCoordinate, zoomLevel: 17, animated: true)
here i am adding circle from current location to radius of 1 km and area of map displaya about 6 km square. i think it will help you.
// adding circle as overlay
func addRadiusCircle(location: CLLocation){
self.mapView.delegate = self
//radius of 1000 meters
let circle = MKCircle(centerCoordinate: location.coordinate, radius: 1000 as CLLocationDistance)
self.mapView.addOverlay(circle)
}
//circle design and coloring
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
let circle = MKCircleRenderer(overlay: overlay)
if overlay is MKCircle {
circle.strokeColor = UIColor.blackColor()
circle.fillColor = UIColor(red: 235/255, green: 174/255, blue: 13/255, alpha:0.3)
circle.lineWidth = 1
}
return circle
}
// initial area to display on map
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
6000, 6000)
mapView.setRegion(coordinateRegion, animated: true)
}

iOS voice recorder visualization on swift

I want to make visualization on the record like on the original Voice Memo app:
I know I can get the levels
- updateMeters
- peakPowerForChannel:
- averagePowerForChannel:
but how to draw the graphic, should I do it custom? Is there free/paid source I can use?
I was having the same problem. I wanted to create a voice memos clone. Recently, I found a solution and wrote an article about it on medium.
I created a subclass from UIView class and drew the bars with CGRect.
import UIKit
class AudioVisualizerView: UIView {
// Bar width
var barWidth: CGFloat = 4.0
// Indicate that waveform should draw active/inactive state
var active = false {
didSet {
if self.active {
self.color = UIColor.red.cgColor
}
else {
self.color = UIColor.gray.cgColor
}
}
}
// Color for bars
var color = UIColor.gray.cgColor
// Given waveforms
var waveforms: [Int] = Array(repeating: 0, count: 100)
// MARK: - Init
override init (frame : CGRect) {
super.init(frame : frame)
self.backgroundColor = UIColor.clear
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.backgroundColor = UIColor.clear
}
// MARK: - Draw bars
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
return
}
context.clear(rect)
context.setFillColor(red: 0, green: 0, blue: 0, alpha: 0)
context.fill(rect)
context.setLineWidth(1)
context.setStrokeColor(self.color)
let w = rect.size.width
let h = rect.size.height
let t = Int(w / self.barWidth)
let s = max(0, self.waveforms.count - t)
let m = h / 2
let r = self.barWidth / 2
let x = m - r
var bar: CGFloat = 0
for i in s ..< self.waveforms.count {
var v = h * CGFloat(self.waveforms[i]) / 50.0
if v > x {
v = x
}
else if v < 3 {
v = 3
}
let oneX = bar * self.barWidth
var oneY: CGFloat = 0
let twoX = oneX + r
var twoY: CGFloat = 0
var twoS: CGFloat = 0
var twoE: CGFloat = 0
var twoC: Bool = false
let threeX = twoX + r
let threeY = m
if i % 2 == 1 {
oneY = m - v
twoY = m - v
twoS = -180.degreesToRadians
twoE = 0.degreesToRadians
twoC = false
}
else {
oneY = m + v
twoY = m + v
twoS = 180.degreesToRadians
twoE = 0.degreesToRadians
twoC = true
}
context.move(to: CGPoint(x: oneX, y: m))
context.addLine(to: CGPoint(x: oneX, y: oneY))
context.addArc(center: CGPoint(x: twoX, y: twoY), radius: r, startAngle: twoS, endAngle: twoE, clockwise: twoC)
context.addLine(to: CGPoint(x: threeX, y: threeY))
context.strokePath()
bar += 1
}
}
}
For the recording function, I used installTap instance method to record, monitor, and observe the output of the node.
let inputNode = self.audioEngine.inputNode
guard let format = self.format() else {
return
}
inputNode.installTap(onBus: 0, bufferSize: 1024, format: format) { (buffer, time) in
let level: Float = -50
let length: UInt32 = 1024
buffer.frameLength = length
let channels = UnsafeBufferPointer(start: buffer.floatChannelData, count: Int(buffer.format.channelCount))
var value: Float = 0
vDSP_meamgv(channels[0], 1, &value, vDSP_Length(length))
var average: Float = ((value == 0) ? -100 : 20.0 * log10f(value))
if average > 0 {
average = 0
} else if average < -100 {
average = -100
}
let silent = average < level
let ts = NSDate().timeIntervalSince1970
if ts - self.renderTs > 0.1 {
let floats = UnsafeBufferPointer(start: channels[0], count: Int(buffer.frameLength))
let frame = floats.map({ (f) -> Int in
return Int(f * Float(Int16.max))
})
DispatchQueue.main.async {
let seconds = (ts - self.recordingTs)
self.timeLabel.text = seconds.toTimeString
self.renderTs = ts
let len = self.audioView.waveforms.count
for i in 0 ..< len {
let idx = ((frame.count - 1) * i) / len
let f: Float = sqrt(1.5 * abs(Float(frame[idx])) / Float(Int16.max))
self.audioView.waveforms[i] = min(49, Int(f * 50))
}
self.audioView.active = !silent
self.audioView.setNeedsDisplay()
}
}
Here is the article I wrote, and I hope that you will find what you are looking for:
https://medium.com/flawless-app-stories/how-i-created-apples-voice-memos-clone-b6cd6d65f580
The project is also available on GitHub:
https://github.com/HassanElDesouky/VoiceMemosClone
Please note that I'm still a beginner, and I'm sorry my code doesn't seem that clean!

Resources