Layout issues when presenting a view controller with UIModalPresentationStyleCurrentContext - ios

I ran into a problem having to do with UISplitViewController and modal view controllers on the iPad, so I tried to reproduce the issue in a small project to see if I could figure out what was going on. Unfortunately, the problem seems to still be occurring, but I can't figure out why.
I've included a very small, complete Swift program below that reproduces the problem. Basically, if you run that code in a new Swift iOS project on the iPad, you'll see the following behavior:
The app will start with the following UI:
If I tap Present, a new modal controller will present over the detail side of the split controller. If I tap Dismiss on that modal controller, I'll return to the same initial interface and all is well.
However, if I tap Present, then tap Present on the modal controller that appears (so I have two modal controllers over the original details view), and then dismiss the top one, the RightViewController takes over the entire screen, eliminating the split view controller:
I've been banging my head against this all afternoon. Is there something I'm missing?
Here is the complete source for the example application:
import UIKit
class RightController: UIViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.title = "Right"
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
let label = UILabel(frame: CGRect(x: 0, y: 100, width: 100, height: 20))
label.text = "Top Left"
self.view.addSubview(label)
let presButton = UIBarButtonItem(title: "Present", style: .Plain, target: self, action: Selector("present:"))
self.navigationItem.rightBarButtonItem = presButton
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Dismiss", style: .Plain, target: self, action: Selector("dismiss:"))
}
func dismiss(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func present(sender: AnyObject) {
let rc = RightController()
let nav = UINavigationController(rootViewController: rc)
nav.modalPresentationStyle = .CurrentContext
self.presentViewController(nav, animated: true, completion: nil)
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let splitViewController = UISplitViewController()
splitViewController.delegate = self
let nav1 = UINavigationController(rootViewController: UIViewController())
nav1.title = "Left"
let nav2 = UINavigationController(rootViewController: RightController())
splitViewController.viewControllers = [nav1, nav2]
self.window!.rootViewController = splitViewController
return true
}
}
extension AppDelegate: UISplitViewControllerDelegate {
func splitViewController(svc: UISplitViewController, shouldHideViewController vc: UIViewController, inOrientation orientation: UIInterfaceOrientation) -> Bool {
return false
}
}
EDIT: Cycling through the device's orientations causes the split view controller to redraw itself correctly, at least until Dismiss is pressed again.
EDIT: I can also get this to work if I use the new .OverCurrentContext presentation style in iOS 8. However, I can't drop compatibility with iOS 7, so I need a different solution.

I have a solution that works although I will be the first to admit, it does feel a little hacky. I think a big part of your issue stems from a change in .CurrentContext and after some testing I found that it functions differently for iOS 7 and 8+. So everything works expectedly if you choose the proper style based on the iOS version:
var presStyle: UIModalPresentationStyle = (UIDevice.currentDevice().systemVersion as NSString).integerValue == 7 ? .CurrentContext : .OverCurrentContext
nav.modalPresentationStyle = presStyle

Related

Framework ViewController View Not showing

I am trying to create a framework (Login VC) which contains a view controller. I have successfully imported the framework and presented the VC, but the view is not showing. I have a print function in the imported viewDidLoad and it is printing. What am I missing?
Framework VC:
public class LoginVC: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
}
}
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewDidLoad() {
super.viewDidLoad()
print("View Loaded") // Firing
}
#objc func buttonPressed() {
print("hello")
}
}
Framework VC Xib:
This is view debugger when I present the framework VC
-- Update: This is how I am showing the VC ---
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = LoginVC()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
-- Update --
Since many comments relate to the app delegate, I first present a general ViewController which then will present my login framework VC.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = ViewController()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(presentNext), for: .touchUpInside)
}
}
#objc func presentNext() {
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
Now, when I present the login framework, all I get is a black screen.
-- Update --
I can change the background color of the view in viewdidLoad, but the xib views are not shown. Why is this?..
Frameworks with xibs connected to viewController require explicit loading from the bundle.
Usually when we create a cocoaTouch UIViewController with xib, this is handled for us. However, when using frameworks, it is not handled.
To solve this, I add load the xib in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Frameworks require loading of the xib to connect xib to view controller
let bundle = Bundle(for: type(of: self))
bundle.loadNibNamed("viewControllerName", owner: self, options: nil)
}
You need to call your presentNext() after the ViewController has actually appeared -- not in viewDidLoad, and not even in viewWillAppear.
Like this:
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentNext()
}
#objc func presentNext() {
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
Here's the working test project:
https://github.com/drewster99/SO_LoginVC
Also, maybe double-check that your ViewController.xib has the IBOutlet actually attached for the button. It's actually got to be that. Everything else looks good.
Here's what I've got:
AppDelegate:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = ViewController()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
}
ViewController.swift:
import UIKit
import LoginFrameworkThing
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(presentNext), for: .touchUpInside)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#objc func presentNext() {
print("Presenting next...")
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
The button is connected in ViewController, and LoginVC (both .xib and .swift) exist in the framework.
I updated the sample project. Check the link.

navigationItem.leftBarButton is not appearing (Xcode Swift 4 IOS)

I have made a UITableViewController(iOS controller for table views) and attempted to put a logout button in the top left corner of the screen with this code:
class ViewController: UITableViewController {
var logoutButton : UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
logoutButton = UIBarButtonItem(title: "Logout", style: .plain, target: self, action: #selector(handleLogout))
navigationItem.leftBarButtonItem = logoutButton
}
The iOS simulator just shows an empty (white) table view. I thought this would be a pretty simple task and have no idea why its not working or where to go from here. Any help would be appreciated.
Update: I have also tried to add navigation buttons to UINavigationController and UIViewController; the code looks like this
class ViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
let logoutButton = UIBarButtonItem(title: "Logout", style: .plain, target: nil, action: nil)
navigationItem.leftBarButtonItem = logoutButton
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let logoutButton = UIBarButtonItem(title: "Logout", style: .plain, target: nil, action: nil)
navigationItem.leftBarButtonItem = logoutButton
}
My AppDelegate looks like this:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
Nothing works. I cannot get a button to display. I don't want to use storyboards but I'm left with few other options if I can't even get a button to load. Any help is appreciated. Thanks!
The problem was in the appDelegate. The rootViewController should have been set like:
window?.rootViewController = UINavigationViewController(rootViewController: UITableViewController())
As opposed to:
window?.rootViewController = UITableViewController()
I think this is what the comments were trying to point out; thanks!
in this case you are have to push a loginViewController again at a logout time instead you need to popToRootViewControllerAnimated code
use this code on your logout button clickEvnet:
[self.navigationController popToRootViewControllerAnimated:YES];
if you are using swift then use this code
self.navigationController?.popToRootViewControllerAnimated(true)

Swift: Setting Different Navigation Bar Buttons for Different Tabs

I created a custom UITabBarController with 5 tabs in Swift 3.0. I've been trying to set different navigation bar items for certain tabs, but it's not working well.
Situation: I put codes that would change the Navigation Bar Buttons in each tab's viewDidLoad().
//The customized Tab Bar controller instance. Contained in each of the tabs.
var mainController: TabBarController?
override func viewDidLoad() {
// ... more code
setupNavBar()
// ... more code
}
func setupNavBar() {
// ... more code
mainController?.navigationItem.leftBarButtonItem = UIBarButtonItem(image: friendsImage, style: .plain, target: self, action: #selector(handleFindFriends))
// ... more code
}
Problem: Let's say Tab #1 is supposed to have NavBarButton A and Tab #2 is supposed to have NavBarButton B.
When I switch from Tab #1 to Tab #2, the code works fine; the NavBarButton changes from A to B.
However, when I click on Tab #1, the NavBarButton still remains B.
How can I make it so that the navigation bar buttons change accordingly even when I click on a tab I was on previously?
I assume you embed a UITabBarController (or it's subclass) into a UIViewController. This is wrong because in this case you tend to make the view controller generic which is usually a bad practice.
Instead I would suggest to change hierarchy of view controller to what you see on the image below.
UPDATE
If you do it in code:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let tabBarController = UITabBarController()
// first tab
let firstViewController = UIViewController()
_ = firstViewController.view
firstViewController.title = "First"
let firstNavigationController = UINavigationController(rootViewController: firstViewController)
// sets a specific button ("Friends") on a navigation bar for the first screen
firstViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Friends", style: .plain, target: nil, action: nil)
// second tab
let secondViewController = UIViewController()
_ = secondViewController.view
secondViewController.title = "Second"
let secondNavigationController = UINavigationController(rootViewController: secondViewController)
// sets a specific button ("Photos") on a navigation bar for the second screen
secondViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Photos", style: .plain, target: nil, action: nil)
// embeds the navigation controllers into the tab bar controller
tabBarController.viewControllers = [firstNavigationController, secondNavigationController]
// creates a window with the tab bar controller inside
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
return true
}
}
I actually found a solution XD
In TabBarController:
override func viewWillAppear(_ animated: Bool) {
//Original code to set up tabs
//Code I added:
for i in 0...4 {
tabBar.items?[i].tag = i
}
}
//Code I added:
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
//Code to run when a certain tab is selected
}
Still, thanks a lot!

Set Back title by the default for UINavigationController

This is how
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
VAXApplicationSettings.configureApplicationAppearance()
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.makeKeyAndVisible()
if let window = window {
guard let welcomeViewController = storyboard.instantiateViewControllerWithIdentifier("WelcomeViewController") as? VAXWelcomeViewController else {
return nil
}
let navigationController = UINavigationController(rootViewController: welcomeViewController)
window.rootViewController = navigationController
}
return true
}
On the Welcome View Controller I push New View Controller which looks like this:
And as you see here is a Cancel button.
This is a source code of class of pushed view controller:
import UIKit
class NewViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .Plain, target: self, action: #selector(back))
}
func back()
{
navigationController?.popViewControllerAnimated(true)
}
}
This has nothing to do with your navigation controller or your navigation bar. The back button's title is, by default, the title of the view controller you would go back to if you tapped it. That view controller can also change the title of the back button that goes back to it. There is no overall "default"; it happens on a view controller by view controller basis.
i've set this for navigation item in my Welcome View controller which is root view controller of navigation controller.
For situations like this I like to create a sublass of UIViewController called BaseViewController Then all my view controllers inheirit from this instead of the standard iOS one. This allows me to add functionality that is applied throughout the app.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.backButtonItem = UIBarButtonItem(title: "Back", style: .Default, target: self, action: #selector(back))
}
func back() {
self.navigationController?.popViewController(animated: true)
}
}
Ideally your app should be built from the start using this but you can do an app-wide find/replace to update your existing view controllers.

Moving from one UIViewController to another in Swift programmatically

I have 2 ViewControllers. I don't have a storyboard. I want to move from the first view to another once everything is done in first viewController. What is the way to do it? It is something like this, the first view shows an image and in the background makes some API calls. After API call succeeds, I want it to move to the second ViewController(LoginActivityViewController). I tried calling this in the first ViewController:
var loginActivity = LoginActivityViewController()
self.navigationController.pushViewController(loginActivity, animated: true)
But, this did not work. How to do this?
Here is my application function in AppDelegate
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let navigationController: UINavigationController = UINavigationController(rootViewController: RootViewController())
navigationController.setNavigationBarHidden(true, animated: false)
window!.rootViewController = navigationController
window!.makeKeyAndVisible()
return true
}
Here is my RootViewController's viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let image1 = UIImage(named: "Default.png")
let imageview = UIImageView(image: image1)
self.view.addSubview(imageview)
var loginActivity = LoginActivityViewController()
self.navigationController.pushViewController(loginActivity, animated: true)
}
this how your method should look with initing a navigation controller properly:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let rootViewController: RootViewController = RootViewController(nibName: "RootViewController", bundle: nil)
let navigationController: UINavigationController = UINavigationController(rootViewController: rootViewController)
window!.rootViewController = navigationController
window!.makeKeyAndVisible()
return true
}
and now you are able to push new view controllers into the hierarchy.
update
if you don't want to show the navigation bar insert this line into the code above.
navigationController.setNavigationBarHidden(true, animated: false)
var next = self.storyboard?.instantiateViewControllerWithIdentifier("DashboardController") as! DashboardController
self.presentViewController(next, animated: true, completion: nil)
don't forget to set ViewController StoryBoard Id in StoryBoard -> identity inspector

Resources