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.
Related
I have a problem. I'm working on making a game. As part of my game I need images to be rotated and then moved in the direction of the rotated angle inside a game loop (using an NSTimer). In essence I'm trying to create the effect of launching a projectile. The code works fine when moving in perpendicular directions such as 0, 90, 180, 270, and 360 degrees, but any other angle and the image starts to glitch out. The object on the screen maintains its correct bounds and contents, but the actual displayed image disappears. Does anybody know what the problem is or someway I could get around it? If needed, I can make and post a video of my problem so you can see what I'm talking about.
Here is a sample of the code I'm using. The "background" variable is just a UIImageView:
angle = 60;
background.transform = CGAffineTransformRotate(object.transform, angle*M_PI/180); //converts degrees to radians and rotates the image
background.frame = CGRectMake( background.frame.origin.x + cos(angle*m_PI/180)*32; background.frame.origin.y -sin(angle*M_PI/180)*32, background.frame.size.width, background.frame.size.height); //moves the image in the direction of the angle
For starters, there is a semicolon after the x origin in your CGRect instead of a comma. Was that just a typo?
The UIView documentation for frame states:
Warning: If the transform property is not the identity transform, the
value of this property is undefined and therefore should be ignored.
Changes to this property can be animated. However, if the transform
property contains a non-identity transform, the value of the frame
property is undefined and should not be modified. In that case, you
can reposition the view using the center property and adjust the size
using the bounds property instead.
So there you have it, you should not be trying to change the frame when setting a custom transform. You are only trying to adjust the position of the view anyway so just modify your code to adjust center instead of the origin coordinates.
To change the size, you can use the bounds.
CGRect bounds = myView.bounds;
bounds.size.width = whatever;
bounds.size.height = whatever;
myView.bounds = bounds;
I have rotated a UIImageView by calling
myView.transform = CGAffineTransformMakeRotation(someRadians)
myView.layer.allowsEdgeAntialiasing = YES;
If I call...
[myView setFrame:CGRectMake(myView.frame.origin.x, myView.frame.origin.y-100, myView.frame.size.width, myView.frame.size.height)];
... my view gets stretched, rather then simply moving upward a bit.
Is this normal behaviour? Im I not supposed to set the frame after rotating a view? Is there a solution? Thanks.
From the UIView documentation:
Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
In other words, don't use a view's frame if it doesn't have the identity transform.
Turns out, as #Scott Berrevoets suggested, there is no way to use the frame property after making a transformation.
Instead we have to move the view with another transformation. In order to not make the move transformation override the rotation, we can concat the translation transformation to the view's existing rotation transformation.
CGAffineTransform moveTransform = CGAffineTransformMakeTranslation(0, -100);
myView.transform = CGAffineTransformConcat(myView.transform, moveTransform);
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.
My transform does not draw after the frame is redrawn with setFrame.
I'm scaling a view when the orientation changes using setFrame. But this view also needs to change position depending on a BOOL argument: On = up in view, off = down off screen. I use setTransform to adjust the position of the view.
First I draw the view by doing a setFrame. This draws the view 100 points just off screen at the bottom. Then I set a transform (-100 on the ty) to bring it up into the view points (as long as the BOOL is set to TRUE, else the transform is 0 and the view is off screen).
[view setFrame:CGRectMake(0, self.view.bounds.size.height, self.view.bounds.size.width, 100)];
[view setTransform:CGAffineTransformMake(1, 0, 0, 1, 0, -100)]
This works fine, as expected, but when change orientation, and this code is re-run (to re-size the view) the transform does not draw, even though it is Logged as being the transform of the view. In other words the view is just off screen, as if the transform.ty was 0.
Log message before re-draw: view.transform.ty -10.000000
Log message after re-draw: view.transform.ty -10.000000
Any help?
A view's frame is a value that is derived from three fundamental values: bounds, center, and transform. The setter for frame tries to do the right thing by reversing the process, but it can't always work correctly when a non-identity transform is set.
The documentation is pretty clear on this point:
If the transform property is not the identity transform, the value of
this property is undefined and therefore should be ignored.
...
If the transform property contains a non-identity transform, the value
of the frame property is undefined and should not be modified. In that
case, you can reposition the view using the center property and adjust
the size using the bounds property instead.
Since your transform only translates the view, I don't see any reason to use it at all. Just change the origin of the view's frame:
[view setFrame:CGRectMake(0, self.view.bounds.size.height - 100, self.view.bounds.size.width, 100)];
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.