How to write my Struct to Firebase - swift

I have this struct:
struct Info {
var name: String = ""
var number = Int()
}
var infoProvided : [Info] = []
I display desired data in a tableView:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "InfoCell") as! InfoTableViewCell
let name = infoProvided[indexPath.row].name
let number = infoProvided[indexPath.row].number
cell.infoLabelLabel.text = "\(name) with \(number)"
return cell
}
I am trying to write data to firebase like this:
self.ref?.child(gotLokasjon).child(location).child(dateAndTime).updateChildValues(["Signed up:" : infoProvided])
This returns the error:
Cannot store object of type _SwiftValue at 0. Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.'
How can I write my Struct to Firebase?. I would like to write it equal to how its displayed in the tableView:
cell.infoLabelLabel.text = "\(name) with \(number)"

I haven't understood where you want the upload to happen(before or after they are displayed on tableView) so adjust it to suit your needs.
guard let name = infoProvided.name else {return}
guard let number = infoProvided.number else {return}
let data = [ "name": name, "number": number] as [String : Any]
self.ref?.child(gotLokasjon).child(location).child(dateAndTime).updateChildValues(data, withCompletionBlock: { (error, ref) in
if error != nil{
print(error!)
return
}
print(" Successfully uploaded")
})

After a bit of fiddling I did this:
let infoArray = infoProvided.map { [$0.number, $0.name] }
let items = NSArray(array: infoArray)
Then implemented that in the above solution. This seams to work.
I don't know if this is a good solution?

Related

real- time currency converter Swift

I'm trying to make a simple currency conversion application . here I am parsing json
static func fetchJson(key: String = "USD", completion: #escaping (ExchangeRates) -> ()) {
guard let url = URL(string: "https://open.er-api.com/v6/latest/\(String(describing: key))") else {return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
guard let safeData = data else {return}
do {
let results = try JSONDecoder().decode(ExchangeRates.self, from: safeData)
completion(results)
} catch {
print(error)
}
}.resume()
}
then I get a dictionary looks like:
"rates": {
"USD": 1,
"AED": 3.67,
"AFN": 105,
"ALL": 107.39,
"AMD": 481.52,
"ANG": 1.79,
"AOA": 538.31,
..... etc
this is how the structure in which the data is stored looks like
struct ExchangeRates: Codable {
let rates: [String: Double]
}
in the ViewController I have created an object that has the type of ExchangeRates struct and call the func fetchJson in viewDidLoad and save the result in finalCurrencies
var finalCurrencies: ExchangeRates?
then I created a tableview with the number of cells equal to
finalCurrencies.rates.count
tableViewCell has a currencyLabel with currency name (finalCurrencies.rates.key) and currencyTextField with currency value (finalCurrencies.rates.value)
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let currencyFetched = finalCurrencies {
return currencyFetched.rates.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell {
if let currencyFetched = finalCurrencies {
cell.currencyLabel.text = Array(currencyFetched.rates.keys)[indexPath.row]
cell.currencyTextField.accessibilityIdentifier = Array(currencyFetched.rates.keys)[indexPath.row]
let selectedRate = Array(currencyFetched.rates.values)[indexPath.row]
cell.currencyTextField.text = "\(selectedRate)"
return cell
}
}
return UITableViewCell()
}
}
And that's what I get in the end as a result enter image description here
QUESTION: I need to make it so that when the value in the textfield for a certain currency changes, the values of the other currencies change in real- time. as a live google currency converter. I should also be able to switch between currencies and change their values, while also the values of other currencies should change. also how to make it so that with each new number an existing dictionary finalCurrencies is used and not a func fetchJson ?
I suppose that I should use this func textField (so that the textField reacts to each addition of a number).
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
}
I'm a beginner and I've been thinking about this for a few days now and I don't know how to do it.thanks in advance.
First you need to calculate the ratio of the change for the currency which had its value modified, you'll then just map the values of the dictionary with that ratio:
extension Dictionary where Key == String, Value == Double {
mutating func applyChange(of currency: Key, with newValue: Value) {
guard
newValue != .zero,
let oldValue = self[currency],
oldValue != newValue,
oldValue != .zero
else { return }
let ratio = 1.0 + ((newValue - oldValue) / oldValue)
indices.forEach { values[$0] *= ratio }
}
}
If I've understood you correctly, I believe this would work.
Each time the user adjusts the value in the textField you'd loop through your rates dictionary, calculate the new rate and return an updated dictionary.
If I've misunderstood your goal please let me know and I'll do my best to assist :)
struct Converter {
let exchangeRates: [String: Double] = [
"USD": 1,
"AED": 3.67,
"AFN": 105,
"ALL": 107.39,
"AMD": 481.52,
"ANG": 1.79,
"AOA": 538.31
]
func convert(_ amount: String) -> [String: Double] {
var newRates = [String: Double]()
exchangeRates.forEach { (code, value) in
if let amount = Double(amount) {
newRates[code] = value * amount
}
}
return newRates
}
}
let converter = Converter()
let userInput = "10.00"
print(converter.convert(userInput))

Index out of range when presenting JSON data in tableview

I am having issue identifying and changing the color of tableview rows that contain the same name value in both [ListStruct] which contains the inital data for the tableview rows, and [HighlightStruct] which contains the name that need to be highlighted.
Initially I have the following JSON array populate my tableview:
private func fetchJSON() {
guard let url = URL(string: "www.test.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "test=test1".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([ListStruct].self,from:data)
DispatchQueue.main.async {
self.tableView.reloadData()
}}catch {print(error)}}.resume()}
struct ListStruct: Codable {
let id: String
let wo: String
let name: String
let type: String
}
Then the same view controller has a second JSON array that is decoded below for highlighting:
func processJSON(_ json: String) {
do{
let mydata = Data(json.utf8)
let decoded = try JSONDecoder().decode(Set<HighlightStruct>.self,from: mydata)
print(decoded)
} catch {
print(error)
}
}
struct HighlightStruct: Codable, Hashable {
var id: Int
var name: String
}
Applying Highlight
var mySet: Set<HighlightStruct> = []
var highlightedStructure = [HighlightStruct]()
var structure = [ListStruct]()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! myCell
let portfolio: ListStruct
portfolio = structure[indexPath.row]
let highlight: HighlightStruct
highlight = highlightedStructure[indexPath.row]
//Highlight those that match in both arrays
if highlight.wo == portfolio.wo {
cell.backgroundColor = .yellow
}
Getting index out of range
You are getting index out of range error because your arrays are empty or there is no index that exist in your arrays. Maybe you can check your service call, the arrays could not be filled properly.
Make sure ur two list count is same size, or process data to one list.
You need to handle exceptions when structure does not have same wo to compare.
struct ListStruct: Codable {
let id: String
let wo: String
let name: String
let type: String
let hightlight:HighlightStruct!
}
func processJSON(_ json: String) {
do{
let mydata = Data(json.utf8)
let decoded = try JSONDecoder().decode(Set<HighlightStruct>.self,from: mydata)
print(decoded)
for hl in decoded{
var filter = structure.filter({$0.wo == hl.wo})
filter.hightlight = hl
}
} catch {
print(error)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! myCell
let portfolio: ListStruct
portfolio = structure[indexPath.row]
//Highlight those that match in both arrays
if portfolio.hightlight?.wo == portfolio.wo {
cell.backgroundColor = .yellow
}

How i implement my JSON API Request into a UITableViewCell?

I have a problem with my current Project. First of all, i like to implement a JSON API Request that allows me to get a title off a URL. The Problem: I want to display the JSON data into a UITableViewCell.
But Xcode throws following Error:
Cannot assign value of type 'FirstViewController.Title' to type
'String?'
Maybe there is more wrong in my code, because i'm just a beginner at Swift/Xcode
I already tried this:
cell.textLabel?.text = course.title as? String
But i got warning message as follows:
Cast from 'FirstViewController.Title' to unrelated type 'String' always fails
This is my code sample:
var courses = [Course]()
let cell = "ItemCell"
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
}
struct Course: Codable {
let title: Title
enum CodingKeys: String, CodingKey {
case title
case links = "_links"
}
}
struct Links: Codable {
}
struct Title: Codable {
let rendered: String
}
fileprivate func fetchJSON() {
let urlString = "ExampleURL"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.async {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(Course.self, from: data)
self.tableView.reloadData()
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}.resume()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return courses.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .value1, reuseIdentifier: "ItemCell")
let course = courses[indexPath.row]
cell.textLabel?.text = course.title as? String // Cast from 'FirstViewController.Title' to unrelated type 'String' always fails
return cell
}
I just want to get WordPress posts into a UITableView - UITableViewCell.
Maybe you can tell me if its the wrong way i tried it but i don't really know how i solve this problem
Thank you in advance
Assign the var before the reload
let res = try JSONDecoder().decode(Course.self, from: data)
courses.append(res)
DispatchQueue.main.async {
self.tableView.reloadData()
}
And set it to the string value
cell.textLabel?.text = course.title.rendered
courses = try JSONDecoder().decode([Course].self, from: data)
print(courses)

load large data from firestore to table view Swift

firestore to store about more than 500 information and I want to display it to table view. Basically, I have successfully display all the data in my cell, but the problem is, it takes more than 1 minute to load all data. While the data loaded, I cannot scroll the table view, unless all data finish load. How to enable scrolling while the data is still loading? If not possible, how to load first 20 data first, and will continue load if user is at the end of the cell? Here is some code that I have tried to
get data from firestore:
func getData () {
db.collection("fund").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
for document in querySnapshot!.documents {
let data = document.data()
let agencyPath = data["agensi"] as? String ?? ""
let title = data["title"] as? String ?? ""
let program = data["program"] as? String ?? ""
let perniagaan = data["perniagaan"] as? String ?? ""
let newMax = data["max"] as? Int
let agencyId = document.documentID
let query = Firestore.firestore().collection("Agensi")
let newQuery = query.whereField("name", isEqualTo: "\(agencyPath)")
newQuery.getDocuments()
{
(querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)");
} else
{
for document in querySnapshot!.documents {
let data = document.data()
let logo = data["logo"] as? String ?? ""
//store to Struct
let newModel = DisplayModel(agency: title, agencyId: agencyId, programTag: program, perniagaanTag: perniagaan, max: newMax, agencyPath: agencyPath, logoUrl: logo, agencyTitle: agencyPath)
self.agencyList.append(newModel)
}
self.tableView.reloadData()
self.dismiss(animated: false, completion: nil)
}
}
}
}
}
}
display data on cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData: DisplayModel
if searchController.searchBar.text != "" {
cellData = filteredData[indexPath.row]
} else {
cellData = agencyList[indexPath.row]
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? HomeTableViewCell
cell?.agencyName.text = cellData.agency
cell?.agencyImage.sd_setImage(with: URL(string: "\(cellData.logoUrl ?? "")"), placeholderImage: UIImage(named: "no_pic_image"))
return cell!
}
Action on last row of cell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if searchController.searchBar.text != "" {
let lastElement = filteredData.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
} else {
let lastElement = agencyList.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
}
}
I really have no idea what method I should do to load 20 data first and continue load at the end of cell row, if there is no solution, at least I could scroll the table view during the load session. Thank You, for your information, i just learn swift last month. Thank you for helping me.
You should definitly adopt the UITableViewDataSourcePrefetching protocol.
Check some blogs, like:
https://www.raywenderlich.com/187041/uitableview-infinite-scrolling-tutorial
and adopt it to pagination as described here:
https://firebase.google.com/docs/firestore/query-data/query-cursors

Dictionary causes index out of range error in Swift

Sorry if this is asked already but I haven't found a solution for it. I'm new to Swift so please bear with me. I can't figure out why I keep getting an error of Thread 1: Fatal Error: Index out of range. I've used the same method before in displaying a txt file to which I never got a problem with before so this is the first time. I'm trying to display coordinates as the text details with the date and time as text in the cells itself.
Date and Time
Latitude, Longitude
Something like the above (Imagine it in a cell)
The following is my code for the program
import UIKit
import MapKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//Array to store the list
var storeCoordinates = [String: String]()
var arrayClient = NSMutableArray()
var readings: [String] = [" "]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Get path of where the file is
let path = Bundle.main.path(forResource: "gps_coords", ofType: "csv")
//Use filemanager to check if the file exist to avoid crashing if it doesn't exist
let fileMgr = FileManager.default
//Display the number of line counts we have
if fileMgr.fileExists(atPath: path!){
do {
let fulltext = try String(contentsOfFile: path!, encoding: String.Encoding.utf8)
readings = fulltext.components(separatedBy: "\n") as [String]
for i in 0..<readings.count{
let listData = readings[i].components(separatedBy: ";") as [String]
storeCoordinates["Latitude"] = "\(listData[0])"
storeCoordinates["Longitude"] = "\(listData[1])"
storeCoordinates["DateAndTime"] = "\(listData[2])"
arrayClient.add(storeCoordinates)
}
} catch let error as NSError {
print("Error: \(error)")
}
}
self.title = "Number of entries: \(arrayClient.count)"
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayClient.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellReuseIdentifier", for: indexPath)
let client = arrayClient[indexPath.row] as AnyObject
cell.textLabel?.text = "\(client.object(forKey: "DateAndTime")!)"
cell.detailTextLabel?.text = "\(client.object(forKey: "Latitude")!) \(client.object(forKey: "Longitude")!)"
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
The error I had is on storeCoordinates["Latitude"] = "\(listData[0])"
Using breakpoints, it shows the Latitude value is not empty along with Longitude and DateAndTime but if I try to run the app in the simulator, it gives the error Thread 1: Fatal Error: Index out of range. So far no luck in trying to figure out how to fix this. If you could help me figure it out, it would mean a lot to me. Please and thank you.
I regard the unreliable CSV format as the origin of the issue.
This is a quick tutorial to use a better format (JSON) and a more robust data source.
Part 1: Convert CSV to JSON
Create a new Blank Playground (press ⌥⇧⌘N) platform macOS
Press ⌘0 to show the project navigator of the Playground.
⌥-drag the CSV file from the project navigator of the main project into the Resources folder of the Playground.
Paste the following code into the Playground, it's based on your code to parse the CSV. It converts the CSV to JSON and creates a file gps_coords.json on the desktop. You'll get a fatal error if any field is missing.
struct Coordinate : Encodable {
let latitude, longitude, dateAndTime : String
}
let url = Bundle.main.url(forResource: "gps_coords", withExtension: "csv")!
let fulltext = try! String(contentsOf: url, encoding: .utf8)
let lines = fulltext.components(separatedBy: .newlines)
let coordinates = lines.map { paragraph -> Coordinate in
let components = paragraph.components(separatedBy: ";")
if components.count != 3 { fatalError("Each line must contain all three fields")}
return Coordinate(latitude: components[0], longitude: components[1], dateAndTime: components[2])
}
do {
let data = try JSONEncoder().encode(coordinates)
let homeURL = URL(fileURLWithPath: NSHomeDirectory())
let destinationURL = homeURL.appendingPathComponent("Desktop/gps_coords.json")
try data.write(to: destinationURL)
} catch { print(error) }
Part 2: Implement the new file
Close the Playground. It's not needed anymore.
Drag the new file from the desktop into the project navigator (make sure Copy If Needed is checked).
Change the ViewController class to
import UIKit
import MapKit
struct Coordinate : Decodable {
let latitude, longitude, dateAndTime : String
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var arrayClient = [Coordinate]()
override func viewDidLoad() {
super.viewDidLoad()
let url = Bundle.main.url(forResource: "gps_coords", withExtension: "json")!
let data = try! Data(contentsOf: url)
arrayClient = try! JSONDecoder().decode([Coordinate].self, from: data)
self.title = "Number of entries: \(arrayClient.count)"
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayClient.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellReuseIdentifier", for: indexPath)
let client = arrayClient[indexPath.row]
cell.textLabel?.text = client.dateAndTime
cell.detailTextLabel?.text = client.latitude + " " + client.longitude
return cell
}
}
Note: The UITableView outlet is missing and I added the line to reload the data. Make also sure that delegate and datasource is connected in Interface Builder from the table view to the ViewController.
Delete the CSV file.
The new code uses a struct Coordinate as data source and decodes the JSON very conveniently with JSONDecoder. Please note the missing type casts and the cumbersome key subscription to get the values from the dictionary.
As in the comment, mentioned by #vadian, It is not possible that you didn't get 0th index. But you add some checks.
Update your code as following:
Some updates are -
Use swift based array [[String: String]] rather NSMutableArray
Initialize the storeCoordinates for each loop cycle and check if your listData have more than 3 items or not
class AViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//Array to store the list
var arrayClient = [[String: String]]()
var readings: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
//Get path of where the file is
let path = Bundle.main.path(forResource: "gps_coords", ofType: "csv")
//Use filemanager to check if the file exist to avoid crashing if it doesn't exist
let fileMgr = FileManager.default
//Display the number of line counts we have
if fileMgr.fileExists(atPath: path!){
do {
let fulltext = try String(contentsOfFile: path!, encoding: String.Encoding.utf8)
readings = fulltext.components(separatedBy: "\n") as [String]
for i in 0..<readings.count {
// Check if you can get data from `readings[i]`
let listData = readings[i].components(separatedBy: ";") as [String]
if listData.count == 3 {
var storeCoordinates = [String: String]()
storeCoordinates["Latitude"] = "\(listData[0])"
storeCoordinates["Longitude"] = "\(listData[1])"
storeCoordinates["DateAndTime"] = "\(listData[2])"
arrayClient.append(storeCoordinates)
}
}
} catch let error as NSError {
print("Error: \(error)")
}
}
self.title = "Number of entries: \(arrayClient.count)"
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayClient.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellReuseIdentifier", for: indexPath)
let client = arrayClient[indexPath.row]
if let dateTime = client["DateAndTime"], let latitude = client["DateAndTime"], let longitude = client["DateAndTime"] {
cell.textLabel?.text = "\(dateTime)"
cell.detailTextLabel?.text = "\(latitude) \(longitude)"
}
return cell
}
}