presentViewController from TableViewCell - ios

I have a TableViewController, TableViewCell and a ViewController. I have a button in the TableViewCell and I want to present ViewController with presentViewController (but ViewController doesn't have a view on storyboard). I tried using:
#IBAction func playVideo(sender: AnyObject) {
let vc = ViewController()
self.presentViewController(vc, animated: true, completion: nil)
}
Error: Value of type TableViewCell has no member presentViewController
Then, I tried
self.window?.rootViewController!.presentViewController(vc, animated: true, completion: nil)
Error: Warning: Attempt to present whose view is not in the window hierarchy!
What am I doing wrong? What should I do in order to presentViewController from TableViewCell? Also how can I pass data to the new presenting VC from TableViewCell?
Update:
protocol TableViewCellDelegate
{
buttonDidClicked(result: Int)
}
class TableViewCell: UITableViewCell {
#IBAction func play(sender: AnyObject) {
if let id = self.item?["id"].int {
self.delegate?.buttonDidClicked(id)
}
}
}
----------------------------------------
// in TableViewController
var delegate: TableViewCellDelegate?
func buttonDidClicked(result: Int) {
let vc = ViewController()
self.presentViewController(vc, animated: true, completion: nil)
}
I receive error: Presenting view controllers on detached view controllers is discouraged
(Please note that I have a chain of NavBar & TabBar behind TableView.)
I also tried
self.parentViewController!.presentViewController(vc, animated: true, completion: nil)
Same Error.
Also tried,
self.view.window?.rootViewController?.presentViewController(vc, animated: true, completion: nil)
Same Error

It seems like you've already got the idea that to present a view controller, you need a view controller. So here's what you'll need to do:
Create a protocol that will notify the cell's controller that the button was pressed.
Create a property in your cell that holds a reference to the delegate that implements your protocol.
Call the protocol method on your delegate inside of the button action.
Implement the protocol method in your view controller.
When configuring your cell, pass the view controller to the cell as the delegate.
Here's some code:
// 1.
protocol PlayVideoCellProtocol {
func playVideoButtonDidSelect()
}
class TableViewCell {
// ...
// 2.
var delegate: PlayVideoCellProtocol!
// 3.
#IBAction func playVideo(sender: AnyObject) {
self.delegate.playVideoButtonDidSelect()
}
// ...
}
class TableViewController: SuperClass, PlayVideoCellProtocol {
// ...
// 4.
func playVideoButtonDidSelect() {
let viewController = ViewController() // Or however you want to create it.
self.presentViewController(viewController, animated: true, completion: nil)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath: NSIndexPath) -> UITableViewCell {
//... Your cell configuration
// 5.
cell.delegate = self
//...
}
//...
}

You should use protocol to pass the action back to tableViewController
1) Create a protocol in your cell class
2) Make the button action call your protocol func
3) Link your cell's protocol in tableViewController by cell.delegate = self
4) Implement the cell's protocol and add your code there
let vc = ViewController()
self.presentViewController(vc, animated: true, completion: nil)

I had the same problem and found this code somewhere on stackoverflow, but I can't remember where so it's not my code but i'll present it here.
This is what I use when I want to display a view controller from anywhere, it gives some notice that keyWindow is disabled but it works fine.
extension UIApplication
{
class func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController?
{
if let nav = base as? UINavigationController
{
let top = topViewController(nav.visibleViewController)
return top
}
if let tab = base as? UITabBarController
{
if let selected = tab.selectedViewController
{
let top = topViewController(selected)
return top
}
}
if let presented = base?.presentedViewController
{
let top = topViewController(presented)
return top
}
return base
}
}
And you can use it anywhere, in my case I used:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "WeekViewController")
UIApplication.topViewController()?.navigationController?.show(vc, sender: nil)
}

So self.presentViewController is a method from the ViewController.
The reason you are getting this error is because the "self" you are referring is the tableViewCell. And tableViewCell doesn't have method of presentViewController.
I think there are some options you can use:
1.add a delegate and protocol in the cell, when you click on the button, the IBAction will call
self.delegate?.didClickButton()
Then in your tableVC, you just need to implement this method and call self.presentViewController
2.use a storyboard and a segue
In the storyboard, drag from your button to the VC you want to go.

Related

Swift: Protocol and Delegates passing data to previous view controller

I am working in Swift and the function categoryPressedFunction is not being called.
Protocol:
protocol categoryPressed: class { //1 create a protocol with function that passes a string
func categoryPressedFunction(category: String)
}
View Controller 2:
//set the delegate
weak var delegate: categoryPressed?
//when cell is selected in tableview, grab the "category" which is a string and then dismiss
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let category = guideCategoryArray[indexPath.row] // gets category
delegate?.categoryPressedFunction(category: category) // sets delegate to know it was pressed
self.presentingViewController?.dismiss(animated: true, completion: nil) //dismisses
}
}
View Controller 1 (Previous)
//set class delegate
...categoryPressed
//add function that sets label to category and looks up items based off category
func categoryPressedFunction(category: String) {
print("categoryPressedFunctionPressed")
resourceArray.removeAll()
resourceLabel.text = category
getItems(item: category, { [self] in
print("got new items for \(category) and refreshed the tableview")
self.resourceTableView.reloadData()
})
}
When returning to ViewController 1, nothing happens. The tableview does not reload, nor does the label change to the category pressed. Am I missing something?
Delegates might be nil. Did you add this line in the ViewDidLoad method?
delegate = self
You might have missed assigning delegate to self while moving from VC1 to VC2.
Hope below code helps you.
//Some Navigation logic
let VC2 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "VC2Identifier") as! VC2;
VC2.delegate = self
self.present(VC2, animated: true, completion: nil)

How can I segue based on which cell was tapped in UITableViewCell?

I have a view controller with a table view embedded inside it, although it is always the same ten cells, I made it dynamic as you cannot make static table view cell inside a UIViewController. My question is, in prepare(for:) how can I segue to a view controller based on which cell was tapped? Each cell should navigatie to a completely different VC, but I think having ten segues is a mess.
Is there a better way?
You can implement the UITableViewDelegate protocol to actually detect the cell being tapped:
extension MyOriginViewController {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// I'm assuming you have only 1 section
switch indexPath.row {
case 0:
// Instantiate the VC
let vc = storyboard?.instantiateViewController(withIdentifier: "MyViewControllerIdentifier")
// Present the view controller, I'm using a UINavigationController for that
navigationController?.push(vc, animated: true)
case 1:
// Another VC and push or present modally
let vc = ...
self.present(vc, animated: true, completion: nil)
default:
// Default case (unconsidered index)
print("Do something else")
}
}
}
So if you have this:
You can do:
let vc = storyboard?.instantiateViewController(withIdentifier: "MyNavController")
self.navigationController?.present(vc, animated: true, completion: nil)

Has no segue with identifier 'searchSegue' through xib file's class

I am using a segue(searchSegue) to connect my search screen (SearchViewController) to the search result page (PhotoStreamViewController).
SearchViewController is a collection view. Every collection view cell has a design in Xib file. (Xib file's class is SearchCategoryRowCell)
Currently, when I triggered searchSegue through SearchViewController it works fine. But whenever I trigger the same segue through SearchCategoryRowCell, I am getting Has no segue with identifier 'searchSegue'
SearchViewController.swift
class SearchViewController: UIViewController,UISearchBarDelegate,UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
...
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchRedirect(keyword: searchBar.text!)
}
func searchRedirect(keyword:String) {
performSegue(withIdentifier: "searchSegue", sender:self)
PhotoStreamViewController.searchString = keyword
navigationController?.setNavigationBarHidden(false, animated: true)
}
...
}
SearchCategoryRowCell.swift
class SearchCategoryRowCell: UICollectionViewCell {
//the method below is triggered by a click action
#objc func searchRedirection(_ sender:UIButton) {
print("we")
print(subCategoryArray[sender.tag].name)
let searchViewcontroller = SearchViewController(nibName: "SearchViewController", bundle: nil)
searchViewcontroller.searchRedirect(keyword: "otherSearchKeyword")
}
...
}
You've explained the reason perfectly. You are saying:
let searchViewcontroller = SearchViewController(nibName: "SearchViewController", bundle: nil)
searchViewcontroller.searchRedirect(keyword: "otherSearchKeyword")
So searchViewcontroller is a completely new view controller instantiated right there in your code. Thus, it has no segues. The view controller with the segues is the one in the storyboard, which is a completely different view controller.
This
let searchViewcontroller = SearchViewController(nibName: "SearchViewController", bundle: nil)
is not the currently presented VC , you have to use delegate or declare self as a property inside the cell class
class SearchCategoryRowCell: UICollectionViewCell {
var sear:SearchViewController?
}
ans set it inside cellForItem in SearchViewController
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as! cellClassName
cell.sear = self
return cell;
}
//
then you can use the presented object
#objc func searchRedirection(_ sender:UIButton) {
print("we")
print(subCategoryArray[sender.tag].name)
sear?.searchRedirect(keyword: "otherSearchKeyword")
}

How to pass textfield data from third viewcontroller to first viewcontroller

I want to send the text which is in textfield in ViewControllerC to another textfield which is in ViewControllerA
By using delegate am trying to pass the text from ViewControllerC to ViewControllerA.
i cant get the logic what to write here delegate?.userDidEnterInformation() in ViewControllerC
could any one help me regarding this
ViewControllerC
protocol DataEnteredInDestinationDelegate: class {
func userDidEnterInformation(info: String)
}
class DestinationSearchViewController: MirroringViewController {
var delegate: DataEnteredInDestinationDelegate?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell: UITableViewCell? = tableView.cellForRow(at: indexPath)
componetsTextField.text = cell?.textLabel?.text
delegate?.userDidEnterInformation()
self.navigationController?.popToRootViewController(animated: true)
}
}
ViewControllerA
class HomeViewController: MirroringViewController, DataEnteredInDestinationDelegate
{
func userDidEnterInformation(info: String){
locationView.destination.text = info
}
}
Firstly you have to always mark delegate as weak e.g.:
weak var delegate: DataEnteredInDestinationDelegate?
and then you need to connect delegate like this:
let vcA = ViewControllerA()
let vcC = ViewControllerC()
vcC.delegate = vcA // Connect delegate
and then your delegate method in ViewControllerC will work after invoking this code:
delegate?.userDidEnterInformation(textString)
Here NotificationCentre can be a good approach instead of delegates. Make Viewcontroller A an observer to receive text information as below.
Write this code in viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(userDidEnterInformation(notification:)), name: NSNotification.Name.init(rawValue: "UserDidEnterInformation"), object: nil)
and write this anywhere in class Viewcontroller A
func userDidEnterInformation(notification: Notification) {
if let textInfo = notification.userInfo?["textInfo"] {
textField.text = textInfo
}
}
In Viewcontroller C post the notification with textInfo by writing below code
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "UserDidEnterInformation"), object: nil, userInfo: ["textInfo": textField.text])
delegate?.userDidEnterInformation(cell!.textLabel!.text)
Also, you should set the delegate of ViewControllerC.
viewControllerC.delegate = viewControllerA
Consider the following example:-
let aVCobjA = UIViewController()
let aVCobjB = UIViewController()
let aVCobjC = UIViewController()
var aNavigation = UINavigationController()
func pushVC() {
aNavigation.pushViewController(aVCobjA, animated: true)
aNavigation.pushViewController(aVCobjB, animated: true)
aNavigation.pushViewController(aVCobjC, animated: true)
//Here you will get array of ViewControllers in stack of Navigationcontroller
print(aNavigation.viewControllers)
//To pass data from Viewcontroller C to ViewController A
self.passData()
}
// To pass data access stack of Navigation Controller as navigation controller provides a property viewControllers which gives you access of all view controllers that are pushed.
func passData() {
let aVCObj3 = aNavigation.viewControllers.last
let aVCObj1 = aNavigation.viewControllers[0]
//Now you have access to both view controller pass whatever data you want to pass
}

Destroy previous viewController when presenting a new one

I have an application with sideBarMenu which has five sections. In my storyboard I've created start MainViewController embedded in NavigationController. Then I've created same five VC's and again embed them in NavigationController. So I have 6 VC's, each of which is wrapped in its own NavigationController.
Each VC implements sideBarDelegate with function that has an index of selected menu.
Here is the code:
// SideBarTableViewController
protocol SideBarTableViewControllerDelegate {
func SideBarControlDidSelectRow(indexPath: NSIndexPath)
}
class SideBarTableViewController: UITableViewController {
// ...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: false)
println(indexPath.row)
delegate?.SideBarControlDidSelectRow(indexPath)
}
}
// SideBar class ------------------------------------------------
#objc protocol SideBarDelegate {
func SideBarDidSelectButtonAtIndex(index: Int)
optional func SideBarWillClose()
optional func SideBarWillOpen()
}
class SideBar: NSObject, SideBarTableViewControllerDelegate {
// ...
func SideBarControlDidSelectRow(indexPath: NSIndexPath) {
println(indexPath.row)
delegate!.SideBarDidSelectButtonAtIndex(indexPath.row)
}
}
Then in this function I want to present needed ViewController:
func SideBarDidSelectButtonAtIndex(index: Int) {
presentViewControllerByIndex(index)
}
func presentViewControllerByIndex(index: Int) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
switch index {
case 0:
var viewController = storyBoard.instantiateViewControllerWithIdentifier("SearchVC") as! SearchViewController
viewController.delegate = self
self.presentViewController(viewController, animated: false, completion: nil)
break
// the same code here for other 4 VC's with their StoryboardId's
default: break
}
}
Then in presented view controller if user select another section in menu should I destroy this VC before presenting selected ViewController? Because when I run my application and started switching between controllers the application memory in xCode increases (screenshot).
When I saw that I've started googling and found this answer. So I have created a protocol in my first menu VC just for test.
protocol myProtocol {
func dissmissAndPresend(index: Int) -> Void
}
And when I need to present new VC I just call:
self.delegate?.dissmissAndPresend(index)
Then in my MainVC I implement this protocol and trying to dismiss previous VC and present the new one:
class MainViewController: UIViewController, SideBarDelegate, myProtocol {
func dissmissAndPresend(index: Int) {
self.dismissViewControllerAnimated(false, completion: {
self.presentViewControllerByIndex(index)
})
}
Now when I launch my program and started to click on the first menu item it opens new VC but my memory increases as before. And also the navigationController for presented view has gone but in storyboard presented VC is embedded in navigationController.
What I am doing wrong? Can someone help me?
UPDATE::-------------------------------
Here is what I've tried:
class MainViewController: UIViewController, SideBarDelegate, searchVCProtocol {
var searchVC: SearchViewController!
var searchNavController: ShadowUINavigationController!
override func viewDidLoad() {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
searchVC = mainStoryboard.instantiateViewControllerWithIdentifier("SearchVC") as! SearchViewController
searchVC.delegate = self
searchNavController = ShadowUINavigationController(rootViewController: searchVC)
}
func SideBarDidSelectButtonAtIndex(index: Int) {
switch index {
case 0:
println("asda")
self.presentViewController(searchNavController, animated: false, completion: nil)
break
}
}
Then it takes the user to SearchViewController and what if the user open menu again and click on SearchViewController again. How can I dismiss it and reopen. Here is what I've tried but it doesn't work:
In SeachViewController I've created a protocol:
protocol searchVCProtocol {
func dismissAndPresent(index: Int) -> Void
}
Then I've added a variable for the delegate:
var delegate: searchVCProtocol?
Then when user selected menu item it fires this event:
func SideBarDidSelectButtonAtIndex(index: Int) {
delegate?.dismissAndPresent(index)
}
And in MainViewController I've implemented this protocol and created dismissAndPresent method but I don't know how to restart presented viewController.
class MainViewController: UIViewController, SideBarDelegate, searchVCProtocol {
func dismissAndPresent(index: Int) {
// code to restart this VC
}
}
What should I code for restarting presented VC?
You are calling instantiateViewControllerWithIdentifier every time, this creates ("instantiates") every time a new ViewController. Better would be to do this only once, keep a reference to it and then re-use it on demand.
Here how you can keep a reference. First you create a new variable on class level:
class MyClass {
var viewController: SearchViewController!
Then, in viewDidLoad, you instantiate the view controller and assign it to the variable.
func viewDidLoad() {
viewController = storyBoard.instantiateViewControllerWithIdentifier("SearchVC") as! SearchViewController
}
viewDidLoad is called only once so it will not happen anymore that you are creating multiple instances.
Then you can reuse this viewcontroller again and again:
func presentViewControllerByIndex(index: Int) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
switch index {
case 0:
// Here you can reuse the formerly created viewcontroller
viewController.delegate = self
self.presentViewController(viewController, animated: false, completion: nil)
break
// the same code here for other 4 VC's with their StoryboardId's
default: break
}
}

Resources