I'm new to iOS development and struggling with orientation checks. I have fixed the orientation for my view and now want to see the orientation it's held in.
How I've fixed it:
APP DELEGATE
return self.orientationLock
}
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo
rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
VIEW CONTROLLER
override func viewWillAppear(_ animated: Bool) {
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.portrait,
andRotateTo: UIInterfaceOrientation.portrait)
}
Now I want to listen for the orientation and print for the moment the changes.
VIEW CONTROLLER
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
switch UIDevice.current.orientation {
case .portrait:
print("portrait")
case .faceUp:
print("faceUp")
case .landscapeLeft:
print("landscape left")
case .landscapeRight:
print("landscape right")
case .portraitUpsideDown:
print("upsidedown portrait")
default:
print("something else")
}
}
The trouble is the above code is waiting for the view to change, the view has been locked, so it'll never change.
How would you go about locking the view and then listening to see how the phone is held?
Much Appreciated
Shawn, thanks for the advice. In the end, that's what I did. Here's my code
You'll also need to import CoreMotion
var motion = CMMotionManager()
var currentOrientation: String = "Portrait"
func oritentationUpdateStart() {
motion.accelerometerUpdateInterval = 1.0
motion.startAccelerometerUpdates(to: OperationQueue.current! ) { (data, error) in
if let accel = data {
if (accel.acceleration.x > 0.5){
self.currentOrientation = "Upsidedown Landscape"
}
else if (accel.acceleration.y > 0.5) {
self.currentOrientation = "Upsidedown Portrait"
}
else if (accel.acceleration.x < -0.5) {
self.currentOrientation = "Landscape"
}
else if (accel.acceleration.y < -0.5) {
self.currentOrientation = "Portrait"
}
}
}
}
Related
Some pages in the app support landscape mode.
After the ios 16 version was released
When turning the device horizontally, the screen becomes black, and only after turning horizontally again to the other side does the screen return to itself and the buttons function.
It is important to note that when I run on a device with iOS version 16 everything works great
I tried everything and could not find a solution
Has anyone tried this?
In my viewController:
var orientationType : UIInterfaceOrientationMask
{
guard let parent = self.parent else { return .allButUpsideDown }
guard parent is BaseChannelViewController else { return .allButUpsideDown }
return .portrait
}
public func animate(fullscreenPresentation completionBlock : AnimationCompletion?)
{
guard displayMode != .fullscreen else { completionBlock?(); return }
guard !isDisplayedModaly else { completionBlock?(); return }
switch transitionType
{
case .default : fallthrough
case .minimizable: fallthrough
case .interactive:
guard let transition = customTransitioningDelegate as? GAVideoPlayerZoomTransition else { break }
guard let presentingVC = dataSource?.presentingViewController(for: self) else { break }
let originFrame = dataSource?.initialFrameForTransitionToFullScreenDisplay(from: displayMode.initialScreenMode, for: self) ?? self.view.frame
transition.currentFrame = originFrame
NotificationCenter.default.post(name: .GAVideoPlayerViewControllerWillEnterFullscreen, object: self)
hideControlsNoAnimation()
if let _ = parent
{
removeFromParent()
}
// FIXME - Need this to prevent bad access crash, will show un balanced animation calls in the logger
view.removeFromSuperview()
let clientOrientationRawValue = clientOrientation.rawValue
presentingVC.present(self, animated: true) { [weak self] in
self?.displayMode = .fullscreen
let currentOrientationRawValue = UIDevice.current.orientation.rawValue
UIDevice.current.setValue(clientOrientationRawValue, forKey: "orientation")
UIDevice.current.setValue(currentOrientationRawValue, forKey: "orientation")
completionBlock?()
}
break
default: break
}
}
fileprivate var clientOrientation: UIDeviceOrientation
{
return UIDeviceOrientation(interfaceOrientationMask: clientSupportedInterfaceOrientations)
}
In AppDelegate:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask
{
guard let viewController = UIApplication.shared.topViewController() as? CustomOrientationViewController else { return .portrait }
return viewController.orientationType
}
According to the iOS & iPadOS 16 Beta 3 Release Notes:- Attempting to set an orientation on UIDevice via setValue:forKey: isn’t supported and no longer works. Instead, they say use: preferredInterfaceOrientationForPresentation.
In my case, force view controller orientation is not working in iOS 16 beta either by using preferredInterfaceOrientationForPresentation or requestGeometryUpdate.
Previously, UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation") was working fine.
It works for me.
In AppDelegate,
var orientation: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return orientation
}
In view controller,
(UIApplication.shared.delegate as? AppDelegate)?.orientation = .landscapeRight
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight))
UIApplication.navigationTopViewController()?.setNeedsUpdateOfSupportedInterfaceOrientations()
In Helper,
extension UIApplication {
class func navigationTopViewController() -> UIViewController? {
let nav = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController
return nav?.topViewController
}
}
My problem with my code below is that I'm trying to do it when closing a modal view and the view under it are not updated quick enough. If I put the requestGeometryUpdate on a separate button then when I close the view it work.
if #available(iOS 16.0, *) {
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait))
}
It works for me:
import Foundation
import UIKit
extension UIViewController {
func setDeviceOrientation(orientation: UIInterfaceOrientationMask) {
if #available(iOS 16.0, *) {
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
} else {
UIDevice.current.setValue(orientation.toUIInterfaceOrientation.rawValue, forKey: "orientation")
}
}
}
extension UIInterfaceOrientationMask {
var toUIInterfaceOrientation: UIInterfaceOrientation {
switch self {
case .portrait:
return UIInterfaceOrientation.portrait
case .portraitUpsideDown:
return UIInterfaceOrientation.portraitUpsideDown
case .landscapeRight:
return UIInterfaceOrientation.landscapeRight
case .landscapeLeft:
return UIInterfaceOrientation.landscapeLeft
default:
return UIInterfaceOrientation.unknown
}
}
}
How to use it?
Just call it on your UIViewController:
setDeviceOrientation(orientation: .landscapeRight)
EDIT
More completed solution:
import UIKit
final class DeviceOrientation {
static let shared: DeviceOrientation = DeviceOrientation()
// MARK: - Private methods
private var windowScene: UIWindowScene? {
return UIApplication.shared.connectedScenes.first as? UIWindowScene
}
// MARK: - Public methods
func set(orientation: UIInterfaceOrientationMask) {
if #available(iOS 16.0, *) {
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
} else {
UIDevice.current.setValue(orientation.toUIInterfaceOrientation.rawValue, forKey: "orientation")
}
}
var isLandscape: Bool {
if #available(iOS 16.0, *) {
return windowScene?.interfaceOrientation.isLandscape ?? false
}
return UIDevice.current.orientation.isLandscape
}
var isPortrait: Bool {
if #available(iOS 16.0, *) {
return windowScene?.interfaceOrientation.isPortrait ?? false
}
return UIDevice.current.orientation.isPortrait
}
var isFlat: Bool {
if #available(iOS 16.0, *) {
return false
}
return UIDevice.current.orientation.isFlat
}
}
extension UIInterfaceOrientationMask {
var toUIInterfaceOrientation: UIInterfaceOrientation {
switch self {
case .portrait:
return UIInterfaceOrientation.portrait
case .portraitUpsideDown:
return UIInterfaceOrientation.portraitUpsideDown
case .landscapeRight:
return UIInterfaceOrientation.landscapeRight
case .landscapeLeft:
return UIInterfaceOrientation.landscapeLeft
default:
return UIInterfaceOrientation.unknown
}
}
}
How to use it:
DeviceOrientation.shared.set(orientation: .portrait)
I noticed my issue seems like resolved by calling method below:
[UIViewController setNeedsUpdateOfSupportedInterface
You may give it a try.
I tried all above solutions seem they're not 100% percent working. After through this post
https://developer.apple.com/forums/thread/707735 i got the hint. Let's try this below code. It’s worked for me.
if #available(iOS 16.0, *) {
DispatchQueue.main.async {
UIViewController.attemptRotationToDeviceOrientation()
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation)) { error in
print(error)
print(windowScene?.effectiveGeometry)
}
navigationController?.topViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
}
}
setValue:forKey is a method of old NSObject (NSKeyValueCoding). It's not official documented and supported by UIDevice class. Using it is considering using a private api. Apple can terminate it anytime they want.
Apple released new API which is replaced with setValue:forKey:"orientation".
Apple update
guard let windowScene = view.window?.windowScene else { return }
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape)) { error in
// Handle denial of request.
}
But I am having problem about UIDevice.orientationDidChangeNotification , it is not working
Following Apple's documentation you need to
Requests an update to the window scene’s geometry using the specified geometry preferences object.
https://developer.apple.com/documentation/uikit/uiwindowscene/3975944-requestgeometryupdate/
So using the code in the example, you can change the way we set the orientation in our views using requestGeometryUpdate and using as well setNeedsUpdateOFSupportedInterface
public extension UIViewController {
func deviceOrientation(orientation: UIInterfaceOrientationMask) {
if #available(iOS 16.0, *) {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
else { return }
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
self.setNeedsUpdateOfSupportedInterfaceOrientations()
}
}
}
I set photoOutput.connection orientation to portrait but image continues to save in landscape mode. Am I supposed to change the settings somewhere else?
func configurePhotoOutput() throws {
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
self.photoOutput = AVCapturePhotoOutput()
self.photoOutput?.connection(with: .video)?.videoOrientation = .portrait
self.photoOutput?.isHighResolutionCaptureEnabled = true
self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey : AVVideoCodecJPEG])], completionHandler: nil)
if captureSession.canAddOutput(self.photoOutput!) { captureSession.addOutput(self.photoOutput!) }
captureSession.startRunning()
}
When photo is being captured, inside delegate method of AVCapturePhotoCaptureDelegate where you get the image at that time you have to provide orientation for the image.
var cameraPosition: AVCaptureDevice.Position = .front
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if let image = photo.cgImageRepresentation()?.takeUnretainedValue() {
let finalImage = UIImage(cgImage: image, scale: 1, orientation: UIDevice.current.orientation.getUIImageOrientationFromDevice(for: cameraPosition))
print(finalImage)
}
}
extension UIDeviceOrientation {
func getUIImageOrientationFromDevice(for position: AVCaptureDevice.Position) -> UIImage.Orientation {
if position == .front {
switch self {
case .portrait, .faceUp: return UIImage.Orientation.leftMirrored
case .portraitUpsideDown, .faceDown: return UIImage.Orientation.left
case .landscapeLeft, .unknown: return UIImage.Orientation.down // this is the base orientation
case .landscapeRight: return UIImage.Orientation.up
}
} else {
switch self {
case .faceUp, .landscapeLeft, .unknown: return UIImage.Orientation.up
case .portraitUpsideDown: return UIImage.Orientation.left
case .portrait, .faceDown: return UIImage.Orientation.right
case .landscapeRight: return UIImage.Orientation.down
}
}
}
}
It's working for me. Please let me know if you're having any issue in the above code.
In an app we are developing we have an option to let the user choose preferred orientation (i.e if they choose Portrait the app will be locked to portrait orientation and the same for Landscape and if Both is opted the app will work on all orientation) I am sharing the code for what I have tried, and I'm not sure whether this functionality is feasible at all.
//MARK:- ORIENTATION
func changeOrientation(orientation: String) {
switch orientation {
case "Portrait":
UserDefaults.standard.set("Portrait", forKey: UserDefaultsKeys.preferredOrientation)
appDelegate.preferredOrientation = "Portrait"
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
break
case "Landscape":
UserDefaults.standard.set("Landscape", forKey: UserDefaultsKeys.preferredOrientation)
appDelegate.preferredOrientation = "Landscape"
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
break
default:
UserDefaults.standard.set("Both", forKey: UserDefaultsKeys.preferredOrientation)
appDelegate.preferredOrientation = "Both"
break
}
/*not necessary*/
let vc = UIViewController()
UIViewController.attemptRotationToDeviceOrientation()//forces to rotate
/*not necessary*/
self.present(vc, animated: false, completion: nil)
UIView.animate(withDuration: 0.3, animations: {
vc.dismiss(animated: false, completion: nil)
})
/*not necessary*/
}
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{
get {
switch appDelegate.preferredOrientation {
case "Portrait":
return .portrait
case "Landscape":
return .landscape
default:
return .all
}
}
}
open override var shouldAutorotate: Bool {
get {
return true
}
}
However if I choose 'Landscape' when in portrait mode, it automatically switches to landscape. But, if I rotate the device back to portrait it is working as well (which shouldn't be working as per the requirement). The requirement is similar to what happens when we setup project only with Portrait mode and how it will behave when the device is rotated to landscape mode.
I just wrote a sample project to test if this is possible. Well, I think it is! Unfortunately
UIViewController.attemptRotationToDeviceOrientation()
does not do the job magically how I hope so - it is a little bit more complex than that.
Please take a loot at the following code. All the magic you need is happening in the action forceChangeOrientations.
class ViewController: UIViewController {
enum Orientation {
case Landscape
case Portrait
mutating func changeOrientation() {
if self == .Portrait {
self = .Landscape
}
else {
self = .Portrait
}
}
func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
switch self {
case .Landscape:
return .Landscape
case .Portrait:
return .Portrait
}
}
func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
switch self {
case .Landscape:
return UIInterfaceOrientation.LandscapeLeft
case .Portrait:
return .Portrait
}
}
}
var currentOrientation: Orientation = .Portrait
// Returns a Boolean value indicating whether the view controller's contents should auto rotate.
override func shouldAutorotate() -> Bool {
return true
}
// Returns all of the interface orientations that the view controller supports.
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return currentOrientation.supportedInterfaceOrientations() //UIInterfaceOrientationMask.All
}
// Returns the interface orientation to use when presenting the view controller.
override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
return UIInterfaceOrientation.Portrait
}
#IBAction func forceChangeOrientations(sender: AnyObject) {
self.currentOrientation.changeOrientation()
let value = self.currentOrientation.preferredInterfaceOrientationForPresentation().rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()
}
}
I have restricted fixed device orientation in my app to portrait only. However just like Instagram I have a camera function and I need to get the current orientation in swift, but the orientation always displays as portrait. Is there a way to use the gyroscope to manually get the orientation?
UIDevice can tell you the orientation even if you do not support auto rotation. I use this to handle rotation in only one view controller:
override func viewDidLoad() {
super.viewDidLoad()
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}
deinit {
UIDevice.current.endGeneratingDeviceOrientationNotifications()
NotificationCenter.default.removeObserver(self)
}
#objc private func orientationChanged(_ notification: Foundation.Notification) {
currentOrientation = UIDevice.current.orientation
}
In Swift 2.3, you can use the following code to set the orientation of the AVCaptureDeviceInput (or replace with your own functionality).
I have use this code in https://github.com/ytakzk/Fusuma -> FSCameraView.swift
import CoreMotion
//var videoInput: AVCaptureDeviceInput?
let motionManager = CMMotionManager()
if motionManager.gyroAvailable {
motionManager.deviceMotionUpdateInterval = 0.2;
motionManager.startDeviceMotionUpdates()
motionManager.gyroUpdateInterval = 1
motionManager.startDeviceMotionUpdatesToQueue(NSOperationQueue.currentQueue()!) {
(data: CMDeviceMotion?, error:NSError?) in
if let videoConnection = self.imageOutput?.connectionWithMediaType(AVMediaTypeVideo) {
if (data?.gravity.x)! > -0.6 && (data?.gravity.x)! < 0.6 {
if (data?.gravity.y)! < 0.4 {
videoConnection.videoOrientation = .Portrait
} else {
videoConnection.videoOrientation = .PortraitUpsideDown
}
} else {
if (data?.gravity.x)! < 0 {
videoConnection.videoOrientation = .LandscapeRight
} else {
videoConnection.videoOrientation = .LandscapeLeft
}
}
}
//print(data?.gravity.x)
//print(data?.gravity.y)
//print(data?.gravity.z)
}
}