2012-04-09

Instagram + Facebook



News of the day, Social media giant Facebook has acquired Instagram for $1 Billion USD.

As expected, the news rock all social media enthusiasts. I heard more dismay than satisfaction, however. (Sorry, our mighty Mark, despite people use your service, they don't like it) Some of you may noticed, the sharing from Instagram to Facebook has changed a bit not so long ago, from a URL posting to Photo uploading. Now we know it's a hint we all missed.

From Facebook point of view, possibly they want to use Instagram as a gateway of Facebook photo uploading. Instagram, of course, have preconfigured filters for their users to enrich their photo, which Facebook can take advantage of. But more important is, Instagram has a very loyal user-based, especially the iOS users (and I have guessed it wrong, Android users have accepted it well since the app launched in their platform, gosh...). Many of them are really happy to share photo on Instagram, but not Facebook.

In some way, different social media services have their own position or function in the social media universe. Some people shared their daily photo (e.g. Baby, Food, Dessert... ) in Facebook. And they saved their artistic shots only for Instagram. That's what I do. It sounds funny but true that I don't want to share those to some of my Facebook friends.

But the mighty Mark probably has another vision. Facebook does not only target to a specific position. Facebook has to be everything. We have been watching Facebook put their palm on every way that the social media universe has ever expanded into. Status, Content Sharing, Likes, Location, Timeline... Now maybe Mark wants all your artistic photo as well. He wanna draw every photo from you onto their social media empire.

I won't be surprised if Instagram had the "Share to Facebook" button missing later. It means, it shares all your posting to Facebook by default.

Not so long ago, we had a news that Instagram finally decided to open their Media Posting API. This API has been missing with a reason that Instagram wanna take control on the photo quality. If they open this API, it means photo posting not necessarily go through their App. And it means third-party App can post freely without control. Those third-party uploaded photo may not as coherent in look and feel of the Instagram-ish photo. This could easily pollute your Instagram feed.

However, they started to open the posting API to certain service. Hipstagram partnered with Instagram so they can grant the Posting API access. Guesses are this posting API will get more open. To other partnered service in the first stage, for example.

Now we have Facebook kicked-in.

My guess is, Instagram will be more open than ever, and be real quick. I used to think Instagram will keep their Hipstagram model, you need to have your services approved by them, to make sure your service won't post some too-low quality photo to Instagram. But now they are under Facebook. Facebook is all about statistic. It's quantity over quality. They probably won't care much if the photo looks good. They just want people keep posting to Instagram, and comes to Facebook eventually.

"They are all mine now!"

Now, share you some (not so) confidential information. I have been developing an Instagram API iOS project which I would like to open source after my Instagram App completed. I have reached a certain bottleneck at the moment (on graphic design). But anyway, Appstore submission is targeted at the end of May, see if I can make the API project launch in July... Not sure if it will be affected by the acquisition. lol

As always, don't panic, happy coding.

2012-04-04

Animated Circle on MKMapView



When your app needs to specific a region on a MKMapView, high chance is your client/customer will ask if you could provide an animated circle similar to the one in the Maps App used for indicating the current location.

Moreover, what you are doing is possibly trying to indicate a region estimation, that you need the circle can re-scale with the zoom-in and zoom-out of the MKMapView, also animate the size a little bit to indicate the estimation.

Do a quick google search, you may find many suggestions, possibly you can't find any concrete solution or example, however.

Let's understand what's going on first. (Or you are too tired to read. Source code is available in the YHMapDemo repository. Make sure you read "Using my code" section first.)

First of all, the animated circle used in the Maps App is not available in the iOS SDK at the moment. It's not in the scope of the public API.

The immediate direction for you can think of, out of many possible solutions, will be subclass the MKOverlayView or MKCircleView and do some magic on that. Yes, I agree with you.

Basically, the most straight forward way is about overriding the drawMapRect:zoomScale:inContext: method and draw what you need. This is the way to do. Many people has been doing this way for custom map overlay I'm pretty sure.

However, things get complicated when it comes to animation.

You can of course convert the size of the circle from pixel to map coordinate (MKMapPoint and MKMapRect), with respect to a scale value changing along with a NSTimer. However, you will encounter a lot of threading issues, which the drawing of circle becomes out of control. Also, the redraw can also be very sluggish.

And if you zoom into the map which your circle cover a lot of space on the map, you will surely address a lot of drawing artifacts. (Due to the map redraws on a lot of small tiles one by one, instead of the whole map at once, your circle will flicker and some squares flashing within).

Issues:
  • Sluggish redraw by NSTimer
  • Threading issue when map redraw
  • Redraw tiling artifact at zoom in
Holy shhh... No, as always, don't panic!

The solution I would like to provide to you, is actually rather simple (everything is simple if it's telling to you by someone else. Ask me how much time I come up with this in private lol): by adding an UIImageView on the MKCircleView subclass (you may change to MKOverlayView on your needs, I want the radius property from MKCircle here, though) . We calculate the size respect to the map coordinate, and using Core Animation to provide the animation you need. (Animate the UIImageView with many images? How much images you will need to prepare for a smooth resize animation? Just saying...)

All the magic is done inside YHAnimatedCircleView, a subclass of MKCircleView which basically do all the specific tricks we have discussed. (The way to use this class is exactly the same as MKCircleView)

Advantages:
  • Seamless animation by Core Animation
  • Perfect performance on any zoom level



Looks promising to you? Let's dig into the code.

On wait. I assume you know all the basic about using the MKMapView, i.e. set up the MKMapView, adding annotation and adding MKCircle as overlay (Learn it here, otherwise). I'll go straight into the YHAnimatedCircleView.

First of all, let's take a look on the helper function first.



-(CGRect)rectForCircle{
   
   //the circle center
   MKMapPoint mpoint = MKMapPointForCoordinate([[self overlay] coordinate]);
   
   //geting the radius in map point
   double radius = [(MKCircle*)[self overlay] radius];    
   double mapRadius = radius * MKMapPointsPerMeterAtLatitude([[self overlay] coordinate].latitude);
   
   //calculate the rect in map coordinate
   MKMapRect mrect = MKMapRectMake(mpoint.x - mapRadius, mpoint.y - mapRadius, mapRadius * 2, mapRadius * 2);
   
   //return the pixel coordinate circle
   return [self rectForMapRect:mrect];
}


rectForCircle helped us to find the rect of the circle in pixel coordinate. We first get the location coordinate of the circle and convert it to MKMapPoint (map coordinate). Then we also convert the radius in meter to the map coordinate. With the two information, we can calculate the MKMapRect as we have the center and radius of the circle already. Finally, convert the MKMapRect to CGRect which will be in pixel coordinate, for us to manage the UIImageView later.



-(void)removeExistingAnimation{
   
   if(imageView){
       [imageView.layer removeAllAnimations];
       [imageView removeFromSuperview];
       imageView = nil;
   }
}

This one is simple, simply remove any existing animation and nullified the imageView.



#define MAX_RATIO 1.2#define MIN_RATIO 0.8
#define MAX_OPACITY 0.5#define MIN_OPACITY 0.05
#define ANIMATION_DURATION 0.8
//repeat forever#define ANIMATION_REPEAT 1e100f 

Also, some constant that may interest you...



Done, let's start explaining the flow.



-(id)initWithCircle:(MKCircle *)circle{
   
   self = [super initWithCircle:circle];
   
   if(self){
       [self start];
   }   
   
   return self;
}



As the circle view is initiated, we want the animation being initiated immediately as well.



-(void)start{

   [self removeExistingAnimation];
       
   CGRect rect = [self rectForCircle];
   
   //create the image
   UIImage* img = [UIImage imageNamed:@"redCircle.png"];
   imageView = [[UIImageView alloc] initWithImage:img];
   imageView.frame = rect;
   [self addSubview:imageView];
   [imageView release];
   
   //opacity animation setup
   CABasicAnimation *opacityAnimation;
   
   opacityAnimation=[CABasicAnimation animationWithKeyPath:@"opacity"];
   opacityAnimation.duration = ANIMATION_DURATION;
   opacityAnimation.repeatCount = ANIMATION_REPEAT;
   //theAnimation.autoreverses=YES;
   opacityAnimation.fromValue = [NSNumber numberWithFloat:MAX_OPACITY];
   opacityAnimation.toValue = [NSNumber numberWithFloat:MIN_OPACITY];
   
   //resize animation setup
   CABasicAnimation *transformAnimation;
   
   transformAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
   
   transformAnimation.duration = ANIMATION_DURATION;
   transformAnimation.repeatCount = ANIMATION_REPEAT;
   //transformAnimation.autoreverses=YES;
   transformAnimation.fromValue = [NSNumber numberWithFloat:MIN_RATIO];
   transformAnimation.toValue = [NSNumber numberWithFloat:MAX_RATIO];
   
   
   //group the two animation
   CAAnimationGroup *group = [CAAnimationGroup animation];

   group.repeatCount = ANIMATION_REPEAT;
   [group setAnimations:[NSArray arrayWithObjects:opacityAnimation, transformAnimation, nil]];
   group.duration = ANIMATION_DURATION;

   //apply the grouped animation
   [imageView.layer addAnimation:group forKey:@"groupAnimation"];
}



This block of code is a bit of large, but actually very simple and clear. First of all, we want to make sure there's no other UIImageView is animating in the every call for start. That's why we call the help function removeExistAnimation to make sure of it.

Then, we create the UIImageView which contains the circle image and add it to our view.

The animation we want here, is the image will change in size along with opacity simultaneously.  Therefore, we initiate two CABasicAnimation objects, which target to the opacity and transform.scale property of the CALayer respectively. Then, the two animation objects set into a CAnimationGroup together and added to the CALayer of the UIImageView.

We used some constant here, which the circle size will vary from 0.8 to 1.2 times of its radius. The opacity will be from 0.5 to 0.05. It means the large the circle, the more transparent it will be.

As we need the Core Animation, remember to add the QuartzCore framework to the project as well.



- (void)drawMapRect:(MKMapRect)mapRect
         zoomScale:(MKZoomScale)zoomScale
         inContext:(CGContextRef)ctx
{
   //get the rect in pixel coordinate and set to the imageView
   CGRect rect = [self rectForCircle];
    
   if(imageView){
       imageView.frame = rect;
   }
}



We also need to override the drawMapRect:zoomScale:inContext: method as well. This part is a bit of magic, we really need to calculate the rect here on to this point, which the circle view will be called for a redraw, so we can get a correct CGRect for the UIImageView.

That's it! you can have a seamless animated circle on your map. Clean and simple.




Looks cool, huh? =)

Of course you can modify all the constant and also the image to have different and much cooler appearance of the circle/overlay and the animation.

Note that, the animation object will be removed from the CALayer of the UIImage when the MKMapView did disappear internally. And If your MKMapView did appear again, the CALayer UIImageView will be without an animation object and it will appear as the image itself. In my sample project,  it's a concrete red circle. Therefore, be minded that you have to manage this in your code. I suggest you can simple remove the overlay in viewDidDisappear and add the overlay back again in viewDidAppear.

The class is quite simple and easy to understand, guess most of you can understand it right away once you look into the YHAnimatedCircleView source code.

Okay, so this is it! Source code is available in the YHMapDemo repository of my GitHub.

***Please read "Using my code" before using the code in your project, either a free or commercial product.***

Enjoy =)


2012-04-03

Hello World

Yeah, saying "hello world", this is what a developer need to do at the very beginning.

I have been dedicated to iOS development since the late 2008. Since then, I have been working on some very challenging projects, either technical-wise or business-wise, which gives me a lot of opportunities to explore many different iOS frameworks and APIs, also experience on dealing and consulting with client or customers on requirement, to get the job done. So I think I do have something to say and share with you guys. :)

No matter how's the opinion of certain people, the iOS "eco-system" is still in a very good position on mobile technology. Experienced and talented iOS developers are still in hot demand. (I know it well. I was unemployed for the last two months. Guess how many job agents contacted me and how many iOS developer positions or freelance jobs popped up to me. =P ) No one knows what will happen next, but all we have is now, huh? =P

As you may have noticed, there are already a lot of resources or tutorials for iOS developers around the internet. Some of them are really well organized, well written and really useful. The aim for me setting up this blog is not repeating what the other blogs is doing, as they are quite "established" and good enough. However, I still think I have some "unique" or "rare" knowledge to share, as a developer, as a consultant or as a friend of you, which is rarely seen in other iOS development blogs.

I can't wait to share you some thought on iOS development by myself, some tips on certain frameworks, and even some personal feelings on certain decisions and trends about iOS.

Stay tuned! =)