xcode/ios7: parallax effect with image clipped inside view - ios

I am trying to implement iOS7 parallax effect. For this purpose I am using standard UIInterpolatingMotionEffect class.
Here is what I am trying to achieve:
Place photo inside UIView. Photo is larger than View's frame (on each side)
Set View's size, and CornerRadius (with masksToBounds = YES)
Move phone and watch parallax effect. Something similar as you are peaking inside hole :)
Almost every tutorial on web is simply moving whole view (by setting center.x) , but I don't know how to move content only (and clip it in same time). I have tried something, but obviously is not working:
Inside viewDidLoad I am doing next:
- (void)viewDidLoad
{
[super viewDidLoad];
_myView.layer.contents = (id) [UIImage imageNamed:#"dog"].CGImage;
_myView.layer.contentsGravity = kCAGravityCenter;
_myView.layer.masksToBounds = YES;
UIInterpolatingMotionEffect *horizontalMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"layer.position.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
horizontalMotionEffect.minimumRelativeValue = #(-80);
horizontalMotionEffect.maximumRelativeValue = #(80);
[_myView addMotionEffect:horizontalMotionEffect];
}
CALayer don't have addMotionEffect so I am accessing view.
Maybe my approach is not good from start, so if you have some other solution - it will help.
Thank you for any help.

I think a better approach here might just be to use 2 separate views.
The wrapping UIView you have now, sized to what you want with a corner radius and clipsToBounds set to YES.
a UIImageView with the image you want, added as a subview of the UIView.
Then, just apply the motion effect to the image view. This will keep the containing view static and achieve the keyhole effect you are looking for.

Related

UIView's layer mask animates setting frame

I want to create a UIView subclass that masks itself so that any child view's I add to it are cropped by a circle. I want this view and it's child views to be defined in IB, so that I can easily define layout constraints to the children. So far I have the following...
#interface BubbleView ()
// eg: this is an example of a child view that would be "under" a mask
#property(weak,nonatomic) IBOutlet UIImageView *imageView;
#end
#implementation BubbleView
// not really sure if this kind of init is the right pattern, but it seems to work and
// I don't think this is my current problem??
+ (instancetype)bubbleViewFromNib {
BubbleView *view = [[NSBundle mainBundle] loadNibNamed:#"BubbleView" owner:nil options:nil][0];
UIImage *_maskingImage = [UIImage imageNamed:#"circlemask"];
CALayer *maskingLayer = [CALayer layer];
[view.layer addSublayer:maskingLayer];
maskingLayer.contents = (__bridge id _Nullable)(_maskingImage.CGImage);
view.layer.mask = maskingLayer;
return view;
}
- (void)layoutSubviews {
self.layer.mask.frame = self.bounds;
}
(note: I give the view a purple color in IB so I can see what's going on)
This almost works, but when the owning view controller resizes this view, like this...
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
BubbleView *b = (BubbleView *)[self.view viewWithTag:222];
//b.transform = CGAffineTransformScale(b.transform, 1.2, 1.2);
b.frame = CGRectInset(b.frame, -30,-30);
}
The mask does something weird: It stays small "jumps" to the upper left corner of the view, then very quickly animates to the correct size (bigger by 30,30). Why would it animate?
Before...
Fast animation like this...
Placing NSLog in layoutSubviews, I notice it gets called twice, which is strange, but still not enough times to explain the quick animation.
Also, when I change the transform instead of the frame, it resizes perfectly, with no animation. But I need to do both frame and transform changes.
Can someone tell me where I've gone wrong?
When setting an animatable property of a layer, unless that layer is a UIView's primary layer, implicit animation is the default. Moreover, the frame property is merely a facade for the bounds and position properties. For this reason, you should never set a layer's frame directly; always set the bounds and position yourself. If you don't want animation, you'll need to turn off implicit animation for these properties; the simplest way is to turn it off entirely for the current CATransaction (setDisableActions to true), but there are more subtle and precise ways to accomplish the same thing.

Custom border for UIView using CALayer and autolayout

I have to draw a custom border for a UIView.
I already have code to do that but it was written before we started using autolayout.
The code basically adds a sublayer of width/height=1.0f
-(void)BordeIzquierdo:(UIColor *)pcColor cfloatTamanio:(CGFloat )pcfloatTamanio
{
CALayer* clElemento = [self layer];
CALayer *clDerecho = [CALayer layer];
clDerecho.borderColor = [UIColor darkGrayColor].CGColor;
clDerecho.borderWidth = pcfloatTamanio;
clDerecho.frame = CGRectMake(0, 1, pcfloatTamanio, self.frame.size.height);
[clDerecho setBorderColor:pcColor.CGColor];
[clElemento addSublayer:clDerecho];
}
Now the problem is that with autolayout, this happens before layoutSubViews. So UIView.layer.frame is (0,0;0,0) and so the sublayer added is not shown because it has width/height=0.0f
Now how can I make this code without transferring this custom drawing to another method executed after viewDidLoad such as didLayoutSubviews.
I would like this styling to be correctly applied before viewDidLoad is called.
Is there anything like autolayout constraints that I can use with CALayers?
Any other ideas?
There is no CALayer autoresizing or constraints in iOS. (There is on Mac OS X, but no in iOS.)
Your requirements here are unreasonable. You say that you refuse to move the call to BordeIzquierdo:. But you can't give clDerecho a correct size until you know the size of self (and therefore self.layer). Half the art of Cocoa touch programming is putting your code in the right place. You must find the right place to put the call to BordeIzquierdo: so that the values you need have meaning at the moment it is called.
What I would do is put it in layoutSubviews along with a BOOL flag so that you only do it once.

CALayer content goes out of bounds - iOS

I am trying to implement camera zoom using CGAffinetransform. Transform is fine, but when I scale it to a bigger size, it goes out of the frame I have assigned to the AVCaptureVideoPreviewLayer. I tried setting masksToBounds property to YES but it didn't help.
Can I contain it within its frame?
Edit:
What I want is that I can specify a specific area for the camera preview layer, if I apply scaling transform to it, (i.e., frame of preview layer gets expanded), the part of the layer outside of the specified area gets clipped.
You should put the layer you are scaling inside of another layer and mask that one instead (the superlayer). The same thing works with views.
I.e. You have two views / layers: clippingView and scalingView where scalingView is the subview of clippingView and clippingView is the view that actually clips to it's bounds.
[clippingView addSubview:scalingView];
clippingView.clipsToBounds = YES;
or using layers
[clippingLayer addSublayer:scalingLayer];
clippingLayer.masksToBounds = YES;
You guys are all partially right I found but I wanted to clarify.
Lets say we added something like AVCaptureVideoPreviewLayer to the view via [self.view.layer addSublayer:previewLayer]
[self clipsToBounds] does NOTHING until you are telling its primary layer to mask to bounds. [self.view.layer masksToBounds];
Just because your view has a frame and so does its layers DOES NOT MEAN IT HAS BOUNDS. If it doesnt have bounds then there is nothing to mask to. So do this self.view.layer.bounds = self.view.frame;
So heres it all together..keep in mind I did this in my own UIView class so I dont need to call self.view.
previewLayer.bounds = self.frame;
self.layer.bounds = self.frame;
self.layer.masksToBounds = YES;
previewLayer.masksToBounds = YES;
[self setBounds:self.frame];
[self clipsToBounds];
clipsToBounds property of the view to which I am adding the layer should have been set to YES.

uilabel disappears when being applied rotation on it

what I am having is
UIView
1.Image1
2.Image2
3.UILabel
like an image below
Then I apply rotation on UILabel by doing
- (void)viewDidLoad
{
testLabel.transform = CGAffineTransformMakeRotation(M_PI*0.25);
[super viewDidLoad];
}
and when I am running the application, the uilabe is disppearing after all. Look at the second image for your reference
Please point out what I am doing wrong here....and how to get the work done
Thanks
Watch out because Autolayout cause a lot of problems.
Try to deselect 'Use Autolayout'
It solves to me all the problems trying to translate objects.
Try multiplying by a smaller value against PI to see if it is rotating or just disappearing. If I remember correctly, rotations are not based on the center, but on the top-left corner, so you have to translate afterwards!
For instance, to rotate a video clip this is what I had to do:
CGAffineTransform rotation = CGAffineTransformMakeRotation(M_PI);
CGAffineTransform translateToCenter = CGAffineTransformMakeTranslation(640, 480);
CGAffineTransform mixedTransform = CGAffineTransformConcat(rotation, translateToCenter);
[firstTrackInstruction setTransform:mixedTransform atTime:kCMTimeZero];
I rotated by PI first (180 degrees), but because the center of rotation is the top left corner, my video clip was now in the opposite quadrant, and needed to be transformed back! This may be what is happening with your label.
So try this, assuming your label is 42x21 dimensions..
CGAffineTransform rotation = CGAffineTransformMakeRotation(M_PI);
CGAffineTransform translateToCenter = CGAffineTransformMakeTranslation(42, 21);
CGAffineTransform mixedTransform = CGAffineTransformConcat(rotation, translateToCenter);
label.transform = mixedTransform;
try putting the [super viewDidLoad] first :
- (void)viewDidLoad
{
[super viewDidLoad];
testLabel.transform = CGAffineTransformMakeRotation(M_PI*0.25);
}
AutoLayout and AutoResizing often cause difficulties when you apply transforms as these seem to alter the frame rather than the bounds and center of a view. Hence the automatic adjustments wreck havoc on your layout. Try to wrap the transformed label in a view that does not change dimensions and layout that view within its superview however you want.

Using UIPopoverBackgroundView class

Apple is missing documentation on how to use UIPopoverBackgroundView class introduced in iOS5. Anyone have an example?
I have tried to subclass it, but my XCode 4.2 on Lion is missing UIPopoverBackgroundView.h
Edit: Unsurprisingly, it should have been imported as #import <UIKit/UIPopoverBackgroundView.h>
To add to the other, link-only answers, here is how this is done.
Create a new subclass of UIPopoverBackgroundView
Declare the following in your interface:
+(UIEdgeInsets)contentViewInsets;
+(CGFloat)arrowHeight;
+(CGFloat)arrowBase;
#property(nonatomic,readwrite) CGFloat arrowOffset;
#property(nonatomic,readwrite) UIPopoverArrowDirection arrowDirection;
The class methods are straightforward: contentViewInsets returns the width of your borders all the way round (not including the arrow), arrowHeight is the height of your arrow, arrowBase is the base of your arrow.
Implement the two property setters, making sure to call [self setNeedsLayout].
In your initialisation method, create two image views, one holding your arrow (which should be the size of the arrow dimensions in your class methods) and one holding your background image (which must be a resizable image) and add these as subviews. It doesn't matter where you put the subviews at this point, as you don't have an arrow direction or offset. You should make sure the arrow image view is above the background image view so it blends in properly.
Implement layoutSubviews. In here, according to the arrowDirection and arrowOffset properties, you have to adjust the frames of your background view and arrow view.
The frame of your background view should be self.bounds, inset by arrowHeight on whatever edge the arrow is on
The frame of the arrow view should be aligned so that the centre is arrowOffset away from the centre of self (correct according to the axis). You have to change the image orientation if the arrow direction is not up, but my popover would only be up so I didn't do that.
Here is the layoutSubviews method for my Up-only subclass:
-(void)layoutSubviews
{
if (self.arrowDirection == UIPopoverArrowDirectionUp)
{
CGFloat height = [[self class] arrowHeight];
CGFloat base = [[self class] arrowBase];
self.background.frame = CGRectMake(0, height, self.frame.size.width, self.frame.size.height - height);
self.arrow.frame = CGRectMake(self.frame.size.width * 0.5 + self.arrowOffset - base * 0.5, 1.0, base, height);
[self bringSubviewToFront:self.arrow];
}
}
Another link only answer, but customising UIPopoverBackgroundView is more work than you might realise given the limited documentation available and this github project has a complete working example which saved me a lot of time: https://github.com/GiK/GIKPopoverBackgroundView
It's fairly straightforward to drop into your own project. The most fiddly part is adapting the cap insets for whatever custom images you're using. I'd recommend doing your customisations in-situ in the project and as it's easy to verify all the popover orientation/direction use cases display correctly in the simulator before migrating it into your own project.

Resources