Trying to append firebase string values to collection view - arrays

I am attempting to add Firebase string values to a collection view but it is giving me an error on the cell.statsLabel.text = statData[indexPath.row] -> Thread 1: EXC_BREAKPOINT (code=1, subcode=0x101ed3b50). My Firebase is correct (I've added values to a label) the problem is appending. It doesn't even go through the retrieveUserData function! The image link below is what my collection view should look like with the left labels being the statHeader array and the right labels being the statData array -> The collectionview image. Any Ideas?
class SceneTwoViewController: UIViewController, GADRewardBasedVideoAdDelegate, UIAlertViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource {
var statHeaders:[String] = []
var statData:[String] = []
var ref: FIRDatabaseReference?
var databaseHandle: FIRDatabaseHandle?
var streakCheck = ""
#IBOutlet var statsCollectionView: UICollectionView!
#IBOutlet var entireView: UIView!
#IBOutlet var activityIndicator: UIActivityIndicatorView!
#IBOutlet var ovImage: UIImageView!
#IBOutlet var Watchbtn: UIButton!
#IBOutlet var StreakImage: UIImageView!
#IBOutlet var StatusLabel: UILabel!
#IBOutlet var TimeDisplay: UIImageView!
#IBOutlet var statusCheck: UILabel!
#IBOutlet var nameLabel: UILabel!
func retrieveUserData() {
let user = FIRAuth.auth()?.currentUser?.uid
ref?.child("users").child(user!).observeSingleEvent(of: .value, with: { snapshot in
print("Dan2")
let value = snapshot.value as? NSDictionary
let statret = value?["status"] as? String ?? ""
let streakret = value?["streakNumber"] as? String ?? ""
let placesret = value?["placesHelped"] as? String ?? ""
self.statData.append(statret)
self.statData.append(streakret)
self.statData.append(placesret)
})
}
override func viewDidLoad() {
super.viewDidLoad()
self.ref = FIRDatabase.database().reference()
let user = FIRAuth.auth()?.currentUser?.uid
print("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW")
retrieveUserData()
statHeaders.append("Status: ")
statHeaders.append("Streak: ")
statHeaders.append("Places Helped: ")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return statHeaders.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "statsCell", for: indexPath) as! statsCollectionViewCell //statsCollectionViewCell is name of external file with labels
print(statData.count) // prints 0
print(statHeaders.count) // prints 3
cell.headerLabel.text = statHeaders[indexPath.row]
cell.statsLabel.text = statData[indexPath.row]
return cell
}
}

You are using statHeaders array as a data source for your collection and in the method cellForItemAt you are trying to retrieve objects from that array and from the statData at the same indexPath while your statData is empty. That's why it cause that error.
If your collection mostly depends on statData not on statHeaders, then you should use statData as a datasource.
Also, in your case is very important to check that both arrays have same count of objects, or use optionals like if statHeaders is the main array and the collection should depend on its values, but the statDate is not important, then don't use statData if it doesn't has an object at current indexPath:
if statData.count > indexPath.row {
//use statData array only in this case
}
Also, I don't see that you are trying to reload your collection after retrieving the data.
I think that you should add that reload if your data retrieving works asynchronously:
ref?.child("users").child(user!).observeSingleEvent(of: .value, with: { snapshot in
print("Dan2")
let value = snapshot.value as? NSDictionary
let statret = value?["status"] as? String ?? ""
let streakret = value?["streakNumber"] as? String ?? ""
let placesret = value?["placesHelped"] as? String ?? ""
self.statData.append(statret)
self.statData.append(streakret)
self.statData.append(placesret)
self.statsCollectionView.reloadData()
})

Related

Swift - User Defaults not loading array of strings when app is launched

So I have an app for a Midwestern car game where you count cows when you're driving and when you see a cemetery you lose half your cows. Whenever someone sees a cemetery, I have an emoji appear as an appended array of string, so they keep adding up. My problem is I can save the array to user defaults and it will print it correctly, but whenever I relaunch the app, the array goes back to a blank array of strings. So I know the data is saved correctly, just not loading when the app launches.
class ViewController: UIViewController {
#IBOutlet weak var playerOneNameText: UITextField!
#IBOutlet weak var numberOfCowsPlayerOne: UILabel!
#IBOutlet weak var playerOneCows: UILabel!
#IBOutlet weak var playerOneCemeteries: UILabel!
let userDefaults = UserDefaults.standard
var cemeteryEmoji: [String] = UserDefaults.standard.object(forKey: "CemeteryEmoji")! as? [String] ?? []
It will also strangely load the correct array in the field for display, but will start over any time a new cemetery is added:
override func viewDidLoad() {
super.viewDidLoad()
if userDefaults.value(forKey: "CemeteryEmoji") != nil{
playerOneCemeteries.text = "\(UserDefaults.standard.object(forKey: "CemeteryEmoji")!)"
print(cemeteryEmoji)
}else {
playerOneCemeteries.text = ""
}
}
And here's the function for all the cemetery data:
#IBAction func playerOneCemetery(_ sender: UIButton) {
let cemeteryCows = UserDefaults.standard.integer(forKey: "TotalCows") / 2
self.userDefaults.set(cemeteryCows, forKey: "TotalCows")
print(cemeteryCows)
self.numberOfCowsPlayerOne.text = "\(self.userDefaults.string(forKey: "TotalCows")!) cows"
addCemeteryEmoji()
print(UserDefaults.standard.object(forKey: "CemeteryEmoji")!)
func addCemeteryEmoji() {
cemeteryEmoji.append("🪦")
print(cemeteryEmoji)
self.playerOneCemeteries.text = "\(cemeteryEmoji.joined())"
userDefaults.set(cemeteryEmoji.joined(), forKey: "CemeteryEmoji")
}
}
So I'm not sure if it's an issue simply when the app loads or if I need to save it a different way (although as I said, that works perfectly fine with all the print statements). Any help would be great.
The error occurs because you join the array right before saving it which creates a single string.
And when you relaunch the app object(forKey: "CemeteryEmoji")! as? [String] fails.
I highly recommend to name the array more meaningful and use the dedicated API array(forKey:).
Name the array in plural form and declare an empty array
var cemeteryEmojis = [String]()
In viewDidLoad load the array from UserDefaults
override func viewDidLoad() {
super.viewDidLoad()
if let emojis = userDefaults.array(forKey: "CemeteryEmoji") as? [String] {
playerOneCemeteries.text = "\(emojis.joined())"
cemeteryEmojis = emojis
print(cemeteryEmojis)
} else {
playerOneCemeteries.text = ""
}
}
And delete joined() in the set line of addCemeteryEmoji
func addCemeteryEmoji() {
cemeteryEmojis.append("🪦")
print(cemeteryEmojis)
self.playerOneCemeteries.text = "\(cemeteryEmojis.joined())"
userDefaults.set(cemeteryEmojis, forKey: "CemeteryEmoji")
}

Change values in dictionary based on which cell selected in TableView

When clicking a cell from the tableView, the cells data (fetched from an array) gets passed on to the 'Detail View Controller' to be displayed in labels. When pressing edit on the Detailview, data gets passed to the 'Edit View Controller' textfields.
When editing those textfields and pressing "Save" I want this data to overwrite the current data in the arrays dictionary based on which cell that was pressed in the tableView.
What would be the best approach to this? Right now data gets passed all the way to the 'EditViewController', but not back to the corresponding dictionary in array when saved.
Main ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // Set up Delegate and Data Source for Table View
#IBOutlet weak var tradeTableView: UITableView!
var tradesList = TradeList()
// Go to detail view of trade when pressing its tableview cell
#IBSegueAction func showDetailView(_ coder: NSCoder) -> DetailViewController? {
guard let indexPath = tradeTableView.indexPathForSelectedRow
else { fatalError("Nothing selected!")}
let trade = tradesList.trades[indexPath.row]
return DetailViewController(coder: coder, trade: trade)
}
override func viewDidLoad() {
super.viewDidLoad()
// Set the table view as the delegate and data source
tradeTableView.dataSource = self
tradeTableView.delegate = self
}
// Delegating functions for Table View
func numberOfSections(in tableView: UITableView) -> Int {
1
}
// Delegating functions for Table View
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
tradesList.trades.count
}
// Delegating functions for Table View
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(TradeCell.self)", for: indexPath) as? TradeCell
else { fatalError("Could not create TradeCell")}
let trade = tradesList.trades[indexPath.row]
// Text to display in cells 'ticker' and 'name' label
cell.tickerLabel?.text = trade.ticker
cell.nameLabel?.text = trade.name
return cell
}
}
DetailViewController:
class DetailViewController: UIViewController {
let trade: Trade
#IBOutlet var tickerLabel: UILabel!
#IBOutlet var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Changes labels according to which cell was pressed in ViewController
tickerLabel.text = trade.ticker
nameLabel.text = trade.name
}
// Initializers
required init?(coder: NSCoder) { fatalError("This should never be called!")}
required init?(coder: NSCoder, trade: Trade) {
self.trade = trade
super.init(coder: coder)
}
// Edit button tapped
#IBAction func editTapped(_ sender: Any) {
self.performSegue(withIdentifier: "DetailVCToEditVC", sender: self)
}
// Prepare data to pass to 'EditViewController'
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "DetailVCToEditVC"){
let displayVC = segue.destination as! EditViewController
displayVC.editTitle = tickerLabel.text
displayVC.editPrice = nameLabel.text
}
}
}
EditViewController:
class EditViewController: UIViewController {
// Variables recieving passed data from 'DetailViewController'
var editTitle: String?
var editPrice: String?
#IBOutlet weak var editTitleField: UITextField!
#IBOutlet weak var editPriceField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Setting the textfields text according to the passed data from 'DetailViewController'.
editTitleField.text = editTitle
editPriceField.text = editPrice
}
#IBAction func editSaveButton(_ sender: UIButton) {
// Dismisses edit screen overlay
self.dismiss(animated: true, completion: nil);
}
}
My array is as follows in another swift file:
struct TradeList {
let trades: [Trade] = [
Trade(ticker: "AAPL", name: "Apple"),
Trade(ticker: "AMD", name: "Advanced Micro Devices")
]
}
Use singleton pattern to update data. You don't need to pass data to view controllers. It will update automatically. Here is how your trade list struct should be
struct TradeList {
static let shared = TradeList(trades: [
Trade(ticker: "AAPL", name: "Apple"),
Trade(ticker: "AMD", name: "Advanced Micro Devices")
])
var trades: [Trade] = []
}
U can use it as following anywhere
for getting values
print(TradeList.shared.trades)
for updating values
TradeList.shared.trades = [...]//Any value here

How to add values to array dynamically if you move from one view controller to another viewcontroller in swift?

In Add_EditAddressViewController i need to show all added address in tableview, for that i have created one ARRAY and appending values to array in NewZoomAddressViewController to show in tableview but all the time i am getting single row in table view.. so here how to add value to array dynamically without replacing into oldvalue in Add_EditAddressViewController
and navigation is:
Add_EditAddressViewController: butnTap -> ProfileVC: btnTap -> NewZoomAddressViewController: btnTap -> Add_EditAddressViewController
here each time when i come to NewZoomAddressViewController need to append \(self.sublocalityName!) \(localityName!) \(self.zipName!) to addressArray to show in tableview of Add_EditAddressViewController
Note: here i have added this question related code in github: https://github.com/SwiftSamples/AddressBug here in profileVC you need to tap on map or continue Button then it navigates to NewZoomAddressViewController
class Add_EditAddressViewController: UIViewController,DataEnteredDelegate {
#IBOutlet weak var addeditTableview: UITableView!
var addressArray = [String]()
var city: String?
var pincode: String?
var locality: String?
override func viewDidLoad() {
super.viewDidLoad()
addeditTableview.register(UINib(nibName: "EditAddressTableViewCell", bundle: nil), forCellReuseIdentifier: "EditAddressTableViewCell")
print("zoooom valuew \(pincode)")
addeditTableview.reloadData()
}
}
extension Add_EditAddressViewController : UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return addressArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: EditAddressTableViewCell = tableView.dequeueReusableCell(withIdentifier: "EditAddressTableViewCell") as! EditAddressTableViewCell
cell.editButton.addTarget(self, action: #selector(editbuttonClicked(sender:)), for: .touchUpInside)
cell.nameHeader.text = "header"
cell.addressLabel.text = addressArray[indexPath.row]
return cell
}
}
NewZoomAddressViewController code:
class NewZoomAddressViewController: UIViewController {
weak var delegate: DataEnteredDelegate? = nil
var addressModel: ProfileModelUserAddress?
var addressArray = [String]()
var zipName: String?
var localityName: String?
var sublocalityName: String?
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var addressLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
print("in Zoom map VC")
mapView.delegate = self
addressLabel.text = "\(self.sublocalityName!) \(localityName!) \(self.zipName!)"
}
#IBAction func confirmBtn(_ sender: Any) {
let viewController = storyboard?.instantiateViewController(withIdentifier: "Add_EditAddressViewController") as! Add_EditAddressViewController
addressArray.append("\(sublocalityName ?? "") \(zipName ?? "") \(localityName ?? "")")
viewController.addressArray = addressArray
print("total address array all rows \(viewController.addressArray)")
navigationController?.pushViewController(viewController, animated: true)
}
}
please try to help to display all added address in tableview. i got stuck here from long time.
In your NewZoomAddressViewController replace confirm button action with
#IBAction func confirmBtn(_ sender: Any) {
for controller in navigationController?.viewControllers ?? [] {
if let listController = controller as? Add_EditAddressViewController {
let string = "\(sublocalityName ?? "") \(zipName ?? "") \(localityName ?? "")"
listController.addressArray.append(string)
navigationController?.popToViewController(controller, animated: true)
return
}
}
}
In Add_EditAddressViewController reload TableView on viewWillAppear
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isHidden=true
addeditTableview.reloadData()
}
Well what you need to do is to have address array in your profile view as well to pass it to other controller.. so your code becomes
First you will have array in profile like this
class ProfileAddressViewController: UIViewController, CLLocationManagerDelegate, UISearchBarDelegate, DataEnteredDelegate {
var addressArray = [String]()
}
Then when you call NewZoomAddressViewController you pass that array to them like this
#objc func triggerTouchAction(_ sender: UITapGestureRecognizer) {
print("Please Help!")
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "NewZoomAddressViewController") as! NewZoomAddressViewController
viewController.delegate = self
viewController.zipName = self.pincodeField.text
viewController.sublocalityName = self.colonyField.text
viewController.localityName = self.cityField.text
viewController.addressArray = addressArray
self.navigationController?.pushViewController(viewController, animated: true);
}
And in your Add_EditAddressViewController where you call profile.. assign array to profile
#objc func editbuttonClicked(sender: UIButton) {
print("in button")
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "ProfileAddressViewController") as! ProfileAddressViewController
viewController.addressArray = addressArray
self.navigationController?.pushViewController(viewController, animated: true)
}

How to add json data to array or similar using Swift

I am fairly new to Swift and I am having a few issues with getting understanding how to do what I want to do.
I am currently testing some stuff with json.
What I am trying to do is to append the data I get from my json data into an array. And when my array contains the data I wish to present it to my UICollectionView. I am assuming that I should be using an array.
import UIKit
import Foundation
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getData() {
let path = "http://myurl/test.json"
let url = URL(string: path)
let session = URLSession.shared
let task = session.dataTask(with: url!) { (data: Data?, response: URLResponse?, error: Error?) in
let json = JSON(data: data!)
for result in json["dokumentstatus"]["dokutskott"]["utskott"].array! {
let punkter = result["punkt"].string!
print("punkt: \(punkt)")
let rubrik = result["rubrik"].string
print("rubrik: \(rubrik)")
let forslag = result["forslag"].string
print("förslag: \(forslag)")
}
}
task.resume()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return //someArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCollectionViewCell
cell.customLabel.text = //Put the data form rubrik variable
cell.customTW.text = //Put the data from foreleg variable
return cell
}
}
the function getData() gets the correct json data I just need help understanding how to put this data to an array.
(Also, I know I probably shouldn't be getting the data in the ViewController, but this is only a test.)
import Foundation
class Information: NSObject {
var punkter: String?
var rubrik: String?
var forslag: String?
}
I'm thinking that maybe I should be using an array that looks something like this:var someArray = [Information]()
But I do not know how to then use my getData()
Or maybe I should be using three different arrays, one for each of my json variables.
Since you're still using a custom class (well done!, three different arrays are horrible) it's correct to declare a data source array like you suggested
var someArray = [Information]()
Most likely a struct is sufficient, I recommend to use non-optional strings
struct Information {
var punkter : String
var rubrik : String
var forslag : String
}
If the properties won't change you could even use let to make the properties constants.
To populate the array use the nil coalescing operator to check for nil,create the Information instance with the memberwise initializer and append the instance to the datasource array. Then reload the collection view on the main thread.
...
for result in json["dokumentstatus"]["dokutskott"]["utskott"].array! {
let punkter = result["punkt"].string ?? ""
let rubrik = result["rubrik"].string ?? ""
let forslag = result["forslag"].string ?? ""
let information = Information(punkter:punkter, rubrik:rubrik, forslag:forslag)
self.someArray.append(information)
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
...
Edit:
To display the data in cellForItemAtIndexPath use
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCollectionViewCell
let information = someArray[indexPath.row]
cell.customLabel.text = information.rubrik
cell.customTW.text = information.foreleg
return cell
}

Wrong order in string array

I have this code that passes the entries from textfield and adds them to a string array in another view controller. The problem is, the order is not correct.
This is my code:
//Textfields
#IBOutlet weak var text1: UITextField!
#IBOutlet weak var text2: UITextField!
#IBOutlet weak var text3: UITextField!
#IBOutlet weak var text4: UITextField!
#IBOutlet weak var text5: UITextField!
#IBOutlet weak var text6: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Delegates each textfield
text1.delegate = self
text2.delegate = self
text3.delegate = self
text4.delegate = self
text5.delegate = self
text6.delegate = self
//Tags each textfield
text1.tag = 1
text2.tag = 2
text3.tag = 3
text4.tag = 4
text5.tag = 5
text6.tag = 6
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
let nextTag: NSInteger = textField.tag + 1;
if let nextResponder: UIResponder! = textField.superview!.viewWithTag(nextTag){
nextResponder.becomeFirstResponder()
}
else {
textField.resignFirstResponder()
}
return false
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! secondView
if segue.identifier == segueID {
//Adds the textfield entries to the string array on the second view controller
destination.stringArray = view.subviews.flatMap { ($0 as? UITextField)?.text }
destination.delegate = self
}
}
But I have no idea what is wrong with my code, why would it send the incorrect order to my array. My array looks like this:
["q", "w", "e", "t", "y", "r"]
when it should be...
["q", "w", "e", "r", "t", "y"]
This is just random letters I chose, the entries could be anything really. But the order is important. Could someone check my code, see where did I go wrong? Thank you in advance.
You need to sort the UITextField(s) you are retrieving
So replace this
destination.stringArray = view.subviews.flatMap { ($0 as? UITextField)?.text }
with this
destination.stringArray = view
.subviews
.flatMap { $0 as? UITextField }
.sort { $0.0.tag < $0.1.tag }
.flatMap { $0.text }
Alternatively, go from the tag to the field instead of the field to the tag:
destination.stringArray = (1...6).flatMap({ (view.viewWithTag($0) as? UITextField).text})

Resources