How to asynchronous load image from a web-server in UICollectionView using NSCache - uicollectionview

I have some issues when loading images from a web-server in UICollectionView using NScache.
The problem:
The images are not proper displayed:
sometimes they are not showned in the corresponding cell
or
the image is changing on scroll
Situation:
I have 3 arrays whitch are properly loaded from the web-server in function viewDidLoad(). These arrays are: vPrice, vTitle and vImages_api
my custom class for cell have:
label for price: cell.lblPrice
label for title: cell.lblTitle
image: cell.imgPicture
I belive that the problem is in function func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
It could be related either to the way I use NSCache or to the way I use and when I use DispatchQueue.
The code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = self.collectionViewPRODUSE.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as! clsCustomCell
cell.lblPrice.text = vPrice[indexPath.item]
cell.lblTitle.text = vTitle[indexPath.item]
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.layer.borderWidth = 0.5
DispatchQueue.global(qos: .userInitiated).async {
//background thread
let ImageString = self.vImages_api[indexPath.item]
let imageUrl = URL(string: ImageString)
let imageData = NSData(contentsOf: imageUrl!)
// main thread to update the UI
DispatchQueue.main.async {
let key1 = self.vImages_api[indexPath.item] as AnyObject
//if i saved allready my image in cache, then i will load my image from cache
if let imageFromCache = self.objCache.object(forKey: key1){
cell.imgPicture.image = imageFromCache as! UIImage
}
else{//if my image is not in cache ......
if imageData != nil {
let myPicture = UIImage(data: imageData! as Data)
cell.imgPicture.image = myPicture
//save my image in cache
self.objCache.setObject(myPicture!, forKey: ImageString as AnyObject)
}
}
}
}
return cell
}
Edited code - version II:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = self.collectionViewPRODUSE.dequeueReusableCell(withReuseIdentifier: "MyCustomCell", for: indexPath) as! clsCustomCell
cell.lblPret.text = vPrice[indexPath.item]
cell.lblTitlu.text = vTitle[indexPath.item]
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.layer.borderWidth = 0.5
let key1 = self.vImages_api[indexPath.item] as AnyObject
if let imageFromCache = self.objCache.object(forKey: key1){
cell.imgPicture.image = imageFromCache as! UIImage
}else{
DispatchQueue.global(qos: .background).async {
let ImageString = self.vImages_api[indexPath.item]
let imageUrl = URL(string: ImageString)
let imageData = NSData(contentsOf: imageUrl!)
let myPicture = UIImage(data: imageData! as Data)
self.objCache.setObject(poza!, forKey: ImageString as AnyObject)
DispatchQueue.main.async {
if imageData != nil {
cell.imgPicture.image = myPicture
}
}
}
}
return cell
}
Edited code - version III
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = self.collectionViewPRODUSE.dequeueReusableCell(withReuseIdentifier: "celula_custom", for: indexPath) as! clsCustomCell
cell.lblPret.text = vPrice[indexPath.item]
cell.lblTitlu.text = vTitle[indexPath.item]
NKPlaceholderImage(image: UIImage(named: "loading"), imageView: cell.imgPicture, imgUrl: self.vImages_api[indexPath.item]
) { (image11) in
cell.imgPicture.image = image11
}
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.layer.borderWidth = 0.5
return cell
}

Try this one it's Working code (Swift 4).
func NKPlaceholderImage(image:UIImage?, imageView:UIImageView?,imgUrl:String,compate:#escaping (UIImage?) -> Void){
if image != nil && imageView != nil {
imageView!.image = image!
}
var urlcatch = imgUrl.replacingOccurrences(of: "/", with: "#")
let documentpath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
urlcatch = documentpath + "/" + "\(urlcatch)"
let image = UIImage(contentsOfFile:urlcatch)
if image != nil && imageView != nil
{
imageView!.image = image!
compate(image)
}else{
if let url = URL(string: imgUrl){
DispatchQueue.global(qos: .background).async {
() -> Void in
let imgdata = NSData(contentsOf: url)
DispatchQueue.main.async {
() -> Void in
imgdata?.write(toFile: urlcatch, atomically: true)
let image = UIImage(contentsOfFile:urlcatch)
compate(image)
if image != nil {
if imageView != nil {
imageView!.image = image!
}
}
}
}
}
}
}
Use Like this :
// Here imgPicture = your imageView and UIImage(named: "placeholder") is Display image brfore download actual image.
imgPicture.image = nil
NKPlaceholderImage(image: UIImage(named: "placeholder"), imageView: imgPicture, imgUrl: "Put Here your server image Url Sting") { (image) in }

Related

How to return a array of names in uicollectionview cells swift 4

I am trying to insert an array of names into each of the cells as a test, but I keep getting an error when i type Example.name.text ECT. How do I return the array of names for each cell to have a name in it? Does the same work for images? My goal is to return UIImage so that each cell contains the next image in the UIImages.
class TestPage: UIViewController, UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout {
var collectView: UICollectionView!
var cellId = "Cell"
override func viewDidLoad() {
super.viewDidLoad()
let layout: UICollectionViewFlowLayout =
UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0,
right: 0)
layout.itemSize = CGSize(width: view.frame.width, height: 80)
collectView = UICollectionView(frame: self.view.frame,
collectionViewLayout: layout)
collectView.dataSource = self
collectView.delegate = self
collectView.register(Example.self, forCellWithReuseIdentifier:
cellId)
collectView.showsVerticalScrollIndicator = false
collectView.backgroundColor = UIColor.white
self.view.addSubview(collectView)
}
let listOfNames = ["John", "Smith", "Doe"]
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return listOfNames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt
indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectView.dequeueReusableCell(withReuseIdentifier:
cellId, for: indexPath) as! Example
return cell
}
}
class Example: UICollectionViewCell {
let name: UILabel = {
let lb = UILabel()
lb.text = "This is a test"
lb.translatesAutoresizingMaskIntoConstraints = false
return lb
}()
// let aMajorScale: UIImageView = {
// let imgV = UIImageView()
// imgV.image = UIImage(named: "Amajorscale")
// imgV.contentMode = .scaleToFill
// imgV.translatesAutoresizingMaskIntoConstraints = false
// return imgV
// }()
// let bMajorScale: UIImageView = {
// let imgV = UIImageView()
// imgV.image = UIImage(named: "bmajorscale")
// imgV.contentMode = .scaleToFill
// imgV.translatesAutoresizingMaskIntoConstraints = false
// return imgV
// }()
//
// let cMajorScale: UIImageView = {
// let imgV = UIImageView()
// imgV.image = UIImage(named: "c-major-scale")
// imgV.contentMode = .scaleToFill
// imgV.translatesAutoresizingMaskIntoConstraints = false
// return imgV
// }()
//
// let dMajorScale: UIImageView = {
// let imgV = UIImageView()
// imgV.image = UIImage(named: "d-major-scale")
// imgV.contentMode = .scaleToFill
// imgV.translatesAutoresizingMaskIntoConstraints = false
// return imgV
// }()
//
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
}
func addViews(){
addSubview(name)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat:
"H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views:
["v0" : name]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat:
"V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: [ .
"v0" : name]))
}

UICollectionView cells not updating after reload

self.filterCollectionView.reloadData()
After calling reload data the visible cell's cellforItem is called but my cell values are not updating until I scroll back and forth
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
cell.backgroundColor = UIColor.clearColor()
cell.selected = false
cell.alpha = 1.0
for each in cell.contentView.subviews{
each.removeFromSuperview()
}
let title = UILabel(frame: CGRectMake(0, 0, cell.bounds.size.width, 40))
title.text = filterArray[indexPath.row]
title.textColor = UIColor.blueColor()
title.textAlignment = .Center
cell.layer.cornerRadius = 4.0
cell.contentView.addSubview(title)
return cell
}

UICollectionView - Horizontal AutoScroll with Timer

I am using Horizontal Auto Scroll using timer. I want it to scroll one by one.
In my case it is scrolling continuos from left to right. and in last it also scroll white space after last cell.
#interface AccountsVC ()<UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout>
{
CGFloat width_Cell;
NSTimer *autoScrollTimer;
}
- (void)viewDidLoad
{
[super viewDidLoad];
width_Cell = 0.0;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self configAutoscrollTimer];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self deconfigAutoscrollTimer];
}
- (void)configAutoscrollTimer
{
autoScrollTimer = [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
}
- (void)deconfigAutoscrollTimer
{
[autoScrollTimer invalidate];
autoScrollTimer = nil;
}
- (void)onTimer
{
[self autoScrollView];
}
- (void)autoScrollView
{
CGPoint initailPoint = CGPointMake(width_Cell, 0);
if (CGPointEqualToPoint(initailPoint, self.collectionView.contentOffset))
{
if (width_Cell < self.collectionView.contentSize.width)
{
width_Cell += 0.5;
}else
{
width_Cell = -self.view.frame.size.width;
}
CGPoint offsetPoint = CGPointMake(width_Cell, 0);
self.collectionView.contentOffset = offsetPoint;
}else
{
width_Cell = self.collectionView.contentOffset.x;
}
}
In the custom class of your collectionViewCell:
self.contentView.frame = self.bounds;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
and remove any other width calculations.
I am using like this
declare variable
var timer:Timer!
call from viewDidLoad
self.addTimer()
func addTimer() {
let timer1 = Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.nextPage), userInfo: nil, repeats: true)
RunLoop.main.add(timer1, forMode: RunLoopMode.commonModes)
self.timer = timer1
}
func resetIndexPath() -> IndexPath {
let currentIndexPath = self.collectionView.indexPathsForVisibleItems.last
let currentIndexPathReset = IndexPath(item: (currentIndexPath?.item)!, section: 0)
self.collectionView.scrollToItem(at: currentIndexPathReset, at: UICollectionViewScrollPosition.left, animated: true)
return currentIndexPath!
}
func removeTimer() {
if self.timer != nil {
self.timer.invalidate()
}
self.timer = nil
}
func nextPage() {
let currentIndexPathReset:IndexPath = self.resetIndexPath()
var nextItem = currentIndexPathReset.item + 1
let nextSection = currentIndexPathReset.section
if nextItem == productImage.count{
nextItem = 0
}
var nextIndexPath = IndexPath(item: nextItem, section: nextSection)
if nextItem == 0 {
self.collectionView.scrollToItem(at: nextIndexPath, at: UICollectionViewScrollPosition.left, animated: false)
}
self.collectionView.scrollToItem(at: nextIndexPath, at: UICollectionViewScrollPosition.left, animated: true)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
self.addTimer()
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.removeTimer()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var visibleRect = CGRect()
visibleRect.origin = collectionView.contentOffset
visibleRect.size = collectionView.bounds.size
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath: IndexPath? = collectionView.indexPathForItem(at: visiblePoint)
pageControlView.currentPage = (visibleIndexPath?.row)!
}

Remove a UIView stored in an array from superview

I know the code I need is [[Array objectAtIndex:index] removeFromSuperview]; but that doesn't work in Swift 2.
I'm trying to remove a UIView stored in an array from the superview. I know I want to remove the object at index 0. Want to remove it at the end of the for loop.
What code will work in Swift?
import UIKit
import Foundation
class PhotoViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var tileStack = [AnyObject]()
let num = 0
//Beginning of simple image selection and display
#IBOutlet weak var displayImageView: UIImageView!
#IBOutlet weak var tiledView: UIImageView!
#IBAction func choosePicFromLibrary(sender: AnyObject) {
let imagePicker: UIImagePickerController = UIImagePickerController()
imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
imagePicker.delegate = self
imagePicker.modalPresentationStyle = UIModalPresentationStyle.Popover
if (imagePicker.popoverPresentationController != nil) {
imagePicker.popoverPresentationController!.sourceView = sender as! UIButton
imagePicker.popoverPresentationController!.sourceRect = (sender as! UIButton).bounds
}
presentViewController(imagePicker, animated: true, completion: nil)
}
#IBAction func takePhoto(sender: AnyObject) {
let imagePicker: UIImagePickerController = UIImagePickerController()
imagePicker.sourceType = UIImagePickerControllerSourceType.Camera
imagePicker.delegate = self
imagePicker.modalPresentationStyle = UIModalPresentationStyle.Popover
if (imagePicker.popoverPresentationController != nil) {
imagePicker.popoverPresentationController!.sourceView = sender as! UIButton
imagePicker.popoverPresentationController!.sourceRect = (sender as! UIButton).bounds
}
presentViewController(imagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
dismissViewControllerAnimated(true, completion: nil)
displayImageView.image = info[UIImagePickerControllerOriginalImage] as! UIImage!
}
func imagePickerControllerDidCancel(picker: UIImagePickerController) {
dismissViewControllerAnimated(true, completion: nil)
}
//Beginning of function to cut photo into 9 tiles, then display them randomly.
//cut selected image into 9 pieces and add each cropped image to tileStack array
#IBAction func randomize(sender: AnyObject) {
let selectedImageWidth = displayImageView.image!.size.width
let selectedImageHeight = displayImageView.image!.size.height
let tileSize = CGSizeMake(selectedImageWidth/3, selectedImageHeight/3)
for (var colI = 0; colI < 3; colI++)
{
for (var rowI = 0; rowI < 3; rowI += 1)
{
let tileRect = CGRectMake(CGFloat(rowI) * tileSize.width, tileSize.height * CGFloat(colI), tileSize.width, tileSize.height)
if let selectedImage = displayImageView.image
{
let tileImage = CGImageCreateWithImageInRect(selectedImage.CGImage, tileRect)
let aUItile = UIImage(CGImage: tileImage!)
tileStack.append(aUItile)
}
}
}
//display tiles in order on screen, remove 1 tile (top left, index 0), (eventually mix them up)
let frameWidth = self.view.frame.width
let frameHeight = self.view.frame.height
var xCen = (frameWidth/3)/2
var yCen = (frameHeight/3)/2
var pieceNumber = 0
for (var v = 0; v < 3; v += 1)
{
for (var h = 0; h < 3; h += 1)
{
var tiledView : UIImageView
tiledView = UIImageView(frame:CGRectMake(0, 0, frameWidth/3, (frameHeight)/3))
//tiledView.backgroundColor = UIColor.redColor()
tiledView.center = CGPointMake(xCen, yCen)
tiledView.image = tileStack[pieceNumber] as? UIImage
tiledView.userInteractionEnabled = true
self.view.addSubview(tiledView)
xCen += (frameWidth/3)
pieceNumber += 1
}
xCen = (frameWidth/3)/2
yCen += (frameHeight/3)
}
tileStack[0].removeFromSuperview()
tileStack.removeAtIndex(0)
}
In Swift 2.0, you need to call the removeFromSubview from the object that you want to remove. In your case you need the following:
dispatch_async(dispatch_get_main_queue(),
{
if self.view.subviews.count > 0
{
self.view.subviews[0].removeFromSuperview()
}
})

How do I animate MKAnnotationView drop?

I have a custom MKAnnotationView where I set my image myself in viewForAnnotation. How do I animate it's drop like I can with MKPinAnnotationView?
My code is
- (MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *AnnotationViewID = #"annotationViewID";
MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
if (annotationView == nil)
{
annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID] autorelease];
}
annotationView.image = [UIImage imageNamed:#"blah.png"];
annotationView.annotation = annotation;
return annotationView;
}
One problem with the code above by Anna Karenina is that it doesn't deal with when you add annotations below where the user is looking at the moment. Those annotations will float in mid-air before dropping because they are moved into the user's visible map rect.
Another is that it also drops the user location blue dot. With this code below, you handle both user location and large amounts of map annotations off-screen. I've also added a nice bounce ;)
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
MKAnnotationView *aV;
for (aV in views) {
// Don't pin drop if annotation is user location
if ([aV.annotation isKindOfClass:[MKUserLocation class]]) {
continue;
}
// Check if current annotation is inside visible map rect, else go to next one
MKMapPoint point = MKMapPointForCoordinate(aV.annotation.coordinate);
if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
continue;
}
CGRect endFrame = aV.frame;
// Move annotation out of view
aV.frame = CGRectMake(aV.frame.origin.x, aV.frame.origin.y - self.view.frame.size.height, aV.frame.size.width, aV.frame.size.height);
// Animate drop
[UIView animateWithDuration:0.5 delay:0.04*[views indexOfObject:aV] options: UIViewAnimationOptionCurveLinear animations:^{
aV.frame = endFrame;
// Animate squash
}completion:^(BOOL finished){
if (finished) {
[UIView animateWithDuration:0.05 animations:^{
aV.transform = CGAffineTransformMakeScale(1.0, 0.8);
}completion:^(BOOL finished){
if (finished) {
[UIView animateWithDuration:0.1 animations:^{
aV.transform = CGAffineTransformIdentity;
}];
}
}];
}
}];
}
}
Implement the didAddAnnotationViews delegate method and do the animation yourself:
- (void)mapView:(MKMapView *)mapView
didAddAnnotationViews:(NSArray *)annotationViews
{
for (MKAnnotationView *annView in annotationViews)
{
CGRect endFrame = annView.frame;
annView.frame = CGRectOffset(endFrame, 0, -500);
[UIView animateWithDuration:0.5
animations:^{ annView.frame = endFrame; }];
}
}
#MrAlek's Answer for swift3
optional func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
print(#function)
var i = -1;
for view in views {
i += 1;
if view.annotation is MKUserLocation {
continue;
}
// Check if current annotation is inside visible map rect, else go to next one
let point:MKMapPoint = MKMapPointForCoordinate(view.annotation!.coordinate);
if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
continue;
}
let endFrame:CGRect = view.frame;
// Move annotation out of view
view.frame = CGRect(origin: CGPoint(x: view.frame.origin.x,y :view.frame.origin.y-self.view.frame.size.height), size: CGSize(width: view.frame.size.width, height: view.frame.size.height))
// Animate drop
let delay = 0.03 * Double(i)
UIView.animate(withDuration: 0.5, delay: delay, options: UIViewAnimationOptions.curveEaseIn, animations:{() in
view.frame = endFrame
// Animate squash
}, completion:{(Bool) in
UIView.animate(withDuration: 0.05, delay: 0.0, options: UIViewAnimationOptions.curveEaseInOut, animations:{() in
view.transform = CGAffineTransform(scaleX: 1.0, y: 0.6)
}, completion: {(Bool) in
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions.curveEaseInOut, animations:{() in
view.transform = CGAffineTransform.identity
}, completion: nil)
})
})
}
}
#mrAlek answer in Swift:
func mapView(mapView: MKMapView!, didAddAnnotationViews views: [AnyObject]!) {
println("didAddAnnotationViews()")
var i = -1;
for view in views {
i++;
let mkView = view as! MKAnnotationView
if view.annotation is MKUserLocation {
continue;
}
// Check if current annotation is inside visible map rect, else go to next one
let point:MKMapPoint = MKMapPointForCoordinate(mkView.annotation.coordinate);
if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
continue;
}
let endFrame:CGRect = mkView.frame;
// Move annotation out of view
mkView.frame = CGRectMake(mkView.frame.origin.x, mkView.frame.origin.y - self.view.frame.size.height, mkView.frame.size.width, mkView.frame.size.height);
// Animate drop
let delay = 0.03 * Double(i)
UIView.animateWithDuration(0.5, delay: delay, options: UIViewAnimationOptions.CurveEaseIn, animations:{() in
mkView.frame = endFrame
// Animate squash
}, completion:{(Bool) in
UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations:{() in
mkView.transform = CGAffineTransformMakeScale(1.0, 0.6)
}, completion: {(Bool) in
UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations:{() in
mkView.transform = CGAffineTransformIdentity
}, completion: nil)
})
})
}
}
#MrAlek's answer in Swift 2:
func mapView(mapView: MKMapView, didAddAnnotationViews views: [MKAnnotationView]) {
print(__FUNCTION__)
var i = -1;
for view in views {
i++;
if view.annotation is MKUserLocation {
continue;
}
// Check if current annotation is inside visible map rect, else go to next one
let point:MKMapPoint = MKMapPointForCoordinate(view.annotation!.coordinate);
if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
continue;
}
let endFrame:CGRect = view.frame;
// Move annotation out of view
view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y - self.view.frame.size.height, view.frame.size.width, view.frame.size.height);
// Animate drop
let delay = 0.03 * Double(i)
UIView.animateWithDuration(0.5, delay: delay, options: UIViewAnimationOptions.CurveEaseIn, animations:{() in
view.frame = endFrame
// Animate squash
}, completion:{(Bool) in
UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations:{() in
view.transform = CGAffineTransformMakeScale(1.0, 0.6)
}, completion: {(Bool) in
UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations:{() in
view.transform = CGAffineTransformIdentity
}, completion: nil)
})
})
}
}
Updated for Swift 4.2
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
var i = -1;
for view in views {
i += 1;
if view.annotation is MKUserLocation {
continue;
}
let point:MKMapPoint = MKMapPoint(view.annotation!.coordinate);
if (!self.mapView.visibleMapRect.contains(point)) {
continue;
}
let endFrame:CGRect = view.frame;
view.frame = CGRect(origin: CGPoint(x: view.frame.origin.x,y :view.frame.origin.y-self.view.frame.size.height), size: CGSize(width: view.frame.size.width, height: view.frame.size.height))
let delay = 0.03 * Double(i)
UIView.animate(withDuration: 0.5, delay: delay, options: UIView.AnimationOptions.curveEaseIn, animations:{() in
view.frame = endFrame
}, completion:{(Bool) in
UIView.animate(withDuration: 0.05, delay: 0.0, options: UIView.AnimationOptions.curveEaseInOut, animations:{() in
view.transform = CGAffineTransform(scaleX: 1.0, y: 0.6)
}, completion: {(Bool) in
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions.curveEaseInOut, animations:{() in
view.transform = CGAffineTransform.identity
}, completion: nil)
})
})
}
}
Rather than implementing mapView(_:didAdd:) in the MKMapViewDelegate, you can also have the annotation view do the animation itself.
class CustomAnnotationView: MKAnnotationView {
override var annotation: MKAnnotation? { didSet { update(for: annotation) } }
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
update(for: annotation)
}
override func prepareForReuse() {
super.prepareForReuse()
removeFromSuperview()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
transform = CGAffineTransform(translationX: 0, y: -100)
alpha = 0
UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.4) {
self.transform = .identity
self.alpha = 1
}.startAnimation()
}
private func update(for annotation: MKAnnotation?) {
// do whatever update to the annotation view you want, if any
}
}
This is useful for avoiding the cluttering of one’s view controller with annotation view animations. E.g. in iOS 11 and later, you might do:
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
And then you can add an annotation to your map view, and you get this animation of the annotation view without any more code in the view controller.
This particular animation is a drop with a little bounce at the end, but obviously you can do whatever animation you want.
The above was written in Swift, but the concept is equally valid in Objective-C, too.