Split view controller segue and connections - ios

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.

Related

add navigation bar but there is no back button

I created a table view and from there let say a user pressed a cell it will go to ListTavleView but the only problem right now is that whenever a user is in ListTableView there is not back button even thought i already embed a navigation controller
and i want the fist view navigation bar is small title second view navigation bar is large title
enter image description here
Below is my code
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showList" {
if let indexPath = tableView.indexPathForSelectedRow {
let items = dataManager.items[indexPath.row]
let controller = (segue.destination as! UINavigationController).topViewController as! ListTableViewController
controller.item = items
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
Below is my storybord setup
Navigation bar with no back button
From the image it seems that view controller is added as a child view controller in current view controller.
There is not need to embedded navigation controller when a cell is pressed becoz there is already a navigation controller at start point so no need to create a new one.(If you present a view controller then you may need to embed navigation controller.)
So the solution is...
Delete the navigation controller.
Connect directly to the destination view controller without navigation controller as there is already.
it is better if you use pushViewController, just get a reference of the other view controller, it will always a back button since you are pushing threw navigation Controller here is a simple example:
let story = UIStoryboard(name: "Main", bundle: nil)
let vc = story.instantiateViewController(withIdentifier: "ExampleViewController") as! ExampleViewController
self.navigationController?.pushViewController(vc, animated: true)
as for the back button, the issue is with your hierarchy.
are you changing the left item of navigation bar in another view controller that might affect navigation bar in your destination view controller.
You are pushing new NavigationController(say Nav.B) to the existing one(Nav.A).
Each navigation controller keeps different navigation stack. The back button is visible when you add viewcontroller to Navigation controller. Read more about UINavigationController.
For your current scenario, you could delete the second navigation controller(i think it not essential) & connect direct segue to ListTableViewController
So this
let controller = (segue.destination as! UINavigationController).topViewController as! ListTableViewController
becomes
let controller = segue.destination as! ListTableViewController
When you need large titles(available 11+), you can add this line in viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
And if it needed only for this Viewcontroller, add in viewWillDisappear() or viewDidDisappear()
navigationController?.navigationBar.prefersLargeTitles = false
If you wanted to have navigation bar back button on next view, then just push the target view on navigation, it will show default navigation back button. No, need to any extra work.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showList" {
if let indexPath = tableView.indexPathForSelectedRow {
let items = dataManager.items[indexPath.row]
guard let controller = segue.destination as? ListTableViewController else {
return
}
controller.item = items
self.navigationController?.pushViewController(controller, animated: true)
}
}}
And if you are pushing the viewcontroller with segue, then no need to add below line self.navigationController?.pushViewController(controller, animated: true)

Swift Dismiss navigation controller after prepare for segue

I have the following schema:
The controller in the upper right corner is SelectAlbumVC
The controller in the lower left corner is AddPhotoVC
In SelectAlbumVC i have this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? AddPhotoPostVC
else { fatalError("unexpected view controller for segue") }
guard let cell = sender as? AlbumListCells else { fatalError("unexpected cell for segue") }
switch SegueIdentifier(rawValue: segue.identifier!)! {
case .showAllPhotos:
destination.fetchResult = allPhotos
destination.headerTitleBtnString = cell.allPhotoTitle.text!
case .showCollection:
// get the asset collection for the selected row
let indexPath = tableView.indexPath(for: cell)!
let collection: PHCollection
switch Section(rawValue: indexPath.section)! {
case .smartAlbums:
collection = smartAlbums.object(at: indexPath.row)
case .userCollections:
collection = userCollections.object(at: indexPath.row)
default: return // not reached; all photos section already handled by other segue
}
// configure the view controller with the asset collection
guard let assetCollection = collection as? PHAssetCollection
else { fatalError("expected asset collection") }
destination.fetchResult = PHAsset.fetchAssets(in: assetCollection, options: nil)
destination.assetCollection = assetCollection
destination.headerTitleBtnString = cell.collectionTitle.text!
destination.isComingFromSelectAlbum = true
}
}
so basically when i click on a cell the segue will be executed and the data passed to AddPhotoVC.
My issue is that when the segue is executed the navigation controller associated with SelectAlbumVC is not dismissed and so when clicking the dismissing button on the AddPhotoVC SelectAlbumVC is presented again (it's the last controller in the stack).
Is there a way to dismiss the navigation controller when the prepare for segue is called?
I've tried to add the the bottom
self.navigationController?.popToRootViewController(animated: false)
but it does not work.
Any help will be really appreciated.
Thank you!
If I understand your code correctly, you're adding another segue back to AddPhotoVC. So if while your app was running and you clicked the "Debug View Hierarchy" (down by the debugger controls in Xcode) you'd see that you now have another AddPhotoVC on top of the original one.
Instead of performing a segue from SelectPhotoVC to AddPhotoVC you may want to consider performing an Unwind Segue instead. This way you could pass the values you want and all the previous VCs would be dismissed.
You are using (multiple) two UINavigationController. That means you are presenting the second screen instead of pushing it.
Now, you mentioned that popToRootViewController does not work, that's because again the two screens have two different UINavigationController. You should dismiss the second screen rather than popping it, because you presented it.
Know the different between pushing/show and presenting viewControllers.
Push/Show --- Pop.
Present --- Dismiss.
Instead of this self.navigationController?.popToRootViewController(animated: false)
Use this:
self.navigationController?.dismiss(animated: true, completion: {
// completion, do something or make it nil.
})

TabBar to view and when closed come back to previous selected view

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

Divide a container view into 2 controllers in swift

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 :)

Problems with Navigation Controller (back button and prepareForSegue)

I promise that I'm completely new to Xcode and Swift, so I know I am making silly mistakes but I don't know where. This is part of my iOS app storyboard:
where the segue between the first table view and the second navigation controller is called myTaskDetailSegue and its type is Show (e.g. Push). Now I have some problems:
Neither in the first table view controller nor in the second the back button is showed and I don't know why. Many people told me that navigation bar and back button are as default in navigation controllers but they did not appear
In the class of the first table view controller here is the method prepareForSegue()
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "myTaskDetailSegue" ) {
let indexPath = self.tableView.indexPathForSelectedRow()
let task = self.taskCollection[indexPath!.row] as Task
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailsMyTasksViewController
controller.detailItem = task
println("segue mostra task \(task.id)")
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
so you can read that the segue identifier is correct but when a row is tapped nothing happens and the second table view controller is not showed.
I don't really know what I am missing because of my inexperience.
Here is the complete storyboard:
You don't need two UINavigationController's to what you want to achieve. Is important to note that every time you push(with a segue or manually) a new UIViewController it's added to the navigation stack.
According to Apple:
Pushing a view controller displays its view in the navigation interface and updates the navigation controls accordingly. You typically push a view controller in response to user actions in the current view controller—for example, in response to the user tapping a row in a table.
So you can remove the second UINavigationController in your Storyboard and make the segue directly to your DetailsMyTaskViewController and update your prepareForSegue like in the following way:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "myTaskDetailSegue" ) {
let indexPath = self.tableView.indexPathForSelectedRow()
let task = self.taskCollection[indexPath!.row] as Task
let controller = segue.destinationViewController as! DetailsMyTasksViewController
controller.detailItem = task
println("segue mostra task \(task.id)")
}
}
And your back button should appear by default as you said before. Nevertheless I strongly recommend you read the following two guides :
UINavigationViewController
View Controller Programming Guide for iOS
For a better understanding of the navigation stack, etc.
I hope this help you.

Resources