Landscape vs. Portrait background image in Asset Catalog - ios

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!

Related

Detect correct iPad orinatation

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.

Prevent UIView with AVCaptureVideoPreviewLayer from rotating with rest of ViewController

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

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

Support landscape orientation for horizontalSizeClass=.Regular in iOS

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

How to set two different sizes of background images for activity as landscape and portrait modes in iOS using swift

I want to set two different sizes of background images: one for landscape mode, another one is portrait mode. I wrote the programme like this, but I knew it was showing only portrait mode. What is the condition I should write here for landscape mode? I am very new to swift language.
#IBOutlet var img: UIImageView!
func loadimage()
{
let image = UIImage (named: "Vertical");
self.img.image = image
}
You can check the device orientation like this and then set the image.
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
let app = UIApplication.sharedApplication()
if (app.statusBarOrientation.isLandscape) {
// Image Code
}
else {
// Image Code
}

Resources