I have a Master-Detail VC setup for a Core Data project. If the searchBar is active, one set of results is displayed, if it's not active, the objects from the fetchedResultsController displays in the MasterVC.
I had been trying to segue using prepareForSegue, but I my instructor suggested I use didSelectRowAtIndexPath to do the segue.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedNote: Note
// Check to see which table view cell was selected.
if tableView == self.tableView {
selectedNote = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Note // <--this is "everything"
} else {
// need this to unwrap the optional
if let filteredObjects = filteredObjects {
selectedNote = filteredObjects[indexPath.row]
}
}
// Set up the detail view controller to show.
let detailViewController = DetailViewController()
detailViewController.detailDescriptionLabel = selectedNote.valueForKey("noteBody") as! UILabel
// Note: Should not be necessary but current iOS 8.0 bug requires it.
tableView.deselectRowAtIndexPath(tableView.indexPathForSelectedRow()!, animated: false)
// original code
navigationController?.pushViewController(detailViewController, animated: true)
}
I'm getting this compiler error:
Variable 'selectedNote' used before being initialized--it's declared at the top of the method!
If I add "self" before selectedNote like so:
detailViewController.detailDescriptionLabel = self.selectedNote.valueForKey("noteBody") as! UILabel
'MasterViewController' does not have a member named 'selectedNote' despite being there. So I'm obviously mucking up something.
I put a breakpoint in before let detailViewController = DetailViewController() and in lldb it's printing out the right object. I've looked around here for a solution, but I'm coming up short. I can't find applicable code that works on GitHub.
class Note: NSManagedObject {
#NSManaged var dateCreated: NSDate
#NSManaged var dateEdited: NSDate
#NSManaged var noteTitle: String
#NSManaged var noteBody: String
}
Any ideas how to pass the selectedNote's properties forward to the detailViewController?
Update:
Based on the responses I've gotten, I've shut up the compiler warnings with this code:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedNote: Note?
// Check to see which table view cell was selected.
if tableView == self.tableView {
selectedNote = self.fetchedResultsController.objectAtIndexPath(indexPath) as? Note // <--this is "everything"
} else {
// need this to unwrap the optional
if let filteredObjects = filteredObjects {
selectedNote = filteredObjects[indexPath.row]
}
}
// Set up the detail view controller to show.
let detailViewController = DetailViewController()
detailViewController.detailDescriptionLabel.text = (selectedNote!.valueForKey("noteBody") as! String)
// Note: Should not be necessary but current iOS 8.0 bug requires it.
tableView.deselectRowAtIndexPath(tableView.indexPathForSelectedRow()!, animated: false)
// original code
navigationController?.pushViewController(detailViewController, animated: true)
}
But I'm getting this in the console when it crashes:
There are already notes in the app
fatal error: unexpectedly found nil while unwrapping an Optional value
However, when I type po selectedObject the object I clicked displays in the console.
You need to declare selectedNote as optional like this:
var selectedNote: Note?
And later check if value exist before using it.
if let note = selectedNote {
// Set up the detail view controller to show.
let detailViewController = DetailViewController()
detailViewController.detailDescriptionLabel = note.valueForKey("noteBody") as! UILabel
// Note: Should not be necessary but current iOS 8.0 bug requires it.
tableView.deselectRowAtIndexPath(tableView.indexPathForSelectedRow()!, animated: false)
// original code
navigationController?.pushViewController(detailViewController, animated: true)
}
Update:
The problem is that you are trying to create DetailViewController
let detailViewController = DetailViewController()
But what you need instead is to have reference to the DetailViewController in order to pass information to it.
So you can create segue from Master to Detail controller in Interface builder. Then remove logic from didSelectRowAtIndexPath
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Note: Should not be necessary but current iOS 8.0 bug requires it.
tableView.deselectRowAtIndexPath(tableView.indexPathForSelectedRow()!, animated: false)
}
And implement it in prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let cell = sender as? UITableViewCell {
let indexPath = tableView.indexPathForCell(cell)!
var selectedNote: Note?
if filteredObjects?.count > 0 {
selectedNote = filteredObjects![indexPath.row]
}else {
selectedNote = self.fetchedResultsController.objectAtIndexPath(indexPath) as? Note // <--this is "everything"
}
if let note = selectedNote {
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = note
}
}
}
}
showDetail - segue identifier which you need to setup in IB.
var detailItem: AnyObject? - you need to declare it in DetailViewController.
Related
I am quite new to this. I am getting an issue with selection items and post back to tableview on specific index cell (reload).
Scenario: I have two view controllers and both contains tableview.
FirstViewController: I am already displaying data from webservice into Tableview but on different action types I have used didSelectRow function which shows to ListViewController for item selection. In FirstViewController main model is Unit.
ListViewController: Simple list of data displaying in tableView for selection.
Issue: is how can I select item from ListViewController and pass data to FirstViewController tableview that change occurs on specific cell and then I have to post values with updated values. How can I reload tableview or model?
Model:
struct Unit : Codable {
let sectionList : [SectionList]?
}
struct SectionList : Codable {
let title : String?
let items : [Item]?
}
struct Item : Codable {
let actionType : Int?
let textField : String?
let textValue : String?
let pickList: [SectionList]?
let multiSelect: Bool?
let selectedValue: [String]?
let version: Int?
let masterId: Int?
let itemValue: String?
}
Updated FirstController:
class FirstViewController: UIViewControlller, ListDelegate {
var selectedIndex: Int?
var selectedSection: Int?
//Click event for navigation from FirstViewController to SecondViewController
#IBAction func BackButtonAction(_ sender: Any) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
func listFunc(listValue: String) {
AppData?.sectionList?[selectedSection!].items?[selectedIndex!].textField = listValue
tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Add these two line in your code
selectedIndex = indexPath.row
selectedSection = indexPath.section
}
}
Updated : ListViewController:
protocol ListDelegate {
func listFunc(listValue: String)
}
class SecondViewController: UIViewControlller {
var delegate: ListDelegate?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let indexPath = tableView.indexPathForSelectedRow
let currentCell = tableView.cellForRow(at: indexPath!)!
//print(currentCell.textLabel?.text as Any)
currentCell.accessoryType = .checkmark
delegate?.listFunc(listValue: currentCell.textLabel?.text ?? "")
self.navigationController?.popViewController(animated: true)
}
}
You've got a handful of options to accomplish this. Assuming that the selected value is the only value that you've got to pass, using closures would be the easiest thing you could do.
1) Declare a closure that holds a type of your need inside ListViewController:
`var didSelectItem : ((YourType, IndexPath) -> ())?`
2) In ListViewController's didSelectRow method, use the closure:
`self.didSelectItem?(yourDataSourceItemAtIndexpath, indexPath)`
3) You'll get a callback for the triggered closure when you use this closure from your ListViewController instance inside FirstViewController like:
let myListInstance = ListViewController()
myListInstance.didSelectItem = { selectedItem, selectedIndex in
//You'll get the selected item inside this block, do model updation here
}
After updating your model with new values, if you want to reload your tableView only at the particular(updated) index, you can use:
self.yourTableView.reloadSections(sections: IndexSet, with: UITableView.RowAnimation) to reload a whole section or..
self.yourTableView.reloadRows(at: [IndexPath], with: UITableView.RowAnimation) to reload a row in a section
(If you're performing multiple updates simultaneously, keep in mind that you'll have to do these reloads inside self.yourTableView.beginUpdates() and self.yourTableView.endUpdates())
NOTE:
You'll have to handle your dataSource properly during these reloads to make sure you have the right number of rows or sections before and after the reload, adding/subtracting the rows/sections that you insert/delete. Else, your app will crash!
If you don't want to risk that, just reload the whole table with yourTableView.reloadData()
Also keep in mind that there are other ways to pass data across controllers and this is just a suggestion based on what I assume your use case to be
I hardly able to understand your code, it's totally messed up according to me.
I found that most of your code is done and I am adding rest your things that you need to do.
In your FirstViewController.swift, didSelectRowAt func,
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
vc.Indexdata = // your data that you wanted to show in your next controller,
// if variable is different then assign data to that var
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
In ViewController.swift, didSelectRowAt func,
// Comment these below lines
// let vc = self.storyboard?.instantiateViewController(withIdentifier: "FirstViewController") as! FirstViewController
// self.navigationController?.pushViewController(vc, animated: true)
// Add below line
self.navigationController?.popViewController(animated: true)
Now check it should work.
I have a question regarding a Swift project.
I have a TableViewController with multiple cells, and when I click on them I go to a ViewController where data is passed from the tableViewController.
I want to implement a button in the ViewController that allows me to display the ViewController with the content of the "next cell" from the table view controller. For instance, I clicked on the 2nd cell of the table view controller, so I go to the ViewController that displays the data corresponding to that cell, and then when I click on that "next" button in the viewController I want to display the content of the 3rd cell of the table view controller. How do I do that in a clean way?
Here is the code I use to pass the data from tableViewController to ViewController :
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let a = articles {
return a.count
}
return 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("ArticleCell", forIndexPath: indexPath)
let article = articles?[indexPath.row]
if let a = article {
cell.textLabel?.text = a.articleName
cell.textLabel?.font = UIFont(name: "Avenir", size: 18)
cell.textLabel?.numberOfLines = 0
if let i = a.tableViewImage {
cell.imageView?.image = UIImage(named: i)
}
}
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowArticle" {
let articleVC = segue.destinationViewController as? ArticleViewController
guard let cell = sender as? UITableViewCell,
let indexPath = tableView.indexPathForCell(cell) else {
return
}
articleVC?.article = articles?[indexPath.row]
}
}
In the viewController, to show the right article, I wrote this in the viewDidLoad() (my viewController shows a web view of an article, and I have a class Article where articleLink comes from):
if let a = article {
articleWebView.loadRequest(NSURLRequest(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(a.articleLink, ofType: "html")!)))
}
I have linked a nextButton in Main.Storyboard:
#IBOutlet weak var nextButton: UIButton!
I'm relatively new to Swift, and have no idea how to do it (my main problem is because I declare my data in the tableViewController, and I don't see how I could stay in the ViewController and "import" data from the tableViewController).
Change your code of TableViewController's prepareForSegue like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowArticle" {
let articleVC = segue.destinationViewController as? ArticleViewController
guard let cell = sender as? UITableViewCell, let indexPath = tableView.indexPathForCell(cell) else {
return
}
articleVC?.articles = articles
articleVC?.selectedIndex = indexPath.row
articleVC?.article = articles?[indexPath.row]
}
}
Now add two global variable in your ViewController like below also change your next button click like this
var articles: [article]!
var selectedIndex: Int!
//Now your button click
#IBAction func nextButtonClick(sender: UIButton) {
if ((self.selectedIndex + 1) < self.articles.count) {
self.selectedIndex++
let nextArticle = self.articles[self.selectedIndex]
articleWebView.loadRequest(NSURLRequest(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(nextArticle.articleLink, ofType: "html")!)))
}
}
I don't know you are using NSArray or Array so create the articles array object same as you created in TableViewController.
Hope this will help you.
I have a tableView displaying all the users on ViewController1(VC1) when the currentUser chooses a cell it segues to ViewController2(VC2) and needs to display all the user information from the cell that was clicked onto labels and a PFImageView. I have all the user info for the last cell displaying. So even though I clicked a different cell the next VC is only displaying the user at the end of the list. So lets say my user cell list display starts with Aisling, roger, dave and john. I click on cell 'Aisling' and segue to VC2, it displays all of Johns profile info.
VC1:
under the class:
var appUsers = [PFUser]()
var userToShow = PFUser()
then:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let singleCell: CustomCell = tableView.dequeueReusableCellWithIdentifier("mySingleCellid") as! CustomCell
let userObject = appUsers[indexPath.row] as PFObject
singleCell.userName.text = userObject["name"] as? String
singleCell.userAge.text = userObject["age"] as? String
singleCell.usersProfileImage.file = userObject["image"] as? PFFile
singleCell.usersProfileImage.loadInBackground()
// when userToShow is here it shows the user detail of the last cell everytime
userToShow = appUsers[indexPath.row]
return singleCell
} // cell for row
// when userToShow is here it crashes the app when the cell is clicked giving the error:unexpectedly found nil while unwrapping an Optional value
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
userToShow = appUsers[indexPath.row]
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "userProfileDetailsSegue" {
if let destinationVC = segue.destinationViewController as? UserProfileDetailsViewController {
destinationVC.userToShowDetail = userToShow
}
}
}
}
On VC2:
below the class:
var userToShowDetail = PFUser()
Then:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
userName.text = userToShowDetail["name"] as? String
userBio.text = userToShowDetail["bio"] as? String
userAge.text = userToShowDetail["age"] as? String
userProfile.file = userToShowDetail["image"] as? PFFile
userProfile.loadInBackground()
}
It only displays in VC2 the user details belonging to the user in the last cell on VC1. Instead of the user details belonging to the user in the cell that was chosen.
Stuck on this for a few weeks now and my deadline is pushing on can't solve it any help would really be appreciated!!! All links and help I could find online is for C# and obj c.
VC1
//PUT THIS IN THE FIRST VC CLASS
var appUserResult: PFUser?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//TAKE OUT LET - YOU'VE ALREADY DEFINED AS CLASS VAR
appUserResult = appUsers[indexPath.row]
print(appUserResult)
self.performSegueWithIdentifier("openVC2", sender: self)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "openVC2" {
let indexPath = self.resultsPageTableView.indexPathForSelectedRow!
//INDEXPATH ISN'T AVAILABLE IN PREPAREFORSEGUE
//let userToShow = appUsers[indexPath.row]
let newVc = segue.destinationViewController as! UserProfileDetailsViewController
//USE THE CLASS VARIABLE TO PASS
newVc.userToShowDetail = appUserResult
VC2
var userToShowDetail: PFUser?
In storyboard, delete the old segue. Create a new one from VC1 to VC2 (not from the table cells). Give it the identifier "openVC2".
I am trying to update the UILabel on a previous Table View using an Unwinding Segue. I have everything working except I am bringing back blank text each time I select something on my modal. I am essentially bringing back the "" of the variable I am creating (myName).
However, I thought since I am updating the variable in my didSelectRowAtIndex that I would be bring that back. I did check to see if I am getting a value when selecting on the Modal and I am. So I think this is something as simple as updating the variable myName.
NewTableViewController (Modal):
var myName = ""
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let row = Int(indexPath.row)
var selectedObject = objects[row] as! PFObject
var selectedName = selectedObject["myName"] as! String
myName = selectedName as String
println(myName)
}
TableViewController (Source View Controller):
#IBAction func unwindName(segue: UIStoryboardSegue) {
println("unwind working")
if let svc = segue.sourceViewController as? NewTableViewController {
self.myNameLabel.text = svc.myName
println(myNameLabel)
}
}
When you invoke a segue (unwind or any other kind) from a cell, the segue will be executed before didSelectRowAtIndexPath is called, so you shouldn't set your variable value there (in fact you don't need to implement that method at all). You should implement prepareForSegue in the source view controller, and set the value of your variable there.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let cell = sender as! UITableViewCell
let indexPath = self.tableView.indexPathForCell(cell)
var selectedObject = objects[indexPath.row] as! PFObject
var selectedName = selectedObject["myName"] as! String
myName = selectedName as String
}
I have a tableView(top) and a view controller(bottom) within container views in the same view. I need to send info and refresh the bottom view when selecting a table row. I'd like it to work like the apple stocks app. I originally had the bottom view on another page and used a segue and it worked great. But I'm not sure how to do it without a segue now.
Here is the code for selecting the row:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
let item = items[indexPath.row]
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
What I had before for the segue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let controller = segue.destinationViewController as SNPDetailViewController
if let indexPath = tableView.indexPathForCell(sender as UITableViewCell) {
controller.itemToEdit = items[indexPath.row]
}
}
And what was working when I used the segue in the (now) bottom view:
override func viewDidLoad() {
super.viewDidLoad()
if let item = itemToEdit {
title = item.name
snpDetails.text = item.details
}
}
Any help is greatly appreciated!
In your table view controller, self.parentViewController will point to the container view controller. Then you can get a reference to the existing detail view controller via the childViewControllers property of the container:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
let item = items[indexPath.row]
if let vc = self.parentViewController {
let siblings = vc.childViewControllers
if siblings.count > 1 {
if let detailVC = siblings[1] as? SNPDetailViewController {
detailVC.itemToEdit = item
} else {
println("Odd, that detail view controller is not the right class")
abort()
}
} else {
println("Odd, there is no detail view controller")
abort()
}
} else {
println("Strange, I'm not embedded in a parent view controller")
abort()
}
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
This assumes your table view controller is at index 0 of childViewControllers, and the detail view controller is at index 1. Amend siblings[1] to siblings[0] if it's the other way around.
You may need to implement a setter method for itemToEdit in order to reload the labels etc when the value changes.
I guess your bottom view is SNPDetailViewController
As you can see in your previous code you had a variable called controller. That variable is missing in your actual code. Try to create it in your first snippet of code:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
let item = items[indexPath.row]
}
let controller = SNPDetailViewController()
// then you can call the _itemToEdit_ method in your other view
if let indexPath = tableView.indexPathForCell(sender as UITableViewCell) {
controller.itemToEdit = items[indexPath.row]
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
I'm not completely sure if that works but the idea is that you need like an instance of your bottom view to access its methods. Let me know if that helps.