My UITableViewCell has a UIImageView in it. When the users taps on the UIImageView, an [IDMPhotoBrowser](https://github.com/ideaismobile/IDMPhotoBrowser) is presented from the UIImageView. The app is portrait only, however, when viewing the photo fullscreen in IDMPhotoBrowser, I'd like to give the user the ability to rotate to any orientation.
The IDMPhotoBrowser is presented on the UITableViewCell's delegate. The delegate is a regular UIViewController with a UITableView. The delegate is also a contained in a UINavigationController which is contained in a UITabBarController. I have tried many different solutions involving extending IDMPhotoBrowser and trying to add functions that add support for all orientations, I have tried presenting the IDMPhotoBrowser on the UITabBarController, the UINavigationController and the delegate UIViewController. I have also tried flipping a boolean located in the UITabBarController subclass that is then referenced in the function that asks for supported orientations, such that the supported orientations changes in relation to when the app presents the IDMPhotoBrowser. Nothing has worked.
The whole app needs to be constrained to only portrait orientation except for when the user is viewing an IDMPhotoBrowser or an AVPlayerViewController in fullscreen.
Make a UINavigationController parent class, inside your UINavigationController(parent) override these methods this way:
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return self.topViewController.supportedInterfaceOrientations
}
in your VC you want to be rotated:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .All
}
in your VC you want NOT to be rotated:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .AllButUpsideDown
}
Then push (or set as root VC etc) your Browser or Browser's parent VC in this navigation controller.
Related
The problem I am having is a bit strange and I can't seem to find any good solutions out there. So I have two UIViewControllers, one is allowed to take all orientations, another can only be viewed in landscape
internal override func shouldAutorotate() -> Bool {
return true
}
internal override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Lanscape
}
If I am in portrait mode on ViewController1, and I push VC2, it rotates just fine, but when I leave VC2, VC1 is stuck in landscape even though device itself is in portrait mode. How can I fix it? I tried calling AttemptToRotateToDeviceOrientation method in ViewDidAppear method of VC1, but it doesn't do anything
You just need to create app delegate instance in your view controller like:
let appDel = UIApplication.shared.delegate as! AppDelegate
On Viewwillappear()of view controller just add this code for landscape
appDel.myOrientation = .landscape
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
And for portrait add this code
appDel.myOrientation = .portrait
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
I have a simple camera app.
The Camera Preview screen is a viewcontroller (name ViewController) which I locked the orientation via
this code (can't find the StackOverflow link to this code), The viewcontroller is embedded in a navigation controller:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let navigationController = self.window?.rootViewController as? UINavigationController {
if navigationController.visibleViewController is ViewController {
return UIInterfaceOrientationMask.portrait
} else {
return UIInterfaceOrientationMask.all
}
}
return UIInterfaceOrientationMask.portrait
}
When the camera starts up, the Camera Preview is locked in .portrait orientation, which is what I desire.
However, if I hit the library button, which segues me to another screen,
and change the orientation of the library screen by rotating my physical device, and THEN return back to my Camera Preview screen via the "back" button, the orientation is broken. It's in landscape mode, instead of portrait mode.
How do I get a hard lock?
Just incase, I made a dummy model of my code here (too long to post here), where you guys can test it out yourself
https://github.com/bigmit2011/Testing-Camera
EDIT:
If the app crashes just try running it one more time.
For some reason the first time the app is ran it crashes. Thank you.
EDIT2:
Attempting to set embedded ViewController as a delegate of UINavigationController.
However, I don't quite understand if I'm doing it correctly:
class ViewController: UIViewController, UINavigationControllerDelegate {
var navigation: UINavigationController?
self.navigation.delegate = self // doesn't seem to work
func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask {
return .portrait
}
Do I need to create a separate swift file for the UiNavigationController?
EDIT3:
Following Matt's answer code is as follows:
class CameraViewController :UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
self.navigationController?.delegate = self
super.viewDidLoad()
func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask {
return .portrait
}
However, this seems to lock everything in portrait mode not just that single viewController embedded within Navigation Controller.
You are making two mistakes.
First, you are trying to govern the orientation of individual view controllers from the app delegate. That's wrong. The app delegate governs the totality of possible orientations for the whole app. But as for view controllers, each individual view controller governs its own orientation. Just implement supportedInterfaceOrientations in each of your two view controllers (not in the app delegate).
Second, your first view controller is in a navigation interface. Therefore it is not the top-level view controller and cannot govern orientation directly (with supportedInterfaceOrientations). The correct way to govern the orientation of a navigation interface is to set yourself as the navigation controller's delegate and implement navigationControllerSupportedInterfaceOrientations(_:).
I also couldn't run your app. But I tested this in Xcode with a main VC, a 2nd VC and embedded the main VC in a navigation controller. I then followed the advice in this article Selective rotation lock in iOS
You add the following to AppDelegate:
var enableAllOrientation = false
func application(_ application: UIApplication,supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if (enableAllOrientation == true){
return UIInterfaceOrientationMask.allButUpsideDown
}
return UIInterfaceOrientationMask.portrait
}
Then in the VC that allows allButUpsideDown (or change this to what you want), you add the following code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.enableAllOrientation = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.enableAllOrientation = false
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
I put this in the 2nd VC. Ran the app in the simulator, segued to the 2nd VC, changed the orientation to landscape then hit back and the main VC was locked in portrait.
I am trying to get device rotation right.
I am testing on iPad 8.x/9.x simulator
I have 4 VCs
VC1 - Both Portrait and Landscape
VC2 - Both Portrait and Landscape
VC3 - Only Portrait
VC4 - Both Portrait and Landscape
Goal: to have VC3 display PortraitView at all times (same as if app orientation was fixed to portrait).
I tried
#implementation RotationAwareNavigationController
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
UIViewController *top = self.topViewController;
return top.supportedInterfaceOrientations;
}
-(BOOL)shouldAutorotate {
UIViewController *top = self.topViewController;
return [top shouldAutorotate];
}
#end
In VC which is portrait
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
But it does not work meaning view not displayed in Portrait dimensions Am I missing something?
I am sure it can be done as when I use ImagePickerController provided my iOS, it is fixed to Portrait. I just dont know how to do it.
To support differing orientations in different view controllers, you will need to do a few things. First, you need to check all the checkboxes for orientations you want to support in your target's General settings tab.
Second, anytime you call presentViewController or dismissViewController in your app, the UIApplicationDelegate method application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask will be called. You can use this method to restrict (or allow) specific orientations each time a new view controller is presented or dismissed. Unfortunately, it isn't as simple as just returning a UIInterfaceOrientationMask here. You will need to find the view controller that is going to be showing on screen and return the orientations that it supports. Here is an example of this:
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
guard let window = window, let rootViewController = window.rootViewController else {
return UIDevice.currentDevice().userInterfaceIdiom == .Pad ? .All : .AllButUpsideDown // iOS defaults
}
// Let the view controller being shown decide what orientation it wants to support. This method will be called anytime a new view controller is presented on screen.
return findVisibleViewController(rootViewController: rootViewController).supportedInterfaceOrientations()
}
/// Searches the view hierarchy recursively and finds the view controller that is currently showing.
private func findVisibleViewController(rootViewController rootViewController: UIViewController) -> UIViewController {
if let presentedViewController = rootViewController.presentedViewController {
// Search for a modal view first.
return self.findVisibleViewController(rootViewController: presentedViewController)
} else if
let navigationController = rootViewController as? UINavigationController,
let visibleViewController = navigationController.visibleViewController {
// Then search navigation controller's views to find its visible controller.
return self.findVisibleViewController(rootViewController: visibleViewController)
} else if let splitViewController = rootViewController as? UISplitViewController {
// Then try the split view controller. This will be the true root view controller. Use the master here since the detail just shows web views.
return self.findVisibleViewController(rootViewController: splitViewController.viewControllers[0])
} else {
// Guess we found the visible view controller, because none of the other conditions were met.
return rootViewController
}
}
findVisibleViewController(_:) is an example from one of my projects and is tailored to the exact view controller hierarchy in my app. You will need to edit this for your own app in a way that makes sense for your hierarchy.
Third, you will need to implement supportedInterfaceOrientations() for most, if not all, of your view controllers.
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return // portrait, landscape, or combinations of those.
}
Finally, this only handles situations where you presented or dismissed something modally. For show (push) segues, the orientation of the navigation controller will be used for every new view controller pushed onto the stack. If you need more fine grained control here, you will need to force the orientation to happen. Here is an example of that:
// Some other view had the screen in landscape, so force the view to return to portrait
UIDevice.currentDevice().setValue(UIInterfaceOrientation.Portrait.rawValue, forKey: "orientation")
Any idea about how the feature I show in this video is done?
It looks like the touch on the "expand" button force an orientation change for the device (infact when I swipe I'm pulling the control centre out).
Anyway only the video player seems to rotate. the scrollview underneath keeps its portrait orientation.
Looks like they're presenting the player controller with a custom segue animation, while having the player controller supporting only landscape.
Try presenting a view controller that has these implemented, like so:
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Landscape.rawValue)
}
override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
return UIInterfaceOrientation.LandscapeLeft
}
override func shouldAutorotate() -> Bool {
return false
}
Then the device should be in landscape. All you have left is to do the custom transition animation between the two and you're done.
From the docs:
preferredInterfaceOrientationForPresentation
If your view controller implements this method, your view controller’s
view is shown in the preferred orientation
I have a ViewController A that presents an MPMoviePlayerViewController. I want to force the video player to landscape, so I use this:
In the AppDelegate, supportedInterfaceOrientationsForWindow
if let viewController = self.window!.rootViewController?.presentedViewController {
if let playbackController = viewController as? MPMoviePlayerViewController {
return Int(UIInterfaceOrientationMask.LandscapeLeft.rawValue) | Int(UIInterfaceOrientationMask.LandscapeRight.rawValue)
}
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
In my video player subclass, I set the supported orientation:
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.LandscapeLeft.rawValue) | Int(UIInterfaceOrientationMask.LandscapeRight.rawValue)
}
When the video player is closed in landscape, however, View Controller A is stuck in landscape. How do I prevent View Controller A from switching to landscape, and keep it in portrait?
Just add
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
to every view controller that needs it and avoid AppDelegate based orientation support.