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
Related
At the Moment I have to ViewControllers. You can switch between ViewControllerA and ViewControllerB with show-segues. The problem is that every time I switch back, the ViewControllers state gets reseted. How can I save the setUp of a ViewController?
From A to B
#IBAction func editButtonTapped(_ sender: Any) {
let imageCollectionView = self.storyboard?.instantiateViewController(withIdentifier: "ImageCollectionVC") as! ImageCollectionViewController
self.navigationController?.pushViewController(imageCollectionView, animated: true)
}
From B to A
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tappedImage = images[indexPath.row]
performSegue(withIdentifier: "backToPopUpView", sender: self)
}
To move between view controllers passing data both ways you need a few things
a consistent method of navigation: segues or pushing/popping via a navigation controller or modally presenting/dismissing, but not a mix of them for one A - B - A transition
protocols/deleagtes to allow data to be passed back from th child to the parent.
In the example below navigation is via the navigation controller, and an image is used as an example of how to pass data back to the parent. It should be trivial to adapt this for other circumstances.
The child class needs an agreed interface with its parent to allow it to communicate. This is done with a protocol. In this example we provide a means for the child to pass its updated image back to the parent:
protocol ClassBDelegate {
func childVCDidComplete(with image: UIImage?)
}
Then create a delegate variable in the child class that can point to any class that adopts the protocol (which in this case will be its parent) and then use that delegate and the protocol's function to pass the image data back. Once the data has been passed back via the delegate, view controller B calls the navigation controller's popViewCntroller method to close itself and return focus to view controller A
class B: UIViewController {
var delegate: ClassBDelegate?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tappedImage = images[indexPath.row]
delegate?.childVCDidComplete(with: tappedImage)
navigationController?.popViewController(animated: true)
}
}
For this all to work, the child view controller's delegate needs to be set to point its parent view controller (A), but before this can happen that class needs to conform to the protocol:
extension A: ClassBDelegate {}
func childVCDidComplete( with image: UIImage?) {
self.image = image
}
}
Now, when instantiating the child view controller the parents set itself as the delegate, thus completing the communication loop.
Class A: UIViewController {
var image: UIImage?
#IBAction func editButtonTapped(_ sender: Any) {
let imageCollectionView = self.storyboard?.instantiateViewController(withIdentifier: "ImageCollectionVC") as! ImageCollectionViewController
imageCollectionView.delegate = self
self.navigationController?.pushViewController(imageCollectionView, animated: true)
}
}
You are created new ViewControllerA, and you have the following result A -> B -> A.
But it is not true, you should dismiss ViewControllerB...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tappedImage = images[indexPath.row]
self.dismiss(animated: true, completion: nil)
//performSegue(withIdentifier: "backToPopUpView", sender: self)
}
I'm using an external delegate file to handle all a UICollectionView's processing and I'm struggling to get the collection view cell to perform a segue based on the selected cell, via the delegate file.
Here's what I currently have inside the delegate file:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
let mainController = MainController()
mainController.performSegue(withIdentifier: "detailSegue", sender: cell)
}
and on the Main Controller I have:
override func performSegue(withIdentifier identifier: String, sender: Any?) {
if identifier == "detailSegue" {
let detailController = DetailController()
self.present(detailController, animated: true)
}
}
The error I'm getting:
Warning: Attempt to present <DetailController: 0x7fbed8e6e4b0> on <MainController: 0x7fbedda79830> whose view is not in the window hierarchy!
I thought I could call up the reference via the delegate and it would present the controller.
Thanks
The reference of MainController that you are trying to get like below is wrong.
let mainController = MainController()
This will not give you the loaded/previewing instance of the object instead of this it will create a new object of MainController for you which is not in the previewing Hierarchy. So you need an actual previewing instance.
Solution
Create a global object of of UIViewController type in your delegate class and pass you previewing class reference so that you may use it when you need.
Example.
class DelegateDataSource: NSObject {
var viewController: UIViewController?
//Other Methods/Objects that you need.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let mainController = viewController as? MainController {
mainController.performSegue(withIdentifier: "detailSegue", sender: cell)
}
}
}
class MainController: UIViewController {
var delegateDataSource: DelegateDataSource?
func initializeDelegates() {
//Initialize you datasource object and do some tasks that you need and then assign your current instance to this class like this.
delegateDataSource.viewController = self
}
}
let mainController = MainController()
mainController.performSegue(withIdentifier: "detailSegue", sender: cell)
This will not work, either use delegate pattern or use singleton pattern for your MainController.
Only an already presented controller can present another controller. Is the class implementing didSelectItemAt a UIViewController? If so, why not just do:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
performSegue(withIdentifier: "detailSegue", sender: cell)
}
Currently we have a uicollectionview that is embedded in a tableview cell. When the collection view cell is selected it's suppose to initiate a push segue to another view controller. The problem is there is no option to perform the segue on the cell. Is there a way around it? Here is the cell:
class CastCell : UITableViewCell {
var castPhotosArray: [CastData] = []
let extraImageReuseIdentifier = "castCollectCell"
let detailToPeopleSegueIdentifier = "detailToPeopleSegue"
var castID: NSNumber?
#IBOutlet weak var castCollectiontView: UICollectionView!
override func awakeFromNib() {
castCollectiontView.delegate = self
castCollectiontView.dataSource = self
}
}
extension CastCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return castPhotosArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = castCollectiontView.dequeueReusableCell(withReuseIdentifier: extraImageReuseIdentifier, for: indexPath) as! CastCollectionViewCell
cell.actorName.text = castPhotosArray[indexPath.row].name
return cell
}
}
extension CastCell: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.castID = castPhotosArray[indexPath.row].id
performSegue(withIdentifier: detailToPeopleSegueIdentifier, sender: self) //Use of unresolved identifier 'performSegue' error
}
}
extension CastCell {
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let peopleVC = segue.destination as! PeopleDetailViewController
peopleVC.id = self.castID
}
}
The problem is there is no option to perform the segue on the cell
There is no such thing as a "segue on a cell". A segue is from one view controller to another. performSegue is a UIViewController method. So you cannot say performSegue from within your CastCell class, because that means self.performSegue, and self is a UITableViewCell — which has no performSegue method.
The solution, therefore, is to get yourself a reference to the view controller that controls this scene, and call performSegue on that.
In a situation like yours, the way I like to get this reference is by walking up the responder chain. Thus:
var r : UIResponder! = self
repeat { r = r.next } while !(r is UIViewController)
(r as! UIViewController).performSegue(
withIdentifier: detailToPeopleSegueIdentifier, sender: self)
1: A clean method is to create a delegate protocol inside your UITableViewCell class and set the UIViewController as the responder.
2: Once UICollectionViewCell gets tapped, handle the taps inside the UITableViewCell and forward the tap to your UIViewController responder through delegatation.
3: Inside your UIViewController, you can act on the tap and perform/push/present whatever you want from there.
You want your UIViewController to know what is happening, and not call push/presents from "invisible" subclasses that should not handle those methods.
This way, you can also use the delegate protocol for future and other methods that you need to forward to your UIViewController if needed, clean and easy.
I'm currently learning Swift and trying to perform a segue when the user taps on one of the tableview cells that the app presents. At the moment, whenever the user performs this action, the next view controller is loaded successfully, but it seems that, for some reason, I cannot access any of its UI elements, as each time that I try to do it, I end up getting this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
The error points to the line in which I try to modify the text of one of the labels that are displayed on the next view controller
This is the didSelectRowAt function:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
self.performSegue(withIdentifier: "segue1", sender: self)
}
and this is the prepareForSegue function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue1" {
let destinationVC = segue.destination as! UserViewController
let selectedRow = tableView.indexPathForSelectedRow!
let selectedCell = tableView.cellForRow(at: selectedRow) as! CustomCell
destinationVC.usernameLabel.text = selectedCell.userName.text //this is where the error is pointing to
destinationVC.bioLabel.text = selectedCell.bio.text
destinationVC.userImage.image = selectedCell.photo.image
}
}
I have no idea about what is causing this problem. My goal is to pass the data from the tapped cell to the next view controller, but this obviously is preventing me from doing so. Does anyone know how I can fix this? Thanks in advance.
Note: I assumed that userName and bio were both UITextFields
Why don't you try something like this?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue1" {
let destination = segue.destination as! UserViewController
// Use of optional binding to make sure an indexPath exists
if let indexPath = tableView.indexPathForSelectedRow {
let cell = tableView.cellForRow(at: IndexPath(row: indexPath.row, section: indexPath.section)) as! CustomCell
// Notice how we are not directly updating the label as before.
destination.usernameText = cell.userName?.text
destination.bioText = cell.bio?.text
}
}
}
Now in UserViewController:
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var bioLabel: UILabel!
// What we will be passing the text to instead.
var usernameText: String?
var bioText: String?
override func viewDidLoad() {
super.viewDidLoad()
// update the labels with the text from the proper cell.
usernameLabel?.text = usernameText
bioLabel?.text = bioText
}
You can just do the same for your image, just different types. This has to do with the outlets not being allocated when used in prepare(for segue:).
i had great issue with the prepare for segue method when trying the same thing with a UICollectionView. The 2 are very similar so you should be able to change collectionview to tableview easily.
this is what i did... using variable selectedPack
in the view controller you want to segue to you need to set the variable
// passed packName from PackViewController
var selectedPack: String!
then in the viewcontroller you are selecting the cell
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle the segue to JourneyViewController with variable "selectedPack"
// not sure why you need to set the storyboard but it works
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
//create instance of the viewcontroller
let transportJourneyViewController = storyBoard.instantiateViewController(withIdentifier: "JourneyViewController") as! JourneyViewController
//value to pass - has been defined in journey
transportJourneyViewController.selectedPack = INSERT_YOUR_VALUE_TO_PASS
//present the vc
self.present(transportJourneyViewController, animated:true, completion:nil)
}
JourneyViewController is the storyboardID and ClassName of the viewcontroller you want to go to.set in the interface builder.
You'll also need to have the tableviewdatasource and tableviewdelegate defined at the top level of your view controllers and in the storyboard itself.
class JourneyViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
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.