I am trying to create a navigation for my iPhone "Tabbed App" which would consist of (obviously) UITabBarController and SWRevealViewController for revealing side-menus.
All the views in my application must have both UITabBarController and UINavigationBar displayed, however, links which appear in left-side menu (handled by SWRevealViewController) must not appear in UITabBarController.
My left-side menu links are handled in this way:
import UIKit
class MenuTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.clearsSelectionOnViewWillAppear = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedIndex = (indexPath as NSIndexPath).row + 1 // hardcoded for time being
let tabBarController = revealViewController().frontViewController as! UITabBarController
let navController = tabBarController.viewControllers![selectedIndex] as! UINavigationController
navController.popToRootViewController(animated: true)
tabBarController.selectedIndex = selectedIndex
revealViewController().pushFrontViewController(tabBarController, animated: false)
}
}
Now, I tried to remove a link for one of the views which I don't want to show in my UITabBarController as follows:
import UIKit
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let index = 2 // hardcoded for time being
viewControllers?.remove(at: index)
}
}
but if I click associated link in left-side menu now, I get an NSRangeException index 2 beyond bounds [0 .. 1] error (of course, because I removed the particular tabBarItem from UITabBarController).
My question is: how can I "hide" the item from UITabBarController but still being able to reference it (and open it) from my side menu?
UPDATE
My storyboard at the moment looks like this:
It's probably not a good idea to use a "menu" to manipulate the Tabs - that's what Apple has designed the More... and Edit... features for.
Depending on your overall design / navigation / user experience flow, two reasonable options would be:
Instead of replacing the current selected Tab, .present a modal view controller, with a "Cancel" or "Save" or "Done" button to .dismiss it (whatever would be appropriate).
Since you state each Tab's ViewController is a NavigationController, you could .push the menu-selected view controller onto the current stack. Then your interface could use the standard "< Back" button navigation.
Good Luck :)
Related
I have a tab bar controller with four tabs. I want to show the first item at the beginning. With a button click from the first item (view), when it is clicked, I want to show the second tab. How can I do that?
I created a custom tabbarController class and tried to give tabbarindex like below. I checked at the beginning without a button click but it didn't work. It always loads the first tab bar item.
class HTabViewController: UITabBarController, UITabBarControllerDelegate {
var controllerArray : [UIViewController] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.selectedIndex = 2
self.tabBar.tintColor = UIColor.red
// defineViewController()
}
}
Note : can we show specific tab item with a button click?
Since self is the UITabBarController, you need to set the selectedIndex on self, not self.tabBarController.
override func viewDidLoad() {
super.viewDidLoad()
selectedIndex = 2
tabBar.tintColor = UIColor.red
}
job done, add self.selectedIndex = needed_index at viewDidLoad() method
Short story:
Going from UIPageViewController (that presents proper DetailViewController) to UITableViewController, then changing (on didSelectRowAtIndexPath) the DetailViewController of the UIPageViewController and navigationController?.popViewControllerAnimated(true), displays proper UIPageViewController with changed DetailViewController but the whole view is moved down by the height of the navigationBar. Clicking on the screen sends it under the bar (where it should be).
Long story:
I embedded UIPageViewController into UINavigationController. Navigation bar translucent
I created a DetailViewController in my storyboard with a scrollView inside it.
I created custom function in the UIPageViewController to return desired UIViewController
func viewDetailViewController(index: Int) -> DetailViewController? {
if let storyboard = storyboard, page = storyboard.instantiateViewControllerWithIdentifier("DetailViewController") as? DetailViewController {
// Setting the page variables that it stores
return page
}
return nil
}
I set up a code (also in UIPageViewController):
override func viewWillAppear(animated: Bool) {
if let viewController = viewDetailViewController(currentIndex) {
setViewControllers(
[viewController],
direction: .Forward,
animated: false,
completion: nil)
}
}
Then I setup an bar button item to and in the storyboard I connected it to so it perform segue to UITableViewController. I want to pop a viewContorller on cell click so:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
self.pageViewController.currentIndex = indexPath.row
navigationController?.popViewControllerAnimated(true)
}
When the view is popped the new view of the UIPageViewController is replaced, the pages work well and everything is great except one thing. The whole view is moved down by the height of the navigationbar. What's even more strange is that (just) tapping on the screen sets the scrollView under the navigationbar (where it should be)
I have also noticed that not only the scrollView is moved down, but what is actually moved down is UIView that contains an UIView that contains the scrollView.
What might be causing this?
My App has a TabBarViewController containing 4 tabs. One of the tabs is Settings which I want to move to a separate storyboard. If I am only consider iOS 9 and above as my deployment target, then I can just refactor the SettingsTab using Storyboard Reference. However I want to target iOS 8 as well. Since Storyboard Reference doesn't support Relationship Segue, I can't rely on it in this case.
So in the main storyboard which contains the TabBarViewController, I keep a dummy SettingsTabViewController as an empty placeholder. And in the function "viewWillAppear" in its class file, I push the view to the real SettingsTabViewController in the Settings.storyboard. This works fine. But the problem is if I keep tabbing the Settings tab, the empty placeholder view controller will show up for a short time and then goes back to the real Settings view.
I tried to implement this delegate to lock the Settings tab:
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
return viewController != tabBarController.selectedViewController
}
However, the other three tabs were locked too after I implemented this delegate.
Is it possible to just lock the Settings tab without locking other three tabs? And in which view controller exactly should I implement this delegate?
Yes, it's possible. You need to check the index;
with the following code not only you can prevent locking other tabs, but also you still have tap on tab goto root view controller feature.
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
let tappedTabIndex = viewControllers?.indexOf(viewController)
let settingsTabIndex = 3 //change the index
if tappedTabIndex == settingsTabIndex && selectedIndex == settingsTabIndex {
guard let navVC = viewController as? UINavigationController else { return true }
guard navVC.viewControllers.count > 1 else { return true }
let firstRealVC = navVC.viewControllers[1]
navVC.popToViewController(firstRealVC, animated: true)
return false
}
return true
}
.
This answers your question, but still you would have the settingsVC showing up for a moment. To avoid this you simply need to turn off the animation while you're pushing it. so you need to override viewWillAppear in the following way.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let theVC = storyboard?.instantiateViewControllerWithIdentifier("theVC") {
navigationController?.pushViewController(theVC, animated: false)
}
}
after adding above code you still would see a back button in your real first viewController. You can hide it:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesBackButton = true
}
In the struggle of developing a Tabbed IOS App with Swift 1.2 and Xcode 6.3 based on MVC, I'm using the visual Storyboard elements instead of to do it programatically because I'm not an experienced developer. In the image attached below you can see the Architecture in the StoryBoard of the App:
The App consists in:
One TabBarController with four TabBar Items.
Each Item has its own ViewController in Storyboard.
All of them are linked with the relationship seque(ViewControllers) in StoryBoard.
Each ViewController in the StoryBoard has its own Class.
The last Item has an embedded NavigationController because I'm using a PageMenu project https://github.com/uacaps/PageMenu to include a paging menu controller with a two child ViewControllers
The Issues I'm having until this point are:
The two child ViewControllers are not linked with the Last TabBar Item in the StoryBoard,as you can see in the figure above, only are instantiated in the parent ViewController Class(PageMenuViewController1), normally this PageMenu works but sometimes the last TabBar Item dissapears, I'm very confused with this issue.
The override func viewWillAppear into the default child ViewController is called twice at the first time, I've include a println("ClubsController viewWillAppear").
The code of the ViewControllers is
import UIKit
class ClubsViewController: UIViewController, UITableViewDataSource{
#IBOutlet var tableview:UITableView!
let apiClient = ApiClient()
var clubs: [Club]!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
println("ClubsController viewWillAppear")
apiClient.clubsService.getList() { clubs, error in
if clubs != nil {
self.clubs = clubs
self.tableview?.reloadData()
}
else {
println("error: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.clubs?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("clubObjectCell") as! ClubTableViewCell
cell.clubObject = self.clubs?[indexPath.row]
return cell
}
}
The code of the PageMenuViewController is
import UIKit
class PageMenuViewController1: UIViewController {
var pageMenu : CAPSPageMenu?
override func viewDidAppear(animated: Bool) {
println("PageMenuViewController1 viewWillAppear")
super.viewDidAppear(animated)
// Array to keep track of controllers in page menu
var controllerArray : [UIViewController] = []
// Create variables for all view controllers you want to put in the
// page menu, initialize them, and add each to the controller array.
// (Can be any UIViewController subclass)
// Make sure the title property of all view controllers is set
// Example:
var controller1 = storyboard!.instantiateViewControllerWithIdentifier("ClubsViewController")! as! ClubsViewController
controller1.title = "CLUBS"
controllerArray.append(controller1)
var controller2 = storyboard!.instantiateViewControllerWithIdentifier("PartiesViewController")! as! PartiesViewController
controller2.title = "PARTIES"
controllerArray.append(controller2)
// Customize page menu to your liking (optional) or use default settings by sending nil for 'options' in the init
// Example:
var parameters: [CAPSPageMenuOption] = [
.MenuItemSeparatorWidth(4.3),
.UseMenuLikeSegmentedControl(true),
.MenuItemSeparatorPercentageHeight(0.1)
]
// Initialize page menu with controller array, frame, and optional parameters
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, 0.0, self.view.frame.width, self.view.frame.height), pageMenuOptions: parameters)
// Lastly add page menu as subview of base view controller view
// or use pageMenu controller in you view hierachy as desired
self.view.addSubview(pageMenu!.view)
}
}
Appreciate help to accomplish the best practices until this point.
I've not familiar with the CAPSPageMenu but there is nothing wrong with having scenes in a storyboard that aren't connected with a segue - this is just a convenience to help with transitions, and instantiating them with instantiateViewControllerWithIdentifier is totally legitimate.
Something that does stand out looking at your storyboard is the way your table view controller with the navigation view controller is wired up.
The navigation viewcontroller should have the relationship with the tab bar controller - not the table viewcontroller.
Here's a screenshot of how the connection should look. Possibly this is why you're sometimes loosing a tab.
My Situation:
There are a View_A (UICollectionViewController&UICollectionViewCell) and View_B (UIViewController). I want to switch to the View_B when I touched one of cell in the View_A with Segue in the StoryBoard.
In the StoryBoard, I connected View_A and View_B with the Push Segue which identifier is SegueToView_B.
And the function of switchViews just worked fine.
My Problem:
With the Push Segue, I do not need to add a BackButton (NavigationItem) to turn back to the View_A, because there is a 'NavigationItem' be crated automatically by system. And I tried other type segues, like Modal, Popover, and the NavigationItem was not created automatically. I want to ask why?
I want to set the specific color, not the default blue, for that NavigationItem which be created by system automatically, but I failed to find it. After that I just set the color in the prepareForSegue(), but it did not work. Please tell how to set the specific color for it?
My Code:
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.collectionView?.setPresenting(true, animated: true, completion: nil)
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
self.selectedCard = delegate.otherCards[indexPath.row]
self.performSegueWithIdentifier("SegueToView_B", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "SegueToView_B" {
let myOtherCardViewController = segue.destinationViewController as? View_BViewController
myOtherCardViewController!.otherCard = self.selectedCard
myOtherCardViewController!.navigationItem.backBarButtonItem?.tintColor = UIColor.whiteColor() // Failed to work!!!
myOtherCardViewController?.navigationItem.leftBarButtonItem?.tintColor = UIColor.whiteColor() // Failed to work, too!!!
}
}
}
Thanks for your help.
Ethan Joe
To set the tintColor for that navigation bar:
myOtherCardViewController.navigationBar.tintColor = .whiteColor()
Why there is no Navigationbar when you use the Modal or PopOver? Because thats how Modal and Popover work! You have to create another Navigation controller for the view you are connecting with the Modal segue, like this:
Another technique I am using is, to create a single NavigationController class, and set all the desired properties (color, font etc.) and then link all the NavigationControllers in the Storyboard to that NavigationController class.
With that you wont have to reconfigure every NavigationController.
Your Solution
You can set back button hidden in View_B controller in viewDidLoad method like this.
class View_BViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true;
// Do any additional setup after loading the view.
}
}
To set tint color, you have to create subclass of UINavigationController, and assign that class to your UINavigationController in UIStoryboard
You subclass will look like this, to set tint color,
class navigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
//self.navigationBar.barStyle = UIBarStyle.Default
self.navigationBar.tintColor = UIColor.redColor()
// Do any additional setup after loading the view.
}
//Other stuff
}
May this help you!!