Swift: Presenting modally and dismissing a navigation controller - ios

I have a very common iOS app scenario:
The MainVC of the app is a UITabBarController. I set this VC as the rootViewController in the AppDelegate.swift file:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow()
window?.rootViewController = MainVC()
window?.makeKeyAndVisible()
}
When the user logs out, I present a navigation controller with the LandingVC as the root view controller of the navigation stack.
let navController = UINavigationController(rootViewController: LandingVC)
self.present(navController, animated: true, completion: nil)
Inside LandingVC you click on Login button and the LoginVC is pushed on the top of the stack.
navigationController?.pushViewController(LoginVC(), animated: true)
When the user successfully logs in I dismiss() the navigation controller from inside the LoginVC.
self.navigationController?.dismiss(animated: true, completion: nil)
Basically, I am trying to achieve the flow below:
Everything works, but the problem is that the LoginVC is never deallocated from the memory. So if a user logs in and logs out 4 times (no reason to do that but still there is a chance), I will see LoginVC 4 times in the memory and LandingVC 0 times.
I don't understand why the LoginVC is not deallocated, but the LandingVC is.
In my mind (and correct me where I am wrong), since the navigation controller is presented and it contains 2 VCs (LandingVC and LoginVC), when I use dismiss() inside LoginVC it should dismiss the navigation controller, and therefore both contained VCs.
MainVC: presenting VC
Navigation Controller: presented VC
From Apple docs:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal.
I believe that something is going wrong when I dismiss the navigation controller within LoginVC. Is there a way to trigger dismiss() inside MainVC (presenting VC) as soon as the user logs in?
PS: using the code below will not do the trick since it pops to the root view controller of the navigation stack, which is the LandingVC; and not to MainVC.
self.navigationController?.popToRootViewController(animated: true)
Any help would be much appreciated!
====================================
My LoginVC code:
import UIKit
import Firebase
import NotificationBannerSwift
class LoginVC: UIViewController {
// reference LoginView
var loginView: LoginView!
override func viewDidLoad() {
super.viewDidLoad()
// dismiss keyboard when clicking outside textfields
self.hideKeyboard()
// setup view elements
setupView()
setupNavigationBar()
}
fileprivate func setupView() {
let mainView = LoginView(frame: self.view.frame)
self.loginView = mainView
self.view.addSubview(loginView)
// link button actions from LoginView to functionality inside LoginViewController
self.loginView.loginAction = loginButtonClicked
self.loginView.forgotPasswordAction = forgotPasswordButtonClicked
self.loginView.textInputChangedAction = textInputChanged
// pin view
loginView.translatesAutoresizingMaskIntoConstraints = false
loginView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
loginView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
loginView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
loginView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
fileprivate func setupNavigationBar() {
// make navigation controller transparent
self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
// change color of text
self.navigationController?.navigationBar.tintColor = UIColor.white
// add title
navigationItem.title = "Login"
// change title font attributes
let textAttributes = [
NSAttributedStringKey.foregroundColor: UIColor.white,
NSAttributedStringKey.font: UIFont.FontBook.AvertaRegular.of(size: 22)]
self.navigationController?.navigationBar.titleTextAttributes = textAttributes
}
fileprivate func loginButtonClicked() {
// some local authentication checks
// ready to login user if credentials match the one in database
Auth.auth().signIn(withEmail: emailValue, password: passwordValue) { (data, error) in
// check for errors
if let error = error {
// display appropriate error and stop rest code execution
self.handleFirebaseError(error, language: .English)
return
}
// if no errors during sign in show MainTabBarController
guard let mainTabBarController = UIApplication.shared.keyWindow?.rootViewController as? MainTabBarController else { return }
mainTabBarController.setupViewControllers()
// this is where i dismiss navigation controller and the MainVC is displayed
self.navigationController?.dismiss(animated: true, completion: nil)
}
}
fileprivate func forgotPasswordButtonClicked() {
let forgotPasswordViewController = ForgotPasswordViewController()
// present as modal
self.present(forgotPasswordViewController, animated: true, completion: nil)
}
// tracks whether form is completed or not
// disable registration button if textfields not filled
fileprivate func textInputChanged() {
// check if any of the form fields is empty
let isFormEmpty = loginView.emailTextField.text?.count ?? 0 == 0 ||
loginView.passwordTextField.text?.count ?? 0 == 0
if isFormEmpty {
loginView.loginButton.isEnabled = false
loginView.loginButton.backgroundColor = UIColor(red: 0.80, green: 0.80, blue: 0.80, alpha: 0.6)
} else {
loginView.loginButton.isEnabled = true
loginView.loginButton.backgroundColor = UIColor(red: 32/255, green: 215/255, blue: 136/255, alpha: 1.0)
}
}
}

After a lot of searching, I think I found the solution:
What inspired me was all guys commenting this question and also this article:
https://medium.com/#stremsdoerfer/understanding-memory-leaks-in-closures-48207214cba
I will start with my philosophy of coding: I like to keep my code separated and clean. So, I always try to create a UIView with all the elements I want and then "link" it to the appropriate view controller. But what happens when the UIView has buttons, and the buttons need to fulfill actions? As we all know, there is no room for "logic" inside the views:
class LoginView: UIView {
// connect to view controller
var loginAction: (() -> Void)?
var forgotPasswordAction: (() -> Void)?
// some code that initializes the view, creates the UI elements and constrains them as well
// let's see the button that will login the user if credentials are correct
let loginButton: UIButton = {
let button = UIButton(title: "Login", font: UIFont.FontBook.AvertaSemibold.of(size: 20), textColor: .white, cornerRadius: 5)
button.addTarget(self, action: #selector(handleLogin), for: .touchUpInside)
button.backgroundColor = UIColor(red: 0.80, green: 0.80, blue: 0.80, alpha: 0.6)
return button
}()
// button actions
#objc func handleLogin() {
loginAction?()
}
#objc func handleForgotPassword() {
forgotPasswordAction?()
}
}
So as the article says:
LoginVC has a strong reference to LoginView, that has strong reference to the loginAction and forgotPasswordAction closures that just created a strong reference to self.
As you can see pretty clearly we have a cycle. Meaning that, if you exit this view controller, it can’t be removed from memory because it’s still referenced by the closure.
That could be the reason why my LoginVC was never deallocated from the memory. [SPOILER ALERT: that was the reason!]
As shown in the question, the LoginVC is responsible for executing all button actions. What I was doing before was:
class LoginVC: UIViewController {
// reference LoginView
var loginView: LoginView!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
fileprivate func setupView() {
let mainView = LoginView(frame: self.view.frame)
self.loginView = mainView
self.view.addSubview(loginView)
// link button actions from LoginView to functionality inside LoginVC
// THIS IS WHAT IS CAUSING THE RETAIN CYCLE <--------------------
self.loginView.loginAction = loginButtonClicked
self.loginView.forgotPasswordAction = forgotPasswordButtonClicked
// pin view
.....
}
// our methods for executing the actions
fileprivate func loginButtonClicked() { ... }
fileprivate func forgotPasswordButtonClicked() { ... }
}
Now that i am aware of what is causing the retain cycle, I need to find a way and break it. As the article says:
To break a cycle, you just need to break one reference, and you will want to break the easiest one. When dealing with a closure you will always want to break the last link, which is what the closure references.
To do so, you need to specify when capturing a variable that you don’t want a strong link. The two options that you have are: weak or unowned and you declare it at the very beginning of the closure.
So what I changed in LoginVC to achieve this was:
fileprivate func setupView() {
...
...
...
self.loginView.loginAction = { [unowned self] in
self.loginButtonClicked()
}
self.loginView.forgotPasswordAction = { [unowned self] in
self.forgotPasswordButtonClicked()
}
self.loginView.textInputChangedAction = { [unowned self] in
self.textInputChanged()
}
}
After this simple code change (yeah it took me 10 days to figure it out), everything is running as before, but the memory is thanking me.
Couple things to say:
When I first noticed this memory issue, I blamed myself for not dismissing/popping the view controllers correctly. You can find out more in my previous StackOverflow question here: ViewControllers, memory consumption and code efficiency
In the process, I learned a lot about presenting/pushing view controllers and navigation controllers; so even though I was looking in the wrong direction, I surely learned a lot.
Nothing comes for free, memory leak taught me that!
Hope I could help others with the same issue as me!

Related

Overlay view being shown, but not removed

I have a BaseViewController and a SideMenu that uses my MenuViewController. There are many possible "Home" screens that all inherit from this same BaseViewController. MenuViewController also inherits from BaseViewController.
I would like an overlay to be shown on the home screen and then disappear when the Menu is no longer in focus. So far, I can only get the overlay to show, but not disappear.
The overlay disappears if I tap one of the menu items, which performs a segue to the appropriate subclass of BaseViewController (for example, the Home screen or Settings screen). This effectively refreshes the screen, and I think I could keep a reference to the caller and segue back to it if I can't find a better solution.
Things I have tried:
overlay.removeFromSuperview()
view.sendSubview(toBack: overlay)
overlay.isHidden = true
overlay.alpha = 0.0
Moving hideOverlay() into MenuViewController.
Using super.overlay within MenuViewController instead of simply overlay or self.overlay.
I can confirm that all lines of code are hit with breakpoints, but the overlay view does not go away. BaseViewController's viewWillAppear() is not called when I tap to make the menu go away, because its subclass is already in view (just pushed to the side a bit).
Here is a minimal reproducible example:
BASE VIEW CONTROLLER
import UIKit
import SideMenu
class BaseViewController: UIViewController {
let overlay = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// Setup
overlay.frame = self.view.frame
overlay.backgroundColor = UIColor.clear
overlay.alpha = 0.5
overlay.isUserInteractionEnabled = false
overlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(overlay)
}
// WORKS
func showMenu() {
// menuLeftNavigationController is MenuViewController.
self.present(SideMenuManager.menuLeftNavigationController!, animated: true) {
UIView.animate(withDuration: 0.2) {
self.overlay.backgroundColor = Constants.Colors.overlayColor // Already defined.
}
// PROBLEM IS HERE
func hideOverlay() {
UIView.animate(withDuration: 0.2) {
self.overlay.backgroundColor = UIColor.clear
self.overlay.setNeedsLayout()
self.overlay.layoutIfNeeded()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
}
}
MENU VIEW CONTROLLER
import UIKit
import SideMenu
class MenuViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Tableview boilerplate
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
// BREAKPOINTS CONFIRM THIS CODE EXECUTES.
override func viewWillDisappear(_ animated: Bool) {
self.hideOverlay()
}
}
In viewWillDisappear when you call self.hideOverlay, you're calling that on your MenuViewController.
When showMenu() is called, you present the MenuViewController and then set the overlay background colour on the presenting view controller.
I guess, what you want to do here is in the completion of the MenuViewController, dismiss method do:
if let presentingViewController = self.presentingViewController as? BaseViewController {
presentingViewController.hideOverlay()
}
Hopefully that makes sense?

Making a conditional tab bar in ios

I'm working on an IOS app that uses tabs for navigation. The app gives access to users to a video library. However there are two types of users, those who purchase individual episodes and those who are subscribed. The former only have access to the videos they purchased while the latter have access to every single video in the library.
In my tab bar (in storyboard) I have a Purchases button, but if the user is a subscriber I don't want this tab to appear.
The app checks if a user is logged in upon launching and checks to see what the user status is (buyer or subscriber). I would like to know if there is a way to load different sets of tabs depending on the user type.
If any one could steer me in the right direction I'd really appreciate it. Thanks!
From the top of my head I can think of several ways but this could do it. I am assuming that you somehow know which kind of user is logged in based on the server's response or something similar.
Create your own class that mutates depending on the user eg:
MyTabBarController: UITabBarController {
override func viewDidLoad() {
if (currentUser == admin) {
setupAdminTabBar()
} else {
setupRegularTabBar()
}
}
}
then on each function do something like
func setupRegularTabBar() {
//do this many as many times as root view controllers you want
let searchNavController = createMyNavController(unselectedImage: UIImage(named: "yourimage"), selectedImage: UIImage(named: "yourimage"), rootViewController: UserSearchController(collectionViewLayout: UICollectionViewFlowLayout()))
//add the other controllers that you create like the one above...
viewControllers = [searchNavController]
}
fileprivate func createMyNavController (unselectedImage: UIImage, selectedImage: UIImage, rootViewController : UIViewController = UIViewController()) -> UINavigationController {
let viewController = rootViewController
let navController = UINavigationController(rootViewController: viewController)
navController.tabBarItem.image = unselectedImage
navController.tabBarItem.selectedImage = selectedImage
return navController
}
Subclass UITabBarController and use setViewControllers(_:animated:):
class MyTabBarController: UITabBarController
{
override func viewDidLoad()
{
super.viewDidLoad()
switch user
{
case .buyer:
guard let vc1 = storyboard?.instantiateViewController(withIdentifier: "first"),
let vc2 = storyboard?.instantiateViewController(withIdentifier: "second") else
{
return
}
setViewControllers([vc1, vc2], animated: true)
case .subscriber:
guard let vc3 = storyboard?.instantiateViewController(withIdentifier: "third"),
let vc4 = storyboard?.instantiateViewController(withIdentifier: "fourth") else
{
return
}
setViewControllers([vc3, vc4], animated: true)
}
}
}
You can use the setViewControllers function of UITabBarController:
func setViewControllers(_ viewControllers: [UIViewController]?, animated: Bool)
Set up all the possible controllers in the storyboard with a separate outlet for each one. Then pass an array of the outlets you wish to appear to setViewControllers

transition between UIViewController from navigation drawer

I am Using Swift 3. I have searched about this and found the solution
navigationDrawerController?.TransitionFromRootViewController
but when I used this line it say TransitionFromRootViewController is not a function.
So I tried Using
navigationDrawerController?.transition(from: RootViewController(), to: destViewController(), duration: 0.2, options: .transitionCrossDissolve, animations: nil, completion: nil)
but it shows error that the:
"child view controller must have a common parent view controller when calling transitionfrom view controller"
Can anyone help me please? If someone can push an example of navigation drawer with the switching would be a great.
Here is the solution, which I posted to the NavigationDrawerController example project in the programmatic directory, Material 2.1.2.
It shows how to transition with multiple navigation controllers, and by itself.
import UIKit
import Material
class LeftViewController: UIViewController {
private var transitionButton: FlatButton!
open override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Color.blue.base
prepareTransitionButton()
}
#objc
internal func handleTransitionButton() {
// Transition the entire NavigationDrawer rootViewController.
// navigationDrawerController?.transition(to: TransitionedViewController(), completion: closeNavigationDrawer)
// Transition the ToolbarController rootViewController that is in the
// NavigationDrawer rootViewController.
(navigationDrawerController?.rootViewController as? ToolbarController)?.transition(to: TransitionedViewController(), completion: closeNavigationDrawer)
}
internal func closeNavigationDrawer(result: Bool) {
navigationDrawerController?.closeLeftView()
}
private func prepareTransitionButton() {
transitionButton = FlatButton(title: "Transition VC", titleColor: Color.white)
transitionButton.addTarget(self, action: #selector(handleTransitionButton), for: .touchUpInside)
view.layout(transitionButton).horizontally().center()
}
}
You can find a reference to the discussion in GitHub issue-546
All the best!

SFSafariViewController loading blank white page

I am using SFSafariViewController to open a URL in my iOS app.. it was working perfectly on iOS 9 but after updating my device to iOS 10, it just loads a blank white page with no URL in the address bar. Even safariViewController(controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) is not getting called after controller is presented.
I have imported this in the view controller:
import SafariServices
code:
let url = NSURL(string: urlString)!
if #available(iOS 9.0, *) {
let safariVC = SFSafariViewController(URL: url)
safariVC.delegate = self
self.presentViewController(safariVC, animated: true, completion: {
self.hideHUD()
})
} else {
// Fallback code
}
here is the link to exact same problem someone else faced
Try commenting out any loading animations you are doing and try without that.
...
// self.showHUD()
// self.hideHUD()
...
Its because of a bug in iOS 10 introduced during a security fix which doesn't let safariViewController load when using progress bars and loaders added as a window above the main window
I really wish Apple had documented the change.
I had to comment out the loading indicator in my current app to get it working.
Came across this as seeing some similar problems that I was seeing when trying to use a SafariViewController. Tracked my problems down to using SafariViewController when a popup was present. Have put together some code to show my problem and possible solution.
ViewController.swift:
/*
This view controller tries to boil down to the essence of a problem seen
when trying to use SafariViewController. The net result is DO NOT present
the SafariViewController when a popup is present.
This controller and the associated popup show 3 ways to
present the SafariViewController:
Always Good: This uses a button on the controller to simply call the
showSVC() routine and never had a problem.
Good: This is a work-around for the "Bad" case to follow. In this case,
we are using a button in a popup to bring up the SafariViewController.
The trick is to get rid of the popup before calling showSVC(). This is
done by dismissing the popup immediately without animation and then
adding a delay before calling showSVC(). Seems to work fine, but using
delays to accomplish things always seems a bit suspect. Use at your own risk.
Bad: When this goes bad, a blank white screen is presented with no way
to escape. Using "Reset Content and Settings..." in the simulator can
get one back to where it will work one time. It seemed originally that
this worked on iOS9 but not on iOS10. But now seems to fail similarly
on both iOS9 and iOS10. This case is dismissing the popup with animation
while trying to bring up the SafariViewController.
Other info: This view controller is embedded in a navigation controller -
basically to provide a navigation bar and button to act as the anchor
point for the popup. The popup provides two buttons that call back
to this controller with the "PopButtonPressedProtocol". This controller
uses the title in the popup's buttons to differentiate the "good" and
"bad" cases.
The problems shown by this example sounded similar to problems various
others reported, but these reports may be for other reasons, which may
or may not be similar.
*/
import UIKit
import SafariServices
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate, PopButtonPressedProtocol {
#IBOutlet weak var btnAlwaysGood: UIButton!
#IBOutlet weak var btnPopup: UIBarButtonItem!
let webAddr = "http://www.google.com"
func delay(_ delay: Double, closure:#escaping ()->()) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
func showSVC() {
let svc = SFSafariViewController(url: URL(string: webAddr)!)
self.present(svc, animated: true, completion: nil)
}
func popButtonPressed(_ button: UIButton) {
if let title = button.currentTitle {
switch title {
case "Bad":
dismiss(animated: true, completion: nil)
showSVC()
case "Good":
dismiss(animated: false, completion: nil)
delay(0.5, closure: { self.showSVC() })
default:
break
}
}
}
func popupButtonPressed() {
performSegue(withIdentifier: "popup", sender: nil)
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle { // Needed for popup
return .none
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
btnAlwaysGood.addTarget(self, action: #selector(showSVC), for: .touchUpInside)
btnPopup.target = self
btnPopup.action = #selector(popupButtonPressed)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? PopVC {
vc.delegate = self
vc.modalPresentationStyle = .popover
vc.preferredContentSize = CGSize(width: 60, height: 100)
if let popover = vc.popoverPresentationController {
popover.permittedArrowDirections = .any
popover.delegate = self
popover.barButtonItem = navigationItem.rightBarButtonItem
}
}
}
}
PopVC.swift:
import UIKit
protocol PopButtonPressedProtocol : class {
func popButtonPressed(_ button: UIButton) // protocol:
}
class PopVC: UIViewController {
#IBOutlet weak var btnBad: UIButton!
#IBOutlet weak var btnGood: UIButton!
weak var delegate : PopButtonPressedProtocol?
func buttonPressed(_ button: UIButton) {
delegate?.popButtonPressed(button)
}
override func viewDidLoad() {
super.viewDidLoad()
btnBad.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
btnGood.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
}
}
Swift 5.2, Xcode 11.4
I had similar problem, what I was doing is I was presenting UIAlertViewController and on action of its button I was present SFSafariController....this code worked perfectly on iOS 13 but iOS 12 gave problem.... apparently I had to dismiss the UIAlertViewController first before presenting SFSafariController. Hope this helps.
or else quick fix would be to use UIApplication.shared.openURL()

Swift / How to hide NavigationBar with Root View Controller / Page View Controller. Little gap on top of screen

I'm using the SwipeViewController.
It's a sub class of UINavigationController, UIPageViewControllerDelegate, UIScrollViewDelegate
The view is instantiated within the AppDelegate.Swift by:
let pageController = UIPageViewController(transitionStyle: .Scroll, navigationOrientation: .Horizontal, options: nil)
let navigationController = ViewController(rootViewController: pageController)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
The complete code of the ViewController is:
import UIKit
import SwipeViewController
class ViewController: SwipeViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationBar.hidden = true
let VC1 = UIViewController()
VC1.view.backgroundColor = UIColor(red: 0.19, green: 0.36, blue: 0.60, alpha: 1.0)
VC1.title = "Recent"
let VC2 = UIViewController()
VC2.view.backgroundColor = UIColor(red: 0.70, green: 0.23, blue: 0.92, alpha: 1.0)
VC2.title = "Random"
setViewControllerArray([VC1, VC2])
setFirstViewController(0)
}
}
My problem is, once the App is started, no matter if in simulator or physical device. There is a gap in the exact height of the status bar.
Once I press on the screen, the view "moves up".
Also. For only one time I'm able to grab the view and grab it down. Then the screen will "run back" to the top (like in a graphic bug) and from that moment on, till next App start, the screen is "locked" to the window. A condition I want all the time....
What is the best practise to delete those "bugs", have the NavigationBar hidden and the views locked to the window? Help is very appreciated.
PS. If I don't hide the NavigationBar, there is no bug at all.
PPS. You can download the sample project from the provided github link. It's exactly the same.
Use viewWillAppear instead of viewDidLoad, it will execute the code before the finish creating the view
override func viewWillAppear(animated: Bool) {
self.navigationController?.navigationBarHidden = true
}
This is added since In iOS 9, XCode 7, Swift 2.0
override func prefersStatusBarHidden() -> Bool {
return true
}
To solve the similar problem I've created singleton class to implement UINavigationControllerDelegate. And implemented following method:
public func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
if (viewController.isMemberOfClass:(YourUIViewControllesSubclass1.self)){
//or another detection
navigationController.navigatonBarHidden = true//or false if needed
} else if (viewController.isMemberOfClass:(YourUIViewControllesSubclass2.self)){
navigationController.navigatonBarHidden = true//or false if needed
}/ ... may check others
I wish it would solve the problem
I found the solution. The following function has to be overwritten in the UINavigationController Subclass (SwipeViewController.swift).
override public func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews();
if self.view.frame.size.height == UIScreen.mainScreen().bounds.height {
for childVC in childViewControllers {
childVC.view.frame = CGRectMake(0, navigationBar.frame.size.height, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height - navigationBar.frame.size.height);
}
}
}

Resources