Passing value from struct array with segue : index out of range - ios

Hopefully this will be the last question i need to ask!
I have been looking into this for 48 hours now and i still cannot find answers.
Here is the code i am using:
DataSource.swift:
struct Game {
var name : String
var cheats : [Cheat]
}
struct Cheat {
var name : String
var code : String
var desc : String
}
GameListViewController.swift
import Foundation
import UIKit
class GameListViewController: UITableViewController {
var gamesArray = [Game]()
var cheatsArray = [Cheat]()
override func viewDidLoad() {
super.viewDidLoad()
gamesArray = [Game(name: "Game1", cheats: [Cheat(name: "cheat1", code: "code1", desc: "desc1")])]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return gamesArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell!
cell.textLabel?.text = gamesArray[indexPath.row].name
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath : NSIndexPath = self.tableView.indexPathForSelectedRow!
let DestViewController = segue.destinationViewController as! CheatListViewController
var DataPass : Cheat
DataPass = cheatsArray[indexPath.row]
DestViewController.cheatnameArray = DataPass.name
var DataPass2 : Cheat
DataPass2 = cheatsArray[indexPath.row]
DestViewController.cheatcodeArray = DataPass2.code
var DataPass3 : Cheat
DataPass3 = cheatsArray[indexPath.row]
DestViewController.cheatdescArray = DataPass3.desc
}
}
CheatListViewController.swift
class CheatListViewController: UITableViewController {
var cheatcodeArray = String()
var cheatnameArray = String()
var cheatdescArray = String()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
When i select "Game 1" from gamesArray i instantly receive an index out of range error from the first instance of "DataPass".
I have structured my datasource in this way so that i do not have to edit arrays separately and keep my objects neat and tidy.
If someone could point me in the right direction i would be forever grateful !
Kind regards
Rory

For me it looks like you haven't populated your cheatsArray variable with any cheats. That's why you receive an index out of range exception.
From your code it is a bit hard to understand what you're looking to achieve, but I think I have it..
Notice I use an optional binding to unwrap the destinationViewController, this is safe because any other segue performed will also trigger the same prepareForSegue.
if let destViewController = segue.destinationViewController as? CheatListViewController {
let indexPath : NSIndexPath = self.tableView.indexPathForSelectedRow!
var game = gamesArray[indexPath.row]
destViewController.cheatnameArray = game.cheats.map({ $0.name })
destViewController.cheatcodeArray = game.cheats.map({ $0.code })
destViewController.cheatdescArray = game.cheats.map({ $0.desc })
}
Change your arrays to actual string arrays and not strings..
var cheatcodeArray = [String]()
var cheatnameArray = [String]()
var cheatdescArray = [String]()
The Basics

Related

Preserve data in table from a View to another

I'm trying to send values from one view to other and print them in a table as an array. The program work and display the data but the problem is that when I try to add another value to the table when I return to the view that have the table the previous values are no longer there.
In this segment of code I sent the data to the other view
import UIKit
class NewContactoViewController: UIViewController, UITextFieldDelegate {
var contacto: String = ""
var numero: String = ""
#IBOutlet weak var contactoField: UITextField!
#IBOutlet weak var numField: UITextField!
let defaultValues = UserDefaults.standard
#IBAction func addButton(_ sender: UIButton) {
contacto = contactoField.text!
numero = numField.text!
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var secondController = segue.destination as! ContactosViewController
secondController.contactos = contactoField.text!
secondController.numerosmov = numField.text!
}
override func viewDidLoad() {
super.viewDidLoad()
self.contactoField.delegate = self
self.numField.delegate = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In this segment of code are the tableviews that display the data
import UIKit
class ContactosViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let defaultValues = UserDefaults.standard
var contactos: String = ""
var numerosmov: String = ""
var tablacontacto = [String] ()
var tablanumero = [String] ()
let cellIdentifier: String = "cell"
let cellIdentifier2: String = "cell2"
#IBOutlet weak var contactoTable: UITableView!
#IBOutlet weak var numTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let backButton = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: navigationController, action: nil)
navigationItem.leftBarButtonItem = backButton
// Do any additional setup after loading the view.
datosRecividos(contactos, numerosmov)
contactoTable.delegate = self
numTable.delegate = self
contactoTable.dataSource = self
numTable.dataSource = self
contactoTable!.reloadData()
numTable!.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
contactoTable.reloadData()
numTable.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func datosRecividos(_ contactosr: String, _ numerosr: String)
{
tablacontacto.append(contactosr)
tablanumero.append(numerosr)
let usercontacto = defaultValues.array(forKey: "contactoTable")
let usernumero = defaultValues.array(forKey: "numeroTable")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (tableView.tag == 1)
{
return(tablacontacto.count)
}
else if (tableView.tag == 2)
{
return(tablanumero.count)
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
if (tableView.tag == 1)
{
cell.textLabel?.text = tablacontacto[indexPath.row] as! String
}
else if (tableView.tag == 2)
{
cell.textLabel?.text = tablanumero[indexPath.row] as! String
}
return(cell)
}
}
There are several things wrong here, I would suggest reading again about tableView's (especially the "Load Initial Data" section) -
https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/CreateATableView.html
Your tables are getting data from the "tablacontacto" and "tablanumero" arrays.
There is no place in the code you sent to populate these arrays. (Do you see anything when these tables are on screen?)
Plus You are updating these arrays with only in the "func datosRecividos(_ contactosr: String, _ numerosr: String)" -
This method is only called once in the viewDidLoad and it is not called when you segue back to this screen
Plus you have no place you get data to your arrays from your userDefaults and there is no place you save the "new" data from "NewContactoViewController" to your userDefaults.
Make this two variables as static variables
internal static var CONTACTTO: String = ""
internal static var NUMERO: String = ""
then access this variables using,
NewContactoViewController.CONTACTTO
NewContactoViewController.NUMERO
Then there is no need sending this values using
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var secondController = segue.destination as! ContactosViewController
secondController.contactos = contactoField.text!
secondController.numerosmov = numField.text!
}
Or otherwise you can save these values in shared preferences
You can declare a delegate for data changing in NewContactoViewController
import UIKit
//Create a delegate for data changing
protocol ContactChageDelegate: class {
func contactChanged(newContact: String, newNumber: String)
}
class NewContactoViewController: UIViewController, UITextFieldDelegate {
//declare delegate variable
weak var contactChageDelegate: ContactChageDelegate?
...
#IBAction func addButton(_ sender: UIButton) {
contacto = contactoField.text!
numero = numField.text!
//if need notify data changing. maybe it will not change
//if data change {
self.contactChageDelegate.contactChanged(newContact: contacto, newNumber: numero)
//}
}
and use this delegate in ContactosViewController like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//let self to delegate
if let newContactoViewController = segue.destination as? NewContactoViewController {
newContactoViewController.contactChageDelegate = self
}
}
...
//implement delegate method
extension ContactosViewController: ContactChageDelegate {
internal func contactChanged(newContact: String, newNumber: String) {
//now you have new values
//change your data array
//and reload table
}
}

iOS - Index Out Of Range

I've stumbled across an error in my application:
fatal error: Index out of range (lldb)
I think I may have an idea as to what the problem is, however, don't have a clue on how to amend the error.
I believe due to the fact I'm using section headers, this is causing the problem. I've proof read the coding as well as trying to fix it and searching online. Below I have posted a sample of my code (didn't want to include it all as it includes a few hundred lines of code).
Essentially, I am using a TableViewController in combination with SWReveal where the user selects an option and text will appear.
class BackTableVC: UITableViewController {
struct Brands {
var sectionName : String!
var sectionBrands : [String]!
}
struct ThirdView {
var ThirdViewArray = [String]()
}
var brandArray = [Brands]()
var ThirdArray = [ThirdView]()
var brandAnswerArray = [String]()
override func viewDidLoad() {
brandArray = [
Brands(sectionName: "Bugatti", sectionBrands: ["EB 110","Veyron"])]
ThirdArray = [ThirdView(ThirdViewArray: ["EB 110","Veyron"])]
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return brandArray[section].sectionBrands.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell!
cell.textLabel?.text = brandArray[indexPath.section].sectionBrands[indexPath.row]
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let DestVC = segue.destinationViewController as! CarDetailsVC
let indexPath : NSIndexPath = self.tableView.indexPathForSelectedRow!
let ThirdAnswerArray : ThirdView
ThirdAnswerArray = ThirdArray[indexPath.row]
DestVC.brandAnswerArray = ThirdAnswerArray.ThirdViewArray
DestVC.FirstString = brandAnswerArray[indexPath.row]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return brandArray.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return brandArray[section].sectionName
}
}
import Foundation
struct ThirdView {
var ThirdViewArray = [String]()
}
class CarDetailsVC: UIViewController {
var FirstString = String()
var brandAnswerArray = [String]()
#IBOutlet var Label: UILabel!
override func viewDidLoad() {
Label.text = FirstString
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
_ = segue.destinationViewController as! CarDetailsVC
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
My ThirdView struct and CarDetailsVC are in separate .swift files.
The line which is giving me grief is:
DestVC.FirstString = brandAnswerArray[indexPath.row]
P.S. if I was to do this:
DestVC.FirstString = "Hello World"
Hello World is shown when selecting only the first option, then the code/application breaks an I get the same error "index out of range" on the line:
ThirdAnswerArray = ThirdArray[indexPath.row]
This simple answer is that your brandAnswerArray doesn't have enough values to give you the thing at index indexPath.row. i.e. If you have an array with 5 values and you ask it for array[8], the app will crash because index 8 doesn't exist.
Specifically, you are telling your table that you have a certain number of cells/rows:
brandArray[section].sectionBrands.count
That means for every integer, from 0, to whatever brandArray[section].sectionBrands.count is, the table is going to ask you to generate a cell. Therefore, that is the range that your indexPath.row can have.
BUT: In your prepareForSegue, you are accessing brandAnswerArray[indexPath.row], and brandAnswerArray simply doesn't have enough values to give you whatever is at that requested index (which is a risk, since you used a different portion of data to build the table).

Array does not stacking Class object

I am trying to initialize and then append Class object to array through delegate func. Delegate Double comes from button press with some data.
var expensesArray = [SpendedMoneyObject]()
var delegatedDouble:Double = 0.0
func setExpenses(expensesFromMainView: Double) {
delegatedDouble = expensesFromMainView
var aSpendedMoneyObject = SpendedMoneyObject(moneySpent: delegatedDouble)
expensesArray += [aSpendedMoneyObject]
self.tableView.reloadData()
}
Problem here is that I am trying to show array at TableViewCell, but it doesn't showing at all, I guess main problem is that expensesArray value is 1 and it's not stacking but replacing same array with other value. Will be really happy to hear what you think.
Edit:
I tried .append and it still the same also TableView func cellForRowAtIndexPath does not getting called.
class ExpensesTableViewController: UITableViewController, ExpensesEnteredDelegate{
//MARK : Properties
var expensesArray = [SpendedMoneyObject]()
var delegatedDouble:Double = 0.0
override func viewDidLoad() {
super.viewDidLoad()
}
func setExpenses(expensesFromMainView: Double) {
delegatedDouble = expensesFromMainView
var aSpendedMoneyObject = SpendedMoneyObject(moneySpent: delegatedDouble)
expensesArray.append(aSpendedMoneyObject)
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return expensesArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "ExpensesCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ExpensesTableViewCell
print("Iam here")
let expense = expensesArray[indexPath.row]
let fromDoubleToString = "\(expense.moneySpent)"
cell.loadItemsToCell(fromDoubleToString, date: expense.date)
return cell
}
Object class:
class SpendedMoneyObject {
var moneySpent: Double
var currentTime = NSDate().toShortTimeString()
var date: String
init(moneySpent: Double) {
self.date = currentTime
self.moneySpent = moneySpent
}}
Edit: I can now add more than one array by moving new ViewController creation from spendButton func which was creating newVC every time i clicked button. Here are edited code:
protocol ExpensesEnteredDelegate {
func setExpenses(expensesFromMainView: Double)
}
class MainViewController: UIViewController {
#IBOutlet weak var moneyTextField: UITextField!
var delegate: ExpensesEnteredDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Defining ExpensesVC
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newExpensesVC = storyboard.instantiateViewControllerWithIdentifier("ExpensesTableView") as! ExpensesTableViewController
delegate = newExpensesVC
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func spentButton(sender: AnyObject) {
// Delegating expenses Double value
if (delegate != nil) {
let myDouble = Double(moneyTextField.text!)
let expenses: Double = myDouble!
delegate!.setExpenses(expenses)
}
}}
But still array's data does not showing up in a tableView
Try this code example:
let aSpendedMoneyObject = SpendedMoneyObject(moneySpent: delegatedDouble)
expensesArray.append(aSpendedMoneyObject)
Or you can use .extend() method if you are looking to append more elements from a different array to your array.
instead of
expensesArray += [aSpendedMoneyObject]
try
expensesArray.append(aSpendedMoneyObject)

Swift display entire array on textview or label

I am working on a cookbook app and I thought I had the knowledge to get the last bit up and running and I have run into a snag. I am trying to get my ingredients array to display on a text field and I cannot get it to work. I have done some research and I have tried a few different things and all of them come up with different errors. I have left them in the code but commented them out so maybe someone looking at it could see what I was doing wrong with what I had going. My code looks as follows for my view controller that will display the ingredients.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var ingredients: UITextView!
#IBOutlet weak var directions: UITextView!
var recipe : Recipe!
// var ingredientText = String()
// let separator = (" / ")
override func viewDidLoad() {
super.viewDidLoad()
self.title = recipe.name
directions.text = recipe.directions
// let ingredientsText = separator.join(recipe.ingredients)
// label.text = ingredientsText
// for ingredient: String in recipe.ingredients {
// ingredientsText.join("%#\n", ingredient)
// }
// label.text = ingredientText
// label.text = recipe.ingredients
// var multiLineString = join("\n", recipe.ingredients)
// label.text = multiLineString
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
this is the table VC
import UIKit
class tableVC: UITableViewController {
var recipes: [Recipe] = []
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Juicing Recipes"
let recipe0: Recipe = Recipe()
recipe0.name = "Number 1"
recipe0.ingredients = ["Pasta","Pasta Sauce","Hamburger"]
recipe0.directions = "Cook pasta to directions on box, brown hamburger, add sauce to hamburger, dump on pasta"
recipes.append(recipe0)
let recipe1: Recipe = Recipe()
recipe1.name = "Number 2"
recipe1.ingredients = ["all kinds of stuff","Then a dash of salt"]
recipe1.directions = "enjoy"
recipes.append(recipe1)
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recipes.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
let recipe: Recipe = recipes[indexPath.row]
cell.textLabel?.text = recipe.name
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath : NSIndexPath = self.tableView.indexPathForSelectedRow!
let destCon : ViewController = segue.destinationViewController as! ViewController
destCon.recipe = recipes[indexPath.row]
}
}
then the recipe class
class Recipe: NSObject {
var name = String()
var ingredients = [String]()
var directions = String()
}
It's another one of those things I know it something simple that I am overlooking but I have been stumped on this for a while.

Swift label not displaying what the selected cell says

I have a tableview that is populated with information from a JSON array. I want to make each selected cell segue into a viewController, and in that viewController I have a label the should display what the selected cell says. For example if my cell says California, when I click on the cell it'll open up my viewController and the label would say California.
Seems simple enough, and I've done this before successfully, however this time I'm using JSON to populate my tableView and I'm guessing I'm doing something wrong. With the code posted below, when I click on a cell the titleLabel doesn't even show up.
(My tableView file and DetailsViewController file are posted below, any other swift file I used can be found in my previous question populating Tableview with a function that uses SwiftyJSON)
import UIKit
class EarthTableViewController: UITableViewController {
var info = [AppModel]()
func getEarthquakeInfo(completion: (results : NSArray?) ->Void ){
DataManager.getEarthquakeDataFromFileWithSuccess {
(data) -> Void in
let json = JSON(data: data)
if let JsonArray = json.array {
for appDict in JsonArray {
var ids: String? = appDict["id"].stringValue
var title: String? = appDict["title"].stringValue
var time: String? = appDict["time"].stringValue
var information = AppModel(idEarth: ids, title: title, time: time)
self.info.append(information)
completion(results: self.info)
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
getEarthquakeInfo { (info) in
self.tableView.reloadData()
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as UITableViewCell
let infoArray = self.info
cell.textLabel!.text = self.info[indexPath.row].title
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "SEGUE" {
let vc = segue.destinationViewController as DetailsViewController
let cell = (sender as UITableViewCell)
let title = cell.textLabel!.text
vc.titleData = title
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return info.count
}
}
My DetailsViewController file:
import UIKit
class DetailsViewController: UIViewController {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var idLabel: UILabel!
#IBOutlet weak var timeLabel: UILabel!
var titleData: String!
var idData: String!
var timeData: String!
override func viewDidLoad() {
super.viewDidLoad()
var earthInfo = EarthTableViewController()
var getEarthInfo: () = earthInfo.getEarthquakeInfo { (info) in
println("\(info)")
}
titleLabel.text = titleData
idLabel.text = idData
timeLabel.text = timeData
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

Resources