I have two view controllers. One is the root VC and contains the UI interface such as the record button. On this view controller, I also display the view of another VC at index 0. This view contains a AVCaptureVideoPreviewLayer.
I would like my video camera to mimic the Apple video camera app, where the interface layout adjusts with the rotation, but the video preview layer does not. You can see how the recording timer (UILabel) in the stock video app disappears and reappears at the top depending on the orientation.
Any idea how to do this? I found one suggestion that recommendeds adding the preview to the app delegate's window, since it won't conform to the rotation of the nav controller, but it didn't work for me.
Thanks!
I have a very similar situation. I just have one view controller and I want to have a AVCaptureVideoPreviewLayer
that doesn't rotate in it. I found the accepted solution by @SeanLintern88 did not work for me; the status bar never moved and the WKWebView I had on the screen was not getting resizes properly.
One of the bigger issues I ran into was that I was putting my AVCaptureVideoPreviewLayer
in the view controller's view. It is much better to create a new UIView
just to hold the layer.
After that I found a technical note from Apple QA1890: Preventing a View From Rotating. This allowed me to produce the following swift code:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition(
{ (UIViewControllerTransitionCoordinatorContext) in
let deltaTransform = coordinator.targetTransform()
let deltaAngle = atan2f(Float(deltaTransform.b), Float(deltaTransform.a))
var currentRotation : Float = (self.previewView!.layer.valueForKeyPath("transform.rotation.z")?.floatValue)!
// Adding a small value to the rotation angle forces the animation to occur in a the desired direction, preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
currentRotation += -1 * deltaAngle + 0.0001;
self.previewView!.layer.setValue(currentRotation, forKeyPath: "transform.rotation.z")
self.previewView!.layer.frame = self.view.bounds
},
completion:
{ (UIViewControllerTransitionCoordinatorContext) in
// Integralize the transform to undo the extra 0.0001 added to the rotation angle.
var currentTransform : CGAffineTransform = self.previewView!.transform
currentTransform.a = round(currentTransform.a)
currentTransform.b = round(currentTransform.b)
currentTransform.c = round(currentTransform.c)
currentTransform.d = round(currentTransform.d)
self.previewView!.transform = currentTransform
})
}
The original tech note did not have the line self.previewView!.layer.frame = self.view.bounds
but I found that very necessary because although the anchor point doesn't move, the frame has. Without that line, the preview will be offset.
Also, since I am doing all of the work keeping the view in the correct position, I had to remove all the positioning constraints on it. When I had them in, they would cause the preview to instead be offset in the opposite direction.
Make sure to set shouldAutorotate to return false:
-(BOOL)shouldAutorotate{
return NO;
}
register for Notifications that orientation changed:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
implement the notification change
-(void)orientationChanged:(NSNotification *)notif {
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
// Calculate rotation angle
CGFloat angle;
switch (deviceOrientation) {
case UIDeviceOrientationPortraitUpsideDown:
angle = M_PI;
break;
case UIDeviceOrientationLandscapeLeft:
angle = M_PI_2;
break;
case UIDeviceOrientationLandscapeRight:
angle = - M_PI_2;
break;
default:
angle = 0;
break;
}
}
and rotate the UI
[UIView animateWithDuration:.3 animations:^{
self.closeButton.transform = CGAffineTransformMakeRotation(angle);
self.gridButton.transform = CGAffineTransformMakeRotation(angle);
self.flashButton.transform = CGAffineTransformMakeRotation(angle);
} completion:^(BOOL finished) {
}];
This is how I implement the screen being locked but rotating the UI, if this works link the stacks post and I can copy it over there and you can tick it :P
You'll find that the Camera app only supports Portrait orientation and rotates view elements as required.
I came here with a similar issue, except I ended up requiring the AVCaptureVideoPreviewLayer
to stay oriented with the rest of the view so i could correctly use the metadataOutputRectConverted(fromLayerRect:)
method.
Like Erik Allens answer above, My solution is based off Technical Q&A QA1890 Preventing a View From Rotating, but has been updated to Swift 5 and removes the rotation transform once the interface transition is complete.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.cameraPreviewView = UIView(frame: view.bounds)
self.cameraPreviewView.layer.addSublayer(self.videoPreviewLayer)
self.videoPreviewLayer.connection?.videoOrientation = UIApplication.shared.statusBarOrientation.asAVCaptureVideoOrientation()
self.videoPreviewLayer.frame = self.cameraPreviewView.bounds
self.view.addSubview(self.cameraPreviewView)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.cameraPreviewView.center = self.view.bounds.midPoint
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let cameraPreviewTransform = self.cameraPreviewView.transform
coordinator.animate(alongsideTransition: { context in
// Keep the camera preview view inversely rotatated with the rest of the view during transition animations
let deltaTransform = coordinator.targetTransform
let deltaAngle: CGFloat = atan2(deltaTransform.b, deltaTransform.a)
var previewCurrentRotation = atan2(cameraPreviewTransform.b, cameraPreviewTransform.a)
// Adding a small value to the rotation angle forces the animation to occur in a the desired direction,
// preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
previewCurrentRotation += -1 * deltaAngle + 0.0001
self.cameraPreviewView.layer.setValue(previewCurrentRotation, forKeyPath: "transform.rotation.z")
}, completion: { context in
// Now the view transition animations are complete, we will adjust videoPreviewLayer properties to fit the current orientation
// Changing the frame of a videoPreviewLayer animates the resizing of the preview view, so we disable animations (actions) to remove this effect
CATransaction.begin()
CATransaction.setDisableActions(true)
self.cameraPreviewView.transform = cameraPreviewTransform
self.cameraPreviewView.frame = self.view.bounds
self.videoPreviewLayer.connection?.videoOrientation = UIApplication.shared.statusBarOrientation.asAVCaptureVideoOrientation()
self.videoPreviewLayer.frame = self.cameraPreviewView.bounds
CATransaction.commit()
})
}
extension UIInterfaceOrientation {
func asAVCaptureVideoOrientation() -> AVCaptureVideoOrientation {
switch self {
case .portrait:
return .portrait
case .landscapeLeft:
return .landscapeLeft
case .landscapeRight:
return .landscapeRight
case .portraitUpsideDown:
return .portraitUpsideDown
default:
return .portrait
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With