How to animate MKOverlayRenderer added in Mapview iOS? - ios

I have a mapview as shown below
The lines drawn in the map are using MKPolyline. I have used apple MapKit framework to display my map. My requirement is when the user selects the annotation on Polyline, the polyline should show direction as shown below
How can I show the animation in my map view? MKOverlayRenderer inherits from NSObject, It is not possible to animate using UIView Animation. I found some link here , its in objective C for iOS 7, IS it possible in swift to do the same?

MKOverlayRender inherits from NSObject. So it is not possible to add CAlayer to MKOverlayRenderer as explained in this sample project. The steps I followed to get the following animation effect are
Subclass MKOverlayRenderer and add a custom image as overlay on the map
class MapOverlayView: MKOverlayRenderer {
var overlayImage: UIImage
var angle: CGFloat
init(overlay: MKOverlay, overlayImage:UIImage, angle: CGFloat) {
self.overlayImage = overlayImage
self.angle = angle
super.init(overlay: overlay)
}
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
let mapImage = overlayImage.cgImage
let mapRect = rect(for: overlay.boundingMapRect)
// Calculate centre point on which image should be rotated
let centerPoint = CGPoint(x: mapRect.midX, y: mapRect.midY)
let a = sqrt(pow(centerPoint.x, 2.0) + pow(centerPoint.y, 2.0))
let sub1 = (centerPoint.y / a) * cos(angle / 2.0)
let sub2 = (centerPoint.x / a) * sin(angle / 2.0)
let deltaX = -2 * a * sin((0 - angle) / 2.0) * (sub1 + sub2)
let sub3 = (centerPoint.x / a) * cos(angle / 2.0)
let sub4 = (centerPoint.y / a) * sin(angle / 2.0)
let deltaY = 2 * a * sin((0 - angle) / 2.0) * (sub3 - sub4)
context.translateBy(x: deltaX, y: deltaY)
context.rotate(by: angle)
context.draw(mapImage!, in: mapRect)
}
}
class MapOverlay: NSObject, MKOverlay {
var coordinate: CLLocationCoordinate2D
var boundingMapRect: MKMapRect
var identifier: String?
init(identifier:String, coord: CLLocationCoordinate2D, rect: MKMapRect) {
self.coordinate = coord
self.boundingMapRect = rect
self.identifier = identifier
}
}
Call a timer to change alpha value of added overlays
var timer = Timer()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.toggle), userInfo: nil, repeats: true)
Function to change alpha of overlay renderer
func changeAlphaValue(identifier: String) {
let overlays = self.mapView.overlays
let tag: String = identifier
for overlay in overlays {
if let overlay = overlay as? MapOverlay {
let identifier = overlay.identifier
if identifier == tag {
let renderer = mapView.renderer(for: overlay)
DispatchQueue.main.async{
renderer?.alpha = 1.0
}
}
else {
let renderer = mapView.renderer(for: overlay)
DispatchQueue.main.async{
renderer?.alpha = 0.0
}
}
}
}
}
This will give the following animation on map
The project is available on GitHub - link

https://github.com/jhurray/iOS7AnimatedMapOverlay
Above link are useful for you Or below code.
- (void)mapView:(MKMapView *)mapView didAddOverlayRenderers:(NSArray *)renderers
{
// Iterate through all overlayRenderers
for (MKOverlayRenderer *overlayRenderer in renderers)
{
// MKPolylineRenderer
// Animate a 'fade'
if ([overlayRenderer isKindOfClass:[MKPolylineRenderer class]])
{
// Get MKPolylineRenderer
MKPolylineRenderer *polylineRenderer = (MKPolylineRenderer *)overlayRenderer;
// Let's manually set alpha to 0
polylineRenderer.alpha = 0.0f;
// Animate it back to 1
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.4 animations:^{
polylineRenderer.alpha = 1.0f
}];
});
}
}
}
Thanks

Related

Resize and Rotate image Annotation added in pdf

Using this Link I have added signature image annotation in PDF file
But i could not find any guide for how to rotate and resize image annotation using the button added on top of annotation image like shown in the image.
What i want to do is:
want to scale/resize signature image(make it small or large by adding this resize button)
want to rotate signature image
For Pinch to zoom i am adding pinch gesture to PDFView but that gesture zoom in / zoom out the main pdf.tried to fix it by below code but not worked.
#objc func scale(sender : UIPinchGestureRecognizer) {
print("----------Scale----------")
let touchLocation = sender.location(in: pdfContainerView)
guard let page = pdfContainerView.page(for: touchLocation, nearest: true)
else {
return
}
let locationOnPage = pdfContainerView.convert(touchLocation, to: page)
switch sender.state {
case .began:
guard let annotation = page.annotation(at: locationOnPage) else {
return
}
if annotation.isKind(of: ImageStampAnnotation.self) {
currentlySelectedAnnotation = annotation
// to disable pinch gesture for pdfview but it is not working
pdfContainerView.minScaleFactor = pdfContainerView.scaleFactor
pdfContainerView.maxScaleFactor = pdfContainerView.scaleFactor
}
case .changed,.ended:
guard let annotation = currentlySelectedAnnotation else {
return
}
let initialBounds = annotation.bounds
//scale annotation
case .cancelled:
break
default:
break
}
}
Thanks in advance!!
I tried to implement rotation, zoom, pan on PDFview.
I created a new view above the PDFAnnotation and then moved, rotated, and scaled the view as above.
about PDFAnnotation:
class PDFImageAnnotation: PDFAnnotation {
let image: UIImage
let originalBounds: CGRect
/// 0 - 360
var angle: CGFloat = 0 {
didSet {
// reload annotation
shouldDisplay = true
}
}
/// scale annotation
var scale: CGFloat = 1 {
didSet {
// Scale on the original size
let width = originalBounds.width * scale
let height = originalBounds.height * scale
// move origin
let x = bounds.origin.x - (width - bounds.width)/2
let y = bounds.origin.y - (height - bounds.height)/2
print("new ---- \(CGRect(x: x, y: y, width: width, height: height))")
// Setting the bounds will automatically re-render
bounds = CGRect(x: x, y: y, width: width, height: height)
}
}
/// move center point
var center: CGPoint = .zero {
didSet {
let x = center.x - bounds.width/2.0
let y = center.y - bounds.height/2.0
// Setting the bounds will automatically re-render
bounds = CGRect(origin: CGPoint(x: x, y: y), size: bounds.size)
}
}
public init(bounds: CGRect, image: UIImage) {
self.image = image
originalBounds = bounds
super.init(bounds: bounds, forType: .ink, withProperties: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(with box: PDFDisplayBox, in context: CGContext) {
super.draw(with: box, in: context)
print("PDFImageAnnotation bounds - \(bounds)")
guard let page = page else {
return
}
UIGraphicsPushContext(context)
context.saveGState()
// rotate annotation
// The origin of the annotation is always at the initial position
let translateX = bounds.width/2 + bounds.origin.x
let translateY = bounds.height/2 + bounds.origin.y
// The page has its own rotation Angle
let newAngle = angle + CGFloat(page.rotation)
context.translateBy(x: translateX, y: translateY)
context.rotate(by: newAngle*(CGFloat.pi/180.0))
context.translateBy(x: -translateX, y: -translateY)
// draw image
if let cgImage = image.cgImage {
context.draw(cgImage, in: bounds)
}
context.restoreGState()
UIGraphicsPopContext()
}
}
use:
extension ViewController: PDFSignAnnotationViewDelegate {
func signAnnotationView(_ view: PDFSignAnnotationView, didMove point: CGPoint) {
guard let page = pdfContainerView.currentPage,
let imageAnnotation = page.annotations.filter({$0.userName == view.identity}).first as? PDFImageAnnotation else {
return
}
let locationOnPage = self.pdfContainerView.convert(point, to: page)
imageAnnotation.center = locationOnPage
}
func signAnnotationView(_ view: PDFSignAnnotationView, didScale scale: CGFloat) {
guard let page = pdfContainerView.currentPage,
let imageAnnotation = page.annotations.filter({$0.userName == view.identity}).first as? PDFImageAnnotation else {
return
}
imageAnnotation.scale = scale
}
func signAnnotationView(_ view: PDFSignAnnotationView, didRotate angle: CGFloat) {
guard let page = pdfContainerView.currentPage,
let imageAnnotation = page.annotations.filter({$0.userName == view.identity}).first as? PDFImageAnnotation else {
return
}
print("didRotate - \(angle)")
imageAnnotation.angle = -angle
}
func signAnnotationView(_ view: PDFSignAnnotationView, close identity: String) {
guard let page = pdfContainerView.currentPage else {
return
}
guard let annotation = page.annotations.filter({$0.userName == identity}).first else {
return
}
page.removeAnnotation(annotation)
}
}
TODO:
Move and zoom the page without sending changes to the upper view
I refer to here: https://medium.com/#rajejones/add-a-signature-to-pdf-using-pdfkit-with-swift-7f13f7faad3e
Demo https://github.com/roMummy/PDFSignature/tree/master/PDFSignature

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
}
}

How to make Circular audio visualizer in swift?

I want to make a visualizer like this Circular visualizer, click the green flag to see the animation.
In my project first I draw a circle, I calculate the points on the circle to draw the visualizer bars, I rotate the view to make the bars feels like circle. I use StreamingKit to stream live radio. StreamingKit provides the live audio power in decibels. Then I animate the visualizer bars. But when I rotate the view the height and width changes according to the angle I rotate. But the bounds value not change (I know the frame depends on superViews).
audioSpectrom Class
class audioSpectrom: UIView {
let animateDuration = 0.15
let visualizerColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
var barsNumber = 0
let barWidth = 4 // width of bar
let radius: CGFloat = 40
var radians = [CGFloat]()
var barPoints = [CGPoint]()
private var rectArray = [CustomView]()
private var waveFormArray = [Int]()
private var initialBarHeight: CGFloat = 0.0
private let mainLayer: CALayer = CALayer()
// draw circle
var midViewX: CGFloat!
var midViewY: CGFloat!
var circlePath = UIBezierPath()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
convenience init() {
self.init(frame: CGRect.zero)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
self.layer.addSublayer(mainLayer)
barsNumber = 10
}
override func layoutSubviews() {
mainLayer.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
drawVisualizer()
}
//-----------------------------------------------------------------
// MARK: - Drawing Section
//-----------------------------------------------------------------
func drawVisualizer() {
midViewX = self.mainLayer.frame.midX
midViewY = self.mainLayer.frame.midY
// Draw Circle
let arcCenter = CGPoint(x: midViewX, y: midViewY)
let circlePath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
let circleShapeLayer = CAShapeLayer()
circleShapeLayer.path = circlePath.cgPath
circleShapeLayer.fillColor = UIColor.blue.cgColor
circleShapeLayer.strokeColor = UIColor.clear.cgColor
circleShapeLayer.lineWidth = 1.0
mainLayer.addSublayer(circleShapeLayer)
// Draw Bars
rectArray = [CustomView]()
for i in 0..<barsNumber {
let angle = ((360 / barsNumber) * i) - 90
let point = calculatePoints(angle: angle, radius: radius)
let radian = angle.degreesToRadians
radians.append(radian)
barPoints.append(point)
let rectangle = CustomView(frame: CGRect(x: barPoints[i].x, y: barPoints[i].y, width: CGFloat(barWidth), height: CGFloat(barWidth)))
initialBarHeight = CGFloat(self.barWidth)
rectangle.setAnchorPoint(anchorPoint: CGPoint.zero)
let rotationAngle = (CGFloat(( 360/barsNumber) * i)).degreesToRadians + 180.degreesToRadians
rectangle.transform = CGAffineTransform(rotationAngle: rotationAngle)
rectangle.backgroundColor = visualizerColor
rectangle.layer.cornerRadius = CGFloat(rectangle.bounds.width / 2)
rectangle.tag = i
self.addSubview(rectangle)
rectArray.append(rectangle)
var values = [5, 10, 15, 10, 5, 1]
waveFormArray = [Int]()
var j: Int = 0
for _ in 0..<barsNumber {
waveFormArray.append(values[j])
j += 1
if j == values.count {
j = 0
}
}
}
}
//-----------------------------------------------------------------
// MARK: - Animation Section
//-----------------------------------------------------------------
func animateAudioVisualizerWithChannel(level0: Float, level1: Float ) {
DispatchQueue.main.async {
UIView.animateKeyframes(withDuration: self.animateDuration, delay: 0, options: .beginFromCurrentState, animations: {
for i in 0..<self.barsNumber {
let channelValue: Int = Int(arc4random_uniform(2))
let wavePeak: Int = Int(arc4random_uniform(UInt32(self.waveFormArray[i])))
let barView = self.rectArray[i] as? CustomView
guard var barFrame = barView?.frame else { return }
// calculate the bar height
let barH = (self.frame.height / 2 ) - self.radius
// scale the value to 40, input value of this func range from 0-60, 60 is low and 0 is high. Then calculate the height by minimise the scaled height from bar height.
let scaled0 = (CGFloat(level0) * barH) / 60
let scaled1 = (CGFloat(level1) * barH) / 60
let calc0 = barH - scaled0
let calc1 = barH - scaled1
if channelValue == 0 {
barFrame.size.height = calc0
} else {
barFrame.size.height = calc1
}
if barFrame.size.height < 4 || barFrame.size.height > ((self.frame.size.height / 2) - self.radius) {
barFrame.size.height = self.initialBarHeight + CGFloat(wavePeak)
}
barView?.frame = barFrame
}
}, completion: nil)
}
}
func calculatePoints(angle: Int, radius: CGFloat) -> CGPoint {
let barX = midViewX + cos((angle).degreesToRadians) * radius
let barY = midViewY + sin((angle).degreesToRadians) * radius
return CGPoint(x: barX, y: barY)
}
}
extension BinaryInteger {
var degreesToRadians: CGFloat { return CGFloat(Int(self)) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
extension UIView{
func setAnchorPoint(anchorPoint: CGPoint) {
var newPoint = CGPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
var oldPoint = CGPoint(x: self.bounds.size.width * self.layer.anchorPoint.x, y: self.bounds.size.height * self.layer.anchorPoint.y)
newPoint = newPoint.applying(self.transform)
oldPoint = oldPoint.applying(self.transform)
var position : CGPoint = self.layer.position
position.x -= oldPoint.x
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
self.layer.position = position;
self.layer.anchorPoint = anchorPoint;
}
}
I drag a empty view to storyBoard and give custom class as audioSpectrom.
ViewController
func startAudioVisualizer() {
visualizerTimer?.invalidate()
visualizerTimer = nil
visualizerTimer = Timer.scheduledTimer(timeInterval: visualizerAnimationDuration, target: self, selector: #selector(self.visualizerTimerFunc), userInfo: nil, repeats: true)
}
#objc func visualizerTimerFunc(_ timer: CADisplayLink) {
let lowResults = self.audioPlayer!.averagePowerInDecibels(forChannel: 0)
let lowResults1 = self.audioPlayer!.averagePowerInDecibels(forChannel: 1)
audioSpectrom.animateAudioVisualizerWithChannel(level0: -lowResults, level1: -lowResults1)
}
OUTPUT
Without animation
With animation
In my observation, the height value and width value of frame changed when rotates. Means when I give CGSize(width: 4, height: 4) to bar, then when I rotate using some angle it changes the size of frame like CGSize(width: 3.563456, height: 5.67849) (not sure for the value, it's an assumption).
How to resolve this problem?
Any suggestions or answers will be appreciated.
Edit
func animateAudioVisualizerWithChannel(level0: Float, level1: Float ) {
DispatchQueue.main.async {
UIView.animateKeyframes(withDuration: self.animateDuration, delay: 0, options: .beginFromCurrentState, animations: {
for i in 0..<self.barsNumber {
let channelValue: Int = Int(arc4random_uniform(2))
let wavePeak: Int = Int(arc4random_uniform(UInt32(self.waveFormArray[i])))
var barView = self.rectArray[i] as? CustomView
guard let barViewUn = barView else { return }
let barH = (self.frame.height / 2 ) - self.radius
let scaled0 = (CGFloat(level0) * barH) / 60
let scaled1 = (CGFloat(level1) * barH) / 60
let calc0 = barH - scaled0
let calc1 = barH - scaled1
let kSavedTransform = barViewUn.transform
barViewUn.transform = .identity
if channelValue == 0 {
barViewUn.frame.size.height = calc0
} else {
barViewUn.frame.size.height = calc1
}
if barViewUn.frame.height < CGFloat(4) || barViewUn.frame.height > ((self.frame.size.height / 2) - self.radius) {
barViewUn.frame.size.height = self.initialBarHeight + CGFloat(wavePeak)
}
barViewUn.transform = kSavedTransform
barView = barViewUn
}
}, completion: nil)
}
}
Output
Run the below code snippet show the output
<img src="https://i.imgflip.com/227xsa.gif" title="made at imgflip.com"/>
GOT IT!!
circular-visualizer
There are two (maybe three) issues in your code:
1. audioSpectrom.layoutSubviews()
You create new views in layoutSubviews and add them to the view hierarchy. This is not what you are intened to do, because layoutSubviews is called multiple times and you should use it only for layouting purposes.
As a dirty work-around, I modified the code in the func drawVisualizer to only add the bars once:
func drawVisualizer() {
// ... some code here
// ...
mainLayer.addSublayer(circleShapeLayer)
// This will ensure to only add the bars once:
guard rectArray.count == 0 else { return } // If we already have bars, just return
// Draw Bars
rectArray = [CustomView]()
// ... Rest of the func
}
Now, it almost looks good, but there are still some dirt effects with the topmost bar. So you'll have to change
2. audioSectrom.animateAudioVisualizerWithChannel(level0:level1:)
Here, you want to recalculate the frame of the bars. Since they are rotated, the frame also is rotated, and you'd have to apply some mathematical tricks. To avoid this adn make your life more easy, you save the rotated transform, set it to .identity, modify the frame, and then restore the original rotated transform. Unfortunately, this causes some dirt effects with rotations of 0 or 2pi, maybe caused by some rounding issues. Never mind, there is a much more simple solution:
Instead of modifiying the frame, you better modify the bounds.
frame is measured in the outer (in your case: rotated) coordinate system
bounds is measured in the inner (non-transformed) coordinate system
So I simply replaced all the frames with bounds in the function animateAudioVisualizerWithChannel and also removed the saving and restoring of the transformation matrix:
func animateAudioVisualizerWithChannel(level0: Float, level1: Float ) {
// some code before
guard let barViewUn = barView else { return }
let barH = (self.bounds.height / 2 ) - self.radius
let scaled0 = (CGFloat(level0) * barH) / 60
let scaled1 = (CGFloat(level1) * barH) / 60
let calc0 = barH - scaled0
let calc1 = barH - scaled1
if channelValue == 0 {
barViewUn.bounds.size.height = calc0
} else {
barViewUn.bounds.size.height = calc1
}
if barViewUn.bounds.height < CGFloat(4) || barViewUn.bounds.height > ((self.bounds.height / 2) - self.radius) {
barViewUn.bounds.size.height = self.initialBarHeight + CGFloat(wavePeak)
}
barView = barViewUn
// some code after
}
3. Warnings
By the way, you should get rid of all the warnings in your code. I didn't clean up my answer code to keep it comparable with the orginal code.
For example, in var barView = self.rectArray[i] as? CustomView you don't need the conditional cast, because the array already contains CustomView objects.
So, all the barViewUn stuff is unnecessary.
Much more to find and to clean up.

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)
}

Sparkle effect iOS

I want to create sparkle effect over an image as shown in the video Sparkle Effect
the only way I could think of doing it animating each particle separately using core animation, but that would be inefficient as well as time consuming.
Is there any other way I can do the same?
Here is a solution from Erica Susan's cook book. See this works for you.
You can add visual interest to your interfaces by using emitters in tandem with user touches. The following class demonstrates how to follow a touch over its lifetime, adding a little sparkle to wherever the user touches on-screen.
The class begins as soon as the user touches the screen, creating an emitter layer and a single emitter cell. The cell defines the particles — their color, their birth rate, their lifetime, velocity, and so forth.
As the user's touch progresses, this class updates the emitter's location, removing the emitter once the touch is removed from the screen. Although this example is written for single touch interaction, you can easily update the code to add an array of emitters (rather than a single instance) for multi-touch interaction.
Emitters are easily added to your projects and efficient to run. While too much animation is never a good design idea, a little sparkle used judiciously can add life and movement.
#interface SparkleTouchView : UIView {
CAEmitterLayer *emitter;
}
#end
#implementation SparkleTouchView
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
float multiplier = 0.25f;
CGPoint pt = [[touches anyObject] locationInView:self];
//Create the emitter layer
emitter = [CAEmitterLayer layer];
emitter.emitterPosition = pt;
emitter.emitterMode = kCAEmitterLayerOutline;
emitter.emitterShape = kCAEmitterLayerCircle;
emitter.renderMode = kCAEmitterLayerAdditive;
emitter.emitterSize = CGSizeMake(100 * multiplier, 0);
//Create the emitter cell
CAEmitterCell* particle = [CAEmitterCell emitterCell];
particle.emissionLongitude = M_PI;
particle.birthRate = multiplier * 1000.0;
particle.lifetime = multiplier;
particle.lifetimeRange = multiplier * 0.35;
particle.velocity = 180;
particle.velocityRange = 130;
particle.emissionRange = 1.1;
particle.scaleSpeed = 1.0; // was 0.3
particle.color = [[COOKBOOK_PURPLE_COLOR colorWithAlphaComponent:0.5f] CGColor];
particle.contents = (__bridge id)([UIImage imageNamed:#"spark.png"].CGImage);
particle.name = #"particle";
emitter.emitterCells = [NSArray arrayWithObject:particle];
[self.layer addSublayer:emitter];
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint pt = [[touches anyObject] locationInView:self];
// Disable implicit animations
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
emitter.emitterPosition = pt;
[CATransaction commit];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[emitter removeFromSuperlayer];
emitter = nil;
}
- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
#end
Don't forget to create a png file named spark.png in order to create the animation.
/*Tested in swift 5.1*/
Import UIKit
enum Images {
static let box = UIImage(named: "Box")!
static let triangle = UIImage(named: "Triangle")!
static let circle = UIImage(named: "Circle")!
static let swirl = UIImage(named: "Spiral")!
}
class ActivationRequiredViewController: UIViewController {
var emitter = CAEmitterLayer()
var colors:[UIColor] = [
Colors.red,
Colors.blue,
Colors.green,
Colors.yellow
]
var images:[UIImage] = [
Images.box,
Images.triangle,
Images.circle,
Images.swirl
]
var velocities:[Int] = [
100,
90,
150,
200
]
override func viewDidLoad() {
super.viewDidLoad()
startSparkle()
}
func startSparkle() {
emitter.emitterPosition = CGPoint(x: self.view.frame.size.width / 2, y: -10)
emitter.emitterShape = CAEmitterLayerEmitterShape.line
emitter.emitterSize = CGSize(width: self.view.frame.size.width, height: 2.0)
emitter.emitterCells = generateEmitterCells()
self.view.layer.addSublayer(emitter)
}
private func generateEmitterCells() -> [CAEmitterCell] {
var cells:[CAEmitterCell] = [CAEmitterCell]()
for index in 0..<16 {
let cell = CAEmitterCell()
cell.birthRate = 4.0
cell.lifetime = 14.0
cell.lifetimeRange = 0
cell.velocity = CGFloat(getRandomVelocity())
cell.velocityRange = 0
cell.emissionLongitude = CGFloat(Double.pi)
cell.emissionRange = 0.5
cell.spin = 3.5
cell.spinRange = 0
cell.color = getNextColor(i: index)
cell.contents = getNextImage(i: index)
cell.scaleRange = 0.25
cell.scale = 0.1
cells.append(cell)
}
return cells
}
private func getRandomVelocity() -> Int {
return velocities[getRandomNumber()]
}
private func getRandomNumber() -> Int {
return Int(arc4random_uniform(4))
}
private func getNextColor(i:Int) -> CGColor {
if i <= 4 {
return colors[0].cgColor
} else if i <= 8 {
return colors[1].cgColor
} else if i <= 12 {
return colors[2].cgColor
} else {
return colors[3].cgColor
}
}
private func getNextImage(i:Int) -> CGImage {
return images[i % 4].cgImage!
}
}
For anyone doing a Mac app, here's the same concept as #thevikasnayak , for an NSViewController
import Cocoa
enum Images {
static let box = NSImage(named: "Box")!
static let triangle = NSImage(named: "Triangle")!
static let circle = NSImage(named: "Circle")!
static let swirl = NSImage(named: "Spiral")!
}
class SparkleVC: NSViewController {
var emitter = CAEmitterLayer()
var colors:[NSColor] = [
NSColor.blue.withAlphaComponent(0.1),
NSColor.blue.withAlphaComponent(0.2),
NSColor.blue.withAlphaComponent(0.3),
NSColor.blue.withAlphaComponent(0.4)]
var images:[NSImage] = [Images.box, Images.triangle, Images.circle, Images.swirl]
override func viewDidAppear() {
super.viewDidAppear()
view.wantsLayer = true
startSparkle()
}
func startSparkle() {
emitter.emitterPosition = CGPoint(x: view.bounds.size.width / 2.0, y: view.bounds.size.height / 2.0)
emitter.emitterShape = CAEmitterLayerEmitterShape.circle
emitter.emitterSize = CGSize(width: view.bounds.size.width / 4.0, height: view.bounds.size.width / 4.0)
emitter.emitterCells = generateEmitterCells()
view.layer!.addSublayer(emitter)
}
private func generateEmitterCells() -> [CAEmitterCell] {
var cells:[CAEmitterCell] = [CAEmitterCell]()
for index in 0..<16 {
let cell = CAEmitterCell()
cell.birthRate = 200.0
cell.lifetime = 0.01
cell.lifetimeRange = 0.03
cell.velocity = 5
cell.velocityRange = 2
cell.emissionLatitude = Double.random(in: 0 ..< Double.pi)
cell.emissionLongitude = Double.random(in: 0 ..< Double.pi)
cell.emissionRange = Double.pi
cell.spin = 10
cell.spinRange = 10
cell.color = colors[index/4].cgColor
cell.contents = nextImage(i: index)
cell.scaleRange = 0.2
cell.scale = 1
cells.append(cell)
}
return cells
}
private func nextImage(i:Int) -> CGImage {
return (images[i % 4]).cgImage(forProposedRect: nil, context: nil, hints: nil)!
}
}
Please check this solution.
https://github.com/GabriellaQiong/CIS581-Project2-Image-Morphing
CIwarpKernel Kets you warp an image according to reference points. That is exactly what you want to do in order to have image morphing.

Resources