CALayer content goes out of bounds - iOS - 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.

Related

Resizing UIView frame causes content to resize

having some issues getting my head around resizing UIViews and drawing in drawRect. Currently I am performing some custom drawing in a UIView in the drawRect. Now I want to resize the frame but keep the drawing inside the same. Using a UISlider I have:
- (IBAction)changeSize:(id)sender {
//where 20,20 is the original frame position and 72*72 the original size
CGRect newFrame = CGRectMake(20, 20, 72*self.slider2.value, 72*self.slider2.value);
self.square.frame = newFrame;
}
The frame is growing but the contents inside are also getting stretched with it. What am I doing wrong? Any pointers on this would be great. Thanks
self.contentMode = UIViewContentModeRedraw;
This will cause view to redraw itself when bounds change.
UIView contains a boolean property named autoresizesSubviews. So check out the current value and try setting it to false
self.square.autoresizesSubviews = NO;

Clear Mask on UIView (actually a video view)

I'm using OpenTok which is a webRTC framework. What I need to do is take the displayed video view and crop it to a circle. Problem is, since this video avatar view will be placed in a view with a clear background, I can't just use a mask as shown in this S.O. question:
Cut Out Shape with Animation
I've also tried to use layer.radius in a UIView category:
-(void)setRoundedViewToDiameter:(float)newSize;
{
CGPoint saveCenter = self.center;
CGRect newFrame = CGRectMake(self.frame.origin.x, self.frame.origin.y, newSize, newSize);
self.frame = newFrame;
self.layer.cornerRadius = newSize / 2.0;
self.center = saveCenter;
}
And then applied like so:
- (void) setUserVideoView:(UIView *)view {
[view setRoundedViewToDiameter:[WSUserView dimForUserAvatar:_sizeIndex]];
self.userVideo = view;
[self.userVideo setRoundedViewToDiameter:[WSUserView dimForUserAvatar:_sizeIndex]];
[self addSubview:self.userVideo];
[self sendSubviewToBack:self.userVideo];
[self layoutSubviews];
}
But it's still an uncropped rectangle. Here's the portion of the video view. I'm showing user image avatars at first, but then when a video stream connects I want to replace the image with the video view, but as a circle. The left image is the stream view that I need make a circle.
Also, here's the inspector view of the video view I'm trying to crop. As you can see, it's a OTGLKVideoView class.
Migrated from my comment:
You should set self.layer.masksToBounds = YES because this ensures that the layer's sublayers are clipped with the corner radius too. I'm assuming that the problem is arising because the ever-changing sublayer that is updated whenever the video's frame changes is thereby ignoring the corner radius.
More details can be found through this answer which solves a similar problem: https://stackoverflow.com/a/11325605/556479

Masking UITableViewCell (paralax effect)

In my App I am trying to get a paralax effect in my table. (e.g it's background) which contains a blurred map for each entry.
How I've set it up is i have a XIB with a UITableViewCell and have added the labels to the contentView. Then in code I add the image to the backgroundView. (The backgroundView doesn't exist, and I create an "empty view" to where I add the image).
Then I add the UIInterpolatingMotionEffect to the image view.
This works. But, since the image has the same dimensions as the table view cell, when a person tilts the phone and the paralax effect kicks in white edges appear. As you can see in the image below.
That is not something I want. So I thought, well I could just put a bigger image in the backgroundView. This works while I am not in editing mode. But in editing mode the background image overlaps partially with the delete button. As seen here:
So I thought, I should clip the backgroundView containing the image view. Which I've done with the following code:
self.backgroundView.clipsToBounds = YES;
or this
// Create a mask layer and the frame to determine what will be visible in the view.
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGRect maskRect = self.bounds;
// Create a path with the rectangle in it.
CGPathRef path = CGPathCreateWithRect(maskRect, NULL);
// Set the path to the mask layer.
maskLayer.path = path;
// Release the path since it's not covered by ARC.
CGPathRelease(path);
// Set the mask of the view.
self.backgroundView.layer.mask = maskLayer;
But neither solutions work. It looks like it is not being clipped. Is there an other way to go about this?

Animate frame property using CABasicAnimation

I'm trying to make an exact "translation" of this UIView block-based animation code:
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
someView.frame = CGRect(0, 100, 200, 200);
}
completion:nil];
using CABasicAnimation instead.
I'm totally aware that the frame property is actually a combination of position, bounds and anchorPoint of the underlying layer, as it is described here: http://developer.apple.com/library/mac/#qa/qa1620/_index.html
... and I already made a solution like that, using two CABasicAnimations one setting the position, one for bounds and it works for that one view.
The problem is however that I have subviews inside my view. someView has a subview of type UIScrollView in which I place still another subview of type UIImageView. UIScrollView subview has autoresizingMask set to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight. That all works perfectly if I use the UIView block-based version, however when I try using CABasicAnimations the subviews start behaving unexpectedly(i.e. get resized to incorrect widths). So it seems autoresizingMask is not working correctly when using CABasicAnimations. I noticed also that subviews don't receive a call to setFrame:, although the frame property of the parent view does change after changes to layer position and bounds are made.
That's why I would like to know what would be the correct code to replicate with CABasicAnimation that what is happening when one uses UIView's animateWithDuration method.
I'm totally aware that the frame property is actually a combination of position, bounds and anchorPoint of the underlying layer
Good, but it's important also to be aware that frame is not an animatable property for layers. If you want to animate with CABasicAnimation you must use properties that are animatable for layers. The CALayer documentation marks every such property as explicitly "animatable". The idea of using bounds and position is correct.
Thus, this code does essentially what you were doing before:
[CATransaction setDisableActions:YES];
// set final bounds and position
v.layer.bounds = CGRectMake(0,0,200,200);
v.layer.position = CGPointMake(100,200);
// cause those changes to be animated
CABasicAnimation* a1 = [CABasicAnimation animationWithKeyPath:#"bounds"];
a1.duration = 0.5;
CABasicAnimation* a2 = [CABasicAnimation animationWithKeyPath:#"position"];
a2.duration = 0.5;
[v.layer addAnimation:a1 forKey:nil];
[v.layer addAnimation:a2 forKey:nil];
However, that code has no effect on the size of any sublayers of v.layer. (A subview of v is drawn by a sublayer of v.layer.) That, unfortunately, is the problem you are trying to solve. I believe that by dropping down to the level of layers and direct explicit core animation, you have given up autoresizing, which happens at the view level. Thus you will need to animate the sublayers as well. That is what view animation was doing for you.
This is an unfortunate feature of iOS. Mac OS X has layer constraints (CAConstraint) that do at the layer level what autoresizing does at the view level (and more). But iOS is missing that feature.

Where to set CGAffineTransformMakeScale(2, 2);

I'm developing an iOS app with latest SDK.
I create a AVCaptureVideoPreviewLayer this way:
// Create the preview layer
_videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
[_videoPreviewLayer setFrame:self.view.bounds];
_videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer insertSublayer:_videoPreviewLayer atIndex:0];
And now I want to do this:
_videoPreviewLayer.affineTransform = CGAffineTransformMakeScale(2, 2);
But if I put it before [_videoPreviewLayer setFrame:self.view.bounds]; I get different results than I put it after.
Where do I have to apply scale?
And, if I want to set CGAffineTransformMakeRotation(-M_PI_2); // -90 degrees, where do I have to do it?
You should not use setFrame on a view whose transform is not the identity. According to Apple's documentation on the frame property of UIView:
If the transform property is not the identity transform, the value of this property [frame] is undefined and therefore should be ignored.
And in UIView.h, above the frame property
do not use frame if view is transformed since it will not correctly reflect the actual location of the view. use bounds + center instead.
So you will probably get the consistent results you're looking for if you switch to using setBounds and setCenter instead of setFrame in this code.
To answer the question directly: There's no correct place to put either of these transforms (the scale or the rotation) if you are also using setFrame. If you use setBounds and setCenter then you will get the same results whether you apply the transforms before setBounds/setCenter or after setBounds/setCenter.
EDIT: VansFannel points out that this is a CALayer, not a UIView, so the above comments don't really apply. I'm leaving them so as not to deprive VansFannel's comment of context, and also because it's still a good warning to have for UIViews
Now, for CALayers, if you set the frame (like [_videoPreviewLayer setFrame:self.view.bounds]) that will cause _videoPreviewLayer's actual rendered frame to be self.view.bounds even if _videoPreviewLayer has a non-identity transform. It's important to remember that CALayer's frame is actually a derived property. It's derived from the layer's bounds, position, anchorPoint, and transform. When you use setFrame QuartzCore will figure out a bounds and position that yields the frame you passed to setFrame under the existing anchorPoint and transform.
So if you want those transforms to have any effect you should put them after your call to setFrame. If you set the transforms first then setFrame will effectively negate them. If you absolutely need to set the transforms first then you must avoid setFrame and work with the layer's bounds and position directly.

Resources