Update, see my comment below, as what I did wrong. Thought someone may benefit from all these newbie mistakes:) : I have a collectionView with section titles and cell titles (UILabels), which is dynamically fed in via cloudkit. I was able to get the code to finally work to select a cell and then send the cell's title to the 2nd ViewController's navigationItem.title property.
However, now the 2nd ViewController is reloading after it appears the first time. I embedded the first CollectionViewController within a navigation controller. And I created a push segue in storyboard from my prototype cell within the CollectionView to the 2nd ViewController, and provided an identifier for the segue. Any idea why it's reloading the 2nd ViewController again after appearing the first time?
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("selected", sender: indexPath)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "selected" {
let indexPaths : NSArray = self.collectionView!.indexPathsForSelectedItems()
let indexPath : NSIndexPath = indexPaths[0] as NSIndexPath
let theSelectedItem = sections[indexPath.section].category[indexPath.item]
let svc = segue.destinationViewController as TableViewControllerNew
svc.navigationItem.title = theSelectedItem
// I created the tableview controller in the storyboard, and then subclassed the UITableViewController, and set the storyboard tableview controller's class to the subclass in the identity inspector
}
Set the self.navigationItem.title = ... in the didSelect... method and then reset it to the old title in the viewWillAppear method of your first VC. This will save you from having to create a global variable in your first VC or having to pass the title as a property to your second VC, although both solutions should work as well.
Related
here is my code.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRow(at: indexPath!) as! monthTableViewCell!;
valueToPass = (currentCell?.monthOutlet.text)!
print(valueToPass)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "toMonthVC") {
// initialize new view controller and cast it as your view controller
let viewController = segue.destination as! monthCellViewController
// your new view controller should have property that will store passed value
viewController.dataFromHoursVC = valueToPass
}
}
So basically I am trying to pass a value from one VC to another. the didSelectRow is working perfectly how expected. However, the prepare function is running late. For example, the first time the code is run, the second vc sees the passed value as nil. But when i go back and then do it again, it says the passed value, but the value is the one that was done before. So simply put it is acting like the prepare function is behind or being called late.
How did you set up your segue? It sounds like the value isn't being set before the segue is performed.
Try doing it this way:
After you give your segue an identification name set up your code like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRow(at: indexPath!) as! monthTableViewCell!;
valueToPass = (currentCell?.monthOutlet.text)!
print(valueToPass)
// invoke the segue manually after value is set
self.performSegue(withIdentifier: "SEGUEID", sender: self)
}
Quick Warning
You really should be careful using force unwraps optionalVar! and force downcasts thing = otherThing as! Type. It's far better to always use if-let and guard-let statements or fail-able down casts as?. Using these will decrease the chances of developing a hard to find nil value bug.
This could happen if you set your tableview's data source and/or delegate to you view controller in interface builder. Depending on the rest of your code, initialization of the view controller may need to access the tableview's delegate or dataSource during initialization (i.e. before the controller is passed to the prepareForSegue function). When it is set in IB, the delegate (or dataSource) allow the functions to be called.
I am writing a Xcode program in Swift. I have a tableview controller with some labels and an image per cell. They have their data from a first view controller. So far so good. Now i would like the user to tap a cell which opens a new controller which contains the same label and image data as the cell. With the following code the new controller opens, nonetheless I don't no how to transfer the data. If someone could help me I would be so so grateful.
Here are the codes that i tend to use:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "TableView"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TableView
let picture = pic[indexPath.row]
cell.label1.text = picture.name1
cell.photoImage.image = picture.photo
cell.label2.text = picture.name2
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let cell = indexPath.row
self.performSegueWithIdentifier("segue", sender: cell)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segue"{
var row = tableView.indexPathForSelectedRow
let viewController = segue.destinationViewController as! vc2
}
}
PS:I have a segue from the image to the new controller with the identifier "segue".
PPS: Of course i have tried the following method: Send data from TableView to DetailView Swift but when i run my program I get an error with the information that my labels were unexpectedly found nil
You should not need to override didSelectRowAtIndexPath or call performSegueWithIdentifier to do this. Connect your segue in the IB file dragging from a table view controller's cell to the second controller. You should then pass the data to the controller in prepareForSegue, in the segue.destinationViewController. You set the public properties on that destination controller.
Also make sure your labels in your prototype cell have been connected in IB. If they are they, should not be nil. Set a breakpoint on the cellForRowAtIndexPath to verify this.
Unless you are talking about the labels in your destination controller. These also need to be hooked up in IB. You would then set them in prepareForSegue. You would get the appropriate data from the
let viewController = segue.destinationViewController as! vc2
var path = tableView.indexPathForSelectedRow
viewController.destLabel.text = arrayData[path.row].myData // or whatever data you have from your model.
I have a UITableviewController where I take a JSON file and break it down into various objects for display on my tableview (this works fine).
I've also created variables to store certain information from the cells generated in the tableview to be passed on to a detailViewController (text and images).
Using the 'prepare for segue' method I am able to pass the information I need to the detailViewController. However, I keep running into an issue where the data displayed in the detailViewController isn't the data of the cell selected but rather the data of what appears to the the last loaded cell on the table.
I am not using the didSelectRowAtIndexPath because it doesn't seem to do much better but create an additional segue screen (automatically).
In summary, my TableViewController displays about 4 cells at a time on the screen. regardless of which cell I select, the information passed to the DetailViewController is always the information on the fourth cell displayed on the screen.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Post Cell", forIndexPath: indexPath) as! TableViewCell
var dict = jsonArr[indexPath.row]
cell.postTitle.text = dict["data"]!["post_title"] as? String
cell.postTag.text = dict["data"]!["post_tags"] as? String
cell.postAddress.text = dict["data"]!["post_address"] as? String
titleToPass = cell.postTitle.text
postTagToPass = cell.postTag.text
addressToPass = cell.postAddress.text
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "ListToDetail") {
let viewController = segue.destinationViewController as! DetailPostViewController
viewController.passedTitle = titleToPass
viewController.passedPostTags = postTagToPass
viewController.passedAddress = addressToPass
}
}
cellForRowAtIndexPath gets called for every cell, so it makes sense that your variables will always have values matching the last cell to be updated.
Instead of setting the data you want to pass in that method, call indexPathForSelectedRow: in prepareForSegue and use the row of the index path to get the right things from jsonArr.
I have used this tutorial to successfully embed a UICollectionView inside a UITableView I have in my ViewController.
The following will probably make more sense if you have a quick look at the linked tutorial's code (plus its a nifty thing to learn too!):
The next step for me is to perform segues from the cells of the UICollectionView inside the UITableViewCells of the tableView, but as the collectionView outlet is established in a separate View Controller, I am not sure how to reference it in the main ViewController.
In the TableViewCell.swift there is:
class TableViewCell: UITableViewCell {
#IBOutlet private weak var collectionView: UICollectionView!
}
extension TableViewCell {
func setCollectionViewDataSourceDelegate<D: protocol<UICollectionViewDataSource, UICollectionViewDelegate>>(dataSourceDelegate: D, forRow row: Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.tag = row
collectionView.reloadData()
}
}
And in the ViewController.swift I need to be able to, for instance, call the collectionView that is in the TableViewCell in the prepareForSegue function that would go in the ViewController.swift file. I just need to fill the gaps with the collectionView outlet:
let destination = segue.destinationViewController as! SecondViewController
let indexPaths = self.___________.indexPathsForSelectedItems()
let indexPath = indexPaths![0] as NSIndexPath
let arrayObject = self.arrayObjects[indexPath.row]
destination.object = arrayObject
'object' is implemented in SecondViewController like so, var object: PFObject!.
I now need to fill the gap, ________, in the above code with the collectionView in order to display the correct 'object' in SecondViewController (the destinationViewController)
Add Push Segue from UICollectionViewCell to YourViewController from IB.
Give an identifier to your segue ("YourSegueIdentifier").
In your custom UITableViewController or UIViewController, override prepareForSegue() method.
This is:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "YourSegueIdentifier" {
if let collectionCell: YourCollectionViewCell = sender as? YourCollectionViewCell {
if let collectionView: UICollectionView = collectionCell.superview as? UICollectionView {
if let destination = segue.destination as? YourViewController {
// Pass some data to YourViewController
// collectionView.tag will give your selected tableView index
destination.someObject = tableObjects[collectionView.tag].someObject
destination.productId = collectionCell.product?.id
}
}
}
}
}
Hi I had the same problem
This is what I did :
1. set a tag for the cell in the extension in your first View Controller
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("advertisementCollectionCell",forIndexPath: indexPath) as! AdvertisementCollectionViewCell
// set your cell information
cell.tag = indexPath.row
return cell
}
2. Do a segue on the storyboard from the UIcollectionViewCell to the new View Controller
3. In prepareForSegue of the first TableViewController :
else if segue.identifier == "identifier"
{
let destinationViewController = segue.destinationViewController as! DestinationViewController
if let cell = sender as? MyCollectionViewCell
{
let indexPath = cell.tag
// use indexPath :D
}
}
i just played with the code you attached. i added segue in story board manually. its easy to call .
1) just add viewcontroller to the storyboard.
2) Controll+ drag and drop segue from collectionview cell
In your code, the reference to CollectionView is connected in TableViewCell.
So, you can refer it through TableViewCell and set datasource and delegate.
To add connect into your code, you have to select the CollectionView with RightButton and drag and drop into #IBOutlet private weak var collectionView: UICollectionView!
When clicks the CollectionViewCell, collectionView(collectionView:UICollectionView, didselecteditemAtIndexPath indexPath: NSIndexPath) function of UICollectionViewDelegate will be called by delegate.
At that time, you can segue by calling performSegue in this function.
As you write in your code, the index of tableview is set by tag, so you can get the index by tag using collectionView.tag()
The code will be as following.
var tableViewIndex = collectionView.tag()
So you can refer the correct cell using tableViewIndex and indexPath.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var tableViewIndex = collectionView.tag;
self.performSegueWithIdentifier("segueIdentifier", sender: self)
}
So I have a UITableView populated with questions. The cells of this table have a segue relationship to another view that contains the answer to the question. This segue has an identifier 'showAnswer'. Now I want to implement search functionality on the question table and I want each cell in the results to also segue to the answers view.
So in the question table controller have the code to add the search bar and its controller (this in Swift by the way but the logic is still the same):
let resultsController = SearchResultsController()
resultsController.questions = questions
searchController = UISearchController(searchResultsController: resultsController)
let searchBar = searchController.searchBar
searchBar.placeholder = "Enter a search term..."
searchBar.sizeToFit()
questionTable.tableHeaderView = searchBar
searchController.searchResultsUpdater = resultsController
Now in the SearchResultsController once a row has been selected I want to segue to the answers view. Here is what I got:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("showAnswer", sender: self)
}
Now this crashes and I get the error that 'reciever SearchResultsController has no segue with identifier 'showAnswer'. This makes sense, but how do I fix this? Since I added the SearchResultsController programmatically there is nothing for me to ctrl+drag to on the storyboard to create a segue relationship between the SearchResultsController and the AnswersController. So what do I do?
You need to connect the segue from the holding view controller to the answers view and perform the segue from the holding view controller instead of from the search controller.
you should also send the appropriate data in the sender argument of the segue so the view controller you are navigating to can change according to the question that was chosen
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
YourViewController.performSegueWithIdentifier("showAnswer", sender: questions[indexPath.row])
}