I am very new to cocoa . Well I have multiple UIImageView (animationView)flying around the screen. I am using UIAnimation class to animate them. While I have one more UIImageView (myObject).I m trying to collide these while i move "myObject" around the screen using touchesMoved method. The problem is I am not able to detect the collision. I m using the following method :
if (CGRectIntersectsRect(animationView.frame, myObject.frame)) {
NSLog(#"Collision occurred");
}
I assume you are talking about CAAnimation, not UIAnimation.
In Core Animation the current values of animated properties are not reflected in the original objects (views, layers) on which they have been applied. Instead, you have to look at a layer's presentationLayer to get the currently effective value:
CGRect viewFrame = ((CALayer*)[animationView.layer presentationLayer]).frame;
CGRect objectFrame = ((CALayer*)[myObject.layer presentationLayer]).frame;
if (CGRectIntersectsRect(viewFrame, objectFrame)) {
NSLog(#"Collision occurred");
}
Related
I'm writing an app with a series of cards in a table view, similar to the layout of the Google app for iOS when Google Now cards are enabled. When the user taps on a card, there should be a custom transition to a new view controller which is basically the card looking bigger, almost filling the screen, and it has more details on it. The custom transition itself should look like the card is animated upward and growing in size until it reaches its final size and position, which is now the new view controller holding the card.
I have been trying to approach this using a custom view controller transition. When the card is tapped, I initiate a custom view controller transition with UIModalPresentationCustom, and I set a transition delegate, which itself vends a custom animator and a custom UIPresentationController. In animateTransition:, I add the new view controller's view into the container view, setting the frame to the card's frame initially (so it looks like the card is still there and unchanged). Then I attempt to perform an animation where the presented view's frame grows in size and changes in position so that it moves into its final position.
Here's some of my code which does what I've described above - I'm trying to keep it short and sweet, but I can provide more info if needed:
Transition Delegate
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// NOWAnimationDelegate is my own custom protocol which defines the method for asking the presenting VC for the tapped card's frame.
UIViewController<NOWAnimationDelegate> *fromVC = (UIViewController<NOWAnimationDelegate> *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *finalVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
// Ask the presenting view controller for the frame of the tapped card - this method works.
toView.frame = [fromVC rectForSelectedCard];
[transitionContext.containerView addSubview:toView];
CGRect finalRect = [transitionContext finalFrameForViewController:finalVC];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toView.frame = finalRect;
}completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
Custom UIPresentationController
-(CGSize)sizeForChildContentContainer:(id<UIContentContainer>)container withParentContainerSize:(CGSize)parentSize {
return CGSizeMake(0.875*parentSize.width, 0.875*parentSize.height);
}
-(CGRect)frameOfPresentedViewInContainerView {
CGRect presentedViewFrame = CGRectZero;
CGRect containerBounds = self.containerView.bounds;
presentedViewFrame.size = [self sizeForChildContentContainer:(UIView<UIContentContainer> *)self.presentedView withParentContainerSize:containerBounds.size];
presentedViewFrame.origin.x = (containerBounds.size.width - presentedViewFrame.size.width)/2;
presentedViewFrame.origin.y = (containerBounds.size.height - presentedViewFrame.size.height)/2 + 10;
return presentedViewFrame;
}
What I'm finding is happening is that the new view is being automatically set to its final size immediately at the start of the animation, and then the animation is just the new view animating upwards. Using breakpoints, I'm noticing that frameOfPresentedViewInContainerView is called during the [transitionContext.containerView addSubview:toView] call, which would probably explain why this is happening - frameOfPresentedViewInContainerView returns "The frame rectangle to assign to the presented view at the end of the animations" according to the UIPresentationController documentation.
However, I'm not sure how to proceed, or if it's even really possible. All the examples of custom view controller transitions I've seen have all had the final size of the presented view controller static and unchanging during the animation. Is there any way to perform a custom view controller transition with a changing size of the presented view during the animation, or do I have to approach this in a different way?
Basically what you need to do is animating your toView.transform using CGAffineTransform in your Transition Delegate. Steps to do:
Before animating set your toView.frame = your frame when it's not showing
Create, let's say, CGAffineTransformMakeScale to scale from hidden frame to your desired final presented frame
On animation block set toView.transform to the transform that you create on step 2
As Aditya mentions CGAffineTransform is the way to go here.
Ive got this working now with it maintaining the width:
CGFloat targetscale=initialHeight/finalHeight;
CGFloat targetyoffset=(finalHeight-(finalHeight*targetscale))/2;
int targety=roundf(initialPosition-targetyoffset);
CGAffineTransform move=CGAffineTransformMakeTranslation(0, targety);
CGAffineTransform scale=CGAffineTransformMakeScale(1,targetscale);
toView.transform=CGAffineTransformConcat(scale,move);
This positions the incoming view taking into account the determined scale and then performs both transforms concurrently so the view scales & moves to the final position and size.
You then just set
toView.transform=CGAffineTransformIdentity;
in the animation block and it'll scale to the final location and size.
Note, this only moves and scales in the vertical dimension but can be adapted to scale in all directions like so:
+(CGAffineTransform)transformView:(UIView*)view toTargetRect:(CGRect)rect{
CGFloat targetscale=rect.size.height/view.frame.size.height;
CGFloat targetxoffset=(view.frame.size.width-(view.frame.size.width*targetscale))/2;
int targetx=roundf(rect.origin.x-view.frame.origin.x-targetxoffset);
CGFloat targetyoffset=(view.frame.size.height-(view.frame.size.height*targetscale))/2;
int targety=roundf(rect.origin.y-view.frame.origin.y-targetyoffset);
CGAffineTransform move=CGAffineTransformMakeTranslation(targetx, targety);
CGAffineTransform scale=CGAffineTransformMakeScale(targetscale,targetscale);
return CGAffineTransformConcat(scale, move);
}
And don't forget to transform the cell's rect the global coordinate space so the start frame is correct for the whole window not just the cells position in the table.
CGRect cellRect=[_tableView rectForRowAtIndexPath:[_tableView indexPathForSelectedRow]];
cellRect=[self.tableView convertRect:cellRect toView:presenting.view];
hope this helps!
Apple provides a wwdc sample app 'LookInside', accompanying talk 228: 'A look inside presentation controllers'. It features 2 custom presentations, of which one animates the size of the presented view controller. A look inside that code should help you out ;)
I have a UIView. I applied the animation to its CALayer.
[view.layer addAnimation:groupAnimation forKey:name];
I want the final state of the layer to be the state of the UIView after the animation. Let's say I rotated by 45degrees and moved to a new position using the layer; is it possible for my view to be in that state after the animation? Because right now, after the animation, it goes back to the original state of the UIView. I hope to receive some help with this. Thanks.
The thing to understand is that layer animation is just animation; it's merely a kind of temporary "movie" covering the screen. When the animation ends, the "movie" is removed, thus revealing the true situation. It is up to you to match that situation with the final frame of the movie.
UIView animation does this for you, to a great extent. But layer animation leaves it entirely up to you.
Thus, let's say you animate a position change; doing something so that the layer or view actually is where you animated to is completely up to you.
The usual thing is to perform the changes yourself as a separate set of commands. But be careful not to do anything that might trigger implicit layer animation, as this will conflict with the animation you are trying to perform explicitly.
Here's example code (not related to yours, but it does show the general form):
CAAnimationGroup* group = // ...
// ... configure the animation ...
[view.layer addAnimation:group forKey:nil];
// now we are ready to set up the view to look like the final "frame" of the animation
[CATransaction setDisableActions:YES]; // do not trigger implicit animation by mistake
view.layer.position = finalPosition; // assume we have worked this out
When animating a CALayer or using a CAAnimationGroup, the following properties must be set, e.g.:
groupAnimation.removedOnCompletion = NO;
groupAnimation.fillMode = kCAFillModeForwards;
See also: After Animation, View position resets
Also note that it may be helpful to apply animations directly to the view itself, rather than by accessing the view's layer. This is accomplished using animation blocks, which I have found to be very useful.
Block style animations can be customized in many ways, but here's a basic example, which could be invoked within a function when your view needs to animate:
- (void) animateMyView
{
CGRect newViewFrame = CGRectMake(x,y,w,h);
[UIView animateWithDuration:0.5
delay:0
options: (UIViewAnimationOptionCurveLinear )
animations:^{
self.myView = CGAffineTransformMakeRotation (45);
[self.myView setBounds:newViewFrame];
}
completion:NULL];
}
For more information, see Apple's documentation on View Animation.
I'm looking for a solution in order to have a beautiful zoom on a drawing view.
In my app, I have a view with an other UIView (which is used like a drawing view) and when I draw a stroke on it, the stroke is perfect. But when I zoom the view, I have this really ugly effect (a pixelised stroke) :
(source: imagup.com)
url image
Is there a solution in order to have a proper stroke ?
My UIViewController has a hierarchy like that :
UIViewController
ScrollView
View zoomable (defined with the viewForZoomingInScrollView method)
Image view
Drawing view
Thanks a lot !
Regards,
Sébastien ;)
I'm in the process of making a vector drawing application and let me tell you, this is NOT a trivial task to do correctly and requires quite a bit of work.
Some issues to keep in mind:
If you are not using vector graphics (CGPaths, for example, are
vectors) you will NOT be able to remove the pixelation. A UIImage,
for example, only has so much resolution.
In order to get your drawing to not look pixelated, you are going to
have to redraw everything. If you have a lot of drawing, this can be
an expensive task to perform.
Having good resolution WHILE zooming is nearly impossible because it would require an excessively large context and your drawing would likely exceed the capabilities of the device
I use core graphics to do my drawing, so the way I solved this issue was by allocating and managing multiple CGContexts and using them as buffers. I have one context that is ALWAYS kept at my least zoomed level (scale factor of 1). That context is drawn into at all times and makes it so that when unzooming completely, no time is spent redrawing since it is already done. Another context is used soley for drawing when zoomed. When not zoomed, that context is ignored (since it will have to be redrawn based on the new zoom level anyway). A high level algorithm for how I perform my zooming is as follows:
- (IBAction)handlePinchGesture:(UIGestureRecognizer *)sender
{
if(sender.state == UIGestureRecognizerStateBegan)
{
//draw an image from the unzoomedContext into my current view
//set the scale transformation of my current view to be equal to "currentZoom", a property of the view that keeps track of the actual zoom level
}
else if(sender.state == UIGestureRecognizerStateChanged)
{
//determine the new zoom level and transform the current view, keeping track in the currentZoom property
//zooming will be pixelated.
}
else if(sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled)
{
if(currentZoom == 1.0)
{
//you are done because the unzoomedContext image is already drawn into the view!
}
else
{
//you are zoomed in and will have to do special drawing
//perform drawing into your zoomedContext
//scale the zoomedContext
//set the scale of your current view to be equal to 1.0
//draw the zoomedContext into the current view. It will not be pixelated!
//any drawing done while zoomed needs to be "scaled" based on your current zoom and translation amounts and drawn into both contexts
}
}
}
This gets even more complicated for me because I have additional buffers for the buffers because drawing images of my paths is much faster than drawing paths when there is lots of drawing.
Between managing multiple contexts, tweaking your code to draw efficiently into multiple contexts, following proper OOD, scaling new drawing based on your current zoom and translation, etc, this is a mountain of a task. Hopefully this either motivates you and puts you on the right track, or you decide that getting rid of that pixelation isn't worth the effort :)
I had the same problem and found a solution: tell the view to use a CATiledLayer as backing layer, then tell the view how many levels of zoom it supports. This worked for me, my drawing methods get automatically called when the (parent) view is zoomed.
A short explanation of levelsOfDetail and levelsOfDetailBias:
levelsOfDetail determine how many zooming levels there are in total
levelsOfDetailBias determine how many of those are zooming in.
So in my example I have 4 zooming levels, 3 are zoomed in and 1 is the non-zoomed level, meaning my view only redraws when zooming in.
#imprementation MyZoomableView
+ (Class)layerClass
{
return [CATiledLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
((CATiledLayer *)self.layer).levelsOfDetail = 4;
((CATiledLayer *)self.layer).levelsOfDetailBias = 3;
}
return self;
}
#end
Use [self setContentScaleFactor:scale]; in your scrollViewDidEndZooming: delegate method.
I have a custom map of a limited area, and have it set up to correctly show the users' location. The map is a 1600px square image within a UIScrollView.
I have a crosshair image to show the current location of the user, which at zoomScale 1.0 is the desired size. When I pinch and zoom the scrollView, the crosshair scales with it. I would like to have the subview remain the same size on screen.
I haven't been able to find any information on this, what would be the best way to go about this?
If there is anything I can provide you with to help the answer, please let me know.
Many thanks!
EDIT -
Having looked in to this further, there is a UIScrollViewDelegate method - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale which I tried using to take the marker's current center and size, then adjust, but this only scales at the end of the zoom. I would prefer to have the marker remain the same size while the user is zooming.
EDIT 2-
Cake has provided a great answer below, but I haven't been able to implement this in the way I imagined it would be.
I have the UIImageView as a placeholder, with alpha set to 0. This placeholder moves around relative to the map to show the user location. This operates as I expect it to. Unfortunately, this resizes with the map, as it is a subview of the map (so it stays in place).
Taking Cake's below answer, I have created the non-scaling crosshair image, and added it as a sibling subview to the scrollview. The maths, once Cake had pointed them out, were quite simple to get the new frame for the crosshair:
CGPoint ULPC = userLocationPlaceholder.center;
float zs = scrollView.zoomScale;
CGRect newFrame = CGRectMake(((ULPC.x * zs) - scrollView.contentOffset.x) - 20, ((ULPC.y * zs) - scrollView.contentOffset.y) - 20, 40, 40);
Where the image is 40points wide. This matches the centers perfectly.
The problem I now have is that I cannot get the crosshair image to stay locked to the placeholder.
I have tried using a self calling animation as such:
-(void)animeUserLocationAttachment
{
[UIView animateWithDuration:0.05
delay:0
options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear )
animations:^{
userLocationDotContainer.frame = newFrame;
} completion:^(BOOL finished){
// Call self
[self animateUserLocationAttachment];
}];
}
As soon as I start scrolling/zooming, this locks the animation so that the crosshair just sits in place until I release the scrolling/zooming, then it correctly updates it's location.
Is there any way I can get around this, or an alternative method I can apply?
Many thanks
EDIT 3 -
I've re-accepted Cake's answer as it covers 90% of the issue. Further to his answer I have implemented the ScrollViewDelegate methods scrollViewWillBeginDragging: andscrollViewWillBeginDecelerating: to scale the placeholder to match the current size of the crosshair relative to the map, show the placeholder (that is a subview of the map image) and hide the crosshair image. The delegate method scrollviewWillBeginZooming:withView: does not show the placeholder because it scales with the map. As Cake recommends, I'll make a new question for this issue.
The counterpart methods (scrollViewDidEndZooming:withView:atScale:, scrollViewDidEndDragging:willDecelerate: and -scrollViewDidEndDecelerating:`) all hide the placeholder, and re-show the crosshair.
The question is old but for the future similar questions I've recently resolved a similar problem applying the hint of Andrew Madsen of another post.
I'had a UIScrollView, with an UIImageView in it. Attached to the UIImageView I had many MKAnnotationView (those are my subviews that I didn't want scaling with the superview).
I did subclass UIImageView and implement setTransform: method like here:
#import "SLImageView.h"
#implementation SLImageView
- (void)setTransform:(CGAffineTransform)transform
{
[super setTransform:transform];
CGAffineTransform invertedTransform = CGAffineTransformInvert(transform);
for (id obj in self.subviews)
{
if ([obj isKindOfClass:[MKAnnotationView class]])
{
[((UIView *)obj) setTransform:invertedTransform];
}
}
}
#end
This works perfectly!
Mick.
Create another crosshair image that's associated with the view or view controller that contains the scrollview. Then have this one always snap to the center of the crosshair image you already have. Then, hide your original crosshair image. Then you can avoid having the scrollview scale the disassociated crosshair, and it should stay the same size.
Relative coordinate systems
Each view in cocoa touch has a frame property that has an origin. In order to position an object owned by one view properly relative to another view, all you have to do is figure out the differences in their origins. If one view is a subview of another, then this isn't too difficult.
Get the origin of the container view
Get the location of the subview inside of the container view
Get the origin of the subview
Calculate the difference in the positions of the origins
Get the location of the object you want to overlap (relative to the subview)
Calculate the location of the object you want to overlap relative to the container view
Move your crosshair to this position
Swift equivalent for Mick's answer:
class MapContainerView:UIView {
#IBOutlet var nonScalingViews: [UIView]!
override var transform: CGAffineTransform {
didSet {
guard let nonScalingViews = nonScalingViews else {
return
}
let invertedTransform = CGAffineTransformInvert(transform)
for view in nonScalingViews {
view.transform = invertedTransform
}
}
}
}
I have an application where, in one window, there is an NSImageView. The user should be able to drag and drop ANY FILE/FOLDER (not only images) into the image view, so I subclassed NSImageView class to add support for those types.
The reason why I chose an NSImageView instead of a normal view is because I also wanted to display an animation (say an arrow pointing downwards and going up and down) when the user hovers over with files ready to drop. My question is this: what would be the best way (most efficient, quickest, least CPU usage, etc) to do this?
In fact, I have already done it, but what made me ask this question is the fact that when I set the images to change at a rate below 0.02 sec it starts to lag. Here is how I did it:
In the NSImageView subclass:
have an ivar: NSTimer* animTimer;
override awakeFromNib, calling [super awakeFromNib] and loading the images into an array (about 45 images) using NSImage
whenever user enters with files, start animTimer with frequency = 0.025 (less and it lags), and a selector that sets the next image in the array (called drawNextImage)
whenever the user exits or ends the drag and drop, call [animTimer invalidate] to stop updating images
Here is how I set the image in the subclass:
- (void)drawNextImage
{
currentImageIndex++; // ivar / kNumberDNDImages is a constant defined as 46
if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
[super setImage: [imagesArray objectAtIndex: currentImageIndex]]; // imagesArray is ivar
}
So, how would I do this quick enough? I'd like the frequency to be about 0.01 secs, but less than 0.025 lags, so that is what I have set for the moment. Oh, and my images are the correct size (+ or - one pixel or something) and they are in .png (I need the transparency - jpegs, for example, won't do it).
EDIT:
I have tried to follow NSResponder's suggestion, and have updated my method to this:
- (void)drawNextImage
{
currentImageIndex++;
if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
NSRect smallImgRect;
smallImgRect.origin = NSMakePoint(kSmallImageWidth * currentImageIndex, [self.bigDNDImage size].height); // Up left corner - ??
smallImgRect.size = NSMakeSize(kSmallImageWidth, [self.bigDNDImage size].height);
// Bottom left corner - ??
NSPoint imgPoint = NSMakePoint(([self bounds].size.width - kSmallImageWidth) / 2, 0);
[bigDNDImage drawAtPoint: imgPoint fromRect: smallImgRect operation: NSCompositeCopy fraction: 1];
}
I have also moved this method and the other drag and drop methods from the NSImageView subclass to an NSView subclass I already had. Everything is exactly the same, except for the superclass and this method. I also modified some constants.
In my early testing of this, I got some error/warning messages that didn't stop execution talking abou NSGraphicsContext or something. These have disappeared now, but just so you know. I have absolutely no idea why they were appearing and what they mean. If they ever appear again I'll worry about them, not now :)
EDIT 2:
This is what I'm doing now:
- (void)drawNextImage
{
currentImageIndex++;
if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
[self drawCurrentImage];
}
- (void)drawCurrentImage
{
NSRect smallImgRect;
smallImgRect.origin = NSMakePoint(kSmallImageWidth * currentImageIndex, 0); // Bottom left, for sure
smallImgRect.size = NSMakeSize(kSmallImageWidth, [self.bigDNDImage size].height);
// Bottom left as well
NSPoint imgPoint = NSMakePoint(([self bounds].size.width - kSmallImageWidth) / 2, 0);
[bigDNDImage drawAtPoint: imgPoint fromRect: smallImgRect operation: NSCompositeCopy fraction: 1];
}
And the catch here is to call drawCurrentImage when drawRect is called (see, it actually was easier to solve than I thought).
Now, I must say I haven't tried this with the composite image, because I couldn't find a good and quick way to merge 40+ images the way I wanted (one next to the other). But for the ones ineterested, I modified this to do the same thing as my NSImageView subclass (reading 40+ images from an array and displaying them) and I found no speed bump: NSView is as laggy below 0.025 as NSImageView. Also I found some problems when using core animation (the image is drawn in weird places instead of the place I tell her to) and some warnings talking about NSGraphicsContext, which I don't know how to solve at all (I'm a complete noob when it comes to drawing and such with Objective-C's tools). So for the time being I'm using NSImageView, unless I find a way to merge all those images and try it with NSView.
Core Animation would probably be quickest, since it'll do everything on the GPU. Create a layer for each image, setting each layer's contents to the CGImage you can make from each image, add them all as sublayers of a single top-level layer, host the top-level layer in a plain NSView, and then just toggle each image layer's hidden property in turn.
I'd probably draw all of the component images into one long image, and draw segments into a view using -drawAtPoint:fromRect:operation:fraction:. I'm sure you could make it faster than that by resorting to OpenGL, though.