iOS viewWillTransitionToSize and device orientation - ios

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

Related

Detect the landscape orientation left or right

My app supports portrait and landscape -> Both left and right. I am able to detect if its landscape. But not able to detect left or right. Here is my code
if UIDevice.current.orientation.isLandscape {
// Do some task
}
When user rotates the device i need to detect whether the user rotated to landscape left or landscape right !
Same way inside my above condition I need to check whether its left or right side. How can I detect that?
Thanks
I think you are looking for something like this
if UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft {
} else if UIDevice.current.orientation == UIDeviceOrientation.landscapeRight {
} else {
//not landscape left or right
}
EDIT --------
based on your comments you are looking for interface orientation instead of device orientation.
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
var text=""
switch UIDevice.current.orientation{
case .portrait:
text="Portrait"
case .portraitUpsideDown:
text="PortraitUpsideDown"
case .landscapeLeft:
text="LandscapeLeft"
case .landscapeRight:
text="LandscapeRight"
default:
text="Another"
}
NSLog("You have moved: \(text)")
}
the code above detects interface orientation...
note how the switch statement still uses UIDeviceOrientation
Below is another method that you may want to use
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation.isLandscape {
print("landscape")
} else {
print("portrait")
}
}
Note again that UIDevice Orientation is still used...
Below is a very different but effective approach.
struct DeviceInfo {
struct Orientation {
// indicate current device is in the LandScape orientation
static var isLandscape: Bool {
get {
return UIDevice.current.orientation.isValidInterfaceOrientation
? UIDevice.current.orientation.isLandscape
: UIApplication.shared.statusBarOrientation.isLandscape
}
}
// indicate current device is in the Portrait orientation
static var isPortrait: Bool {
get {
return UIDevice.current.orientation.isValidInterfaceOrientation
? UIDevice.current.orientation.isPortrait
: UIApplication.shared.statusBarOrientation.isPortrait
}
}
}}
I am doing it like this for now:
public extension UIScreen {
public class var isLandscapeLeft: Bool { UIApplication.shared.statusBarOrientation == .landscapeLeft }
public class var isLandscapeRight: Bool { UIApplication.shared.statusBarOrientation == .landscapeRight }
}
It's as extension to UIScreen just for convenience so its easy to find..
Also to detect when app actually rotates from left to right when I have just landscape enabled, I had to use AppDelegate's function:
func application(_ application: UIApplication, didChangeStatusBarOrientation oldStatusBarOrientation: UIInterfaceOrientation) { ... }
For whatever reason nothing else get's triggered.
Looks like also, you have to use UIApplication.shared.statusBarOrientation when checking from didChangeStatusBarOrientation instead of UIApplication.shared.delegate?.window??.rootViewController?.interfaceOrientation I used for other situations.

Cannot catch orientation changes from "upsidedown" to landscapes on iOS

I need to invalidate my collection view layout when screen orientation changes. Since iPhone X and others have safe area insets; I need my cells' subviews positioned according to screen safe area insets.
If you switch between left-right landscapes from portrait there is no problem. But if you switch from upsidedown I cannot catch the orientation change and automatically cannot
I tried two existing approaches.
First is using NotificationCenter. Second is overriding viewWillTransitionToSize:withTransitionCoordinator: function. It's all the same.
in CollectionViewController:
NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: nil) { (notif) in
self.collectionViewLayout.invalidateLayout()
}
in Cell:
override func layoutSubviews() {
super.layoutSubviews()
let orientation = UIDevice.current.orientation
if let insets = UIApplication.shared.keyWindow?.safeAreaInsets {
if orientation == .landscapeLeft {
titleLabelLeadingConstraint.constant = insets.left + 20
titleLabelTrailingConstraint.constant = 20
} else if orientation == .landscapeRight {
titleLabelLeadingConstraint.constant = 20
titleLabelTrailingConstraint.constant = insets.right + 20
} else {
titleLabelLeadingConstraint.constant = 20
titleLabelTrailingConstraint.constant = 20
}
}
print("orin: \(orientation.rawValue) | left: \(titleLabelLeadingConstraint.constant) | right: \(titleLabelTrailingConstraint.constant)")
}
The print() function not being called when you switch from/to upsidedown. But collectionviewlayouts invalidateLayout() function is being called.
As you've already tried, you can detect orientation change using viewWillTransitionToSize:withTransitionCoordinator:.
I don't know your first attempt, however, try this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
//If you need to invalidate your collectionView layout, do it in this function
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
//
} else {
//
}
}
If you need to know the difference between landscapeLeft or Right, try your code in this way:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
//If you need to invalidate your collectionView layout, do it in this function
super.viewWillTransition(to: size, with: coordinator)
if let insets = UIApplication.shared.keyWindow?.safeAreaInsets {
if UIDevice.current.orientation == .landscapeLeft {
titleLabelLeadingConstraint.constant = insets.left + 20
titleLabelTrailingConstraint.constant = 20
} else if UIDevice.current.orientation == .landscapeRight {
titleLabelLeadingConstraint.constant = 20
titleLabelTrailingConstraint.constant = insets.right + 20
} else {
titleLabelLeadingConstraint.constant = 20
titleLabelTrailingConstraint.constant = 20
}
}
}
NOTE: You don't need to register an observer
OK. At last, I came up with the thing I did not want to do. Observed orientation changes from the cells' themselves.
override func awakeFromNib() {
super.awakeFromNib()
NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: nil) { (notif ) in
// doing my things already stated in the question's itself.
}
}
Now they can observe all the changes. But I do not know if this much registration is a wise thing; even though collection view reuses cells and -in my case- 7 to 8 cells are on screen at most.

Read orientation of device even though portrait is only enabled [duplicate]

I was wondering how I can get the current device orientation in Swift? I know there are examples for Objective-C, however I haven't been able to get it working in Swift.
I am trying to get the device orientation and put that into an if statement.
This is the line that I am having the most issues with:
[[UIApplication sharedApplication] statusBarOrientation]
you can use:
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
var text=""
switch UIDevice.currentDevice().orientation{
case .Portrait:
text="Portrait"
case .PortraitUpsideDown:
text="PortraitUpsideDown"
case .LandscapeLeft:
text="LandscapeLeft"
case .LandscapeRight:
text="LandscapeRight"
default:
text="Another"
}
NSLog("You have moved: \(text)")
}
SWIFT 3 UPDATE
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
var text=""
switch UIDevice.current.orientation{
case .portrait:
text="Portrait"
case .portraitUpsideDown:
text="PortraitUpsideDown"
case .landscapeLeft:
text="LandscapeLeft"
case .landscapeRight:
text="LandscapeRight"
default:
text="Another"
}
NSLog("You have moved: \(text)")
}
or
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
}
with Notification you can check: IOS8 Swift: How to detect orientation change?
NOTE : didRotateFromInterfaceOrientation is Deprecated Use
viewWillTransitionToSize for iOS 2.0 and later
In case of Face up and Face Down this will not work.
So we need to use the following.
if UIApplication.shared.statusBarOrientation.isLandscape {
// activate landscape changes
} else {
// activate portrait changes
}
To get the status bar (and therefor UI) orientation like the Objective-C code you have, it's simply:
UIApplication.sharedApplication().statusBarOrientation
You can also use the orientation property of UIDevice:
UIDevice.currentDevice().orientation
However, that may not match what orientation your UI is in. From the docs:
The value of the property is a constant that indicates the current
orientation of the device. This value represents the physical
orientation of the device and may be different from the current
orientation of your application’s user interface. See
“UIDeviceOrientation” for descriptions of the possible values.
struct DeviceInfo {
struct Orientation {
// indicate current device is in the LandScape orientation
static var isLandscape: Bool {
get {
return UIDevice.current.orientation.isValidInterfaceOrientation
? UIDevice.current.orientation.isLandscape
: UIApplication.shared.statusBarOrientation.isLandscape
}
}
// indicate current device is in the Portrait orientation
static var isPortrait: Bool {
get {
return UIDevice.current.orientation.isValidInterfaceOrientation
? UIDevice.current.orientation.isPortrait
: UIApplication.shared.statusBarOrientation.isPortrait
}
}
}}
swift4 answer:
this is how I do it,
1.works for all kinds of view controller
2.also work when the user rotates the app
3.also for the first time install the app
Apple recently got rid of the idea of Landscape vs. Portrait and prefers we use screen size. However, this works:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.currentDevice().orientation.isLandscape.boolValue {
print("landscape")
} else {
print("portrait")
}
}
Swift 4:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation.isLandscape {
print("landscape")
} else {
print("portrait")
}
}
To find current device orientation simply use this code:
UIApplication.sharedApplication().statusBarOrientation
for swift 3.0
UIApplication.shared.statusBarOrientation
statusBarOrientation is deprecated, so no longer available to use like
in above answers
In this code can get orientation without worrying about depreciation. Swift 5
ioS 13.2 Tested 100%
Your application should allow working in both portrait and landscape to use the below code, otherwise, results will be different
windows.first is main window
windows.last is your current window
struct Orientation {
// indicate current device is in the LandScape orientation
static var isLandscape: Bool {
get {
return UIDevice.current.orientation.isValidInterfaceOrientation
? UIDevice.current.orientation.isLandscape
: (UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isLandscape)!
}
}
// indicate current device is in the Portrait orientation
static var isPortrait: Bool {
get {
return UIDevice.current.orientation.isValidInterfaceOrientation
? UIDevice.current.orientation.isPortrait
: (UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isPortrait)!
}
}
}
I had issues with using InterfaceOrientation, it worked OK except it wasn't accessing the orientation on loading. So I tried this and it's a keeper. This works because the bounds.width is always in reference to the current orientation as opposed to nativeBounds.width which is absolute.
if UIScreen.mainScreen().bounds.height > UIScreen.mainScreen().bounds.width {
// do your portrait stuff
} else { // in landscape
// do your landscape stuff
}
I call this from willRotateToInterfaceOrientation(toInterfaceOrientation:
UIInterfaceOrientation, duration: NSTimeInterval) and from viewDidLoad but it flexible.
Thanks to zSprawl for the pointer in that direction. I should point out that this is only good for iOS 8 and later.
So, if Apple is deprecating the whole orientation string thing ("portrait","landscape"), then all you care about is the ratio of width to height. (kinda like #bpedit's answer)
When you divide the width by the height, if the result is less than 1, then the mainScreen or container or whatever is in "portrait" "mode". If the result is greater than 1, it's a "landscape" painting. ;)
override func viewWillAppear(animated: Bool) {
let size: CGSize = UIScreen.mainScreen().bounds.size
if size.width / size.height > 1 {
print("landscape")
} else {
print("portrait")
}
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
if size.width / size.height > 1 {
print("landscape")
} else {
print("portrait")
}
}
(I'm guessing that if you use this approach then you probably don't really care about specifically handling the condition when the ratio is exactly 1, equal width and height.)
Swift 3+
Basically:
NotificationCenter.default.addObserver(self, selector: #selector(self.didOrientationChange(_:)), name: .UIDeviceOrientationDidChange, object: nil)
#objc func didOrientationChange(_ notification: Notification) {
//const_pickerBottom.constant = 394
print("other")
switch UIDevice.current.orientation {
case .landscapeLeft, .landscapeRight:
print("landscape")
case .portrait, .portraitUpsideDown:
print("portrait")
default:
print("other")
}
}
:)
Swift 5 – Solution: Check orientation on app start & during device rotation:
// app start
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let orientation = self.view.window?.windowScene?.interfaceOrientation {
let landscape = orientation == .landscapeLeft || orientation == .landscapeRight
}
}
// on rotation
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let landscape = UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight
}
Swift 3, based on Rob's answer
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if (size.width / size.height > 1) {
print("landscape")
} else {
print("portrait")
}
}
I found that the alternative code in Swift for the Obj-C code
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
is
if UIApplication.shared.statusBarOrientation.isLandscape
Note: we are trying to find the status bar orientation is landscape or not. If it is landscape then the if statement is true.
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
if (toInterfaceOrientation.isLandscape) {
NSLog("Landscape");
}
else {
NSLog("Portrait");
}
}
Swift 5
Works in SwiftUI and storyboard based app. Also, check rotation and trait handlers:
struct Orientation {
/// true - if landscape orientation, false - else
static var isLandscape: Bool {
orientation?.isLandscape ?? window?.windowScene?.interfaceOrientation.isLandscape ?? false
}
/// true - if portrait orientation, false - else
static var isPortrait: Bool {
orientation?.isPortrait ?? (window?.windowScene?.interfaceOrientation.isPortrait ?? false)
}
/// true - if flat orientation, false - else
static var isFlat: Bool {
orientation?.isFlat ?? false
}
/// valid orientation or nil
static var orientation: UIDeviceOrientation? {
UIDevice.current.orientation.isValidInterfaceOrientation ? UIDevice.current.orientation : nil
}
/// Current window (for both SwiftUI and storyboard based app)
static var window: UIWindow? {
guard let scene = UIApplication.shared.connectedScenes.first,
let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate,
let window = windowSceneDelegate.window else {
return UIApplication.shared.windows.first
}
return window
}
}
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
layoutAll()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
print("viewWillTransition")
layoutAll()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
print("traitCollectionDidChange")
layoutAll()
}
/// Layout content depending on the orientation
private func layoutAll() {
// Layout as you need
print("layoutAll: isLandscape=\(Orientation.isLandscape), isPortrait=\(Orientation.isPortrait), traitCollection=\(traitCollection)")
}
}
For anyone seeing this past iOS 13:
The most reliable way to me is deprecated now, though it is (still) working:
print(UIApplication.shared.statusBarOrientation.isPortrait)
What seems to be the way to go now:
if UIApplication.shared.windows.first?.
windowScene?.interfaceOrientation.isPortrait ?? true {
print("Portrait")
} else {
print("Landscape")
}
Keeping it simple:
let orientation = UIApplication.shared.statusBarOrientation.isLandscape ? "landscape" : "portrait"
I think the best way to do this in modern Objective C and accounting for now deprecated functions is...
UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation]);
In swift, that would be.
UIDevice.current.orientation.isLandscape
Try to use horizontalSizeClass & verticalSizeClass:
import SwiftUI
struct DemoView: View {
#Environment(\.horizontalSizeClass) var hSizeClass
#Environment(\.verticalSizeClass) var vSizeClass
var body: some View {
VStack {
if hSizeClass == .compact && vSizeClass == .regular {
VStack {
Text("Vertical View")
}
} else {
HStack {
Text("Horizontal View")
}
}
}
}
}
Found it in this tutorial. Related Apple's documentation.
UIDevice.current.orientation did not work for my compass-type application, because it reports "faceUp" or "faceDown" when the phone is horizontal, regardless of how the screen is oriented. I want to show the direction the user is facing, assuming they're holding the phone in a natural orientation.
UIApplication.shared.statusBarOrientation gave me a deprecated warning "'statusBarOrientation' was deprecated in iOS 13.0: Use the interfaceOrientation property of the window scene instead."
The following did what I needed.
var headingOffset = 0.0
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
switch windowScene?.interfaceOrientation {
case .portrait:
headingOffset = 0.0
print("Reporting orientation as portrait")
case .portraitUpsideDown:
headingOffset = 180.0
print("Reporting orientation as portraitUpsideDown")
case .landscapeLeft:
headingOffset = -90.0
print("Reporting orientation as landscapeLeft")
case .landscapeRight:
headingOffset = 90.0
print("Reporting orientation as landscapeRight")
default:
headingOffset = 0.0
print("Reporting orientation as default")
}

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))

iOS: How to run a function after Device has Rotated (Swift)

I have one UIView which is not using Auto-Layout and some components are displayed based on their percent of X and Y co-ordinates from the main view.
Previously I would have run a function to update their positions in didRotateFromInterfaceOrientation however I see this is now deprecated in iOS8.
I've taken a look at viewWillTransitionToSize but it's giving weird results, and there doesn't appear to be a viewDidtransitionToSize function.
Is there an easy way (in Swift) to run a function after a device rotation?
The viewWillTransitionToSize delegate method gets called with a UIViewControllerTransitionCoordinator conforming object. A method that protocol declares is animateAlongsideTransition(_:animation, completion:). You can use that to have code execute after the transition is complete.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil) { _ in
// Your code here
}
}
Although not asked for here Objective C version:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// change any properties on your views
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
if( UIDeviceOrientationIsPortrait(orientation) ) {
NSLog(#"portrait");
} else {
NSLog(#"landscape");
}
}];
}

Resources