Push New VC from Subview Table View Controller - ios

I have an Arranged Subview (a Table View) within a parent View Controller.
Let's call the parent VC CollectionViewController and the subview (Table VC) ResultsTableViewController.
I've added the table view to my stackview (in CollectionViewController) like this:
let hitsTableController = ResultsTableViewController()
stackView.addArrangedSubview(hitsTableController.tableView)
Then I've implemented didSelectRowAt in the ResultsTableViewController (the subview):
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let hit = hitsSource?.hit(atIndex: indexPath.row)
print("The ID is " + hit?.id)
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ItemDetailViewController") as? ItemDetailViewController
self.navigationController?.pushViewController(vc!, animated: true)
}
}
I'm able to print information about the cell I select (the ID in this case), but I can't figure out how to navigate to the Details View Controller when a cell is pressed.
I've tried this with no luck:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ItemDetailViewController") as? ItemDetailViewController
self.navigationController?.pushViewController(vc, animated: true)
The code runs, but nothing happens. Any help would be appreciated.
SOLUTION
Add this
addChild(hitsTableController)
hitsTableController.didMove(toParent: self)
before this
stackView.addArrangedSubview(hitsTableController.tableView)
As Frankenstein mentioned below, the problem was that ResultsTableViewController had no access to the navigation stack. That's because I had to make it a child of the parent VC, CollectionViewController. Now I'm able to call this without any problems.
self.navigationController?.pushViewController(vc!, animated: true)

try to force unwrap your objects if the app crash then give us the crash details
let vc = self.storyboard!.instantiateViewController(withIdentifier: "ItemDetailViewController") as! ItemDetailViewController
self.navigationController!.pushViewController(vc, animated: true)

Update: With the details that you've added it has become evident that the issue was that the view controller isn't embedded in a UINavigationController. You see that navigationController is an optional and it get allocated only if the controller is embedded in a UINavigationController. Try this:
let hitsTableController = UINavigationController(rootViewController: ResultsTableViewController())
stackView.addArrangedSubview(hitsTableController.tableView)
Seems the storyboard either has no ItemDetailViewController in it or you've forgotten to set the identifier which seems unlikely in your scenario. Hence I conclude that the ItemDetailViewController is created programmatically. So you should be initializing ItemDetailViewController with the empty initializer for presenting it.
let vc = ItemDetailViewController()
self.navigationController?.pushViewController(vc, animated: true)

Related

How to pass data between unrelated ViewControllers

I'm building a simple movie app by fetching some data from an API. My app navigation pattern looks like this: NavigationController as InitialViewController > TabBarController. TabBarController includes two ViewControllers: HomeViewController and FavouriteMoviesViewController. I have a button inside HomeViewController which pushes the SeachViewController page using navigation. I also have DetailsViewController. Whenever I press movie poster whether in HomeViewController or SearchViewController CollectionViewCell, it presents me DetailsPageViewController. I'm using delegation pattern(i set FavouriteMoviesViewController as a delegate of the DetailsViewController when I present DetailsViewController from HomeViewController) to update data inside FavouriteMoviesViewController from DetailsViewController, but the problem is that I can't set delegate when I access DetailsViewController from SearchViewController because these two ViewControllers are not related in any way. I tried to set an observer inside FavouriresViewController, but it won't work until I access that viewController
here's my code when i present DetailsViewController from HomeViewController and pass fetched data
// Cell action
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Instantiate DetailsViewController and set it's delegate to FavouritesViewController to pass data
let targetVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: DetailsViewController.identifier) as! DetailsViewController
let favMoviesScreen = self.tabBarController?.viewControllers?[0] as! FavouritesViewController
targetVC.changeToFavouriteDelegate = favMoviesScreen
switch collectionView {
case self.trendingMoviesCollectionView:
let selectedMovie = trendingMovies[indexPath.row]
targetVC.movie = selectedMovie
targetVC.genreIDs = selectedMovie.genreIDs
self.navigationController?.present(targetVC, animated: true, completion: nil)
case self.upcomingMoviesCollectionView:
let selectedMovie = upcomingMovies[indexPath.row]
targetVC.movie = selectedMovie
targetVC.genreIDs = selectedMovie.genreIDs
self.navigationController?.present(targetVC, animated: true, completion: nil)
case self.topRatedMoviesCollectionView:
let selectedMovie = topRatedMovies[indexPath.row]
targetVC.movie = selectedMovie
targetVC.genreIDs = selectedMovie.genreIDs
self.navigationController?.present(targetVC, animated: true, completion: nil)
default: break
}
}
everything's good here.
here's DetailsViewController where i show movie details, i mark favourite movie here and add/remove it in my FavouriteMoviesViewController, this is delegate method:
// MARK: - Button Actions
#objc private func markAsFavourite() {
guard let favouriteMovie = movie else {
print("invalid movie object")
return
}
// check whether the movie is already added into favourite movies array
if !DetailsViewController.favouriteMovies.contains(where: { favouriteMovie.movieId == $0.movieId }) {
addToFavouritesButton.tintColor = .appRedColor
DetailsViewController.favouriteMovies.append(favouriteMovie)
changeToFavouriteDelegate?.addMovie(favouriteMovie)
} else {
addToFavouritesButton.tintColor = .white
DetailsViewController.favouriteMovies.removeAll(where: { $0.movieId == favouriteMovie.movieId})
changeToFavouriteDelegate?.removeMovie(favouriteMovie)
}
}
now the problem is here, in my SearchViewController,i guess because of the fact that this ViewController doesnt' belong to tabBar
// Cell action
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let targetVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: DetailsViewController.identifier) as! DetailsViewController
let tabBar = (self.navigationController?.viewControllers[0]) as! UITabBarController
let favMoviesController = tabBar.viewControllers![0] as! FavouritesViewController
targetVC.changeToFavouriteDelegate = favMoviesController
let selectedMovie = filteredMovies[indexPath.row]
targetVC.movie = selectedMovie
targetVC.genreIDs = selectedMovie.genreIDs
self.navigationController?.present(targetVC, animated: true, completion: nil)
}
this just doesn't work...
targetVC.changeToFavouriteDelegate = favMoviesController
Is there anything I can do to solve this problem?
This might not answer your question completely however my intention is to point you in the right direction.
The issue could be one of several and it will be hard to debug without being able to run your code. Here are some possibilities:
Your delegate could be set correctly but because the Favorites View is not on the screen, the UI updates are not executed and you do not do any reloading / refreshing when you go back there
Maybe you are not accessing the desired instance of the Favorites ViewController
One reason notifications would have not worked, maybe you aren't listening to it
etc
It's hard to say easily.
In my opinion, the implementation to achieve what you want is quite complex.
I would recommend to simplify in the following ways:
Simplest solution - Maintain a struct or a class of favorite movies which should be passed around between the classes
One step further would be to look at MVVM as one of the commenters posted and using that with observers (Notifications) to update UI. I recommend checking this stanford lecture which I liked
Might be an overkill at this stage but can read up on CoreData to see if it suits your needs

How to present a controller in a specific area of the screen in iOS

I have two controller in the screen as the pic shows.
now I wanna present the ThirdViewController in the red area when the tableView is selected, I try to use present() but it shows in the whole screen. how Can I fix that?
Try this
Download 3 files from Folder Presentation
Add this to the VC from which you will open Second VC
lazy var slideInTransitioningDelegate = SlideInPresentationManager()
When going to another VC;
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Ident") as? YourViewController
vc?.delegate = self
let navigationController = UINavigationController(rootViewController: vc!)
slideInTransitioningDelegate.direction = .bottom // or .left, .right
slideInTransitioningDelegate.disableCompactHeight = true
navigationController.transitioningDelegate = slideInTransitioningDelegate
navigationController.modalPresentationStyle = .custom
self.present(navigationController, animated: true, completion: nil)
Tutorial - https://www.raywenderlich.com/915-uipresentationcontroller-tutorial-getting-started
Project - https://koenig-media.raywenderlich.com/uploads/2016/08/Medal_Count_Completed.zip
You can do this by first adding your SecondViewController into you MainViewController as child controller and then add SecondViewController's view as subview in UITableViewCell.
var secondViewController : SecondViewController!
override func viewDidLoad() {
super.viewDidLoad()
secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.addChildViewController(setupProfileViewController)
}
Now somewhere in your code when you want to show that SecondViewController view controller like on didSelectRowAt indexPath method of UITableViewCell or any other button action, add following line of code:
let cell = yourTableView.cellForRow(at: selectedIndexPath)
cell.contentView.addSubview(secondViewController.view)
There is no image attached, so I am not sure what exactly you want; but to show a view controller in area of another view controller, you could use container view, this is a useful tutorial about container view
and to load by some action, you could present your container view when your action done.

Dismiss the view controller which have navigation controller and pass the value back to main view controller

I have an mainViewcontroller in that one button called get value.Then i am calling dataviewcontroller to select any item in collection view cell.Once user select any cell.That particular dataviewcontroller will dismiss and while dismiss it will have the user selected item name and it will display in mainViewcontroller .Now the view controller is not dismissing.
Here the code :
In my mainViewcontroller:
var SelectedName: String? = ""
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
currentTF.text = SelectedName
}
Now dataviewcontroller :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView.cellForItem(at: indexPath) != nil {
selectedNamesss = allitems[indexPath.row] as String
}
calldismiss()
print(selectedNamesss)
}
func calldismiss() {
if let presenter = presentingViewController as? mainViewcontroller {
presenter.SelectedName = selectedNamesss
}
dismiss(animated: true, completion: nil)
}
Now how can i solve this.My viewcontroller is not dismising and values also not showing.
Thanks in advance ~
My Suggestion is to use "UnwindSegue" technique, which is simply works like "PerformSegue", So you can send back values very easily using prepare for segue and will be back to main controller. Here is tutorial for unwind segue. Or you can find more from google.
For The problem where data is not showing, problem is with this condition:
if let presenter = presentingViewController as? mainViewcontroller {
presenter.SelectedName = selectedNamesss
}
this condition will never be true as the presentingViewController would always be a UINavigationController. so you need to change that may be something like follows:
if let presenter = (presentingViewController as! UINavigationController).viewControllers.first! as? mainViewcontroller {
presenter.SelectedName = name
}
Here i am using viewControllers.first! in condition as mainViewcontroller was at first index of the viewControllers array. Check the viewControllers array and find the index of mainViewcontroller and make changes accordingly.
I made this changes and the code is working for me. I am getting the selected name on the mainViewcontroller.
Also i would like to mention that this is not the right way to transfer data backwards. You can use delegates, blocks or unwind segue to achieve this.
Though this is working i am attaching a gif to show this working.
Regarding the issue where your controller is not being dismissed, i am presenting the controller in following way:
let dataVC = self.storyboard?.instantiateViewController(withIdentifier: "dataviewcontroller") as! dataviewcontroller
self.present(vc, animated: true, completion: nil)
Try this and see if this helps you :)

SWRevealController setting frontViewController from code

I was debugging my app using Instruments and found out that every time I perform self.revealViewController().pushViewController() memory usage is increasing. I've changed this line to be self.revealViewController().setFront() and now it looks fine, but I am kinda confused about how it works. What do both of these functions do? How should I navigate if I perform this in didSelectRowAt indexPath method like:
case "MenuText":
let targetVC = storyboard!.instantiateViewController(withIdentifier: "AllSelectionsNC") as! UINavigationController
let destinationVC = targetVC.topViewController as! AllSelectionsTableViewController
self.revealViewController().setFront(targetVC, animated: true)
self.revealViewController().setFrontViewPosition(.left, animated: true)
self.setStatusBarStyle(.default)
Am I doing something wrong here? Most of tutorials perform segues using storyboard, but I have to perform them from code

Attempt to present whose view is not in the window hierarchy when a cell is tapped

I'm currently working on an app with a UITableViewController. The idea is that when a cell is tapped on, an info page will pop up showing some information about it. So when tapped on, I attempt to present another ViewController in another class. However, the warning shows up and I'm not sure why.
I've tried looking at questions similar to mine on Stackoverflow and other websites, but they don't make much sense to me. Can anyone explain the problem behind this warning and how to fix it?
Some of my code:
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
print("showing item info")
let cell = tableView.cellForRow(at: indexPath)
currentInfo = (cell?.textLabel?.text)!
currentDes = descriptions[indexPath.row]
currentBool = itemBools[indexPath.row]
currentIndexPath = indexPath
showInfo()
}
func showInfo() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ShowInfoViewController") as! ShowInfoViewController
self.present(vc, animated: true, completion: nil)
}
Warning message is:
Attempt to present <Things_to_Do.ShowInfoViewController: 0x100b2fe20> on <Things_to_Do.TableViewController: 0x100b05eb0> whose view is not in the window hierarchy!
Try to add presentation style
Swift 2
vc.modalPresentationStyle = .FormSheet
vc.modalTransitionStyle = .CoverVertical
You are trying to present the ViewController from a class which is not there in the window hierarchy. You need to check self.present(vc, animated: true, completion: nil) whether the self in this line is a ViewController which is added to the window hierarchy before you execute that line.
I found my problem. The problem was cause I had created a segue on the storyboard, connecting a table cell to the new ViewController. By writing code to present the same ViewController, XCode got confused.

Resources