Data in the TableView are removed (Swift) - ios

Data in the TableView is removed (Swift)
I have a code for my application "Notes".
But sometimes the data from the table is merely deleted .
How can I fix this?
If you scroll the page back, the data will be erased
** I use to save and load data:**
func save() {
UserDefaults.standard.set(myData, forKey: "notes")
UserDefaults.standard.synchronize()
}
func load(){
if let loadData = UserDefaults.standard.value(forKey: "notes") as? [String] {
myData = loadData
table.reloadData()
}
}
ViewController 1
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var table: UITableView!
var myData: [String] = []
var selectedRow: Int = -1
var newRowText:String = ""
var detailView: DetailViewController!
override func viewDidLoad() {
super.viewDidLoad()
load()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if selectedRow == -1 {
return
}
myData[selectedRow] = newRowText
if newRowText == "" {
myData.remove(at: selectedRow)
}
table.reloadData()
save()
}
#objc func AddNewNotes(){
if table.isEditing{
return
}
let name:String = ""
myData.insert(name, at: 0)
let indexPath: IndexPath = IndexPath(row: 0, section: 0)
table.insertRows(at: [indexPath], with: .automatic)
table.selectRow(at: indexPath, animated: true, scrollPosition: .none)
self.performSegue(withIdentifier: "detail", sender: nil )
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = myData[indexPath.row]
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let detailView:DetailViewController = segue.destination as!DetailViewController
selectedRow = table.indexPathForSelectedRow!.row
detailView.masterView = self
detailView.setText(t: myData[selectedRow])
}
}

Since the data source array that you are filling the table view from is myData, it seems that you would need to call your save() method before reloading the table view (table.reloadData()) in the viewWillAppear(_:) life cycle method:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// ...
save()
myTableView.reloadData()
// ...
}
Also, (obviously) make sure that myData data source contains the saved desired data.
However, I would recommend to save the data before even leaving the second view controller, probably viewWillDisappear(_:) would be a good place to do it; By applying this, there is no need to call save() in the first view controller anymore, all you have to do is to reload the table view. That would be more logical because the first view controller should displays the saved data, but not saving it, it shall be in the second view controller where you are adding data.

Related

Swift Tableview Load Data from Firebase Database

I am currently trying to display data from my real-time database of Firebase in different tableviews.
In my first tableview I load my first level of my database structure (this already works).
Now I want to see what the user selects in the first tableview, and then display the next level of my database in a new TableView.
I have a function in my second TableViewController.swift file where to save in the selected row from the first TableView.
This way I want to save the next level from my database into an array so that this data will be displayed in my second tableview. When I then debug my new array, I also see the correct data in the new array. However, the data is not displayed in the second TableView.
I guess it's because the data is not 100% ready before the TableView loads.
Do you have a tip?
Firebase Structure:
-sports
-Bicycle
-BMX
-Bike 1
-img: „bike1.png“
-text: „bike 1“
-Bike 2
-img: „bike2.png“
-text: „bike 1“
-Car
-Audi
-Car 1
-img: „car1.png“
-text: „car 1“
-Car 2
-img: „car2.png“
-text: „car 2“
FirstTableViewController:
import UIKit
import Firebase
class FirstTableViewController: UITableViewController {
var categorie = [String]()
func loadData() {
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("sports").observeSingleEvent(of: .value, with: { snapshot in
if let sports = snapshot.value as? [String: Any] {
for (title, _) in sports {
self.categorie.append(title)
self.tableView.reloadData()
}
}
})
}
override func viewDidLoad() {
loadData()
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categorie.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "sportCell")
cell?.textLabel?.text = categorie[indexPath.row]
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the segue's identifier in the Storyboard
performSegue(withIdentifier: "firstToSecond", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "firstToSecond" {
guard let destination = segue.destination as? SecondTableViewController,
let indexPath = tableView.indexPathForSelectedRow else { return }
destination.detailedValue = categorie[indexPath.row]
}
}
}
SecondTableViewController:
import UIKit
import Firebase
class SecondTableViewController: UITableViewController {
var detailedValue: String?
var secondArray = [String]()
func setIndex(value: String) {
loadData(index: value)
}
func loadData(index: String) {
var ref: DatabaseReference!
ref = Database.database().reference()
if (index != "") {
ref.child("sports").child(index).observeSingleEvent(of: .value, with: { snapshot in
if let sports = snapshot.value as? [String: Any] {
for (title, _) in sports {
self.secondArray.append(title)
self.tableView.reloadData()
}
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let detailedValue = detailedValue {
loadData(index: detailedValue)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return secondArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "sorteCell")
cell?.textLabel?.text = secondArray[indexPath.row]
return cell!
}
}
UPDATE 1:
Thanks to #Jay Lee for the above code.
UPDATE 2:
You are not loading the data to the SecondTableViewController instance that is presented on your screen, but to a new SecondTableViewController instance that you created in the func tableView(_:=,cellForRowAt:) method in your FirstTableViewController.
The logs are printed from the multiple instances you created from it.
This is not what you want, as you are creating multiple SecondTableViewController instances every time a new cell shows in your FirstTableViewController.
You should rather get a reference to the actual SecondTableViewController that is presented and supply the data it.
If you are using a storyboard, you can use prepare(for:sender:) to do that.
We have two choices: provide the entire data from the FirstTableViewController to SecondTableViewController using a delegate design pattern, or just provide value to SecondTableViewController and leave the fetching to it.
Based on your code, you can just supply the SecondTableViewController with value that your setIndex(value:) method in the SecondTableViewController uses, and get the data after the SecondTableViewController loads.
For example, in your SecondTableViewController:
class SecondTableViewController: UITableViewController {
...
var detailedValue: String?
override func viewDidLoad() {
super.viewDidLoad()
if let detailedValue = detailedValue {
setIndex(value: detailedValue)
}
}
...
}
and in your FirstTableViewController:
class FirstTableViewController: UITableViewController {
...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the segue's identifier in the Storyboard
performSegue(withIdentifier: "yourIdentifier", sender: self)
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier" {
guard let destination = segue.destination as? SecondTableViewController,
let indexPath = tableView.indexPathForSelectedRow else { return }
destination.detailedValue = categories[indexPath.row]
}
}
}
But note that you already have a data to be shown on SecondTableViewController in your FirstTableViewController, so you should probably make a protocol and set FirstTableViewController its delegate.
EDIT:
Your segue should not be connected like this:
but like this:

TableView Only Updates after Screen Rotates

Image of the tableview
I have a tableview with a collection view in each cell, all linked to an array. Each collection view has tags, so when I have stuff in the array from the beginning, all tableview cells and collection view cells appear properly in the app. But when I add an element to the array in the app itself (I have a second view controller with the stuff to do that), it works but the new table view cell only appears after the screen rotates (really odd). I have tried adding an object of the view controller with the table view in the second view controller where I add an element to the array. Then in the second view controller in ViewWillDisappear, I reloadData() through that object like this:
var vc : ViewController? = ViewController()
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
vc?.listOfActs.reloadData()
}
But this results in an EXC_BAD_INSTRUCTION
Then I tried adding self.listOfActs.reloadData() in the prepareForSegue in the view controller with the table view just so that I could see that it at least refreshes the data at some point in time but even that doesn't work when I click on add scene a second time.
UPDATE: New MainViewController
This is the new first view controller with the table view. I renamed it and have implemented the method for adding to array and reloading. It kind of works if I use an if let on the reloadData but then I'm back to square one where it only updates when I rotate the screen. When I get rid of the if let so it can actually try to update the table view, it gives me a Fata error: unexpectedly found a nil while unwrapping.
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//The Table View
#IBOutlet var AddActButton: UIButton!
#IBOutlet weak var listOfActs: UITableView!
var sectionTapped : Int?
var indexitemTapped : Int?
override func viewDidLoad() {
super.viewDidLoad()
listOfActs.delegate = self
listOfActs.dataSource = self
}
//Table View Functions
func numberOfSections(in tableView: UITableView) -> Int {
return actsCollection.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "actCell", for: indexPath) as! ActCell
cell.setCollectionViewDataSourceDelegate(self, forSection: indexPath.section)
return cell
}
//Add To Table View
func addObjects(appendage: Act) {
actsCollection.append(appendage)
if let shit = listOfActs {
shit.reloadData()
}
}
//Header Cell
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableView.dequeueReusableCell(withIdentifier: "headerCell") as! HeaderCell
cellHeader.headerName.text = actsCollection[section].actName
return cellHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
}
//Scene Collection in Act Cell
extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return actsCollection[collectionView.tag].actScenes.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sceneCell", for: indexPath) as! SceneCell
cell.sceneTitle.text = actsCollection[collectionView.tag].actScenes[indexPath.item].sceneTitle
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
sectionTapped = collectionView.tag
indexitemTapped = indexPath.item
performSegue(withIdentifier: "showDetail", sender: self)
}
//Segue Prepare
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let detailsVC = segue.destination as! SceneDetailController
detailsVC.textToAppearInSceneName = actsCollection[sectionTapped!].actScenes[indexitemTapped!].sceneTitle
}
}
}
UPDATE:New second view controller, the one that adds to the array.
class AddActController: UIViewController, UITextFieldDelegate {
#IBOutlet var sceneLiveName: UILabel!
#IBOutlet var sceneNameTextField: UITextField!
#IBOutlet var sceneDescriptionTextField: UITextField!
#IBOutlet var AddSceneButton: UIButton!
#IBOutlet var cardBounds: UIView!
var newName: String? = ""
#IBOutlet var cardShadow: UIView!
var shit = MainViewController()
override func viewDidLoad() {
super.viewDidLoad()
sceneNameTextField.delegate = self
AddSceneButton.alpha = 0.0
cardBounds.layer.cornerRadius = 20.0
cardShadow.layer.shadowRadius = 25.0
cardShadow.layer.shadowOpacity = 0.4
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.2){
self.AddSceneButton.alpha = 1.0
}
}
#IBAction func exitButton(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
#IBAction func addSceneButton(_ sender: UIButton) {
if newName == "" {
sceneLiveName.text = "Enter Something"
sceneNameTextField.text = ""
}
else {
let appendAct: Act = Act(actName: newName!, actTheme: "Action", actScenes: [Scene(sceneTitle: "Add Act", sceneDescription: "")])
shit.addObjects(appendage: appendAct)
dismiss(animated: true, completion: nil)
}
}
//MARK: textField
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text: NSString = (sceneNameTextField.text ?? "") as NSString
let resultString = text.replacingCharacters(in: range, with: string)
sceneLiveName.text = resultString
newName = String(describing: (sceneLiveName.text)!.trimmingCharacters(in: .whitespacesAndNewlines))
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
sceneNameTextField.resignFirstResponder()
return true
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Here is the class for the uitableviewcell that contains its own collection view.
class ActCell: UITableViewCell {
#IBOutlet fileprivate weak var sceneCollection: UICollectionView!
}
extension ActCell {
func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource & UICollectionViewDelegate>(_ dataSourceDelegate: D, forSection section: Int) {
sceneCollection.delegate = dataSourceDelegate
sceneCollection.dataSource = dataSourceDelegate
sceneCollection.tag = section
sceneCollection.reloadData()
}
}
And here is the model with the user's data including the acts and scenes.
struct Scene {
var sceneTitle: String
var sceneDescription: String
//var characters: [Character]
//var location: Location
}
struct Act {
var actName: String
var actTheme: String
var actScenes : [Scene] = []
}
var actsCollection : [Act] = [
Act(actName: "dfdsfdsfdsf", actTheme: "Action", actScenes: [Scene(sceneTitle: "Example Act", sceneDescription: "")])
]
Any help is greatly appreciated. Thank you to all.
So if I'm not mistaken I believe the viewDidLoad method gets call during screen rotations. So this explains why it update during so. Now to get it to update without rotating the device, I would add an observer in the notificationCenter to watch for any updates to the tableView then call a #selector to do the reloadData(). So here is an example of this. In the viewDidLoad method add
NotificationCenter.default.addObserver(self, selector: #selector(refreshTable), name: NSNotification.Name(rawValue: "load"), object: nil)
Then add the method refreshTable()
func refreshTable() {
listOfActs.reloadData()
}
This is basically how I handle keeping the tableView refreshed.
Well - viewDidLoad is loaded only for the first time controller loads his view (not sure about rotation).
If you really need - you can reload tableView in viewWillAppear - but I wouldn't do this.
Instead of
actsCollection.append(appendAct)
dismiss(animated: true, completion: nil)
create a method on the first controller like addObjectToList(appendAct) and in that method, just easily append object to your list array and reload tableView after adding.
You will be reloading tableView only when you really add something to your list and not every time controller appears, you also don't need notification observer.
EDIT - UPDATE
What is this?
if newName == "" {
sceneLiveName.text = "Enter Something"
sceneNameTextField.text = ""
}
else {
let appendAct: Act = Act(actName: newName!, actTheme: "Action", actScenes: [Scene(sceneTitle: "Add Act", sceneDescription: "")])
shit.addObjects(appendage: appendAct)
dismiss(animated: true, completion: nil)
}
I mean - what is shit.AddObjects? Shit is defined as tableView - but you have to call this method on instance of your controller.
Another thing - change your setup from sections == number of items with 1 row to be one section with number of rows == number of items. :)

How can I pass one just one part of a struct?

I'm trying to pass just the image part of a struct (containing also two textFields, and another imageView I'd like to not pass).
Here is the tableViewController
import UIKit
class SentMemesTableViewController: UITableViewController {
var _tableView: UITableView!
var memeData: [Meme] = []
//calling memes from array in Delegate
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var memes: [Meme] {
return appDelegate.memes
}
override func viewWillAppear(_ animated: Bool) {
tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.isScrollEnabled = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MemeDetailViewController" ,
let nextScene = segue.destination as? MemeDetailViewController ,
let indexPath = tableView.indexPathForSelectedRow {
let selectedMeme = memes[indexPath.row].memedImage
nextScene.sentMemeView.image = Meme.memedImage
}
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//navigationController!.pushViewController(MemeDetailViewController, animated: true)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return memes.count
}
// Here it is! -----
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "sentMemesTableView") as! MemeTableViewCell
let meme = memes[indexPath.row]
tableViewCell.tableViewImage.image = meme.memedImage
tableViewCell.tableViewLabel.text = "\(meme.topText)...\(meme.bottomText)"
return tableViewCell
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return false
}
func deleteMemesInTableViewCell(_ index: Int) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.memes.remove(at: index)
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
tableView.beginUpdates()
deleteMemesInTableViewCell(indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.left)
tableView.endUpdates()
}
}
}
Here is the Meme and SentMemeView structs.
import Foundation
import UIKit
struct Meme {
let topText: String
let bottomText: String
let originalImage: UIImage
let memedImage: UIImage
}
struct SentMemeImageView {
var memedImageDetailVC: UIImageView
}
I can't find a way to call it successfully.
Here is the MemeDetailViewController. I need just for the sentMemeView to display the SentMemeImageView.
class MemeDetailViewController: UIViewController {
var meme = SentMemeImageView?.self
#IBOutlet weak var sentMemesBtn: UIBarButtonItem!
#IBOutlet weak var editBtn: UIBarButtonItem!
#IBOutlet weak var sentMemeView: UIImageView!
func displayMeme(_ meme: SentMemeImageView) {
}
#IBAction func launchMemeEditorViewController(_ sender: Any) {
_ = navigationController?.popViewController(animated: true)
}
//unwinding to the view before (the collectionView, or the tableView)
#IBAction func unwindVC(for unwindSegue: UIStoryboardSegue, towardsViewController subsequentVC: UIViewController) {
self.dismiss(animated: true, completion: nil)
}
}
First of all, sad to say that, your code is quite messed up:
You have with three different kinds of data sources, some are ignored, some you work with, but not in a consistent way:
data from the app delegate (deletion of data)
memeData property (display of data)
memes property (ignored)
You should really focus on where the data is stored and how to access it.
Secondly, you won't send views from one view controller to the other, but data. So rather use UIImage than UIImageView. So you'll hand in a SentMemeImage to the details controller.
I tried to clean up the code a little, but just to answer your explicit question. Please refactor it!
struct Meme {
let topText: String
let bottomText: String
let originalImage: UIImage
let memedImage: UIImage
}
struct SentMemeImage {
var memedImage: UIImage
}
class SentMemesTableViewController: UITableViewController {
var memeData: [Meme] = []
// ----8<---- snipp ----8<----
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MemeDetailViewController" ,
let nextScene = segue.destination as? MemeDetailViewController ,
let indexPath = tableView.indexPathForSelectedRow {
let selectedMeme = memeData[indexPath.row]
let sentMemeImage = SentMemeImage(memedImage: selectedMeme.memedImage)
nextScene.meme = sentMemeImage
}
}
// ----8<---- snipp ----8<----
}
class MemeDetailViewController: UIViewController {
var meme:SentMemeImage?
#IBOutlet weak var sentMemesBtn: UIBarButtonItem!
#IBOutlet weak var editBtn: UIBarButtonItem!
#IBOutlet weak var sentMemeView: UIImageView!
func displayMeme() {
self.sentMemeView.image = self.meme?.memedImage
}
}

View doesn't get updated until the second time loaded

I have a main view which is a table view with a list of countries. When clicking on any country name (cell), another view is loaded via segue which is passing the name of the country to the next view controller's navigation bar title.
The problem is on the first click the title isn't updated, but when I click back button (dismissing the current view) and click on another country name, the second view loads again and shows the previous title that was suppose to be shown on the first attempt.
The code for the first main view controller:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var sectionsArray = [String]()
var sectionsCountries = [Array<AnyObject>]()
#IBOutlet weak var countries: UITableView!
internal func numberOfSections(in tableView: UITableView) -> Int {
// Return the number of sections.
return self.sectionsArray.count
}
internal func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return self.sectionsCountries[section].count
}
internal func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sectionsArray[section]
}
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CountryCell", for: indexPath)
cell.textLabel?.text = self.sectionsCountries[indexPath.section][indexPath.row] as? String
return cell
}
var valueToPass:String!
internal func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.row)!")
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRow(at: indexPath!) as UITableViewCell!;
valueToPass = currentCell?.textLabel?.text
performSegue(withIdentifier: "cellSegue", sender: self)
//print(valueToPass)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "cellSegue" {
let destination = segue.destination as! CountryViewController
destination.passedValue = valueToPass
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = URL(string: "http://cyber7.co.il/swift/countries/countries-list.json")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else {
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers)
for result in jsonResult as! [Dictionary<String, AnyObject>]{
self.sectionsArray.append(result["sectionName"] as! String)
self.sectionsCountries.append(result["sectionCountries"] as! Array<String> as [AnyObject])
}
} catch {
print("JSON Processing Failed")
}
DispatchQueue.main.async(execute: { () -> Void in
self.countries.reloadData()
})
}
}
}
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
the code for the second view controller:
import UIKit
class CountryViewController: UIViewController {
var passedValue:String!
#IBOutlet weak var navBar: UINavigationBar!
#IBAction func backButton(_ sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
self.navBar.topItem?.title = passedValue
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
When you have a segue set up from a table view cell to a view controller in the storyboard then it is performed automatically when a cell is selected. Your call to perform a segue in your cell selection method is performing the segue a second time after the first one has already been performed.
Remove your tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) method and do all the data passing logic in prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "cellSegue" {
let destination = segue.destination as! CountryViewController
let indexPath = countries.indexPathForSelectedRow
let currentCell = countries.cellForRow(at: indexPath!)
destination.passedValue = currentCell?.textLabel?.text
}
}

Passing coredata from tableview to another tableview

I am struggling with getting my care data to populate my second tableview controller. The data is populating the first tableview and I can select a row and the segue is used to go to the second table but the labels are not populated.
I've looked all over and have found older samples or obj-c but I cannot figure it out, so any help pointing this n00b in the right direction will be helpful.
Here is what I have, I think I am missing how to populate a variable to pass in prepareForSegue in the list tableview, but I could be wrong. I get a warning error in that function (Warning cannot assign value of type 'ListEntity' to type '[ListEntity]').
CoreData
Entity = ListEntity
Attributes = title, event & location (all as Strings)
listTableViewController
import UIKit
import CoreData
class ListTableViewController: UITableViewController {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var lists = [ListEntity]()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "The List"
let addButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: #selector(ListTableViewController.addButtonMethod))
navigationItem.rightBarButtonItem = addButton
}
func addButtonMethod() {
print("Perform action")
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
reloadData()
tableView.reloadData()
}
func reloadData() {
let fetchRequest = NSFetchRequest(entityName: "ListEntity")
do {
if let results = try managedObjectContext.executeFetchRequest(fetchRequest) as? [ListEntity] {
lists = results
}
} catch {
fatalError("There was an error fetching the list!")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lists.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ListCell") as! ListTableViewCell
let list = lists[indexPath.row]
cell.configurationWithSetup(list)
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("DetailsSegue", sender: self)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "DetailsSegue" {
let destinationVC = segue.destinationViewController as! DetailsTableViewController
let indexPath : NSIndexPath = self.tableView.indexPathForSelectedRow!
print(indexPath.row) // Print the Row selected to console
// Place the code to pass data here?
// destinationVC.lists = lists[indexPath.row]
// Warning cannot assign value of type 'ListEntity' to type '[ListEntity]'
}
}
}
listTableViewCell
import UIKit
class ListTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
func configurationWithSetup(list: AnyObject) {
titleLabel.text = list.valueForKey("title") as! String?
}
}
detailsTableViewController
import UIKit
import CoreData
class DetailsTableViewController: UITableViewController {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var lists = [ListEntity]()
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DataCell") as! DetailsTableViewCell
let list = lists[indexPath.row]
cell.configurationWithSetup(list)
return cell
}
}
detailsTableViewCell
import UIKit
import CoreData
class DetailsTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var eventLabel: UILabel!
#IBOutlet weak var locationLabel: UILabel!
func configurationWithSetup(list: AnyObject) {
titleLabel.text = list.valueForKey("title") as! String?
eventLabel.text = list.valueForKey("event") as! String?
locationLabel.text = list.valueForKey("location") as! String?
}
}
The warning contains the answer - just change
var lists = [ListEntity]() to
var lists = ListEntity(), or var lists:ListEntity! and when you prepare for segue set that value.
Then you will need to change
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DataCell") as! DetailsTableViewCell
// as data source is not array you can just you the item you passed
// let list = lists[indexPath.row]
cell.configurationWithSetup(lists)
return cell
}
You should use a static table view if you just want one cell
More info per you current issue
class DetailsTableViewController: UITableViewController {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var theDetailListEntity:ListEntity!
override func viewDidLoad() {
super.viewDidLoad()
print(theDetailListEntity) // check that you passed it across
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DataCell") as! DetailsTableViewCell
cell.configurationWithSetup(theDetailListEntity)
return cell
}
}
Don't forget to add prepare for segue in the listTableViewController otherwise theDetailListEntity won't be set... and then it will crash.
Depending on how you set up your segue, it may differ. But this is what you need
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("showMyDetailView", sender: indexPath)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showMyDetailView" {
guard let
vc = segue.destinationViewController as? DetailsTableViewController,
ip = sender as? NSIndexPath else { fatalError() }
let item = lists[ip.row]
vc.theDetailListEntity = item
// set the item in the next VC
tableView.deselectRowAtIndexPath(ip, animated: true)
}
}

Resources