I'm creating a custom tab bar based on this tutorial: https://github.com/codepath/ios_guides/wiki/Creating-a-Custom-Tab-Bar
Everything is fine but when I want to segue from a certain view controller to its "details view", the details view covers the bottom bar with menu. This behaviour is logical as I'm pushing a new view controller but how would I have to do it in order to keep the bottom bar always visible and functional?
I'm using segue for this because I need to pass some data. I need custom bar because the functionality and the look would be very difficult to implement using the Apple's one.
Any tips or suggestions?
Thanks
EDIT:
Here all "tab" are working well but when you tap on a row I navigate to "details" view
In the details view, bottom bar is not visible.
have you tried setting hidesBottomBarWhenPushed = false for the details view?
Solved it. Instead of calling
self.performSegueWithIdentifier("detailsViewSegue", sender: self)
in
tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
I add DetailsView view to the hierarchy like so:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let detailsView = storyboard.instantiateViewControllerWithIdentifier("contactDetailsView")
addChildViewController(detailsView)
detailsView.view.frame = CGRectMake(self.view.frame.width, 0, self.view.frame.width, self.view.superview!.frame.height)
self.view.addSubview(detailsView.view)
UIView.animateWithDuration(0.25) {
detailsView.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.superview!.frame.height)
}
detailsView.didMoveToParentViewController(self)
then when user taps on the back arrow I do:
#IBAction func goBackButtonAction(sender: UIButton) {
self.willMoveToParentViewController(self)
UIView.animateWithDuration(0.25, animations: {
self.view.frame = CGRectMake(self.view.frame.width, 0, self.view.frame.width, self.view.frame.height)
}) { (completed) in
if completed {
self.view.removeFromSuperview()
self.removeFromParentViewController()
}
}
}
Related
In working on this app with a TabBar at the bottom, NavBar at the top with a Segmented Control:
I have an issue where the View A (Segment One) with a UITableView, upon selecting a cell and displaying a new view with more details, when I click back, the Segmented control at the top will disappear and the TableView from View A will be pushed up.
This doesn't always happen - sometimes after many tries or sometimes just one. I haven't found any correlation to what's causing it.
I have found that if I select View B from the segmented Control, then back to View A, then click on one of the table cells to get to the details screen and then click back, 100% of the time the Top Nav Bar disappears with the segmented control.
TabBarItemOneViewController
let segmentOneVC: SegmentOneViewController
let segmentTwoVC: SegmentTwoViewController
var currentViewController: UIViewController
let viewControllerYLoc = 60 // statusBarHeight + topBarHeight
let viewWidth = Helper.getViewWidth()
let tabBarHeight = 40
func pressedSegItem(segControl: UISegmentedControl){
let viewControllerHeight = Int(self.view.frame.height)
let viewFrame = CGRect(x: 0, y: viewControllerYLoc, width: Int(viewWidth), height: viewControllerHeight)
let selectedIndex = segControl.selectedSegmentIndex
previouslySelectedMyLoadsIndex = selectedIndex
self.currentViewController.removeFromParentViewController()
if(selectedIndex == 0){
currentViewController = segmentOneVC
}
else if(selectedIndex == 1){
currentViewController = segmentTwoVC
}
self.view.addSubview(self.currentViewController.view)
self.currentViewController.didMove(toParentViewController: self)
}
public init() {
segmentOneVC = SegmentOneViewController(nibName: nil, bundle: nil)
segmentTwoVC = SegmentTwoViewController(nibName: nil, bundle: nil)
if(previouslySelectedIndex == 0){
currentViewController = segmentOneVC
}
else{
currentViewController = segmentTwoVC
}
super.init(nibName: nil, bundle: nil)
self.calculateItems()
self.addSegmentedControl()
let viewControllerHeight = (Int(self.view.frame.height) - viewControllerYLoc) - tabBarHeight
let viewFrame = CGRect(x: 0, y: viewControllerYLoc, width: Int(viewWidth), height: viewControllerHeight)
self.currentViewController.view.frame = viewFrame
self.addChildViewController(segmentOneVC)
self.addChildViewController(segmentTwoVC)
self.view.addSubview(self.currentViewController.view)
self.currentViewController.didMove(toParentViewController: self)
}
SegmentOneViewController (note: SegmentTwoViewController is identical)
let cellReuseIdentifier = "ItemDetailTableViewCell"
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row
let dataItem = self.dataArray[row]
let itemDetailVC = ItemDetailViewController()
itemDetailVC.dataItem = dataItem
self.present(itemDetailVC, animated: true, completion: nil)
}
func addTableView(){
self.tableView = UITableView()
tableView.register(ItemDetailTableViewCell.self, forCellReuseIdentifier: self.cellReuseIdentifier)
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
tableView.frame = CGRect(x: 0, y: 0, width: Int(viewWidth), height: (Int(self.view.frame.height) - bottomOfTopNavBar) - heightOfTabBar)
self.view.addSubview(tableView)
}
override func viewDidAppear(_ animated: Bool){
super.viewDidAppear(animated)
loadData()
tableView.dataSource = self
tableView.delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
addTableView()
}
ItemDetailViewController
// Connected to a back button in a top Navigation Bar
func goBack(){
self.dismiss(animated: false, completion: nil)
}
Nice graphics BTW... that illustration make it much easier to understand your problem.
Also BTW, I'm an Obj-C person, still learning the nuances of Swift, so please let me know if my syntax or otherwise is incorrect. I'm also relatively inexperienced in using container VC's.
I've written my response in two parts.
The First Part is my attempt to solve your problem.
The Second Part is my suggestion for an alternative for you to consider.
First Part
This is my understanding of the order/sequence of execution in your code...
Parent View with Segmented Control
public init () : on instantiation of the parent view controller, two child VCs (segmentOneVC and segmentTwoVC) are instantiated and depending on previous selection are assigned as currentViewController. Then you add a segmented control to the TabBarItemOneViewController.
User taps a segmented control.
Depending on user input, either the SegmentOneViewController or SegmentTwoViewController view is added as a subview to the TabBarItemOneViewController.view. (Note that this is also done when the VC is initialised.)
Child View
override func viewDidLoad() : once the view did load, you call the function addTableView.
func addTableView() : in this custom function you instantiate your table view and place it within the SegmentOneViewController, which is itself I assume a UIViewController.
override func viewDidAppear(_ animated: Bool) : you call the custom function loadData and set your table view data source and delegate.
Back Button
User taps the back button.
Child VC is dismissed and the TabBarItemOneViewController becomes the active view on screen.
Let's look at what does not happen in the view controller lifecycle when the back button is pressed... Item 1 in the list.
This may explain the inconsistency.
Try this... run the app, tap the tab control to take you to TabBarItemOneViewController. Don't tap the segmented control. Tap a line in your table view. Tap the back button in your child VC. I'd take a guess your segmented control is still there.
Now try this... run the app, tap the tab control to take you to TabBarItemOneViewController. Tap the segmented control. Tap a line in your table view. Tap the back button in your child VC. I'd take a guess your segmented control is no longer there.
Why? Because the custom function pressedSegItem that I assume has a target action assigned to the segmented control, will overwrite your init, which is where you add the segmented control into the tab bar view controller.
So by way of example, try placing the code to instantiate your segmented control instead in an override function of viewWillAppear of the TabBarItemOneViewController VC.
So a couple of concepts to think about...
lazy loading to save memory allocation - only instantiate the objects you need when the user specifically requests that function in the app;
order of execution of each function in the UIViewController lifecycle; and
which functions are executed once and which are executed each time your view becomes first responder / the active view.
Some reading recomendations:
This SO question titled :Looking to understand the iOS UIViewController lifecycle" presents a lot of good information, but understand that some of the information is incorrect due to deprecation of viewDidUnload from iOS 6.
Which is why you should always go to the Apple documentation for UIViewController to refer to the latest API reference.
Second Part
By providing this alternative, I'm not suggesting that your approach is incorrect, however I am suggesting an alternative for you to consider.
I've always used tab bar controllers to change views and segmented controls to filter data sets or change the appearance of the current view.
Think about using a UISegmentedControl to manage or adjust the data set within only one table view. This will alleviate the need for multiple view controllers and the juggling act of managing these.
For example, when writing your data source and delegate methods / functions for your tabel view, you can include the following code to ensure the table view loads and responds accordinagly:
let selectedIndex = segControl.selectedSegmentIndex
if(selectedIndex == 0) {
rowHeight = 20 'for example
} else {
rowHeight = 30 'for example
}
Then you'd need to relaod your table view to effect the changes.
I'm developing an iOS app with swift in which I have a TabBarController with 5 tab bar items. All of them points to a navigation controller and then to a view controller. One of them I want to show a view controller without the tab bar and when the user press cancel it should go back to the previous tab bar item/view that was selected (previously - sorry for the redundancy). They are all linked/referenced by a "Relationship "view controllers" to "name of the view", but I don't have any specific segue or whatsoever.
This is the code for that specific "button" which I call in the viewDidLoad function:
func setupMiddleButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 64, height: 64))
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height
menuButtonFrame.origin.x = self.view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.white
menuButton.layer.cornerRadius = menuButtonFrame.height/2
menuButton.setImage(UIImage(named: "klein_fototoestel_2"), for: UIControlState.normal) // 450 x 450px
menuButton.contentMode = .scaleAspectFit
menuButton.addTarget(self, action: #selector(menuButtonAction), for: UIControlEvents.touchUpInside)
self.view.addSubview(menuButton)
self.view.layoutIfNeeded()
}
func menuButtonAction(sender: UIButton) {
self.selectedIndex = 2
}
I tried to perform the behaviour I want by delegating the tab bar controller with the following code but this function is never called when the central button is selected (though the correct view shows up..!):
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
print("the selected index is : \(tabBar.items?.index(of: item))")
}
What I really want to know is what is the correct way to implement that behaviour I want. Remembering that all views have a navigationController before. I read a lot of people suggesting using UserDefaults to store the index of the previous controller but to be honest I really don't think that's appropriate.
Any help is appreciated.
Thanks in advance.
I think you were on the right track - just need to get the correct connections.
In this diagram (it's kinda big - easier to read if you open it in a new tab), you see a "standard" UITabBar structure. The key is putting a default "do-nothing" view controller as the 3rd tab, and then adding a "special" view controller which will be loaded via code:
Then, your "action" function will look something like this:
func menuButtonAction(sender: UIButton) {
// Don't navigate to the tab index
//self.selectedIndex = 2
// instead, load and present the view you really want to see
if let vc = storyboard?.instantiateViewController(withIdentifier: "SpecialVC") as? SpecialViewController {
vc.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.present(vc, animated: true, completion: nil)
}
}
You can see and download a working example here: https://github.com/DonMag/SWTabsWithSpecialTab
I need to show a custom tableview in 1 side and the detail in other in ipad like with splitview controller. And so, the detail is visible after a selection of a button on the cell.
My problem comes from the displaying of the detail controller. I sent data from the button in tableview to container controller via a delegate method
let containerController = self.storyboard?.instantiateViewController(withIdentifier: "ContainerController") as! ContainerViewController
containerController.reactionViewControllerResponse(selectedMechanism: selectedMechanism)
self.navigationController?.pushViewController(containerController, animated: true)
and in the container, I create the detail controller via
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
if (selectedMechanism != "" && self.mechanismViewController != nil){
self.mechanismViewController = self.storyboard?.instantiateViewController(withIdentifier: "MechanismController") as! MechanismViewController?
self.mechanismViewController?.selectedMechanism = selectedMechanism
self.addChildViewController(mechanismViewController!)
mechanismViewController?.view.frame = CGRect(x: self.containerView.frame.size.width/2, y: 0, width: self.containerView.frame.size.width/2, height: self.containerView.frame.size.height)
self.containerView.addSubview((mechanismViewController?.view)!)
mechanismViewController?.didMove(toParentViewController: self)
}
}
but due to this line in the tableview controller
self.navigationController?.pushViewController(containerController, animated: true)
the detail controller is not shown in the same container controller, but in another one. I tried several things, but either nothing appears or in a different container.
Please help me!!!
P.S.: I don't use splitview controller since I don't need it as inital view controller, and I've already tried and had problems to display only the master on the whole screen, customize the tableview cell....
Problem solved, the delegate was badly instantiated.
Thanks to me :)
I'm building an app in which a part of the app should list all the shops in our town and show some details about them. This part works, but it relies on a split view controller, as you can see in these pictures . I also added a video of the problem.
I didn't know how to use the split view controller, really.. So what i did was the following: I set the split view controller as the initial view controller, and connected the navigation controller which should open up first as the detail view controller. The first navigation controller of the table view is set as master view controller.
The problem now is that when i start the app, i arrive at the homepage (which is good, check the video in the drive), but in the upper left corner, you can see that there is a navigation button to the table view. Is there a way to delete that button, and make my homepage navigation controller the initial view controller again?
I guess I'd have to link the split view controller differently, set the first view controller to initial view controller again, and add a segue to the split view controller, but i don't know what that segue should look like or how I should program it. There's a segue to the first view controller of the table view right now.
In my homepage view controller, this is the code for the segue pushing to the first view controller of my table view right now:
func pushRegisterViewShoppen()
{
self.performSegueWithIdentifier("SegueShoppen", sender: self)
}
let shoppen = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
shoppen.setTitle("Shoppen", forState: .Normal)
shoppen.setTitleColor(UIColor.whiteColor(), forState: .Normal)
shoppen.addTarget(self, action: #selector(ViewController.pushRegisterViewShoppen),
forControlEvents: .TouchUpInside)
let BergStraatFoto = UIImage.init(named: "Bergstraat")
shoppen.setBackgroundImage(BergStraatFoto!, forState: .Normal)
tempView.addSubview(shoppen)
This is the prepareForSegue in the tableViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let winkel: Winkel
if searchController.active && searchController.searchBar.text != "" {
winkel = filteredWinkels[indexPath.row]
} else {
winkel = winkels[indexPath.row]
}
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailWinkel = winkel
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
controller.navigationItem.setHidesBackButton(false, animated: true)
}
}
}
Does anyone know how i could fix this? Thanks in advance!
Try this UISplitViewController's delegate method, the detail view controller gets displayed as there is not much space in the iPhone's portrait view so you need to override that using the below delegate method.
func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool {
//handle it efficiently to decide based on certain conditions.
return true
}
Try this thread which elaborates more on the problem.
I'm using Swift 2 and xcode 7.1.1.
My app is a tabbar + navigationbar based application.
What I'd like to do:
From my first viewcontroller (CollectionVC) I have a collectionview of images(tabbar at bottom and navbar at top). When you select an image, I transition programmatically to a modalviewcontroller (ImageZoomVC) that allows them to zoom on the image (no tabbar or navbar). From the ImageZoomVC I want to transition programmatically to the UserProfileVC. The UserProfileVC should have the tabbar and navbar like the first view. The navbars back button should also take us back to the ImageZoomVC.
This workflow is similar to facebook. When you select an image, you go to a black screen with just that image and some more info. You can click on anyone tagged in the image and it will push their profile onscreen with the tabbar and navbar back in place.
The Problem:
Once in the ImageZoomVC, I can't transition to the UserProfileVC without losing the tabbar and navbar. I've been able to add a navbar programmatically but there is no back button, and setting a back button hasn't worked. Also, there is no tabbar.
Code:
From my CollectionVC, I transition to the ImageZoomVC from my collectioncell like this:
#IBAction func PhotoButtonPressed(sender: UIButton) {
let imageZoom = ImageZoomVC()
imageZoom.showFrom(self.parentView!)
}
Here is the showFrom function that presents the ImageZoomVC modally. This code is inside the ImageZoomVC:
public func showFrom(viewController: UIViewController) {
//Some stuff here, edited for length
viewController.presentViewController(self, animated: false) {
UIView.animateWithDuration(ImageZoomVC.TransitionAnimationDuration,
delay: 0,
options: [.BeginFromCurrentState, .CurveEaseInOut],
animations: {
[weak self] in
self?.collectionView.alpha = 1
self?.collectionView.transform = CGAffineTransformIdentity
},
completion: {
[weak self] finished in
self?.view.userInteractionEnabled = finished
}
)
}
}
This is the code I'm attempting to use to transition to the UserProfileVC with navbar and tabbar. This doesn't work. It will present the VC, but there is no tabbar or back button.
func userSelected (sender: UIButton) {
let vc = self.presentingViewController?.storyboard?.instantiateViewControllerWithIdentifier("User Profile") as! UserProfileVC
let newNav = UINavigationController(rootViewController: vc)
newNav.navigationBar.barTintColor = UIColor().specialPurple()
newNav.navigationBar.translucent = false
self.presentViewController(newNav, animated: true, completion: nil)
}
A couple notes: The ImageZoomVC doesn't exist in storyboard. It is called and instantiated programmatically.
The ImageZoomVC is based off Agrume from Github found here:
https://github.com/JanGorman/Agrume
I'd like to push the UserProfileVC like facebook does. This current code presents it from the bottom. The code pushViewController is not recognized here in xCode.
Let me know what you think. All thoughts are welcome. Thanks.