Setting up a UITabBarController with nib ViewControllers - ios

Ok stuck on this one, help would be most appreciated.
So I have a tab controller inside a navigation controller in my storyboard.
Then I have two view controllers with nib files that I want to load into the tab controller programmatically. I'm using the following code but getting a blank screen when I load the app.
class MainTabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
let verbViewController = VerbViewController(nibName: "VerbViewController", bundle: nil)
let communityViewController = CommunityViewController(nibName: "CommunityViewController", bundle: nil)
let tabIcon1 = UITabBarItem(title: nil, image: UIImage(named: "VerbTab"), tag: 0)
let tabIcon5 = UITabBarItem(title: nil, image: UIImage(named: "CommunityTab"), selectedImage: nil)
verbViewController.tabBarItem = tabIcon1
communityViewController.tabBarItem = tabIcon5
let tabControllers = [verbViewController, communityViewController]
self.tabBarController?.setViewControllers(tabControllers, animated: true)
self.viewControllers = tabControllers
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Delegate method
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
return true
}
}

You are calling self.tabBarController from within a UITabBarController which doesn't make much sense.
UIViewController's tabBarController returns "The nearest ancestor in the view controller hierarchy that is a tab bar controller." which in your case is probably nil because your tab bar controller is not contained in yet another tab bar controller.
You probably want to just call self.setViewControllers directly.
This behavior would also probably make more sense in viewDidLoad rather than viewWillAppear, do you really want to replace the controllers for each tab every time the tab bar appears?

Related

Resetting the navigation stack on a UITabBarController doesn't work

I'm trying to reset the navigation stack on a subclass of UITabViewController embedded in a UINavigationController but it doesn't work.
My navigation stack, which I create programmatically, is like this:
UINavigationController => ControllerA (a subclass of UIViewController) =>
ControllerB (a subclass of UIViewController) => ControllerC (a
subclass of UITabBarController).
When users press on the "Back" button or swipe back from ControllerC, the app should go back to ControllerA, not ControllerB.
Usually, when I want to reset the navigation stack, I do this in the Controller's viewDidLoad() method:
override func viewDidLoad() {
super.viewDidLoad()
// usually work, but not in a subclass of UITabBarController as self.navigationController is nil
if let navigationController = self.navigationController {
// keep only the root controller (0) and the current controller
navigationController.viewControllers = [navigationController.viewControllers[0], self]
}
}
but this doesn't work in ControllerC (the subclass of UITabViewController) as self.navigationController is nil.
If I do this instead (still in ControllerC's viewDidLoad() method):
/// ControllerC's viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
if let navigationController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
// keep only the root controller (0) and the current controller
navigationController.viewControllers = [navigationController.viewControllers[0], self]
}
}
This works, but then there is no animation between ControllerB and ControllerC when I do:
controllerB.navigationController?.pushViewController(ControllerC(), animated: true)
I also tried to override ControllerC's viewWillDisappear() method:
/// ControllerC's viewWillDisappear
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
if let navigationController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
navigationController.popToRootViewController(animated: true)
}
}
This works, but ControllerB is briefly visible before ControllerA is shown.
Any help would be greatly appreciated!
In the ControllerC instead of trying to override viewWillDisappear() method you can override viewDidAppear() like that:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let navC = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
// keep only the root controller (0) and the current controller
navC.viewControllers = [navC.viewControllers[0], self]
}
}
And ControllerB won’t be briefly visible before ControllerA when you navigate backwards.

One ViewController, multiple tabBar items

I have a tabBar with one item at the moment. For this Item I have a ViewController.
Now I would like to dynamically add more items to the tabbar which should all open the same ViewController. I will check inside the viewcontroller which button was pressed and customizie the content.
How can I add more items to the tabbar linking to the same Viewcontroller?
I tried to just add UITabBarItems as a list but this does not work out.
Any advice?
Although I don't like your original idea of multiple SAME VC in the tabController, actually it is feasible.
import UIKit
class MyTabViewController : UIViewController{
override var tabBarItem: UITabBarItem!{
get{ return UITabBarItem.init(title: "temp", image: nil, tag: 100) }
set{ super.tabBarItem = newValue} }
}
class MyTabController: UITabBarController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
perform(#selector(change), with: nil, afterDelay: 3.0)
perform(#selector(printViewController), with: nil, afterDelay: 5.0)
}
#objc func printViewController(){
print (viewControllers!)
}
#objc func change(){
if let viewController = self.viewControllers?[0]{
let label = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 100))
label.text = "testing"
viewController.view.addSubview(label)
setViewControllers([viewController,viewController,viewController,viewController,viewController], animated: true)
}
}
}
After 5 seconds, you can see you got 5 same vc in your tabController.
You should be able to subclass UITabBarController and use the viewControllers property or the setViewController(_:animated:) method in viewDidLoad. I'd recommend using a .nib for laying ViewController out, and instantiating it using init(nibName: String?, bundle: Bundle?).
Instead of having the ViewController determine configuration based on it's tabBarItem property, you should have that configuration occur before setting the viewController property on your UITabBarController.
Something like so:
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
var controllers = [UIViewController]()
let firstViewController = ViewController(nibName: "NIBNAME", bundle: Bundle.main)
// Configure unique properties for firstViewController here, including
// the tabBarItem.
controllers.append(firstViewController)
// Configure the rest of the ViewControllers with unique properties and add them to controllers
setViewControllers(controllers, animated: false)
}
}
Also, note that if you have more than 5 controllers you will need to utilize the moreNavigationController property in your UITabBarController subclass.
I recommend you read the documentation for UITabBarController to get an idea on how to do all of this.

UIPageViewController viewController disappears after transition

I've embedded a UIPageViewController in a UINavigationController, which in turn is embedded in a UITabBarController. I'm simply trying to make it so that the pageViewController loops through its viewControllers that are stored in an array. However every time I try to move to the next page, the first viewController snaps back into place before disappearing.
I've made the first viewController red and the second one blue and oddly enough when loading them in I'm presented with the second viewController.
This gif shows what I mean
I've tried to set up a pageViewController in the same manner in a new project and everything worked as expected so I can't see where the problem is.
import UIKit
final internal class TabBarController: UITabBarController, ApplicationLoginDelegate {
private let newsFeedTableViewController: NewsFeedTableViewController = NewsFeedTableViewController(style: UITableViewStyle.grouped)
private let substitutionPlanTableViewController: SubstitutionPlanTableViewController = SubstitutionPlanTableViewController(style: UITableViewStyle.grouped)
private let loginTableViewController: LoginTableViewController = LoginTableViewController(style: UITableViewStyle.grouped)
private let timeTableViewController: TimeTablePageViewController = TimeTablePageViewController(transitionStyle: UIPageViewControllerTransitionStyle.scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.horizontal, options: nil)
private let moreTableViewController: MoreTableViewController = MoreTableViewController(style: UITableViewStyle.grouped)
//
// MARK: - Override point
//
/**
Called after the controller's view is loaded into memory.
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView() method. You usually override this method to perform additional initialization on views that were loaded from nib files.
*/
override func viewDidLoad() {
super.viewDidLoad()
self.setUpTabBar()
}
/**
Notifies the view controller that its view is about to be added to a view hierarchy.
This method is called before the view controller's view is about to be added to a view hierarchy and before any animations are configured for showing the view. You can override this method to perform custom tasks associated with displaying the view. For example, you might use this method to change the orientation or style of the status bar to coordinate with the orientation or style of the view being presented. If you override this method, you must call super at some point in your implementation.
For more information about the how views are added to view hierarchies by a view controller, and the sequence of messages that occur, see Supporting Accessibility.
*/
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if UIApplication.boolForKey(UserDefaultKey.openSubstitutionPlanOnStartup) == true {
self.selectedViewController = self.viewControllers?[1]
}
}
//
// MARK: - Functions
//
private func setUpTabBar() {
// General
self.tabBar.tintColor = UIColor.applicationBaseColor
self.tabBar.unselectedItemTintColor = UIColor.lightGray
self.tabBar.backgroundColor = UIColor.white
// Create tab bar items
let newsFeedTabBarItem: UITabBarItem = UITabBarItem(title: "Aktuelles", image: #imageLiteral(resourceName: "News"), tag: 0)
let substitutionTabBarItem: UITabBarItem = UITabBarItem(title: "Vertretungen", image: #imageLiteral(resourceName: "SubstitutionPlan"), tag: 1)
let timeTableTabBarItem: UITabBarItem = UITabBarItem(title: "Stundenplan", image: #imageLiteral(resourceName: "TimeTable"), tag: 2)
let moreTabBarItem: UITabBarItem = UITabBarItem(title: "Entdecken", image: #imageLiteral(resourceName: "MoreMenu"), tag: 3)
// Link items and controllers
self.newsFeedTableViewController.tabBarItem = newsFeedTabBarItem
self.substitutionPlanTableViewController.tabBarItem = substitutionTabBarItem
self.loginTableViewController.tabBarItem = substitutionTabBarItem
self.timeTableViewController.tabBarItem = timeTableTabBarItem
self.moreTableViewController.tabBarItem = moreTabBarItem
// Set delegates
self.loginTableViewController.delegate = self
// Set tab bar view controllers
var viewControllers: [UIViewController] = []
if UIApplication.boolForKey(UserDefaultKey.isUserLoggedIn) == true {
viewControllers = [newsFeedTableViewController, substitutionPlanTableViewController, timeTableViewController, moreTableViewController]
} else {
viewControllers = [newsFeedTableViewController, timeTableViewController, moreTableViewController]
}
self.viewControllers = viewControllers.map({ (controller) -> UIViewController in
controller.navigationItem.largeTitleDisplayMode = .always
let navigationController = UINavigationController(rootViewController: controller)
navigationController.navigationBar.prefersLargeTitles = true
return navigationController
})
if UIApplication.boolForKey(UserDefaultKey.isUserLoggedIn) == false {
self.viewControllers?.insert(self.loginTableViewController, at: 1)
}
}
}
The UITabBarController and the UIPageViewController:
class TimeTablePageViewController: UIPageViewController, UIPageViewControllerDataSource {
private var timeTableViewControllers: [UIViewController]!
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.timeTableViewControllers = Array.init(repeating: UIViewController(), count: 2)
self.timeTableViewControllers[0].view.backgroundColor = .red
self.timeTableViewControllers[1].view.backgroundColor = .blue
self.setViewControllers([self.timeTableViewControllers[0]], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let index = self.timeTableViewControllers.index(of: viewController) {
if viewController == self.timeTableViewControllers.first {
return self.timeTableViewControllers.last
} else {
return self.timeTableViewControllers[index - 1]
}
} else {
return nil
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let index = self.timeTableViewControllers.index(of: viewController) {
if viewController == self.timeTableViewControllers.last {
return self.timeTableViewControllers.first
} else {
return self.timeTableViewControllers[index + 1]
}
} else {
return nil
}
}
}
The repeatedValue parameter of Array.init(repeating repeatedValue: Array.Element, count: Int) is not a closure. It's a single object that will be used to fill the array.
The code won't call UIViewController() for each element it creates. You are creating an array that contains the same UIViewController instance two times. A view can't have two superViews, so when you scroll to the second page, the UIPageViewController adds the view of the only viewController to its view, which means that it will be removed from its view as well.
Replace
self.timeTableViewControllers = Array.init(repeating: UIViewController(), count: 2)
with
self.timeTableViewControllers = [UIViewController(), UIViewController()]

Unable to see controller added to UITabBar programmatically swift

I added 2 tabBar items from storyboard and one UITabBarItem - Menu programmatically. I am successfully able to open the controllers corresponding to tabBarItems which I created using storyboard. However, when I click on "Menu" a blank black screen appears,
#objc public class MainScreenTabsController : UITabBarController {
public override func viewDidLoad() {
super.viewDidLoad()
let tabController = MyViewController()
let tabBarItem = UITabBarItem(title: "Menu", image: UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options"))
tabController.tabBarItem = tabBarItem
var array = self.viewControllers
array?.append(tabController)
self.viewControllers = array
}
public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
return true;
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
I followed couple of tutorials for adding tab bar item but all of them had the code I wrote. Am I missing out something very basic?
EDIT:
Class for Menu Controller
#objc public class MyViewController:UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
Your app is doing exactly what your code is telling it to do. You are creating an instance of MyViewController and adding it to the UITabBarController's array of View Controllers.
Your MyViewController class file simply defines a blank, black view.
I'm guessing you created a ViewController in your Storyboard that you want to use as MyViewController? If so, you need to instantiate that from the storyboard.
When you're editing your storyboard, assign the MyViewController class to the VC you want to use, and also give it a Storyboard ID - such as MyVC. Then, edit your viewDidLoad function to this:
public override func viewDidLoad() {
super.viewDidLoad()
// wrong way
// let tabController = MyViewController()
if let tabController = storyboard?.instantiateViewController(withIdentifier: "MyVC") as? MyViewController {
let tabBarItem = UITabBarItem(title: "Menu", image: UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options"))
tabController.tabBarItem = tabBarItem
var array = self.viewControllers
array?.append(tabController)
self.viewControllers = array
}
}
Since you're creating the ViewController programatically ie. without nib/storyboard, you are responsible for instantiating a UIView object and setting the view property of the view controller. to do that implement the loadView method and assign the view object to view property of the viewController. then you can add custom views to the view object, check the code below.
class MyViewController: UIViewController {
override func loadView() {
// super.loadView() // DO NOT CALL SUPER
//create view
view = UIView()
view.backgroundColor = UIColor.white
//Add a custom view with red color
let customView = UIView()
customView.translatesAutoresizingMaskIntoConstraints = false
customView.backgroundColor = UIColor.red
view.addSubview(customView)
NSLayoutConstraint.activate(
[customView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
customView.topAnchor.constraint(equalTo: view.topAnchor),
customView.bottomAnchor.constraint(equalTo: view.bottomAnchor)]
)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
It would be good to use Storyboard/Nib for this purpose as you can easily configure custom views/controls using the autolayout in interface builder rather than doing it programmatically. :)
Edit:
if your'e using storyboard then instantiate view controller like given in below code
class MainScreenTabsController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let tabController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainViewController") as! MainViewController //if using storyboard
let icon = UITabBarItem(title: "Menu", UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options")))
tabController.tabBarItem = icon
var controllers = self.viewControllers
controllers?.append(tabController)
self.setViewControllers(controllers!, animated: true)
}
}

Pushing Viewcontroller through navigation controller , Showing Black screen

I being searching this issue in stack overflow but couldn't get an exact answer to the issue, i being stuck in it for long time.
Mine issue is i'm trying to push a TestViewController through navigation controller. when i click the button the TestViewController is being load with navigation bar and the UIScreen is in black colour.
Code of TestViewController
import UIKit
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
navigationItem.title = "Test page"
}
}
Code of Button
#IBAction func secondButtonClicked(sender: AnyObject) {
buttonPressedNumber = "Two Clicked"
buttonTextColor = UIColor.magentaColor()
let a = TestViewController()
let b:UIViewController = a as UIViewController
navigationController?.pushViewController(b, animated: false)
}
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let resultViewController = storyBoard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
self.navigationController?.pushViewController(resultViewController, animated: true)
You have not set the background color of your view.
The default color of the of UIWindow is Black. So if you have not set any other background colors in the stack they will all be transparent.
Not setting an appropriate background color for your UIViewController's view will also cause weird visuals during a transition.
let nextVC = self.storyboard?.instantiateViewControllerWithIdentifier("storyboardID") as! viewController
self.navigationController?.pushViewController(nextVC, animated: true)
not sure the background colour is set to white these days.
therefore, if view controller is created programmatically
in viewDidLoad/viewWillAppear for a white background colour
view.backgroundColor = UIColor.whiteColor()

Resources