I am trying to create a protocol delegate between two UICollectionViewController. with the code I have I don't get any errors or warnings however, I cannot get the delegate to work. What am I missing?
Second Collection View
public protocol LettersCollectionViewDelegate: class {
func DidSelectLetter(collectioView: UICollectionView,letter: Character, resultString:String)
}
class LettersCollectionView: UICollectionViewController {
// DELEGATE
weak var delegate: LettersCollectionViewDelegate?
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath)! as! LetterCellView
delegate?.DidSelectLetter(collectionView, letter: "T", resultString:"TestString")
}
}
First Collection View
class AnswerCollectionView: UICollectionViewController {
let lettersView = LettersCollectionView()
override func viewDidLoad() {
super.viewDidLoad()
self.lettersView.delegate = self
}
}
extension AnswerCollectionView: LettersCollectionViewDelegate {
func DidSelectLetter(collectioView: UICollectionView, letter: Character, resultString: String) {
print(letter)
}
}
UPDATE
You need your delegate to be the instance of the AnswerCollectionView that is embedded in your root view controller. Similarly, you need to set the delegate on the LettersCollectionView instance that is in the root view. let lettersView = LettersCollectionView() creates a new instance.
You can get the required references in prepareForSegue in your root view controller. You need to give the two embded segues in your storyboard identifiers, so you can identify them.
class ViewController: UIViewController {
var lettersView: LettersCollectionView?
var answersView: AnswersCollectionView?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "lettersSegue" {
let lettersView = segue.destinationViewController as? LettersCollectionView
} else if segue.identifier = "answersSegue" {
let answersView = segue.destinationViewController as? AnswersCollectionView
}
self.lettersView?.delegate = self.answersView
}
Related
I have tableView within collectionView and i created using xib. I want to pushViewController when item is selected .I tried pushing the view controller in itemForRowAt method but it's not possible
class SecondTableView: UITableViewCell , UIcollectionViewDelegate ,UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// let vc = DataTableViewController()
// vc.delegate = self
// vc.pushNav()
let memeVC = storyboard?.instantiateViewController(withIdentifier: "MemeViewController") as! MemeViewController
memeVC.imagePassed = image
navigationController?.pushViewController(memeVC, animated: true)
print("item tapped\(indexPath)")
}
}
errors
Use of unresolved identifier 'storyboard'; did you mean 'UIStoryboard'?
Add segue as mentioned in the image and select show
Set segue identifier in your storyboard as mentioned in the image
Add below Protocol:
protocol CollectionViewCellDelegate: class {
func userDidTap()
}
Add below property in your table view class where the collection view delegate returns.
weak var delegate: CollectionViewCellDelegate?
Update delegate method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate.userDidTap()
print("item tapped\(indexPath)")
}
}
confirm CollectionViewCellDelegate delegate to your first view controller & add this method:
func userDidTap() {
performSegue(withIdentifier: "showMemeViewController", sender: nil)
}
Add prepare for segue in your first VC.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showMemeViewController" {
let memeVC: MemeViewController = segue.destination as! MemeViewController
memeVC.imagePassed = image
}
}
Please try this let me know if you faced any other issue. Thanks.
You're trying to access storyboard property in a tableview cell, which isn't there in the first place, it's a UIViewController property not a UITableViewCell property, hence the error, you'll have to delegate your selection to the viewController then let the controller do the segue normally.
You can start doing that via delegates as I mentioned above, for example if you're passing image to the controller you wanna segue to:
protocol ImageTableViewCellDelegate: class {
// pass the the image or any variable you want in the func params
func userDidTap(_ image: UIImage)
}
then you'd add it to the cell and confirm the controller to it
class SecondTableView {
weak var delegate: ImageTableViewCellDelegate?
}
In your controller:
extension MyViewController: ImageTableViewCellDelegate {
func userDidTap(_ image: UIImage){
let memeVC = storyboard?.instantiateViewController(withIdentifier: "MemeViewController") as! MemeViewController
memeVC.imagePassed = image
navigationController?.pushViewController(memeVC, animated: true)
print("item tapped\(indexPath)")
}
}
this should work, let me know if you had any further problems
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I know this question has been asked so many many times. But here the scenario is changed. I am using MMParallaxView and that looks pretty awesome.
But Now at this point, I want to communicate between these two View controllers. Let me tell you that this github code helps us to make a view like ios Map App. I mean you can have Map (as 1st view controller) and you can have a list of places view controller on top of 1st View Controller. Just same like Map app.
Now I want to click on the UITableView cell and want to navigate on map. For this I know how to catch delegated method. And I am successfully getting of taps on the UItableView cell. But How to send that clicked item data to 1stView Controller so that It can show or mark selected area on Map.
I know this can also be done. But how?
To modify the example app for MMParallaxView...
In ChildBottomViewController.swift
Add this at the top (outside of the class):
protocol CellTapDelegate {
func didSelectRow(indexPath: IndexPath)
}
Add this line at the beginning of the class:
var cellTapDelegate: CellTapDelegate?
Change didSelectRowAt function to:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
DispatchQueue.main.async { [weak self] in
self?.cellTapDelegate?.didSelectRow(indexPath: indexPath)
}
}
In MapViewController.swift
Change the class declaration to:
class MapViewController: UIViewController, CellTapDelegate {
and add this function:
#objc func didSelectRow(indexPath: IndexPath) {
print("Delegate got didSelectRow for: \(indexPath)")
}
Then, in SecondViewController.swift
Add this at the end of viewDidLoad():
var mapVC: MapViewController?
var bottomVC: ChildBottomViewController?
for vc in self.childViewControllers {
if vc is MapViewController {
mapVC = vc as? MapViewController
} else if vc is ChildBottomViewController {
bottomVC = vc as? ChildBottomViewController
}
}
// if we found valid child view controllers, set the delegate
if mapVC != nil && bottomVC != nil {
bottomVC?.cellTapDelegate = mapVC
}
Now, selecting a row from the "slide up from bottom" table view will send the selected indexPath to its delegate - the map view controller.
protocol CellTapDelegate {
func didTapOnItem(obj: MyObject)
}
class MyViewControllerContainingTheTableView : UIViewcontroller {
weak var delegate: CellTapDelegate?
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
let item = arrayOfObjects[indexpath.row]
self.delegate.didTapOnItem(item)
}
}
//View Controller where you you will have the listner
class FirstViewController : UIViewController {
func setupParalax() {
// you have to modify this according to your need
let vc = MyViewControllerContainingTheTableView()
vc.delegate = self
}
}
extension FirstViewController: CellTapDelegate {
func didTapOnItem(obj: MyObject) {
// you have your required object here
}
}
This should be possible by implementing your own delegate, but my knowledge of MMParrallaxView is that there is only 1 view controller which displays two views meaning the view controller should be able to pass things between the two views as it is. In this case the view controller would implement the table view delegate methods and add the table view to the bottom view. Then add the map to the top view. This should allow you to catch the table view cell selection and update the map in the top view accordingly.
Example based on the structure I believe you are using:
public class FirstViewController {
public let parallaxView = MMParallaxView()
private var secondViewController: SecondViewController
private var thirdViewController: ThirdViewController
override open func viewDidLoad() {
secondViewController = SecondViewController()
thirdViewController = ThirdViewController()
super.viewDidLoad()
self.view.addSubview(parallaxView)
thirdViewController.delegate = secondViewController
parallaxView.parallaxTopView = secondViewController.view
self.addChildViewController(secondViewController)
secondViewController.didMove(toParentViewController: self)
parallaxView.parallaxBottomView = thirdViewController.view
self.addChildViewController(thirdViewController)
thirdViewController.didMove(toParentViewController: self)
}
}
public class SecondViewController: ThirdViewControllerDelegate {
public func updateMap() {
// Update map
}
}
public class ThirdViewController: UITableViewDelegate {
weak var delegate ThirdViewControllerDelegate?
private var table: UITableView = UITableView()
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.updateMap()
}
}
protocol ThirdViewControllerDelegate: class {
public func updateMap() {}
}
if the view controllers are instantiated through the Storyboard then you can try this:
public class MasterViewController {
var secondViewController: SecondViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.performSegue(withIdentifier: "MMParallaxTop", sender: nil)
self.performSegue(withIdentifier: "MMParallaxBottom", sender: nil)
}
override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let secondVC = segue.destination as? SecondViewController {
secondViewController = secondVC
}
if let thirdVC = segue.destination as? ThirdViewController, let second = secondViewController {
thirdVC.delegate = second
}
}
}
i need an help, see this class
import UIKit
protocol TypesTableViewControllerDelegate: class {
func typesController(controller: TypesTableViewController, didSelectTypes types: [String])
}
class TypesTableViewController: UITableViewController {
let possibleTypesDictionary = ["bakery":"Bakery", "bar":"Bar", "cafe":"Cafe", "grocery_or_supermarket":"Supermarket", "restaurant":"Restaurant"]
var selectedTypes: [String]!
weak var delegate: TypesTableViewControllerDelegate!
var sortedKeys: [String] {
return possibleTypesDictionary.keys.sort()
}
// MARK: - Actions
#IBAction func donePressed(sender: AnyObject) {
delegate?.typesController(self, didSelectTypes: selectedTypes)
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return possibleTypesDictionary.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TypeCell", forIndexPath: indexPath)
let key = sortedKeys[indexPath.row]
let type = possibleTypesDictionary[key]!
cell.textLabel?.text = type
cell.imageView?.image = UIImage(named: key)
cell.accessoryType = (selectedTypes!).contains(key) ? .Checkmark : .None
return cell
}
// MARK: - Table view delegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let key = sortedKeys[indexPath.row]
if (selectedTypes!).contains(key) {
selectedTypes = selectedTypes.filter({$0 != key})
} else {
selectedTypes.append(key)
}
tableView.reloadData()
}
}
here the user can tap a cell of the tableView so that his prefer types are used on the next viewController for a search, now i need to build a class that do the same thing but there is no a tableview rather only 6 buttons in a view that the user can tap (so a viewController with only 6 different buttons to tap). The problem is that i don't know how to pass to the next viewController what buttons have been pressed and what are not, how can i build this class?
here is the function in the other class that need to know what buttons have been pressed
func fetchNearbyPlaces(coordinate: CLLocationCoordinate2D) {
mapView.clear()
dataProvider.fetchPlacesNearCoordinate(coordinate, radius:searchRadius, types: searchedTypes) { places in
for place: GooglePlace in places {
let marker = PlaceMarker(place: place)
marker.map = self.mapView
where is "types: serchedTypes"
What you wanna do is called delegation here is how you do it:
Make a protocol like this one:
protocol TransferProtocol : class
{
func transferData(types:[String])
}
Make the view controller with the buttons conform to that protocol, I like to do it by adding extensions to my classes like so:
extension ButtonsViewController:TransferProtocol{
func transferData(types:[String]){
//Do whatever you want here
}
}
Declare a variable in your Table View Controller class with the protocol you created as its type, this is called a delegate
weak var transferDelegate:TransferProtocol?
Before you segue to the Buttons View Controller you want to set that view controller as the delegate you just created like so:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as? ButtonsViewController
transferDelegate = vc
vc?.transferData(types: selected)
}
If done correctly you should be able to work with the array you built in the Table View Controller(TypesTableViewController)
I have a view crontroller called "subcateory2", this viewcontroller has a uicollectionview wit custom cell. I need two segues from my app. One of the called "to_videostable" from the viewcontroller to other view controller and the other calles "reload_collection" from the cell to the same viewcontroller(because the subcategory can have n-level of subcategories). The problem is with my prepareForSegue(i check in this function the identifier , that is defined in the " func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)",and execute different actions). When i select a cell this should happen:
first: go to my "func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)", check the condition and define my identifier for segue.
second: go to the prepareforsegue, check the condition and execute the actions.
but actually this happen:
first: in my ios simulator i select a cell.
second: my code go the prepareforsegue and go always for the segue called "reload_collection"(before going to my func collectionView(...)), and create a white views. In this moment is like a two threads are created, one of them go to the white windows and the other to the next stop.
third: this "second theard" go to the func collectionview(...) and check the condition, define the identifier, call to the performSegueWithIdentifier and go to the prepareforsegue function. In the prepareforsegue check the identifier and execute the differentes actions.
This is my code:
import UIKit
class Subcategory2: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
let viewUtils = ViewControllerUtils()
var result_category: Array<JSON> = []
#IBOutlet weak var collectionview: UICollectionView!
var tit: String!
var url: String!
var end_url:String = "?page_size=100"
var id_category: Int!
var index: NSIndexPath!
var url_children : String = ""
let imagePath = "http://d1rkb03u2ginv9.cloudfront.net/wp-content/uploads/"
override func viewDidLoad() {
self.viewUtils.showActivityIndicator(self.view)
super.viewDidLoad()
self.viewUtils.showActivityIndicator(self.view)
if self.result_category.isEmpty{
var category = category_function()
self.url = self.url + self.end_url
self.result_category = category.load_subcategory(self.url)}
self.viewUtils.hideActivityIndicator(self.view)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath)!
println("entroooooooo")
if (self.result_category[indexPath.row]["children"].string != nil){
self.url_children = self.result_category[indexPath.row]["children"].string!
//while(self.url_children.isEmpty){}
println("voy a reloadcollection")
performSegueWithIdentifier("reload_collection", sender: cell)
//performSegueWithIdentifier("reload_collection3", sender: self)
}else{
println("voy a to_videostables")
performSegueWithIdentifier("to_videostable", sender: cell)
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//println(result_category.count)
return result_category.count }
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) ->UICollectionViewCell {
let cell: CollectionViewCellController2 = collectionView.dequeueReusableCellWithReuseIdentifier("cell2", forIndexPath: indexPath) as! CollectionViewCellController2
println(self.result_category[indexPath.row]["slug"].stringValue)
cell.label.text = self.result_category[indexPath.row]["slug"].stringValue
if (self.result_category[indexPath.row]["images"]["file"].string != nil){
//println("+++++++++++++++++")
var image = self.result_category[indexPath.row]["images"]["file"].stringValue
cell.image.sd_setImageWithURL(NSURL(string:self.imagePath + (image as! String)))
}else{
var image = "http://www.camping-oaza.com/images/joomlart/demo/default.jpg"
cell.image.sd_setImageWithURL(NSURL(string: image))
//cell.image.image = UIImage(named: image)
}
cell.NumberVideosLabel.text = self.result_category[indexPath.row]["videos_count"].stringValue
cell.NumberSubcategoryLabel.text = self.result_category[indexPath.row]["children_count"].stringValue
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "to_videostable"{
println("-------------")
println("voy a to_videostables")
let cell = sender as! UICollectionViewCell
let index = self.collectionview!.indexPathForCell(cell) // this return -> NSIndexPath?
//if (self.result_category[index!.row]["children"].string != nil){
// self.loaddata(self.result_category[index!.row]["children"].string!)
//}else{
let vc : VideosViewController = segue.destinationViewController as! VideosViewController
println(self.result_category[index!.row]["id"].intValue)
vc.id_category = self.result_category[index!.row]["id"].intValue
}
if segue.identifier == "to_livevideos"{
println("-------------")
println("to_livevideos")
println("-------------------")
let vc : SWRevealViewController = segue.destinationViewController as! SWRevealViewController
}
if segue.identifier == "reload_collection"{
println("-------------")
println("reload_collection")
println("-------------------")
var category = category_function()
let vc : Subcategory2 = segue.destinationViewController as! Subcategory2
vc.url = self.url_children
println(category.load_subcategory(self.url_children + self.end_url))
}
}
}
With this problem, always is created a white windows and after is created a windows with the real information.
the order of the println is :
- "reload_collection"
- "entroooooooo"
- "voy a reloadcollection" or "voy a to_videostables"
In this pictures show my main.stoyboard and the windwos that i can see in my app.
Updated Answer
You have a situation where you want to decide which segue to take when a cell is selected. You have wired one of your segues directly from the cell, which means the storyboard will create that segue for you. You also are calling performSegueWithIdentifier which creates another segue. You need to implement shouldPerformSegueWithIdentifier to cancel the "reload_collection" segue when you want segue to "to_videostables".
In the Original Answer below, I suggested you wire both segues from the viewController, but that won't work because one of your segues is back to the same viewController.
So, another way to do this is to:
Modify didSelectItemAtIndexPath to remove the code that handles the "reload_collection" segue. The Storyboard will be making that segue:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath)!
println("entroooooooo")
if result_category[indexPath.row]["children"].string == nil {
println("voy a to_videostables")
performSegueWithIdentifier("to_videostable", sender: cell)
}
}
Wire the segue "reload_collection" from the cell to the viewController. This will allow the Storyboard to perform this segue for you.
Implement shouldPerformSegueWithIdentifier to tell the Storyboard when it should make this segue:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if segue.identifier == "reload_collection" {
let indexPath = collectionView.indexPathForCell(sender as! UICollectionViewCell)
return result_category[indexPath.row]["children"].string != nil
}
return true
}
In prepareForSegue you will need to set up url_children since it is no longer being done by didSelectItemAtIndexPath:
if segue.identifier == "reload_collection"{
println("-------------")
println("reload_collection")
println("-------------------")
var category = category_function()
let vc : Subcategory2 = segue.destinationViewController as! Subcategory2
let indexPath = collectionView.indexPathForCell(sender as! UICollectionViewCell)
url_children = result_category[indexPath.row]["children"].string!
vc.url = url_children
println(category.load_subcategory(self.url_children + self.end_url))
}
Original Answer
Your segue is getting auto-called because you have wired it from the cell. If you want to trigger it with performSegueWithIdentifier it needs to be wired from the viewController like the other segue. Just remove the segue from the cell and rewire it from the viewController and give it the same identifier it had when you wired it from the cell and it should work.
How is it possible to perform a segue from a UITabBarController Child ViewController to a DetailView of an UITableViewController with UINavigationController in between?
There is a TabBarController with two childs, FirstView(Most Viewed Symbol) and NavigationController(Contacts Symbol).
The FirstView has a button, which should perform a segue to Show Profile VC.
The NavCont has a TableView with subclass All ProfilesTVC with a prototype cell as child.
AllProfilesTVC has an array with three names which are displayed by the reused cell.
Which viewcontroller do I have to instantiate and prepare at the function prepareForSegue in FirstView (HomeVC) and where should the segue, which I create in storyboard, direct to? So that I'm at "John's" DetailView.
And is it possible that when I performed a segue to ShowProfileVC, that I can push the Back Button to return to the All ProfilesTVC?
There is a github repo for those who want to try at github repo
FirstView / HomeVC.swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// what do we do here ???
}
AllItemsTVC
class AllItemsTVC: UITableViewController {
let profiles = ["Joe", "John", "Ken"]
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return profiles.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
let profile = profiles[indexPath.row] as String
cell.textLabel?.text = profile
return cell
}
#IBAction func cancelFromShowProfile(segue: UIStoryboardSegue) {
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "ShowProfile":
if let showProfileVC = segue.destinationViewController as? ShowProfileVC {
// pass the data to the destinationVC
let selectedProfile = profiles[tableView.indexPathForSelectedRow()!.row] as String
showProfileVC.name = selectedProfile
}
default: break
}
}
}
ShowProfileVC
class ShowProfileVC: UIViewController {
#IBOutlet weak var textLabel: UILabel! {
didSet {
textLabel.text = name
}
}
var name = "Label"
override func viewDidLoad() {
super.viewDidLoad()
}
}
Thanks for any help.
You cannot do this with a segue, but that's not a problem because you can do it quite simply in code. Just give your view controllers an identifier in storyboard and instantiated them via this identifier with the appropriate UIStoryboard API.
Start by switching to the other tab bar item in code, and then first tell the navigation controller to popToRootViewController, after which you can push all the necessary controllers onto the navigation stack in turn. You can do all the configuration you normally do in prepareForSegue just before pushing the controllers.
The trick is to do it all with animated set to false except the last step.