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))
Related
I retrieve data from MySql via PHP file to get users information and scores to load them in a table. I need to get the value of the first 3 users and put them in a Label outside the Table, it is like game leaders list. I attached an image to explain the idea.
Here is the structure code:
import Foundation
protocol HomeModelProtocol: AnyObject {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, URLSessionDataDelegate {
weak var delegate: HomeModelProtocol!
let urlPath = "https://mywebsite.com/folder/callUserList.php" //this will be changed to the path where service.php lives
func downloadItems() {
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let users = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let user = UsersModel()
//the following insures none of the JsonElement values are nil through optional binding
if let name = jsonElement["name"] as? String,
let email = jsonElement["email"] as? String,
let phoneNumber = jsonElement["phone"] as? String,
let userImage = jsonElement["image"] as? String
{
user.name = name
user.email = email
user.phoneNumber = phoneNumber
user.userImage = userImage
}
users.add(user)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.itemsDownloaded(items: users)
})
}
}
Here is the model:
import Foundation
class UsersModel: NSObject {
//properties
var name: String?
var email: String?
var phoneNumber: String?
var userImage: String?
//empty constructor
override init()
{
}
//construct with #name, #address, #latitude, and #longitude parameters
init(name: String, email: String, phoneNumber: String, userImage: String) {
self.name = name
self.email = email
self.phoneNumber = phoneNumber
self.userImage = userImage
}
//prints object's current state
override var description: String {
return "Name: \(String(describing: name)), Email: \(String(describing: email)), Phone Number: \(String(describing: phoneNumber)), User Image: \(String(describing: userImage))"
}
}
Here is the code in the TableView controller:
var feedItems: NSArray = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
let homeModel = HomeModel()
homeModel.delegate = self
homeModel.downloadItems()
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cellIdentifier: String = "BasicCell"
let myCell: WinnerTableCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! WinnerTableCell
// Get the location to be shown
let item: UsersModel = feedItems[indexPath.row] as! UsersModel
// Get references to labels of cell
myCell.lbTextName!.text = item.name
return myCell
}
The data shows in the Table but I have no idea how to fill the 3 label with the 3 first users from the Table.
How can I get these values from the table and pass it to a label in the same ViewController?
Thanks
When adding this code:
if feedItems.count >= 3 {
lblFirstWinner.text = feedItems[0].name // 1st winner
lblSecondWinner.text = feedItems[1].name // 2nd winner
lblThirdWinner.text = feedItems[2].name // 3rd winner
}
it shows error: Value of type 'Any' has no member 'name'
Change itemsDownloaded method as
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
for (index, user) in items.enumerated() {
let user = user as! UserModel
switch index {
case 0: // 1st winner
lblFirstWinner.text = user.name
case 1: // 2nd winner
lblSecondWinner.text = user.name
case 2: // 3rd winner
lblThirdWinner.text = user.name
}
}
}
OR
Change your HomeModelProtocol method and feedItems type to [UsersModel]
protocol HomeModelProtocol: AnyObject {
func itemsDownloaded(items: [UsersModel]) // Changed
}
var feedItems =[UsersModel]() // Changed
override func viewDidLoad() {
super.viewDidLoad()
let homeModel = HomeModel()
homeModel.delegate = self
homeModel.downloadItems()
}
func itemsDownloaded(items: [UsersModel]) {
feedItems = items
self.listTableView.reloadData()
if feedItems.count >= 3 {
lblFirstWinner.text = feedItems[0].name // 1st winner
lblSecondWinner.text = feedItems[1].name // 2nd winner
lblThirdWinner.text = feedItems[2].name // 3rd winner
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cellIdentifier: String = "BasicCell"
let myCell: WinnerTableCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! WinnerTableCell
// Get references to labels of cell
myCell.lbTextName!.text = feedItems[indexPath.row].name // Changed
return myCell
}
Just you need to add a few lines in the below function and your solution will be done.
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
if feedItems.count >= 3 {
lblFirstWinner.text = feedItems[0].name // 1st winner
lblSecondWinner.text = feedItems[1].name // 2nd winner
lblThirdWinner.text = feedItems[2].name // 3rd winner
}
}
Let me know... is it working for you? and please also refer to #vadian comment on your question.
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
}
I use the realm database for my application (to-do list), everything works fine, BUT once I flew to another country and noticed that the records in the database are empty (the application gives out an empty list), upon arrival back to my country everything returned to normal ... Now I am again in a different country and the situation repeats again (database is empty), for some reason the database gives an empty list result, can you please explain why this is happening and how to fix that?
Output example
var dbToDoList = DBrealmToDoList()
var arrayToDoList: Results<RealmToDoList> {
get {
return dbToDoList.getArray()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
dbToDoList.realm = realm
let current = arrayToDoList.filter { (_todo) -> Bool in
return _todo.date == date
}.first
self.selectedDate = date
if current != nil {
self.selectedLists = current?.lists
self.selectedListsSorted = self.selectedLists?.sorted(by: { (val, val2) -> Bool in
return (!val.value && val2.value)
})
}
}
And then in tableView I display the data from the selectedListsSorted
// MARK: UITableView
extension ToDoListViewController : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return selectedListsSorted?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ToDoListTableViewCell
let current = selectedListsSorted?[indexPath.row]
cell.nameLabel.text = current?.key
cell.checkBox.isSelected = current?.value ?? false
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
}
Here is a class for working with db
class RealmToDoList: Object {
#objc private dynamic var dictionaryData: Data?
var lists: [String: Bool] {
get {
guard let dictionaryData = dictionaryData else {
return [String: Bool]()
}
do {
let dict = try JSONSerialization.jsonObject(with: dictionaryData, options: []) as? [String: Bool]
return dict!
} catch {
return [String: Bool]()
}
}
set {
do {
let data = try JSONSerialization.data(withJSONObject: newValue, options: [])
dictionaryData = data
} catch {
dictionaryData = nil
}
}
}
#objc dynamic var date : Date?
}
class DBrealmToDoList {
var realm: Realm!
func write(_ data: RealmToDoList) throws -> Bool {
var result = false
if (realm != nil) {
try! realm.write {
realm.add(data)
result = true
}
return result
} else {
throw RuntimeError.NoRealmSet
}
}
func getArray() -> Results<RealmToDoList> {
return realm.objects(RealmToDoList.self)
}
func delete(_ data: RealmToDoList) throws -> Bool {
var result = false
if (realm != nil) {
try! self.realm.write {
self.realm.delete(data)
result = true
}
return result
} else {
throw RuntimeError.NoRealmSet
}
}
func update(ofType:Object,value:AnyObject,key:String)->Bool{
do {
let realm = try Realm()
try realm.write {
ofType.setValue(value, forKeyPath: key)
}
return true
}catch let error as NSError {
fatalError(error.localizedDescription)
}
}
func filter(id:Int) -> RealmToDoList? {
let match = realm.objects(RealmToDoList.self).filter("id == %#",id).first
return match
}
func newToDoList(date : Date?,lists: [String: Bool]) -> RealmToDoList{
let pill = RealmToDoList()
pill.date = date
pill.lists = lists
return pill
}
}
I doubt that the matter is in the database, but I cannot understand what it is, because I don’t do a filter by country, etc.
The issue is the date because the date will change based on time zone and if you're selecting today's date/time in one time zone, it will be different that's what's in the database. So if a filter is based on this date
#objc dynamic var date : Date?
then that date will be "today" for whatever time zone you're in but a "today" date that was created this morning in a different time zone will not return the current time zones date.
e.g. if you create a new date/time it will be today in this timezone but could be yesterday in a different time zone.
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)
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?