How to make `AVCaptureVideoPreviewLayer` clip to the view border during rotation - ios

I add a AVCaptureVideoPreviewLayer pretty much based on Apple's documentation about AVCaptureVideoPreviewLayer as the following:
AVCaptureSession *captureSession = <#Get a capture session#>;<br>
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];<br>
UIView *aView = <#The view in which to present the layer#>;<br>
previewLayer.frame = aView.bounds; // Assume you want the preview layer to fill the view.<br>
[aView.layer addSublayer:previewLayer];
I added the didRotateFromInterfaceOrientation function in order to handle the rotation with the following code:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[self.videoPreviewLayer setFrame:self.view.layer.bounds];
[self.videoPreviewLayer.connection setVideoOrientation:[self getAVCaptureVideoOrientation]];
}
- (AVCaptureVideoOrientation)getAVCaptureVideoOrientation {
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
if ( deviceOrientation == UIDeviceOrientationLandscapeLeft )
return AVCaptureVideoOrientationLandscapeRight;
else if
// and other 3 orientations
// ...
}
The result looks okay but but animation not so good. The camera layer doesn't start resizing until the rotation finished and I can see the background view during the rotation.
http://yzhong.co/wp-content/uploads/2015/03/My_Movie_1_-_Small.gif
Then I start looking around and found these two SO questions: Problems automatically resizing AVCaptureVideoPreviewLayer on rotation and AVCaptureVideoPreviewLayer smooth orientation rotation. I tried the solutions proposed in those question and added the following function:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (_videoPreviewLayer) {
if (toInterfaceOrientation==UIInterfaceOrientationPortrait) {
[self.videoPreviewLayer setAffineTransform:CGAffineTransformMakeRotation(0)];
} else if (toInterfaceOrientation==UIInterfaceOrientationLandscapeLeft) {
[self.videoPreviewLayer setAffineTransform:CGAffineTransformMakeRotation(M_PI/2)];
} else if (toInterfaceOrientation==UIInterfaceOrientationLandscapeRight) {
[self.videoPreviewLayer setAffineTransform:CGAffineTransformMakeRotation(-M_PI/2)];
} else {
[self.videoPreviewLayer setAffineTransform:CGAffineTransformMakeRotation(M_PI)];
}
self.videoPreviewLayer.frame = self.view.bounds;
}
}
And I get this following, which is a lot better. However, the animation is still a little weird. It seems like it is rotating backward and then forward to the right position? Now I have no clue what to do next.
http://yzhong.co/wp-content/uploads/2015/03/My_Movie_2_-_Small.gif
What I am looking for is really very simple. I want to have a nice, smooth and natural transition/rotation such as the Apple's default camera app does. Could anyone tell me what I am doing wrong here?
http://yzhong.co/wp-content/uploads/2015/03/My_Movie_3_-_Small.gif
Please help!

Related

AVCaptureVideoPreviewLayer wrong orientation when UI rotation is disabled

I have a AVCaptureVideoPreviewLayer instance added to a view controller view hierarchy.
- (void) loadView {
...
self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:nil];
self.previewLayer.frame = self.view.bounds;
self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer: _previewLayer];
// adding other UI elements
...
}
...
- (void) _setupPreviewLayerWithSession: (AVCaptureSession*) captureSession
{
self.previewLayer.session = self.captureManager.captureSession;
self.previewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
}
The layer frame is updated in -viewDidLayoutSubviews method. View controller orientation is locked to UIInterfaceOrientationMaskLandscapeRight.
The issue is as following:
the device is held in landscape orientation
the view controller is presented modally - video layer displays correctly.
the device is then locked and while it's locked the device is rotated to portrait orientation.
the device is then unlocked while still being in portrait orientation and for several seconds the video layer is displayed rotated 90 degrees. However, the frame for the video layer is correct. All other UI elements display correctly. After several seconds the layer snaps to correct orientation.
Please find the bounds for the layer and UI elements below
I've tried updating the video layer orientation as following (with no results):
subscribing to AVCaptureSessionDidStartRunningNotification and UIApplicationDidBecomeActiveNotification notifications
calling the update when -viewWillTransitionToSize:withTransitionCoordinator: method is called
on -viewWillAppear:
The issue doesn't seem to be connected to video layer orientation itself, but more to view hierarchy layout.
UPDATE:
As suggested, I also tried updating the video layer orientation on device orientation change which didn't help.
I also noticed that the issue mostly happens after the application is launched and the screen is presented for the first time. On subsequent screen presentations during the same session, the reproduce rate for the issue is really low (something like 1/20).
Try this code:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
-(void)orientationChanged:(NSNotification *)notif {
[_videoPreviewLayer setFrame:_viewPreview.layer.bounds];
if (_videoPreviewLayer.connection.supportsVideoOrientation) {
_videoPreviewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:[UIApplication sharedApplication].statusBarOrientation];
}
}
- (AVCaptureVideoOrientation)interfaceOrientationToVideoOrientation:(UIInterfaceOrientation)orientation {
switch (orientation) {
case UIInterfaceOrientationPortrait:
return AVCaptureVideoOrientationPortrait;
case UIInterfaceOrientationPortraitUpsideDown:
return AVCaptureVideoOrientationPortraitUpsideDown;
case UIInterfaceOrientationLandscapeLeft:
return AVCaptureVideoOrientationLandscapeLeft;
case UIInterfaceOrientationLandscapeRight:
return AVCaptureVideoOrientationLandscapeRight;
default:
break;
}
// NSLog(#"Warning - Didn't recognise interface orientation (%d)",orientation);
return AVCaptureVideoOrientationPortrait;
}

Best method to resize UIView when rotated to landscape and back

Since I am very new to ios programming I have more of a general-design question.
I have a ViewController which contains a GraphView (UIScrollView + UIView) which works fine. When I am rotating to landscape I want the GraphView to resize its height to the display height (so it fills the whole screen) but only 300pts when in portrait.
What I did so far is implementing viewWillLayoutSubviews in the ViewController and resetting the constraints:
- (void)viewWillLayoutSubviews{
_graphViewHeightConstraint.constant = ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait) ? 300:[[UIScreen mainScreen] bounds].size.height-self.navigationController.navigationBar.frame.size.height - 2*_distanceToTopView.constant;
}
and in GraphView.m:
- (void)layoutSubviews{
kGraphHeight = self.frame.size.height;
[self setNeedsDisplay];
}
(because I need the variable kGraphHeight in the code to draw the Graph). This does not seem like a very elegant solution so I wanted to ask what the better way would be? Many thanks for your inputs :)
In GraphView.m
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
kViewWidth = <GET_SCREEN_WIDTH_HERE>;
kViewHeight = <GET_SCREEN_HEIGHT_HERE>;
[self updateViewDimensions];
}
and updateViewDimensions method will set the frame of UIScrollView and UIView
- (void)updateViewDimensions
{
scrollView.frame = self.view.frame;
yourView.frame = CGRectMake(kViewXStartsFrom, kViewYStartsFrom, kViewWidth, kViewHeight);
}
after rotating view to Landscape viewDidLayoutSubviews will be called.
It's working for me.

How to not to rotate a certain UIView allowing UIViewController's rotation

Does anyone know fix(not to rotate) a certain UIView with allowing rotation of UIViewController?
I want to implement a interface like camera app UI. A view showing camera's view does not rotate but HUD(like buttons) does rotate.
You can apply transformation to the view you don't want to rotate.
Use following method to do it.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if(interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
view.transform = CGAffineTransformMakeRotation(-M_PI/2);
}
else if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
view.transform = CGAffineTransformMakeRotation(M_PI/2);
}
return YES;
}

CGAffineTransformMakeRotation(M_PI_2) generate error in console: CGAffineTransformInvert: singular matrix

The first time I turn my iphone turns the button. The second time I turn the iphone fails.
- (void)configureViewsLandscapeMode
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
[self.button setTransform:CGAffineTransformMakeRotation(M_PI_2)];
} else if (deviceOrientation == UIDeviceOrientationLandscapeRight) {
[self.button setTransform:CGAffineTransformMakeRotation(-M_PI_2)];
}
}
I read others similar answers:
UIView scale to 0 using CGAffineTransformMakeScale
CGAffineTransformInvert: singular matrix
I'm starting with IOS and I do not understand the problem. I would like to understand the problem more than having a solution, I would appreciate some guidance
When making a rotation animation, make sure you are not changing view frame. That can happen for example when autoresizing is used or when you are trying to change frame explicitely.
If you change frame and transform at the same time, especially inside an animation, iOS tries to generate an animation from the original position to the target position. That involves some matrix calculations and that can end in an error if multiple changes (e.g. frame) are involved.
Try this :
- (void)configureViewsLandscapeMode
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
[self.button setTransform:CGAffineTransformRotate(CGAffineTransformIdentity, M_PI_2)];
} else if (deviceOrientation == UIDeviceOrientationLandscapeRight) {
[self.button setTransform:CGAffineTransformRotate(CGAffineTransformIdentity, -M_PI_2)];
}
}

Views moved out of place when rotating

I created a UIViewController (based on How to switch views when rotating) to switch between 2 views when the device rotates. Each view is "specialized" for a particular orientation.
It uses the UIDeviceOrientationDidChangeNotification notification to switch views:
-(void) deviceDidRotate: (NSNotification *) aNotification{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
NSLog(#"Device rotated to %d!", orientation);
if ((orientation == UIDeviceOrientationPortrait) ||
(orientation == UIDeviceOrientationPortraitUpsideDown)) {
[self displayView:self.portraitViewController.view];
}else if ((orientation == UIDeviceOrientationLandscapeLeft) ||
(orientation == UIDeviceOrientationLandscapeRight)) {
[self displayView:self.landscapeViewController.view];
}
}
and sort of works. The problems shows up when I rotate to Landscape and then back to Portrait. When going back to portrait the subviews aren't displayed in the right place, specially the UIPickerView:
First Time Portrait:
Rotate to Landscape:
Back to Portrait:
If I repeat the rotation process, things just get worse. What am I doing wrong?
The source code is here: http://dl.dropbox.com/u/3978473/forums/Rotator.zip
Thanks in advance!
To solve your offset problems, rewrite the displayView: method as below.
-(void) displayView: (UIView *)aView{
self.view = aView;
}
Rotations however are strange. you should review that part of code.
Use the UIViewController rotation methods
(void)willRotateToInterfaceOrientation:duration:
(void)didRotateFromInterfaceOrientation:
instead of -(void)deviceDidRotate:
Much simpler, you will avoid that strange bouncing, and you don't need notifications any more.
Do some reading on the apple documentation on the methods i specified above.
Hope this helps.
OK, I found the error. It's pretty simple and stupid: I mixed frame and bounds.
In the displayView: code I was setting the frame of the child view to the frame of the parent view, when it should be the bounds of the parent.

Resources