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))
Related
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))) }
}
}
I'm trying to change orientation with just one view, the rest are anchored to Portrait. I've set a method in my AppDelegate as below
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if globalVariables.gIsDosageView == "Y" {
if UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight {
return UIInterfaceOrientationMask.all;
} else {
return UIInterfaceOrientationMask.portrait;
}
} else {
return UIInterfaceOrientationMask.portrait;
}
}
The global variable gIsDosageView is set to "Y" when the Dosage view is selected. I've created two separate views, portraitView (375x812) and landscapeView (812x375). I've used the viewWillTransition method to catch each change in orientation as below
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if size.width < 400 {
self.userImg.isHidden = false
self.deviceImg.isHidden = false
self.portraitView.isHidden = false
self.landscapeView.isHidden = true
}
else {
self.userImg.isHidden = true
self.deviceImg.isHidden = true
self.portraitView.isHidden = true
self.landscapeView.isHidden = false
}
}
When I go from the main screen to Dosage it displays the correct screen and will toggle between both orientation views without issue. However, if I go to another screen and come back to the Dosage screen it only shows the screen that was first loaded in both orientation screens. I've stepped through the code and it hides the right screens but this is not reflected in the resulting view. If I select the screen in portrait first, it will toggle successfully between portrait and landscape but if I go to the next screen and return to Dosage, it will only show the Portrait screen regardless of orientation and appears to ignore the code in viewWillTransition().
Why is this and what have I missed?
viewWillTransition is only called when the device is rotated. When you switch between open views without rotating, it won't get called, so won't set the view up how you want it. Try putting the test in viewWillAppear instead.
How can I just rotate the buttons when my device is in portrait or landscape mode? I know something like that:
button.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))
But where I have to call it up in my code?
I don't want to set my device in landscape mode, I just want to rotate the icons when it should be in landscape mode.
Thanks in advance!
You should override func
viewWillTransitionToSize(_ size: CGSize, withTransitionCoordinator coordinator:UIViewControllerTransitionCoordinator)
And call transform there
Don't forget to assign CGAffineTransformIdentity when rotated back
More about rotation: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIContentContainer_Ref/index.html#//apple_ref/occ/intfm/UIContentContainer/viewWillTransitionToSize:withTransitionCoordinator:
The best way to do this is viewDidLoad add this line of code bellow:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(rotate), name: UIDeviceOrientationDidChangeNotification, object: nil)
and then in function rotate in case of device orientation do some code like this:
func rotate(){
if UIDeviceOrientationIsLandscape(UIDevice.currentDevice().orientation) {
//your code here in landscape mode
}
if UIDeviceOrientationIsPortrait(UIDevice.currentDevice().orientation){
//your code in portrait mode
}
}
this solution is simple and really easy to do.
I guess you know how to "rotate" the buttons already, so I'll just tell you how to know that the device has rotated.
In a view controller, override willRotateToInterfaceOrientation:
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
}
In the method body, you can check the value of toInterfaceOrientation to know which orientation the device is rotating to. For example:
switch toInterfaceOrientation {
case Portrait:
// some code here...
default:
break
}
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.
I'm using viewWillTransitionToSize to detect when a device is rotating to landscape. Depending on the target size, I can detect if heading for landscape and adjust my traits as required...
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
if size.width > size.height {
self.setOverrideTraitCollection(UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClass.Regular), forChildViewController: viewController)
}
else{
self.setOverrideTraitCollection(nil, forChildViewController: viewController)
}
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
However, I want to be able to detect whether my device is transitioning to landscape-left or landscape-right. This will allow me to create different behaviours or views, depending on specific orientation of device. (left or right).
Is this possible without using any deprecated functions?
I thought of using status-bar orientation...
let orientation = UIApplication.sharedApplication().statusBarOrientation;
if( orientation == UIInterfaceOrientation.LandscapeLeft )
{
// Do something
}
else if( orientation == UIInterfaceOrientation.LandscapeRight )
{
// Do something else
}
...but that doesn't help because this appears to give the 'old' status orientation.
How can I get the specific target orientation?
You can get orientation while rotating use UIViewControllerTransitionCoordinator as follows:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// will execute before rotation
coordinator.animate(alongsideTransition: { (context: UIViewControllerTransitionCoordinatorContext) in
// will execute during rotation
let orientation = UIApplication.shared.statusBarOrientation
if orientation == .landscapeLeft
{
// Do something
}
else if orientation == .landscapeRight
{
// Do something else
}
}) { (context: UIViewControllerTransitionCoordinatorContext) in
// will execute after rotation
}
}
Use [UIDevice currentDevice].orientation