Custom TableView Error - arrays

I am trying to create a custom table view but stumble upon every code. As of right now, I have this below. It's messy and probably wrong but could someone help?
Also, I keep getting an error code
unexpectedly found nil while unwrapping optional value
import UIKit
class tableOutlineViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var names = ["Max", "Bill", "Reagan", "Mikayla", "Jessie", "Sierra", "Jeff", "Erik", "Landon"]
var numbers = ["35", "33", "29", "27", "25", "23", "19", "15", "11"]
var photo = [UIImage(named: "Person1"), UIImage(named: "Person2"), UIImage(named: "Groceries"), UIImage(named: "Person3"), UIImage(named: "Person4"), UIImage(named: "Person5"), UIImage(named: "Person6"), UIImage(named: "Person7")]
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.
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell
cell.images.image = photo[indexPath.row]
cell.name.text = names[indexPath.row]
cell.number.text = numbers[indexPath.row]
return cell
}
}

The error is at the following line because it's unable to force unwrap CustomCell:
let cell = self.tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell
As you may be using Nib for your custom cell, you should firstly get it registered in viewDidLoad.
let nib = UINib(nibName: YOUR_CUSTOM_CELL_NIB_NAME, bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "CustomCell")

Seems like you forgot to define identifier(CustomCell) inside attribute inspector of your Xib file of custom cell. So you when you are doing force unwrapped you are getting nil. Also avoid doing force unwrapped to avoid the crash. Have refactored the code :-
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = self.tableView.dequeueReusableCell(withIdentifier: "CustomCell") as? CustomCell else {return UITableViewCell()}
cell.images.image = photo[indexPath.row]
cell.name.text = names[indexPath.row]
cell.number.text = numbers[indexPath.row]
return cell
}

Firs of all please do not populate your tableview with multiple arrays. These arrays can easily go out of sync and can cause a crash. You should ideally create a struct class having name, age and photo as its elements. Then you should have an array of struct class to populate your table view.
Here as well in you name array you have 9 elements whereas your image array has 8 elements. This is causing your app to crash as cell for row at indexpath method is not able to find the 9th image.
cell.images.image = photo[indexPath.row]

So I took everyone's advice and when I run the code below, I get zero errors. But in the simulator, it shows an empty tableView and the image I placed above the tableView is another image from another View Controller. I don't know if it's the code or something else.
let nib = UINib(nibName: "CustomCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "Cell")
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = self.tableView.dequeueReusableCell(withIdentifier: "Cell") as? CustomCell else {return UITableViewCell()}
cell.images.image = photo[indexPath.row]
cell.name.text = names[indexPath.row]
cell.numbers.text = numbers[indexPath.row]
return cell
and my CustomCell
import UIKit
class CustomCell: UITableViewCell {
#IBOutlet weak var images: UIImageView!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var numbers: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

Related

How to implement userDefaults with a Global Variable array

I have an empty array set as a global variable that is populated with array items from a tableview. This is used to populate another tableview. This data needs to persist so that when the user returns to the app, their tableview data is in the same state they left it, i.e. populate with data from the array.
Though I've looked for dozens of tutorials and examples. I've also hacked at it myself to make it work and every time I reload the app, the array is empty. How can I get that global variable array to hold onto it's array data?
var sharedData = [String]()
This is my 1st VC where I have setup functions for the UserDefaults. And I've executed my saveArray() func every time a change is made to the array. I've then executed retrieveArray() func every time I need to load from the array.
import UIKit
var sharedData = [String]()
struct Keys {
static let arrayKey = "arrayKey"
}
let defaults = UserDefaults.standard
func saveArray() {
defaults.set(sharedData, forKey: Keys.arrayKey)
}
func retrieveArray() {
var savedData = defaults.object(forKey: Keys.arrayKey) as? [String] ?? []
savedData.append(contentsOf: sharedData)
}
class ViewController: UIViewController {
var effect:UIVisualEffect!
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet var tableView: UITableView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
#IBOutlet weak var visualEffectView: UIVisualEffectView!
let materialData = ["One", "Two", "Three", "Four"]
var searchMaterial = [String]()
var searching = false
#IBAction func favoritesButtonArrayUpdate(_ sender: UIBarButtonItem) {
print(sharedData)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
saveArray()
retrieveArray()
print(sharedData)
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
print(self.materialData[indexPath.row], "selected!")
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let favorite = UITableViewRowAction(style: .default, title: "Favorite") { (action, indexPath) in
var data: String
if self.searching {
data = self.searchMaterial[indexPath.row]
} else {
data = self.materialData[indexPath.row]
}
sharedData.append(data)
saveArray()
print(sharedData)
}
favorite.backgroundColor = UIColor.orange
return [favorite]
}
}
This is my 2nd VC which displays the array data stored in the global variable array sharedData. I've again added all the func when making changes to the array and pulling data from the array.
import UIKit
class FavoritesViewController: UIViewController {
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
saveArray()
retrieveArray()
}
}
extension FavoritesViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
retrieveArray()
return sharedData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
retrieveArray()
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = sharedData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
sharedData.remove(at: indexPath.row)
saveArray()
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
}
}
The problem could be here:
let savedData: [String] = userDefaults.object(forKey: "arrayKey") as? [String] ?? []
Try changing it with:
let savedData: [String] = userDefaults?.object(forKey: "arrayKey") as? [String] ?? []
This is because UserDefaults must be unwrapped to refer to member object. Give it a try
Based on MrHim recommendations I removed the saveArray and retrieveArray func from the viewDidLoad of my first VC and left retrieveArray in viewDidLoad of my second VC. Having saveArray in my viewDidLoads was overwriting the array with empty data. I then needed to retrieve the array data in the proper place in my second VC. Then in my numberOfRowsInSection I removed retrieveArray.

How to save an Array in UserDefault

I'm using a UITabBarController to create a contact list, but when I'm trying to save the array to load the data when I restart the app is giving me problems where the data isn't displayed. I'm using UserDefaults to save the data and the restore when the app is restarted.
In this code I sent data from a textfield to the array named list.
import UIKit
class NewContactoViewController: UIViewController {
#IBOutlet weak var input: UITextField!
#IBAction func add(_ sender: Any) {
if (input.text != "") {
list.append(input.text!)
UserDefaults.standard.set(list, forKey: "SavedValue")
input.text = ""
}
}
}
In this code I'm printing the data in a table, and trying to save it with user defaults.
import UIKit
var list = [String]()
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let x = UserDefaults.standard.object(forKey: "SavedValue") as? String {
return (x.count)
}
return (0)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
if let x = UserDefaults.standard.dictionary(forKey: "SavedValue") as? String {
cell.textLabel?.text = [x[indexPath.row]]
}
return(cell)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
list.remove(at: indexPath.row)
myTableView.reloadData()
}
}
override func viewDidAppear(_ animated: Bool) {
myTableView.reloadData()
}
#IBOutlet weak var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
You are saving an array of strings but you are reading a single string (or even a dictionary) which obviously cannot work. There is a dedicated method stringArray(forKey to read a string array.
Apart from the issue never read from UserDefaults to populate the data source in the table view data source and delegate methods, do it in viewDidLoad or viewWillAppear for example
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let savedArray = UserDefaults.standard.stringArray(forKey: "SavedValue") {
list = savedArray
}
myTableView.reloadData()
}
Put the data source array in the view controller. A global variable as data source is very bad programming habit.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var list = [String]()
...
In numberOfRowsInSection return the number of items in list and return is not a function, there are no parentheses
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
Same in cellForRow. Get the item from list and use reusable cells and again, return is not a function.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = list[indexPath.row]
return cell
}
Note :
Consider that UserDefaults is the wrong place to share data between view controllers. Use segues, callbacks or protocol / delegate.

Table View To seperate view with segue

This has stumped me and Ive tried a few ways of doing of tutorials and on stack answers but its still not building.
So basically:
Im getting core data and then placing that into an array. (works fine)
After that Im just display first and last name in the cells (works fine)
User taps on cell to see athleteDetalView
I have placed a few print statements to see where its going wrong.
Thanks In advance for your input.
import UIKit
import CoreData
class athleteViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//labels
#IBOutlet weak var athleteLabel: UILabel!
//buttons
#IBOutlet weak var athleteCreate: UIBarButtonItem!
#IBOutlet weak var athleteTableView: UITableView!
var athleteArray:[Athlete] = []
var myIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
athleteTableView.delegate = self
athleteTableView.dataSource = self
self.fetchData()
self.athleteTableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return athleteArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "athleteName", for: indexPath)
let name = athleteArray[indexPath.row]
cell.textLabel!.text = name.firstName! + " " + name.lastName!
let athleteName = name.firstName!
let lastName = name.lastName!
let age = name.age!
let sport = name.sport!
let email = name.email!
print(athleteName)
print(lastName)
print(age)
print(sport)
print(email)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myIndex = indexPath.row
let currentCell
//let storyboard = UIStoryboard(name: "Main", bundle: nil)
//let destination = storyboard.instantiateViewController(withIdentifier: "athleteDetailsViewController") as! athleteDetailsViewController
//let athName = athleteArray[myIndex]
//testdata
//test data
performSegue(withIdentifier: "athleteDetailsSegue", sender: self)
//self.navigationController?.pushViewController(destination, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "athleteDetailsSegue") {
var destination = segue.destination as! athleteDetailsViewController
destination.firstNameString = athleteName
destination.lastNameString = lastName
destination.ageString = age
destination.sportString = sport
destination.emailString = email
//destination.getImage = name.image
}
}
func fetchData(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do{
athleteArray = try context.fetch(Athlete.fetchRequest())
}
catch{
print(error)
}
}
The variables that you are setting in tableView:cellForRowAt:indexPath: are local variables and are not available in the function prepareForSegue:. Try to declaring the variables at the top as properties of the class.

Load table with titles from JSON

I have a page with a TableView that fills each cell with a hardcoded UILabel of some text. I would like it to fill up with UILabels from a JSON that I get online.
Storyboard:
The code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var objects: NSMutableArray! = NSMutableArray()
override func viewDidLoad(){
super.viewDidLoad()
self.objects.add("iPhone")
self.objects.add("Apple Watch")
self.objects.add("Mac")
self.objects.add("Test")
self.tableView.reloadData()
}
override func didReceiveMemoryWarning(){
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(_ tableView: UITableView) -> Int{
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return self.objects.count
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell{
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.titleLabel.text = self.objects.object(at: (indexPath as NSIndexPath).row) as? String
//cell.logButton.tag = (indexPath as NSIndexPath).row;
//cell.logButton.addTarget(self, action: #selector(ViewController.logAction(_:)), for: .touchUpInside)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath){
self.performSegue(withIdentifier: "showView", sender: self)
}
#IBAction func logAction(_ sender: UIButton) {
let titleString = self.objects[sender.tag] as? String
let firstActivityItem = "\(titleString!)"
let activityViewController : UIActivityViewController = UIActivityViewController(activityItems: [firstActivityItem], applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "showView"){
let upcoming: NewViewController = segue.destination as! NewViewController
let indexPath = self.tableView.indexPathForSelectedRow!
let titleString = self.objects.object(at: indexPath.row) as? String
upcoming.titleString = titleString
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
}
Once in simulator and I open up the page, the TableView will have four cells with the "Label" changing to whatever was adding to the objects array, which in this case is iPhone, Apple Watch, Mac, and Test. Rather than having those hardcoded, I would like to have the items loaded from a JSON file.
I have the same thing done with a PickerView, but I am struggling to figure out how to do it with this. Here is how it was done with my PickerView if it helps:
Alamofire.request("example.com/file.json").responseJSON{ response in
if let JSON = response.result.value as? [String:AnyObject] {
self.mypickerview.delegate = self
self.mypickerview.dataSource = self
let result = JSON.values.flatMap({ String(describing: $0) })
self.pickerData.append(contentsOf: result)
self.pickerData.sort()
self.verbose.text = "Content saved!"
self.mypickerview.reloadAllComponents()
self.mypickerview.delegate = self;
self.verbose.text = "Finished Loading!"
}
}
The JSON file:
{"One":"Mac","Two":"Apple iPhone","Three":"Test"}
1.On viewDidLoad fire the webService.
2.On Webservice completion handler ,retreive the label values and assign to objects.
3.Reload tableview.
your JSON should look like this:
{
"titles": [
"mac",
"iphone",
"test"
]
}
and you will do something like cell.titleLabel.text = [[yourJSON valueForKey:#"titles"] objectAtIndex:indexPath.row];
(this is Obj-C version, but ofc it can be done also in swift)

NSUserDefaults save and read in tableview as an Array

ok so I have the user input a name into a textfield and a button passes the name to a tableview in another view, the table view lists the name but when a new name is added it overwrites the previous name instead of listing all names added to the tableview. here is my code:
viewcontroller1:
#IBAction func addPlant(sender: AnyObject) {
let array = self.title
NSUserDefaults.standardUserDefaults().setObject(array, forKey: "userName")
NSUserDefaults.standardUserDefaults().synchronize()
}
viewcontroller2:
#IBOutlet weak var tableView: UITableView!
var userDefaults = NSUserDefaults.standardUserDefaults()
var ourText = String()
var textArray:[String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//self.tableview.delegate = self
self.tableView.dataSource = self
ourText = userDefaults.stringForKey("userName")!
textArray.append(ourText)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return textArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = textArray[indexPath.row]
return cell
}
You can use
textArray = NSUserDefaults.standardUserDefaults().arrayForKey("userName")
Or
textArray = NSUserDefaults.standardUserDefaults().objectForKey("userName")

Resources