I have to detect the orientation of the device and so something configuration based on it.
I write a simple function to handle it
public func isLandscape() -> Bool {
return UIScreen.main.bounds.width > UIScreen.main.bounds.height
}
It works perfectly on iPhone, but on iPad it's not. Sometimes when iPad is on landscape, it shows that it's UIScreen.main.bounds.height is larger than UIScreen.main.bounds.width, which is super weird.
So for iPad, I change to it like that
public func isLandscape() -> Bool {
if DeviceType.isPhone {
return UIScreen.main.bounds.width > UIScreen.main.bounds.height
} else {
return UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight || UIDevice.current.orientation == .unknown
}
}
But still not working probably on iPad. The case of unknown is called sometimes on portrait and sometimes on landscape.
Is there any other way, that could works flawlessly? I really need to have a prefect way to always show the the iPad and iPhone that they are in portrait and landscape? Those unknown, faceDown and faceUp are really confusing and make problems in our workflow.
Your help will be appreciated
if(UIApplication.shared.statusBarOrientation.isLandscape)
{
print("[UI] Landscape")
}
else
{
print("[UI] Portriat")
}
You will receive a warning saying that .statusBarOrientataion is deprecated, but I tested with Xcode 14 on an iPad Air 4th generation (iPadOS 15.7), and iPhone 12 (iOS 16), and it worked.
Related
There are some questions about device orientation and some users answered like this:
if UIDevice.current.orientation.isLandscape {
print("landscape")
} else {
print("portrait")
}
Which is totally wrong because they don't know that it can have the third result: isFlat
So it can return three different results:
if UIDevice.current.orientation.isLandscape {
print("landscape")
} else if UIDevice.current.orientation.isFlat {
print("flat")
} else { // UIDevice.current.orientation.isPortrait
print("portrait")
}
So if a device is in flat position (isFlat) how to check if that device shows content in landscape or portrait mode?
Update
Only one of them can be true
If isFlat returns true then both isLandscape and isPortrait return false
Update 2
I ended up using:
private func isLandscape() -> Bool {
return self.view.frame.width > self.view.frame.height
}
Or we can use UIApplication.shared.statusBarOrientation.isLandscape
When my simulator is in portrait and when my viewcontroller loads initially, it prints out Landscape instead of Portrait but when I change the orientaiton, it correctly displays the orientation so forth. I did the following
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if (UIDevice.current.orientation == UIDeviceOrientation.portrait || UIDevice.current.orientation == UIDeviceOrientation.portraitUpsideDown)
{
print("Portrait")
}
else
{
print("Landscape")
}
}
I have no idea why it is displaying wrong orientation when it loads initially but once I rotate everything seems to work.
P.S. It seems like when simulator initially loads, the orientaiton is unknown, so it is choosing the else condition, how to avoid this from happening and identify the correct orientaiton?
You can check current orientation by UIApplication.shared.statusBarOrientation.isPortrait or UIApplication.shared.statusBarOrientation.isLandscape.
if UIDevice.currentDevice().orientation.isLandscape.boolValue {
print("landscape")
} else {
print("portrait")
}
I am using a UISplitViewController in my app. This works just fine on iPad where primary and secondary are always visible, and it works just fine on most iPhones where it acts like a UINavigationController.
On iPhone 6+ and 6S+ the split view acts like an iPhone in portrait and like an iPad in landscape. This splitting in landscape is causing me problems and I'd like to avoid it.
Is there any way to prevent the UISplitViewController from showing primary and secondary controllers in iPhone 6+ landscape? I just want it to show the secondary controller, the same as it would do for other iPhones.
Thanks.
I was able to do this by subclassing the UISplitViewController and then overriding the trait collection to return a compact horizontal size class when the device is not an iPad. I know checking the interface idiom is a faux-pas these days, but I wasn't sure how else to do it.
I simply added this method to my UISplitViewController subclass:
-(UITraitCollection *)traitCollection {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return [super traitCollection];
} else {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
}
}
Any suggestions for a better way to do this are certainly welcome.
Here is the same answer in Swift but also with a fix where the vertical size class would be wrong on phone in landscape:
override var traitCollection: UITraitCollection {
if UI_USER_INTERFACE_IDIOM() == .pad {
return super.traitCollection
} else {
let horizontal = UITraitCollection(horizontalSizeClass: .compact)
let vertical = UITraitCollection(verticalSizeClass: super.traitCollection.verticalSizeClass)
return UITraitCollection.init(traitsFrom: [horizontal, vertical])
}
}
I had some issues with UINavigationControllers not displaying correctly with the code above. This is the method that worked for me (Swift 5):
1) Create a UIViewController containing a UIContainerView
2) Embed your UISplitViewController in that container
3) Add the following code:
class SplitViewContainerViewController: UIViewController {
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UI_USER_INTERFACE_IDIOM() != .pad {
performOverrideTraitCollection()
}
}
private func performOverrideTraitCollection() {
for childVC in self.children {
setOverrideTraitCollection(UITraitCollection(horizontalSizeClass: .compact), forChild: childVC)
}
}
}
4) Set the view controller containing the container view to be SplitViewContainerViewController
Update For iOS 13
The code above no longer works on iOS 13. Use the following instead on the SplitViewContainerViewController class:
override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
if UIDevice.current.userInterfaceIdiom != .pad {
return UITraitCollection(horizontalSizeClass: .compact)
} else {
return super.traitCollection
}
}
I'm trying to build an app that supports portrait AND landscape orientations for iOS devices with regular horizontal size class, and portrait only for the rest.
At the time of this writing, it would be: Portrait only (for iPhones except 6 Plus/6s Plus) and Portrait AND Landscape for iPhone 6 Plus / 6s Plus, and iPad.
This is a similar behavior performed by the native Mail app.
I've tried among other things, the following:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return [.Portrait, .LandscapeLeft, .LandscapeRight]
}
override func shouldAutorotate() -> Bool {
return (traitCollection.horizontalSizeClass == .Regular)
}
However, shouldAutororate is obviously called before the interface rotates, so that happens before traitCollection gets updated.
So, the question is, how to achieve this? I'm trying to accomplish this in the cleanest way possible without referencing explicitly userInterfaceIdiom, screen size, etc.
override var supportedInterfaceOrientations:UIInterfaceOrientationMask{
return [.portrait, .landscapeLeft, .landscapeRight]
}
override var shouldAutorotate:Bool {
return (traitCollection.horizontalSizeClass == .regular) || (traitCollection.displayScale > 2);
}
I have a use case that seems to fall into the cracks between asset catalogs and size classes. My Universal app needs a full screen background image that is different in Landscape and Portrait orientations, and Landscape is only supported for the iPad & iPhone 6.
Since I can't add landscape images to the asset catalog for a new image set, my current solution looks like this (supports iOS 7 & 8):
// permit landscape mode only for iPads & iPhone 6 (would prefer to use size classes, but...?)
override func shouldAutorotate() -> Bool {
let size = view.frame.size
let maxv = max(size.width, size.height)
return ((maxv > 700.0) || (maxv == 512.0)) ? true : false
}
// this will be triggered only for iOS 7, as long as viewWillTransitionToSize:withTransitionCoordinator: is also implemented!
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
adjustToOrientation(toInterfaceOrientation)
}
// this will be triggered only for iOS 8
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
let orientation = UIApplication.sharedApplication().statusBarOrientation
// need to reverse the sense of the orientation here; Application still has the previous orientation
switch orientation {
case .Portrait, .PortraitUpsideDown:
adjustToOrientation(.LandscapeLeft)
case .LandscapeLeft, .LandscapeRight:
adjustToOrientation(.Portrait)
default:
adjustToOrientation(.Portrait)
}
}
func adjustToOrientation(newInterfaceOrientation: UIInterfaceOrientation) {
let size = view.frame.size
// rotation already permitted only for iPad & iPhone 6, but need to know which one (size classes useless here?)
switch (max(size.width, size.height)) {
case 1024.0:
if UIInterfaceOrientationIsLandscape(newInterfaceOrientation) {
backgroundImage.image = UIImage(named: "Background-Landscape#2x~ipad.png")
} else {
backgroundImage.image = UIImage(named: "Background#2x~ipad.png")
}
case 736.0:
if UIInterfaceOrientationIsLandscape(newInterfaceOrientation) {
backgroundImage.image = UIImage(named: "Background-Landscape#3x~iphone.png")
} else {
backgroundImage.image = UIImage(named: "Background#3x~iphone.png")
}
case 512.0:
if UIInterfaceOrientationIsLandscape(newInterfaceOrientation) {
backgroundImage.image = UIImage(named: "Background-Landscape~ipad.png")
} else {
backgroundImage.image = UIImage(named: "Background~ipad.png")
}
default:
break
}
}
This works, but seems fragile. Is there a more correct way to identify the device as something that supports a regular size class in Landscape, before actually being in Landscape mode? Or some way that I've missed to specify the Landscape equivalents for an image set?
You can use size classes in assets catalog or you can have two image sets: one for portrait and one for landspace mode. Then you can access images by asset name. This will reduce your code a little. But I'd recommend to cosider using size classes everywhere it's possible.
So the final solution was:
-Set the Width property on the image asset to Any+Regular and added a couple of Landscape images for the iPhone 6+ and iPad, and UIImage automatically does the rest
-A more elegant way to permit rotation only for those two devices, in both iOS 7 & iOS 8:
override func shouldAutorotate() -> Bool {
let scale = UIScreen.mainScreen().scale
let idiom = UIDevice.currentDevice().userInterfaceIdiom
return ((idiom == .Pad) || (scale > 2.0)) ? true : false
}
So all of the above down to 5 lines of code!