Animating a UIImageView with a mask but the mask is static - objective-c

I am trying to have an animation in which a mask is placed in the center of the view. Any image view that passes behind the mask appears. So here is my code so far, but the problem with this code is that it animates the mask with the image view.
UIView *maskView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.worldImageView.frame.size.height, self.worldImageView.frame.size.height)];
// ...position maskView...
maskView.layer.position = CGPointMake(CGRectGetMidX([self.view bounds]), CGRectGetMidY([self.view bounds]));
maskView.layer.backgroundColor = [UIColor blackColor].CGColor;
maskView.layer.cornerRadius = 90;
self.worldImage = [[UIImageView alloc] init];
self.worldImage.frame = self.worldImageView.frame;
self.worldImage.image = [UIImage imageNamed:#"continents.png"];
[maskView addSubview:self.worldImage];
[self.view addSubview:maskView];
CABasicAnimation* worldAnimation = [[CABasicAnimation alloc] init];
[worldAnimation setFromValue:[NSValue valueWithCGPoint:CGPointMake(self.view.frame.size.width + 50, CGRectGetMidY([maskView bounds]))]];
[worldAnimation setToValue:[NSValue valueWithCGPoint:CGPointMake(-50, CGRectGetMidY([maskView bounds]))]];
worldAnimation.keyPath = #"position";
worldAnimation.duration = 6.0f;
worldAnimation.repeatCount = HUGE_VALF;
[[self.worldImage layer] addAnimation:worldAnimation forKey:nil];
I don't know if I explained this well enough. Please tell me if you need more info. Thank you in advance!

Your issue here is that the mask property of UIView is attached to that UIView - they can't move separately. Technically, it's actually attached to the view's underlying layer, but whatever.
If you want the image to appear as if it's passing in front of a circular window (so that you can see it as it goes by) you'll need to add it as a subview of the view with the mask. So, you might do something like this:
UIView *maskView = [[UIView alloc] init];
// ...position maskView...
maskView.layer.cornerRadius = 90;
maskView.clipsToBounds = YES;
self.worldImage = [[UIImageView alloc] init];
self.worldImage.frame = self.worldImageView.frame;
self.worldImage.image = [UIImage imageNamed:#"continents.png"];
[maskView addSubview:self.worldImage];
[self.view addSubview:maskView];
// ...animate worldImage back and forth...
Note that instead of using a CAShapeLayer, I simply put rounded corners on maskView; since you want a circular mask, it's a much simpler trick. Also, when animating worldImage, consider using one of UIView's block-based animation methods – they're a lot easier to handle for simple stuff like this.

Related

How to animate a CALayer attached to UIImageView?

I am using this code proposed by Bartosz to add a mask to an UIImageView. It works fine.
#import <QuartzCore/QuartzCore.h>
CALayer *mask = [CALayer layer];
mask.contents = (id)[[UIImage imageNamed:#"mask.png"] CGImage];
mask.frame = CGRectMake(0, 0, 320.0, 100.0);
yourImageView.layer.mask = mask;
yourImageView.layer.masksToBounds = YES;
In addition, I want to animate the mask, e.g. sliding the mask to the right, so that at the end of the animation, the mask is not applied to the UIImageView any more.
In my specific case, the mask uses a fully transparent image, so the UIImageView is not visible at the initial state (which works fine), but is expected to be so at the end of the animation. However, the idea may be reused to any other use case were masks need to be animated.
The idea is to manipulate the x-origin portion of the frame of the mask. So, I came up with this code:
[UIView animateWithDuration: 0.2
delay: 0
options: UIViewAnimationCurveEaseInOut
animations:^{
CGRect maskFrame = yourImageView.layer.mask.frame;
maskFrame.origin.x = 320.0;
yourImageView.layer.mask.frame = maskFrame;
}
completion:^(BOOL finished){}];
Unfortunately, the mask is applied to the whole UIImageView at any time, it's not sliding to the right.
UPDATE 1:
This is the code I am actually using the set up the view and mask: It's a UITableViewCell.
APPCell.m (APPCell.h "extends" UITableViewCell)
#import "APPCell.h"
#import <QuartzCore/QuartzCore.h>
#interface APPCell()
#property (strong, nonatomic) UIImageView *menu;
#property (strong, nonatomic) CALayer *menuMask;
...
#end
#implementation APPCell
...
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self.menu = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320.0, 88.0)];
[self.menu setBackgroundColor:[UIColor clearColor]];
[self.menu setImage:[UIImage imageNamed:#"cell_back"]];
[self addSubview:self.menu];
self.menuMask = [CALayer layer];
self.menuMask.contents = (id)[[UIImage imageNamed:#"cell_mask"] CGImage];
self.menuMask.frame = CGRectMake(0, 0, 320.0, 88.0);
self.menu.layer.mask = self.menuMask;
self.menu.layer.masksToBounds = YES;
}
...
Instead of animating with the help of UIKit, I am now using implicit animation of CoreAnimation to move the mask layer:
APPCell.m
...
- (void)swipeLeft
{
self.menuMask.position = CGPointMake(-320.0, 0.0);
}
...
I can confirm that swipeLeft is called. I expect the mask "to be gone" and to see the [UIImage imageNamed:#"cell_back"]], which I do when I uncomment self.menu.layer.mask = self.menuMask.
Solution:
Instead of setting the content on the CALayer, I set the background color to white. This is the code I am using:
self.menuSubMenuMask = [CALayer layer];
self.menuSubMenuMask.backgroundColor = [[UIColor whiteColor] CGColor];
self.menuSubMenuMask.frame = CGRectMake(320.0, 0.0, 320.0, 88.0);
self.tableCellSubMenu.layer.mask = self.menuSubMenuMask;
self.tableCellSubMenu.layer.masksToBounds = YES;
In order to show the UIImageView the CALayer is applied to, the CALayer must NOT be "above" the UIImageView.
Animation with UIKit of UIViews is much more limited than using Core Animation directly. In particular what you are trying to animate is not one of animatable properties of a UIView. In addition as clarified in the View Programming Guide for iOS:
Note: If your view hosts custom layer objects—that is, layer objects without an associated view—you must use Core Animation to animate any changes to them.
This is the case in your example. You have added a CALayer to your view and UIKit will not be able to animate the result for you. On the other hand you can use Core Animation directly to animate the motion of your mask layer. You should be able to do this easily using implicit animation as described in the Core Animation Programming Guide. Please note that from the list of CALayer Animatable Properties that frame is not animatable. Instead you should use position.
You can achieve something you want by using CATransition, although this might not be the solution you want:
1) At first, set mask for your layer just as you did
2) When you want to remove mask and reveal your image, use the following code:
CATransition* transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;
[mask addAnimation:transition forKey:kCATransition];
imageView.layer.mask.contents = [UIImage imageNamed:#"black.png"].CGImage;
The main trick here - we created transition animation for our mask layer, so this animation will be applied when you change any (i'm not sure about any) property of mask layer. Now we set mask's content to completely black image to remove masking at all - now we've got smooth pushing animation where our masked image is going to the left and unmasked image is getting into its place
The easiest way is to use CoreAnimation itself:
CGPoint fromPoint = mask.position;
CGPoint toPoint = CGPointMake(fromPoint.x*3.0, fromPoint.y);
mask.position = toPoint; // CoreAnimation animations do *not* persist
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [NSValue valueWithCGPoint:fromPoint];
animation.toValue = [NSValue valueWithCGPoint:toPoint];
animation.duration = 4.0f;
[mask addAnimation:animation forKey:#"position"];

Cocoa: Why assigning a frame to a frame doesn´t work but assigning bounds does

I´ve been reading the different questions ( Cocoa: What's the difference between the frame and the bounds?, UIView frame, bounds and center) related to the difference between frame and bounds but still I don´t understand why when something like this:
UILabel *newMark = [[UILabel alloc] initWithFrame:self.frame];
newMark.text = #"|A3";
[self addSubview:newMark];
or this:
UILabel *newMark = [[UILabel alloc] init];
newMark.text = #"|A3";
newMark.frame = self.frame;
[self addSubview:newMark];
The label is not displayed, but when doing the equivalent with the bounds, like this:
UILabel *newMark = [[UILabel alloc] initWithFrame:self.bounds];
newMark.text = #"|A3";
[self addSubview:newMark];
or this
UILabel *newMark = [[UILabel alloc] init];
newMark.text = #"|A3";
newMark.frame = self.bounds;
[self addSubview:newMark];
it is displayed. I wouldn´t have a problem with using the bounds, but I think is not placing the label on the right place, as it can be seen on the following screenshot:
Where, as you can see "|", "A" and "3" are cut because they are displayed more at the bottom than the screen of the ipad. Any ideas?
The frame is relative to its superview. So if your frame's origin is at (100,200) within your superview, and you set a subview's frame to be the same as your frame, then your subview will be at (100,200) within you.
Typically (provided there aren't transforms involved), when you want to tell a subview to be the same size as the superview, you say:
subview.frame = superview.bounds;

Lag with CALayer when double tap on the home button

When I put shadow etc. with CALayer my app is lagging when I double tap on the home button to see tasks running. I don't have any other lag, just when I double tap.
I call this method 20 times to put 20 images :
- (UIView *)createImage:(CGFloat)posX posY:(CGFloat)posY imgName:(NSString *)imgName
{
UIView *myView = [[UIView alloc] init];
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [UIColor blueColor].CGColor;
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = [UIColor blackColor].CGColor;
sublayer.shadowOpacity = 0.8;
sublayer.frame = CGRectMake(posX, posY, 65, 65);
sublayer.borderColor = [UIColor blackColor].CGColor;
sublayer.borderWidth = 2.0;
sublayer.cornerRadius = 10.0;
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = sublayer.bounds;
imageLayer.cornerRadius = 10.0;
imageLayer.contents = (id) [UIImage imageNamed:imgName].CGImage;
imageLayer.masksToBounds = YES;
[sublayer addSublayer:imageLayer];
[myView.layer addSublayer:sublayer];
return myView;
}
I have commented all my code except this, so I'm sure the lag comes from here. Also I've checked with the Allocations tools and my app never exceeded 1Mo. When I'm just putting images without shadow etc. everything works fine.
Try setting a shadowPath on the layer as well. It will need to be a rounded rect since you've got rounded corners on your layer.
CALayer has to calculate where it is drawing, and where to put the shadow, if it doesn't have a shadow path. This has a big effect on animation performance.
Another way to improve performance with CALayers is to set the shouldRasterize property to YES. This stores the layer contents as a bitmap and prevents it having to re-render everything.

Stacking views ... code review needed

The problem:
On top of existing UIView, load a background image
From the card deck image (big image), cut 1 card (a part of a big image) and
Place that card on top of the background image
The following code works. It does exactly what i need it do to. I ask you though ..
Am i doing it right? Is this the way it's done?
Are all steps i used really needed? I have a strong suspicion i made it more complicated then it needs to be
Thank you for your time
// Define overall UIView area and create a view
UIView *background = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
// Gain access to background image
UIImage *backgroundImage = [UIImage imageNamed:#"green_background.jpg"];
// Put background image on top of a
UIImageView *myImageView = [[UIImageView alloc] initWithImage:backgroundImage];
// So now we have the view of some size with some image bahind it
[background addSubview:myImageView];
// #####################################################################
CGRect positionOnParentView = CGRectMake(40, 40, 50, 130);
CGImageRef bigImage = [UIImage imageNamed:#"cards.png"].CGImage;
CGImageRef partOfABigImage = CGImageCreateWithImageInRect(bigImage, CGRectMake(0, 0, 200, 50));
// get an image to be displayed
UIImage *partOfImage = [UIImage imageWithCGImage:partOfABigImage];
// Put it on it's onw view
UIImageView *cardImage = [[UIImageView alloc] initWithImage:partOfImage];
// create a UIview and add image
UIView *card = [[UIView alloc] initWithFrame:positionOnParentView];
[card addSubview:cardImage];
[background addSubview:card];
[[self view] addSubview:background];
You're creating this view hierarchy:
UIView (self.view)
UIView (background)
UIImageView (backgroundImage - backgroundImage)
UIView (card)
UIImageView (cardImage - partOfABigImage)
A UIImageView can have subviews. So do you need all of those intermediate views? Would this simpler hierarchy be sufficient?
UIImageView (self.view - backgroundImage)
UIImageView (cardImage - partOfABigImage)
A couple of things:
Maybe you just didn't include your release statements, but make sure you are releasing the items you call alloc on. You can also autorelease them. For example,
UIView *background = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)] autorelease];
Yes, you are doing it right, the only way you will really reduce the amount of code you have here is to give each card it's own image so that you don't have to crop within the big image.

UITableViewCell custom selectedBackgroundView background is transparent

I have the following code that creates a UIView that I assign to my UITableViewCell's selectedBackgroundView property. Everything works as expected, with the exception of the subview's background, which is transparent.
I use the same code to create a custom view that I assign to backgroundView, and that works fine.
What is causing that subview to be transparent for selectedBackgroundView, and how can I avoid that?
- (UIView*) makeSelectedBackgroundView
{
// dimensions only for relative layout
CGRect containerFrame = CGRectMake(0, 0, 320, 40);
UIView* containerView = [[UIView alloc] initWithFrame:containerFrame];
containerView.autoresizesSubviews = YES;
// dimensions only for relative layout
CGRect subframe = CGRectMake(5, 5, 310, 30);
UIView* subview = [[UIView alloc] initWithFrame:subframe];
subview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
subview.backgroundColor = [UIColor redColor];
subview.layer.cornerRadius = 5;
subview.layer.borderWidth = 2;
subview.layer.borderColor = [UIColor greenColor].CGColor;
[containerView addSubview:subview];
return containerView;
}
As we can see from name of ivar selectedBackgroundView, this background shown by cell when it was selected.
I've to reload few methods (– setSelected:animated: and – setHighlighted:animated:) of UITableViewCell subclass to reset background color of subviews back to their values. Look's like UIKit do some magic in this template methods (iterating over all UIView subclasses and set their background to clearColor)
This code might be helpful for you:
UIImageView *cellImageView = [[UIImageView alloc]
initWithFrame:CGRectMake(0,
0,
cell.frame.size.width,
cell.frame.size.height
)];
cellImageView.contentMode = UIViewContentModeScaleAspectFit;
// normal background view
[cellImageView setImage:[UIImage imageNamed:#"*<ImageName>*"]];
[cell addSubview:cellImageView];
[cell sendSubviewToBack:cellImageView];
[cellImageView release], cellImageView = nil;
Here cell is an object of custom UITableViewCell.
Also you can set backgroundColor property.
I would try to set the alpha for both containerView and subView to 1.0
[containerView setAlpha:1.0];
...
[subview setAlpha:1.0];
this should make your controls totally opaque.
You could also create some images for the background and use that images in state of creating 2 views. Let's say you create 2 image (normalBackground.png and selectedBackground.png) and then set this images as cell background. Here is a nice tutorial.
Try setOpaque:YES on your views.
In the end, I ended up subclassing UITableViewCell which contained a custom view object, and that worked.