Prevent UIView with AVCaptureVideoPreviewLayer from rotating with rest of ViewController - ios

I have a view controller, embedded in a tab bar controller, that, among other things, presents a AVCaptureVideoPreviewLayer in a UIView.
When the device is rotated, I want the view controller to rotate with it- except for the aforementioned UIView.
Unlike this related question, however, I am not just rotating/transforming my other views in the view controller. The other views need to use their configured autolayout rotation behavior.
I've tried several things, including simply setting the video orientation to portrait:
previewLayer.connection.videoOrientation = .portrait
to extracting the UIView to a separate view controller, embedding that view controller into the original view controller, and setting its autoRotation properties
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
but then I learned here that iOS only looks at the top-level view controller for those properties.
With everything I have tried, the video preview is rotating with the rest of the view controller- ending up sideways.
The only thing that works, but is hacky and sometimes causes the video preview to become misaligned, is this
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if let videoPreviewLayerConnection = previewLayer.connection {
if let newVideoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue) {
videoPreviewLayerConnection.videoOrientation = newVideoOrientation
}
}
}
}
I basically need the opposite of this question.
How can I force the video preview to not rotate but also allow the rest of the view controller to rotate normally? (Same behavior as iOS Camera app except that the other UI elements rotate normally instead of the 90° rotation transform)

The following is possibly as hacky as your solution but it looks cleaner visually.
In viewWillTransition I set the affine transform of the previewView to counteract the orientation set by rotating the phone. It looks cleaner than just setting the videoOrientation as the affine transform animates at the same speed as the orientation change. It is done as follows.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let orientation = UIDevice.current.orientation
var rotation : CGFloat = self.previewView.transform.rotation
switch(orientation) {
case .portrait:
rotation = 0.0
case .portraitUpsideDown:
rotation = CGFloat.pi
case .landscapeLeft:
rotation = -CGFloat.pi/2.0
case .landscapeRight:
rotation = CGFloat.pi/2.0
default:
break
}
let xScale = self.previewView.transform.xScale
let yScale = self.previewView.transform.yScale
self.previewView.transform = CGAffineTransform(scaleX:xScale, y:yScale).rotated(by:rotation)
self.view.layoutIfNeeded()
}
Here is the extension to CGAffineTransform the code above uses
extension CGAffineTransform {
public var xScale: CGFloat {
get {return sqrt(self.a * self.a + self.c * self.c) }
}
public var yScale: CGFloat {
get {return sqrt(self.b * self.b + self.d * self.d) }
}
public var rotation: CGFloat {
get {return CGFloat(atan2f(Float(self.b), Float(self.a))) }
}
}

Related

Prevent MTKView camera feed rotation, but allow other on-screen VCs to rotate

Goal
With an MTKView, replicate the gravity of the AVCaptureVideoPreviewLayer or Apple's Camera app. Video device orientation does not change. The camera feed's edges do not budge a pixel, never revealing the screen background. Other on-screen VCs rotate normally.
Observed
Applying Tech QA 1890's transform during viewWillTransition, the MTKView does counter-rotate... BUT that rotation is still uncomfortably visible. The edges of the view come unpinned during the animation, masking some camera pixels and showing a white background set for the VC holding the MTKView.
Question
How can I make those edges stick to screen bounds like a scared clam?
I assume my error is in constraints, but I'm open to being wrong in other ways. :)
View Hierarchy
A tiny camera filter app has an overlay of camera controls (VC #1) atop an MTKView (in VC #2) pinned to the screen's edges.
UINavigationController
│
└─ CameraScreenVC
│
├── CameraControlsVC <- Please rotate subviews
│
└── MetalCameraFeedVC
└── MTKView <- Please no rotation edges
Code
Buildable demo repo
Relevant snippets below.
MetalCameraVC.swift
final class MetalCameraVC: UIViewController {
let mtkView = MTKView() // This VC's only view
/// Called in viewDidAppear
func setupMetal(){
metalDevice = MTLCreateSystemDefaultDevice()
mtkView.device = metalDevice
mtkView.isPaused = true
mtkView.enableSetNeedsDisplay = false
metalCommandQueue = metalDevice.makeCommandQueue()
mtkView.delegate = self
mtkView.framebufferOnly = false
ciContext = CIContext(
mtlDevice: metalDevice,
options: [.workingColorSpace: CGColorSpace(name: CGColorSpace.sRGB)!])
}
...
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
// blank
}
func draw(in mtkview: MTKView) {
image = image.transformed(by: scaleToScreenBounds)
image = image.cropped(to: mtkview.drawableSize.zeroOriginRect())
guard let buffer = metalCommandQueue.makeCommandBuffer(),
let currentDrawable = mtkview.currentDrawable
else { return }
ciContext.render(image,
to: currentDrawable.texture,
commandBuffer: buffer,
bounds: mtkview.drawableSize.zeroOriginRect(),
colorSpace: CGColorSpaceCreateDeviceRGB())
buffer.present(currentDrawable)
buffer.commit()
}
}
extension MetalCameraVC {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mtkView)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
mtkView.frame = view.frame
if let orientation = AVCaptureVideoOrientation.fromCurrentDeviceOrientation() {
lastOrientation = orientation
}
}
}
+Rotation
/// Apple Technical QA 1890 Prevent View From Rotating
extension MetalCameraVC {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
mtkView.center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate { [self] context in
let delta = coordinator.targetTransform
let deltaAngle = atan2(delta.b, delta.a)
var currentAngle = mtkView.layer.value(forKeyPath: "transform.rotation.z") as? CGFloat ?? 0
currentAngle += -1 * deltaAngle + 0.1
mtkView.layer.setValue(currentAngle, forKeyPath: "transform.rotation.z")
} completion: { [self] context in
var rounded = mtkView.transform
rounded.a = round(rounded.a)
rounded.b = round(rounded.b)
rounded.c = round(rounded.c)
rounded.d = round(rounded.d)
mtkView.transform = rounded
}
}
}
I think the "easiest" solution is to actually prevent your UI from rotating and observing device orientation changes to manually rotate only the interface elements you want to rotate.
In your view controller, you can override shouldAutorotate to prevent auto-rotation and use supportedInterfaceOrientations to only return the one allowed orientation.
Rotating UI elements manually can be done using their transform property.

How to exactly replicate native Camera.app UI?

I'm currently implementing a custom camera app, and it turns out that replicating Camera.app's UI is quite tricky!
The question that bothers me the most is of course autorotation. Camera.app creates an illusion that UI doesn't rotate, except for some buttons, but it does rotate! This is clear, because:
Home indicator also rotates with the device
Control Center and Notification Center can be pulled down from the top
According to this Q&A, looks like Camera.app uses viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator), and rotates the elements in opposite direction to create the illusion that nothing actually rotates. But there's a catch: to achieve this effect, views should not use Auto Layout, which makes lots of things more complicated than needed.
Using sample code from the aforementioned Q&A I created a simple app that uses a UIImageView with a screenshot of the Camera.app.
class ViewController: UIViewController {
let imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imageView)
imageView.image = UIImage(named: "camera_screenshot.PNG")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
imageView.frame = view.bounds
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.imageView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate { context in
let deltaTransform = coordinator.targetTransform
let deltaAngle = atan2(deltaTransform.b, deltaTransform.a)
if let currentRotation = self.imageView.layer.value(forKeyPath: "transform.rotation.z") as? CGFloat {
let newRotation = currentRotation + (-1 * deltaAngle + 0.0001)
self.imageView.layer.setValue(newRotation, forKeyPath: "transform.rotation.z")
}
} completion: { context in
// Integralize the transform to undo the extra 0.0001 added to the rotation angle.
var currentTransform = self.imageView.transform
currentTransform.a = round(currentTransform.a)
currentTransform.b = round(currentTransform.b)
currentTransform.c = round(currentTransform.c)
currentTransform.d = round(currentTransform.d)
self.imageView.transform = currentTransform
}
}
override var prefersStatusBarHidden: Bool {
return true
}
}
Good! But not good enough! As you can see in this video the image view doesn't rotate but there's still system animation of rotating the UIWindow (I guess?). Interestingly enough, this system animation appears on iPhone 7 (iOS 13.4.1), but doesn't appear on iPhone 11 Pro (iOS 14.3).
So my questions are:
How to get rid of that "system animation"?
Is it possible to replicate Camera.app UI WITH Auto Layout?
Note: please, do not suggest the approach with multiple UIWindow's because it feels quite hacky to me.

Swift 3 -Flip entire screen/view On Orientation change

I need to a way to constantly monitor if the user has rotated the iPad.
UserDidRotate() {
if(orientation = portrait.upsideDown){
//
//Code that will Present View upside down...
//
}
else if(orientation = portrait){
//
//Code that will Present View right side up...
//
}
}
How can I check for orientation change and also manually present the view upside down for my Swift 3 app?
EDIT:
I have tried:
override func viewWillTransition(to size: CGSize, with coordinator:
UIViewControllerTransitionCoordinator){}
and that method is never hit.
Try:
self.view.transform = self.view.transform.rotated(by: CGFloat(M_PI))

Preventing AVCaptureVideoPreviewLayer from rotating, but allow UI layer to rotate with orientation

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
}
}
}
On the view that shows the camera output add:
AVCaptureVideoPreviewLayer *layer = (AVCaptureVideoPreviewLayer *)self.layer;
if ([layer.connection isVideoOrientationSupported]) {
[layer.connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
}
AVCaptureVideoOrientationPortrait is just one option. You can choose from the following:
typedef NS_ENUM(NSInteger, AVCaptureVideoOrientation) {
AVCaptureVideoOrientationPortrait = 1,
AVCaptureVideoOrientationPortraitUpsideDown = 2,
AVCaptureVideoOrientationLandscapeRight = 3,
AVCaptureVideoOrientationLandscapeLeft = 4,
}
This must be done after you setup the session.

Disable autorotate on a single subview in iOS8

I'm writing a drawing app and I don't want the drawing view to rotate. At the same time, I want other controls to rotate nicely depending on the orientation of the device. In iOS 7 I've solved this via:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
float rotation;
if (toInterfaceOrientation==UIInterfaceOrientationPortrait) {
rotation = 0;
}
else if (toInterfaceOrientation==UIInterfaceOrientationLandscapeLeft) {
rotation = M_PI/2;
} else if (toInterfaceOrientation==UIInterfaceOrientationLandscapeRight) {
rotation = -M_PI/2;
}
self.drawingView.transform = CGAffineTransformMakeRotation(rotation);
self.drawingView.frame = self.view.frame;
}
But on iOS 8 even though the function is called and the transform is set correctly, it does not prevent the view from rotating.
I've tried creating a view controller which simply prevents the rotation of it's view and add it's view to the view hierarchy, but then it doesn't respond to user input.
Any ideas?
Thanks!
Okay after some fighting with subviews and transitionCoordinators I've finally figured it out:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
CGAffineTransform targetRotation = [coordinator targetTransform];
CGAffineTransform inverseRotation = CGAffineTransformInvert(targetRotation);
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.drawingView.transform = CGAffineTransformConcat(self.drawingView.transform, inverseRotation);
self.drawingView.frame = self.view.bounds;
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
}];
}
What I do is, I calculate the inverse of the transform applied to the view and then use it to change the transform. furthermore I change the frame with the view bounds. This is due to it being full screen.
Dimitri's answer worked perfectly for me. This is the swift version of the code in case someone needs it...
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
let targetRotation = coordinator.targetTransform()
let inverseRotation = CGAffineTransformInvert(targetRotation)
coordinator.animateAlongsideTransition({ context in
self.drawingView.transform = CGAffineTransformConcat(self.drawingView.transform, inverseRotation)
self.drawingView.frame = self.view.bounds
context.viewControllerForKey(UITransitionContextFromViewControllerKey)
}, completion: nil)
}
Swift 4 has a lot of updates, including the viewWillTransition function.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let targetRotation = coordinator.targetTransform
let inverseRotation = targetRotation.inverted()
coordinator.animate(alongsideTransition: { context in
self.drawingView.transform = self.drawingView.transform.concatenating(inverseRotation)
self.drawingView.frame = self.view.bounds
context.viewController(forKey: UITransitionContextViewControllerKey.from)
}, completion: nil)
}
I managed to obtain this, check:
rotation working as desired
Note: The green and the red views are subviews of the controller's view. The blue view is subview of the red view.
Idea
According to https://developer.apple.com/library/archive/qa/qa1890/_index.html, we need to apply an inverse rotation to the view when it is transitioning to a new size.
In addition to that, we need to adjust the constraints after rotation (landscape/portrait).
Implementation
class MyViewController: UIViewController {
var viewThatShouldNotRotate = UIView()
var view2 = UIView()
var insiderView = UIView()
var portraitConstraints: [NSLayoutConstraint]!
var landscapeConstraints: [NSLayoutConstraint]!
override func viewDidLoad() {
super.viewDidLoad()
viewThatShouldNotRotate.backgroundColor = .red
view2.backgroundColor = .green
insiderView.backgroundColor = .blue
view.addSubview(viewThatShouldNotRotate)
view.addSubview(view2)
viewThatShouldNotRotate.addSubview(insiderView)
portraitConstraints = createConstraintsForPortrait()
landscapeConstraints = createConstraintsForLandscape()
}
func createConstraintsForLandscape() -> [NSLayoutConstraint] {
return NSLayoutConstraint.autoCreateConstraintsWithoutInstalling {
viewThatShouldNotRotate.autoMatch(.height, to: .width, of: view)
viewThatShouldNotRotate.autoMatch(.width, to: .height, of: view)
viewThatShouldNotRotate.autoCenterInSuperview()
view2.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(), excludingEdge: .top)
view2.autoSetDimension(.height, toSize: 100)
insiderView.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
insiderView.autoSetDimension(.height, toSize: 100)
}
}
func createConstraintsForPortrait() -> [NSLayoutConstraint] {
return NSLayoutConstraint.autoCreateConstraintsWithoutInstalling {
viewThatShouldNotRotate.autoMatch(.height, to: .height, of: view)
viewThatShouldNotRotate.autoMatch(.width, to: .width, of: view)
viewThatShouldNotRotate.autoCenterInSuperview()
view2.autoPinEdges(toSuperviewMarginsExcludingEdge: .top)
view2.autoSetDimension(.height, toSize: 100)
insiderView.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
insiderView.autoSetDimension(.height, toSize: 100)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if view.bounds.size.width > view.bounds.size.height {
// landscape
portraitConstraints.forEach {$0.autoRemove()}
landscapeConstraints.forEach { $0.autoInstall() }
} else {
landscapeConstraints.forEach {$0.autoRemove()}
portraitConstraints.forEach { $0.autoInstall() }
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let viewToRotate: UIView = viewThatShouldNotRotate
coordinator.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) -> Void in
let deltaTransform = coordinator.targetTransform
let deltaAngle = atan2f(Float(deltaTransform.b), Float(deltaTransform.a))
var currentRotation = (viewToRotate.layer.value(forKeyPath: "transform.rotation.z") as! NSNumber).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 = currentRotation + (-1 * Float(deltaAngle)) + 0.0001
viewToRotate.layer.setValue(currentRotation, forKeyPath:"transform.rotation.z")
}) { (UIViewControllerTransitionCoordinatorContext) -> Void in
var currentTransform = viewToRotate.transform;
currentTransform.a = round(currentTransform.a);
currentTransform.b = round(currentTransform.b);
currentTransform.c = round(currentTransform.c);
currentTransform.d = round(currentTransform.d);
viewToRotate.transform = currentTransform;
}
}
}
In iOS 8 transforms aren't applied to individual views owned by view controllers. Instead the rotation transforms are applied to the UIWindow. The result is that developers never see a rotation being applied, but rather a resize.
In iOS 8 you can either override the callbacks for size class changes and perform your own transform there, or you can get the orientation events as described here: https://developer.apple.com/library/prerelease/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/motion_event_basics/motion_event_basics.html.

Resources