Change GMSCircle radius with Animation - ios

I am using Google Maps iOS sdk for my app .In my app user can draw a fence(a circle) and later can edit to change and resize the radius of circle .
Its resizing properly but when radius value changes its instant,not a smooth animation like map zoom in/out.Is it achievable with latest GMaps sdk for ios?
Apparently, its not possible becuase what i see is GMSCircle is inherited from GMSOverlay which is child of NSObject,so its defineltly not a view,rather that overlay is drawn with some layer or something like that .
Any help is appreciated..!!
Thanks..!!

I found that you could just change the radius, and the circle changes.
So I wrote a helper class to do this:
#interface TAMapCircle : GMSCircle
{
CLLocationDistance _from;
CLLocationDistance _to;
NSTimeInterval _duration;
}
#property (nonatomic, copy) void(^handler)();
#property (nonatomic, strong) NSDate * begin;
#end
#implementation TAMapCircle
// just call this
-(void)beginRadiusAnimationFrom:(CLLocationDistance)from
to:(CLLocationDistance)to
duration:(NSTimeInterval)duration
completeHandler:(void(^)())completeHandler {
self.handler = completeHandler;
self.begin = [NSDate date];
_from = from;
_to = to;
_duration = duration;
[self performSelectorOnMainThread:#selector(updateSelf) withObject:nil waitUntilDone:NO];
}
// internal update
-(void)updateSelf {
NSTimeInterval i = [[NSDate date] timeIntervalSinceDate:_begin];
if (i >= _duration) {
self.radius = _to;
self.handler();
return;
} else {
CLLocationDistance d = (_to - _from) * i / _duration + _from;
self.radius = d;
// do it again at next run loop
[self performSelectorOnMainThread:#selector(updateSelf) withObject:nil waitUntilDone:NO];
}
}
#end
Hopefully my answer can help.

I have done something similar using CADisplayLink. It makes it very easy to do simple animations in a GMSMapView.
https://developer.apple.com/library/prerelease/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/index.html

You need to wrap the animation changes that you are attempting to make in UIView animation block. This has changed in the latest version of the iOS SDK such that the method signature is [UIView beginAnimation:]. Wrap any changes you make in this block and they will be animated by UIKit. If that doesn't work you can go to a lower level with CoreAnimation transactions.

Related

Wrong region when map view is subclassed

If I subclass mkmapview I am unable to programmatically zoom in to the full extent. If I do not subclass, zooming works as expected.
My mkmapview subclass:
MyMapView.h
#import <MapKit/MapKit.h>
#interface MyMapView : MKMapView {
}
-(id)initWithLocation:(NSInteger)location;
#end
MyMapView.m
#import "MyMapView.h"
#implementation MyMapView
-(id)initWithLocation:(NSInteger)location; {
self = [super init];
if (self) {
}
return self;
}
my viewcontroller.m:
MyMapView *theMap = [[MyMapView alloc] initWithLocation:0];
theMap.frame = CGRectMake(0, 0, 320, 320);
[self.view addSubview:theMap];
float lat = 33.78;
float lon = -84.56;
CLLocationCoordinate2D newCenter = CLLocationCoordinate2DMake(lat, lon);
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance (newCenter, 10, 10);
[theMap setRegion:region animated:YES];
The resulting map view region is zoomed out much farther than I want. I know the 10 meters is probably tighter than can fit, but I expect the map to be zoomed in as tightly as it can. After the map finishes zooming, I can still manually zoom in 2 more levels.
I could fix the whole thing by including all the code in my main view controller, but I want to abstract the map a bit and keep my viewcontroller.m file cleaner and smaller.
What stupid mistake am I doing?
According to Apple's documentation, you should not subclass MKMapView.
Although you should not subclass the MKMapView class itself, you can get information about the map view’s behavior by providing a delegate object.

iOS Multi-Touch Not Working

I have the regular OpenGL / EAGL setup going on:
#interface EAGLView : UIView {
#public
EAGLContext* context;
}
#property (nonatomic, retain) EAGLContext* context;
#end
#implementation EAGLView
#synthesize context;
+ (Class)layerClass {
return [CAEAGLLayer class];
}
#end
#interface EAGLViewController : UIViewController {
#public
EAGLView* glView;
}
#property(nonatomic, retain) EAGLView* glView;
#end
#implementation EAGLViewController
#synthesize glView;
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
for (UITouch* touch in touches) {
CGPoint location = [touch locationInView:glView];
int index;
for (index = 0; index < gCONST_CURSOR_COUNT; ++index) {
if (sCursor[index] == NULL) {
sCursor[index] = touch;
break;
}
}
}
[super touchesBegan:touches withEvent:event];
}
That implementation includes corresponding touchesEnded/Canceled/Moved as well. The code fully works and tracks well.
I also make sure that I'm giving proper values for everything:
sViewController = [EAGLViewController alloc];
CGRect rect = [[UIScreen mainScreen] applicationFrame];
sViewController.glView = [[EAGLView alloc] initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)];
Assert(sViewController.glView);
sViewController.glView.userInteractionEnabled = YES;
sViewController.glView.multipleTouchEnabled = YES;
sViewController.glView.exclusiveTouch = YES;
It all compiles just fine, but I'm never receiving more than one UITouch. I don't mean in a single touchesBegan, but the index never goes past 0. I also set a breakpoint for the second time it enters that function, and putting two fingers on doesn't make it trigger.
If you want to detect multiple touches (and/or distinguish between a one finger, two finger etc. touch), try using a UIPanGestureRecognizer. When you set it up, you can specify the minimum and maximum number of touches. Then attach it to the view where you want to detect the touches. When you receive events from it, you can ask it how many touches it received and branch accordingly.
Here's the apple documentation:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIPanGestureRecognizer_Class/Reference/Reference.html
If you do this, you might not need to use the touchesBegan/Moved/Ended methods at all and, depending on how you set up the gesturerecognizer, touchesBegan/Moved/Ended may never get called.
Use [event allTouches] in place of touches. touches represents only the touches that have 'changed'. From the apple docs:
If you are interested in touches that have not changed since the last
phase or that are in a different phase than the touches in the
passed-in set, you can find those in the event object. Figure 3-2
depicts an event object that contains touch objects. To get all of
these touch objects, call the allTouches method on the event object.
It seems all I was missing was this:
sViewController.view = sViewController.glView;

Objc (IOS) , simple free fall animation gone bad

I'm currently taking my first shaky steps in ios development. I'm trying to animate a free falling ball. I have a button connected to an IBAction, and and UIImageView containing an image of my ball.
Inside my action, I have a while loop that's timed using NSTimeInterval, and based on the time it takes it calculates a new position until the ball reaches 'the ground'(Yes, I realize this is probably the least optimal way to do it. But I'm having a hard time grasping the syntax (and therein my problem probably lays), the optimisation will have to come later). From what I can understand, NSTimeInterval returns the elapsed time in seconds, so even though it will increment incredibly small steps, it should work. But I may have a serious case of brain fart.
So far so good. But when I tap the button, the ball moves straight from it's starting point to it's finishing point without an animation.
-(IBAction)doshit:(id)sender{
int G = 10;
CGPoint center = [myImage center];
NSDate *startTime = [NSDate date];
NSTimeInterval T;
T = fabs([startTime timeIntervalSinceNow]);
while (center.y<480)
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:T];
center.y=center.y+((G*T*T)/2);
[myImage setCenter:center];
T = fabs([startTime timeIntervalSinceNow]);
[UIView commitAnimations];
}
}
I welcome all suggestions! =)
One way to do it is to use a CADisplayLink to provide the timing -- it is tied to the display refresh rate of 1/60 of a second. If you 're using auto layout (which is on by default now), it is better to animate a constraint, rather then set a frame. So, this example uses a button at the top of the screen, whose constraint to the top of the view is connected to the IBOutlet topCon.
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#property (nonatomic, strong) CADisplayLink *displayLink;
#property (weak,nonatomic) IBOutlet NSLayoutConstraint *topCon;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(startDisplayLink) withObject:nil afterDelay:2];
}
- (void)startDisplayLink {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)stopDisplayLink {
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink {
static BOOL first = YES;
static double startTime = 0;
if (first) {
startTime = displayLink.timestamp;
}
first = NO;
double T = (double)displayLink.timestamp - startTime;
self.topCon.constant += ((10 * T * T)/2);
if (self.topCon.constant > 420) {
[self stopDisplayLink];
}
}
As Carl notes, you cannot perform animations by repeatedly changing things in the middle of a method call. See What is the most robust way to force a UIView to redraw? for more discussion on that.
As you may suspect, not only is this non-optimal, it actively fights iOS. iOS has many easy-to-use techniques for performing smooth animations without resorting to timers of any kind. Here is one simple approach:
- (IBAction)dropAnimate:(id)sender {
[UIView animateWithDuration:3 animations:^{
self.circleView.center = CGPointMake(100, 300);
}];
}
In the animations block, you change the thing you want to change to the final value (myImage.center in your case). UIKit will sample the current value and the final value, and figure out a path to get you there in the time you requested. For a full, runnable example with a few other features (like chained animations), see the example code from iOS:PTL Chapter 9.
The above code will use the default timing function. Once you understand that, you can move on to customizing the timing function. See Animation Pacing in the Animation Types and Timing Programming Guide. Also How to create custom easing function with Core Animation? and Parametric acceleration curves in Core Animation. But I would get your head around simple, default animations first.

Cocos2D magnification - making the code dynamic and using an image

I have found the following code and I need help with editing it. I am not really familiar with texture rendering.
First of all, init method takes a rect and magnifies only that area? How can I make it more dynamic and magnify only whatever is underneath the magnifying glass?
Secondly, Is it possible to change the shape to circle rather than rectangle?
Or Can I use an image as the frame of the magnifying glass?
Here is the code..
Cheers..
.h file
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Magnify : CCNode {
BOOL active;
CGRect rect;
CGFloat magnifyScale;
CCNode *renderNode;
CCRenderTexture *renderTexture;
}
- (id)initWithNodeToMagnify:(CCNode *)n rect:(CGRect)rectToMagnify scale:(CGFloat)scale;
- (void)enable;
- (void)disable;
.m file
#import "Magnify.h"
#implementation Magnify
- (id)initWithNodeToMagnify:(CCNode *)n rect:(CGRect)rectToMagnify scale:(CGFloat)scale
{
if (self = [super init]) {
self.visible = active = NO;
renderNode = n;
rect = rectToMagnify;
magnifyScale = scale;
renderTexture = [[CCRenderTexture renderTextureWithWidth:rect.size.width height:rect.size.height] retain];
[self addChild:renderTexture];
}
return self;
}
- (void)enable
{
self.visible = active = YES;
[self scheduleUpdate];
}
- (void)disable
{
self.visible = active = NO;
[self unscheduleUpdate];
}
- (void)drawAreaToTexture
{
[renderTexture beginWithClear:0.0 g:0.0 b:0.0 a:1.0];
// shift the renderNode's position to capture exactly the rect we need
CGPoint originalPosition = renderNode.position;
renderNode.position = ccpSub(originalPosition, rect.origin);
// scale the node as we want
CGFloat originalScale = renderNode.scale;
renderNode.scale = magnifyScale;
[renderNode visit];
// shift renderNode's position back
renderNode.position = originalPosition;
// scale back
renderNode.scale = originalScale;
[renderTexture end];
}
- (void)update:(ccTime)dt
{
[self drawAreaToTexture];
}
- (void)dealloc
{
[renderTexture release];
[super dealloc];
}
#end
OK, so, as I mentioned above for something like this, one possible answer is to use the CCLens3D class to get the "effect" of magnifying something in a circular manner.
I found using this to be a little tricky because it doesn't seem to work unless it's a child of the top level node of your 'scene'.
Here is some code I use to create a lens that moves around the screen, and then disappears:
// Create the lens object first.
//
CCLens3D *lens =
[CCLens3D actionWithPosition:fromPos
radius:50
grid:ccg(50, 50)
duration:2.0];
// Set the "size" of the lens effect to suit your needs.
//
[lens setLensEffect:1.0];
// In my case, I then move the lens to a new position. To apply an action on
// a lens, you need to give the actions to the actionManager in the
// CCDirector instance.
//
CCMoveTo *move = [CCMoveTo actionWithDuration:2.0 position:toPos];
// I had another action in this array, but this will do.
//
CCSequence *seq = [CCSequence actions:move, nil];
// Now tell the actionManager to move the lens. This is odd, but it works.
//
[[[CCDirector sharedDirector] actionManager] addAction:seq target:lens paused:NO];
// Now just for some more weirdness, to actually make the lens appear and operate
// you run it as an action on the node it would normally be a child of. In my case
// 'self' is the CCLayer object that is the root of the current scene.
//
// Note that the first action is the lens itself, and the second is a special
// one that stops the lens (which is a "grid" object).
//
[self runAction:[CCSequence actions:lens, [CCStopGrid action], nil]];
I imagine that you should be able to stop the grid by running the CCStopGrid action when you want to. In my case it is a programmed thing. In yours it might be when the user lets go of a button.

iOS: iTunes-like Badge in UITabbar

I have got a UITabBarController in a Storyboard. Right now, it has got 5 UITabBarItems. When I am in the other UITabBarItem, I want to update the Badge on the other UITabBarItem(my "Downloads") just like the iTunes App does with this "jump-like" animation when you buy a song or album. Is this possible? If Yes, how?
Thank you.
Yes...
There is a lot to an animation like the I'll call it "send to Downloads" type animation. I'll answer this question using an example.
Warning: this example breaks the MVC paradigm more than I'd like, but it's long enough as it is.
I'll use a simple Storyboard like this (in fact, exactly this):
I'll start by describing the "First View Controller - First":
Those many buttons in the view are connected to the one listed IBAction method. And that's about all the description needed for that view controller. Here is its .m file:(truncated)
//#import "First_View_Controller.h"
#interface First_View_Controller ()
#property (weak, nonatomic) DownloadViewController *downloadViewController;
#end
#implementation First_View_Controller
#synthesize downloadViewController = _downloadViewController;
-(DownloadViewController *)downloadViewController{
if (!_downloadViewController){
// Code to find instance of DownloadViewController in the tabBarController's view controllers.
for (UIViewController *vc in self.tabBarController.viewControllers) {
if ([vc isKindOfClass:[DownloadViewController class]]){
_downloadViewController = (DownloadViewController *)vc;
break;
}
}
}
return _downloadViewController;
}
-(IBAction)buttonPush:(UIButton *)button{
[self.downloadViewController addADownload:nil withViewToAnimate:button];
}
// Other typical VC crap...
#end
The IBAction is fairly self-explanatory. It gets reference to the instance of DownloadViewController, by looking through the tabBarController's view controllers, and passes the view to animate to that instance.
Now for DownloadViewController.m. It's a lot of code. I've commented it, to try to make it clear:
#import "DownloadViewController.h"
#import <QuartzCore/QuartzCore.h>
// A Category on UITabBar to grab the view of a tab by index.
#implementation UITabBar (WhyIsntThisBuiltIn)
-(UIView *)nj_ViewOfTabNumber:(NSUInteger)number{
if (number == NSNotFound) return nil;
// Fairly standard method for getting tabs, getting the UIControl objects from the 'subviews' array.
// I pulled the next few lines from an SO question.
NSMutableArray *tabs = [[NSMutableArray alloc] init];
[self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
if ([(NSObject *)obj isKindOfClass:UIControl.class]){
[tabs addObject:obj];
}
}];
// The code above gets the tabs' views, but they may not be in the correct order.
// This sort is required if a view controller has been replaced,...
// Since, in that case, the order in which the tabs' views appear in the 'subviews' array will not be the left-to-right order.
[tabs sortUsingComparator:^NSComparisonResult(UIView *obj1, UIView *obj2){
CGFloat v1 = obj1.center.x;
CGFloat v2 = obj2.center.x;
if (v1<v2) return NSOrderedAscending;
if (v1>v2) return NSOrderedDescending;
return NSOrderedSame;
}];
// This if is required for the case where the view controller is in the "more" tab.
if (number >= tabs.count) number = tabs.count-1;
return [tabs objectAtIndex:number];
}
#end
// A Category on UITabBarController to get the view of a tab that represents a certain view controller.
#implementation UITabBarController (WhyIsntThisBuiltIn)
-(UIView *)nj_viewOfTabForViewController:(UIViewController *)viewController{
// Find index of the passed in viewController.
NSUInteger indexOfViewController = [self.viewControllers indexOfObject:viewController];
if (indexOfViewController == NSNotFound) return nil;
// Return the view of the tab representing the passed in viewController.
return [self.tabBar nj_ViewOfTabNumber:indexOfViewController];
}
#end
// Insert required warning about using #defines here.
#define MY_ANIMATION_DURATION 0.8
#implementation DownloadViewController{
NSUInteger _numberOfDownloads;
}
-(void)updateBadgeValue{
self.tabBarItem.badgeValue = [NSString stringWithFormat:#"%i",_numberOfDownloads];
}
// This method creates a "snapshot" of the animation view and animates it to the "downloads" tab.
// Removal of the original animationView must, if desired, be done manually by the caller.
-(void)addADownload:(id)someDownload withViewToAnimate:(UIView *)animationView{
// update model...
_numberOfDownloads++;
// Animate if required
if (animationView){
// Create a `UIImageView` of the "animationView" name it `dummyImageView`
UIGraphicsBeginImageContextWithOptions(animationView.frame.size, NO, [[UIScreen mainScreen] scale]);
[animationView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *dummyImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *dummyImageView = [[UIImageView alloc] initWithImage:dummyImage];
dummyImageView.frame = animationView.frame;
// Determine UIView of tab using non-private API.
UITabBarController *tabBarController = self.tabBarController;
UIView *downloadsTab = [tabBarController nj_viewOfTabForViewController:self];
// Determine animation points in tabBarController's view's coordinates.
CGPoint animationStartPoint = [tabBarController.view convertPoint:dummyImageView.center fromView:dummyImageView.superview];
CGPoint animationEndPoint = [tabBarController.view convertPoint:downloadsTab.center fromView:downloadsTab.superview];
CGFloat totalXTravel = animationEndPoint.x - animationStartPoint.x;
// This is an arbitrary equation to create a control point, this is by no means canonical.
CGPoint controlPoint = CGPointMake(animationEndPoint.x, animationStartPoint.y - fabs(totalXTravel/1.2));
// Create the animation path.
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:animationStartPoint];
[path addQuadCurveToPoint:animationEndPoint controlPoint:controlPoint];
// Create the CAAnimation.
CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
moveAnimation.duration = MY_ANIMATION_DURATION;
moveAnimation.path = path.CGPath;
moveAnimation.removedOnCompletion = NO;
moveAnimation.fillMode = kCAFillModeBoth;
[tabBarController.view addSubview:dummyImageView];
dummyImageView.center = animationStartPoint;
// Animate the move.
[dummyImageView.layer addAnimation:moveAnimation forKey:#""];
// Use the block based API to add size reduction and handle completion.
[UIView animateWithDuration:MY_ANIMATION_DURATION
animations:^{
dummyImageView.transform = CGAffineTransformMakeScale(0.3, 0.3);
}
completion:^(BOOL b){
// Animate BIG FINISH! nah, just...
[dummyImageView removeFromSuperview];
[self updateBadgeValue];
}];
}
}
// Other typical VC crap...
#end
And that's about it. When run, this code produces a fairly strong jump from the buttons on the top left, but the buttons on the right, especially on the lower right, are sort-of tossed. And as the animation ends the badge on the downloads tab counts up. A pretty decent knock-off of the effect Apple uses when you purchase content on iTunes.
Remember to add the Quartz Framework to your app.

Resources