Environment:
Using the latest Xcode 8.3
Language: Swift3
Devices are all updated (iPhone 7 v10.3.1)
(Target -> Deployment Info -> Device Orientation) is set to Portrait for all options for 'Devices'
Scenario:
I have 2 ViewControllers (VC1 and VC2), both which inherit from UIViewController.
VC1 is always meant to be portrait
VC2 is always meant to be landscape
VC1 and VC2 have all their subviews and objects configured and added to the ViewController's view in the viewDidLoad call.
I am presenting VC2 from VC1 via the call (as I need a nav bar):
let vc2 = UINavigationController(rootViewController: VC2())
self.present(vc2, animated: true, completion: nil)
Whether VC2 inherits from a UINavigationController or UIViewController has made no difference to the occurrence to the issue.
AppDelegate.swift enforces the orientation as such:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController) {
if rootViewController is VC2 {
let vc2 = rootViewController as! VC2
if vc2.isPresented {
return .landscapeRight
}
}
return .portrait
}
The above function uses this function to find the topViewControllerWithRootViewController:
private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
if (rootViewController == nil) {
return nil
}
if (rootViewController.isKind(of: UITabBarController.self)) {
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
} else if (rootViewController.isKind(of: UINavigationController.self)) {
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
} else if (rootViewController.presentedViewController != nil) {
return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
}
return rootViewController
}
Issue:
This usually always works however in some instances the view will result in VC2 being presented vertically with properties being derived from a horizontal view:
Here
This happens randomly with no errors being shown in Xcode (including it's debug console).
Additional Notes:
Please note there are tasks being run on background threads that require the use of the GPU. These are declared from ViewControllers other than VC1 and VC2.
Perhaps this might cause time sensitive device orientation functions to be called in the incorrect order.
I have tried the implementations from the following links:
http://www.jairobjunior.com/blog/2016/03/05/how-to-rotate-only-one-view-controller-to-landscape-in-ios-slash-swift/
Related
I want to pop current view controller on some condition from appDelegate but I don't know how to do so, if any idea please help me out...................................................................................
import UIKit
import IQKeyboardManagerSwift
let kSharedAppDelegate = UIApplication.shared.delegate as? AppDelegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.main.bounds)
IQKeyboardManager.shared.enable = true
IQKeyboardManager.shared.shouldResignOnTouchOutside = true
IQKeyboardManager.shared.enableAutoToolbar = false
//IQKeyboardManager.shared.toolbarTintColor = .white
//IQKeyboardManager.shared.toolbarBarTintColor = ColorSet.appTheamColor
kSharedAppDelegate?.moveToSplashVC()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Check we can access the application window
guard let window = UIApplication.shared.windows.first else {
return
}
// Check we can access the root viewController
guard let vc = window.rootViewController else {
return
}
// Check the root vc is the type that we want to dismiss
if vc is NoInternetPopUpViewController {
vc.dismiss(animated: true, completion: nil)
}
}
//MARK:- Show No Internet connection VC
func showNoInterNetVC() {
guard let controller = UIStoryboard(name: Storyboards.kNoInternet, bundle: nil).instantiateViewController(withIdentifier: Identifiers.kNoInternetPopUpViewController) as? NoInternetPopUpViewController else {return}
controller.modalPresentationStyle = .overFullScreen
controller.modalTransitionStyle = .crossDissolve
kSharedAppDelegate?.window?.rootViewController?.present(controller, animated: true, completion: nil)
//window.present(controller , animated: true)
}
}
I think pop is the wrong terminology here unless you are using a navigation controller.
If you want to dismiss the currently presented viewController you could check the rootViewController of the applications Window like this.
// Check we can access the application window
guard let window = UIApplication.shared.windows.first else {
return
}
// Check we can access the root viewController
guard let vc = window.rootViewController else {
return
}
// Check the root vc is the type that we want to dismiss
if vc is NoInternetPopUpViewController {
vc.dismiss(animated: true, completion: nil)
}
I also just noticed that you may not need to access the application singleton via the shared property, as applicationDidBecomeActive(_ application: UIApplication) is passing you the Application already - that line would become:
guard let window = application.windows.first else {
There is another way to do this, in newer iOS versions you directly have access to topViewController() so you access it like UIApplication.topViewController() the only downside of it is that you need yo wrap it into an if let statement to check if it is not null. FYI, It won’t be null in most cases if you have let your didFinishLaunching() method run at least once in your app delegate. So that it can stack a view controller to be a top view controller. This won’t be a problem for you since all of the other methods will fail as well if this is the case.
Todo a pop view controller now all you need to do it use top view controller and perform pop on its navigation view controller, or you can dismiss it in case there is no navigation view controller.
now I am developing messenger
MessengerBox is tableViewController, when user tap the one of the cell, then chatRoomViewcontroller is presented.
if the app is not running and message arrived, then push notifications show. And user Tap the notification, App shows chatRoomViewcontroller directly.
Initially, I implemented this code by using window.rootViewController
But the problem happened. when I tap Back Button of chatRoomviewController, change is not happened because this view controller is rootview and its presentingViewController is empty!
so I fixed it like below
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
///some code for notification..
let mVC //this is MessengerBoxViewcontroller
let crVC //this is ChatRoomViewController
CRVC?.sender = "asdf"
do{
self.window?.rootViewController = mVC
self.window?.makeKeyAndVisible()
defer{
mVC?.presentChatRoomVC()
}
}
/// some code...
}
It works! But I'd like to know better way.
And Also I think I should study How window and viewcontrollers works.
please recommend me the better way, and reference documents.
Thank you.
You need to handle back button action programatically in ChatRoomViewControllerto solve your problem :
On back button Click :
Check for MessengerBoxViewcontroller is present in navigationController stack.
if MessengerBoxViewcontroller is in navigationController stack then pop to move back to MessengerBoxViewcontroller.
Else present MessengerBoxViewcontroller.
if let viewControllers = self.navigationController?.viewControllers {
for viewController in viewControllers {
if viewController.isKindOfClass(MessengerBoxViewcontroller) {
print("Your controller exist")
navigationController?.popViewController(animated: true)
}
}
}
else {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "messengerBoxViewcontroller") as! MessengerBoxViewcontroller
self.present(vc, animated: true, completion: nil)
}
I have an app that accepts a Deeplink URL and opens a viewcontroller with variables from the link and it works well if the App is opened/run for the first time by the user using the Deeplink.
However, if the App is already open/or in the background and has that view controller open... it then opens the same viewcontroller back up again so then I have two. I do not want to open the viewcontroller an additional time.
Is there some way I can identify that viewcontroller that is already open and pass the variables from the Deeplink to it?
or do I need to close it in some way and re-open it?
I am open to suggestions.... thanks in advance.
Try using UIApplication.shared.keyWindow?.rootViewController and testing what class it is. For example:
if let vc = UIApplication.shared.keyWindow?.rootViewController {
if vc is SomeViewController {
// Do something.
}
}
You can find the visible view controller with the following method
func getVisibleViewController(_ rootViewController: UIViewController?) -> UIViewController? {
var rootVC = rootViewController
if rootVC == nil {
rootVC = UIApplication.shared.keyWindow?.rootViewController
}
if rootVC?.presentedViewController == nil {
return rootVC
}
if let presented = rootVC?.presentedViewController {
if presented.isKind(of: UINavigationController.self) {
let navigationController = presented as! UINavigationController
return navigationController.viewControllers.last!
}
if presented.isKind(of: UITabBarController.self) {
let tabBarController = presented as! UITabBarController
return tabBarController.selectedViewController!
}
return getVisibleViewController(presented)
}
return nil
}
you can then switch on the presented view
if let presentedView = getVisibleViewController(window?.rootViewController) {
switch presentedView {
//code
default:
//code
}
}
and of course in the switch present a view controller if it is not the one that you want to be open.
No need to close a viewcontroller before opening it!
I have an app which starts with different VCs depending whether the user is already logged in or not.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow.init(frame: UIScreen.mainScreen().bounds)
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let initialViewController: UIViewController
if DataManager.getInstance().getUserInfo() == nil {
initialViewController = storyboard.instantiateViewControllerWithIdentifier("authenticationViewController")
} else {
initialViewController = storyboard.instantiateViewControllerWithIdentifier("locationsNavigationViewController")
}
window!.rootViewController = initialViewController;
window!.makeKeyAndVisible();
return true
}
If the user is not logged in, the app starts with AuthenticationViewController, otherwise it starts with LocationsNavigationViewController, which is a NavigationViewController
In the latter VC, the is a button for logout. The problem is when the user taps on that button, I don't know if I have to dismiss the LocationsNavigationViewController (because AuthenticationViewController is in background) or if I have to dismiss LocationsNavigationViewController and perform a segue for opening the AuthenticationViewController.
So far, I have just covered the first use case. So in LocationsNavigationViewController I call this function
func showAuthentication() {
dismissViewControllerAnimated(true, completion: nil)
}
But when the app starts with LocationsNavigationViewController dismiss the VC is not enough of course, because the the AuthenticationViewController has never been instantiated.
How can I solve this please?
self.navigationController?.viewControllers
This is an array which will contain all your previous view controller. You can enumerate it and check whether your view controller is exist or not.
If you are using UINavigationController then you can check any UIViewController present or not!
let rootViewController = application.windows[0].rootViewController as! UINavigationController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if !rootViewController.viewControllers.contains(UIViewController_Class()){
let notificationVC = mainStoryboard.instantiateViewControllerWithIdentifier(constInstance.notificationsIdentifier) as! UIViewController_Class_Name
rootViewController.pushViewController(notificationVC, animated: false)
}
Hope this helps!
Thanks to Sohil's answer, I change the showAuthentication function in this way
func showAuthentication() {
//Since the app can start with different VC, I have to check which is the window root VC
if UIApplication.sharedApplication().windows[0].rootViewController is AuthenticationViewController {
dismissViewControllerAnimated(true, completion: nil)
} else {
performSegueWithIdentifier("authenticationSegue", sender: self)
}
}
And added a segue from the NavigationViewController to the AuthenticationViewController, called authenticationSegue
Pretty simple solution.
You can create one function in appdelegate for logout
and in logout function chanege rootviewcontroller of window like
func logoutUser()
{
var login: UIViewController?
login = LoginViewController(nibName : "LoginViewController", bundle : nil)
let nav = UINavigationController(rootViewController: login!)
self.window?.rootViewController = nav
}
Hey Stackoverflow Members,
maybe you could help me to get my problem fixed.
The problem is I want to lock the orientation for all UIViewControllers to "Portrait" but if the MoviePlayer appears it should switch into landscape mode and back if the movie player disappears.
Until Swift 1.2 I used:
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow) -> UIInterfaceOrientationMask {
//If the video is being presented, let the user change orientation, otherwise don't.
if let presentedViewController = window.rootViewController?.presentedViewController? {
if (presentedViewController.isKindOfClass(MPMoviePlayerViewController) && !presentedViewController.isBeingDismissed()) {
return .AllButUpsideDown
}
}
return .Portrait
}
With Swift 1.2 some things changed and so I ended up with the following code:
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
//If the video is being presented, let the user change orientation, otherwise don't.
if let presentedViewController = window?.rootViewController?.presentedViewController {
if (presentedViewController.isKindOfClass(MPMoviePlayerViewController) && !presentedViewController.isBeingDismissed()) {
return Int(UIInterfaceOrientationMask.AllButUpsideDown.rawValue)
}
}
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
But my code doesn't work, the Movie Player (XCDYoutube) is locked in portrait mode. Device orientation Settings should be fine!
Thanks in advance for your help!
I had similar logic to yours but ending up returning support for all orientations.
return UIInterfaceOrientationMaskAll in appdelegate.
Depending on how many view controllers you have - you may want to create an abstract subclass of UIViewController and return only support for Portrait / and then hack your youtube view controller to support landscape.
(NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;
}
I just had the exact same issue. I found a way to fix it by reaching the top of the controller stack:
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
//If the video is being presented, let the user change orientation, otherwise don't.
if var presentedViewController = window?.rootViewController?.presentedViewController {
// Get the controller on the top of the stack
while (presentedViewController.presentedViewController) != nil {
presentedViewController = presentedViewController.presentedViewController!
}
if (presentedViewController.isKindOfClass(MPMoviePlayerViewController) && !presentedViewController.isBeingDismissed()) {
return Int(UIInterfaceOrientationMask.AllButUpsideDown.rawValue)
}
}
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
You can also try to display the type of presentedViewController to be sure it's the right one:
println("presentedViewController type: \(presentedViewController.dynamicType)")