Swift - Passing data from model to controller using delegate design pattern - swift

i am a swift learner, and need assistance in passing data
Here i have ViewController and CoinManager model. What i am trying to do is call didUpdatePrice method that's declared inside my ViewController from CoinManager, this function is responsible to update the UI. I have implemented CoinManagerDelegate protocol in ViewController, but every time i am trying to call didUpdatePrice method it throws an error like this:
Type '(any CoinManagerDelegate)?' has no member 'didUpdatePrice'
When i tried to declare delegate in my ViewController like this:
coinManager.delegate = self
it also returns an error like this:
Cannot assign value of type 'ViewController' to type '(any
CoinManagerDelegate)?.Type'
Here's my ViewController code:
import UIKit
class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, CoinManagerDelegate {
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
coinManager.delegate = self
currencyPicker.dataSource = self
currencyPicker.delegate = self
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return coinManager.currencyArray.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return coinManager.currencyArray[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let selectedCurrency = coinManager.currencyArray[row]
coinManager.getCoinPrice(for: selectedCurrency)
}
#IBOutlet weak var currencyPicker: UIPickerView!
#IBOutlet weak var currencyLabel: UILabel!
#IBOutlet weak var bitcoinLabel: UILabel!
func didUpdatePrice(price: String, currency: String) {
DispatchQueue.main.async {
self.bitcoinLabel.text = price
self.currencyLabel.text = currency
}
}
}
And here's my CoinManager model code:
import Foundation
protocol CoinManagerDelegate: NSObjectProtocol {
func didUpdatePrice(price: String, currency: String)
}
struct CoinManager {
var delegate = CoinManagerDelegate?.self
let baseURL = "https://rest.coinapi.io/v1/exchangerate/BTC"
let apiKey = "#API_KEY"
let currencyArray = ["AUD", "BRL","CAD","CNY","EUR","GBP","HKD","IDR","ILS","INR","JPY","MXN","NOK","NZD","PLN","RON","RUB","SEK","SGD","USD","ZAR"]
func getCoinPrice(for currency: String){
let urlString = "\(baseURL)/\(currency)?apikey=\(apiKey)"
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print("error")
}
if let safeData = data {
let data = parseJSON(safeData)
let dataString = String(format: "%.2f", data!)
self.delegate.didUpdatePrice(price: dataString, currency: currency)
}
}
task.resume()
}
}
func parseJSON(_ data: Data) -> Double? {
let decoder = JSONDecoder()
do{
let decodedData = try decoder.decode(CoinData.self, from: data)
let lastPrice = decodedData.rate
return lastPrice
} catch {
return nil
}
}
}
Any help is much appreciated!
On the internet, this method should works fine. But it didn't work on my machine.
I've tried to surf the internet and implemented their answers, but it didn't help me, i tried to ask chatGPT as well but the result is nil TwT. Thanks a lot fellow coders!

change the following in your code this should work at your end
in ViewController change coinManager.delegate = ViewController to coinManager.delegate = self
and in CoinManager update the following
var delegate = CoinManagerDelegate?.self
to
var delegate : CoinManagerDelegate?
on call back with delegate first make sure delegate connection is successfully connect
if let del = self.delegate
{
del.didUpdatePrice(price: dataString, currency: currency)
}

Related

How can I get value of a Dictionary item from first item of an Array in Swift?

I have defined Dictionary in an Array object below:
var dataList : [[String:Any]]?
And then I have loaded some data in to it. Here is the output when i run this code below:
print("DATA LIST:\n \(myRecordList)")
Output:
Optional([["itemCode": 0, "itemText": please select city], ["itemCode": 1, "itemText": City A], ["itemCode": 2, "itemText": City B], ["itemCode": 3, "itemText": City C], ["itemCode": 4, "itemText": City D], ["itemCode": 5, "itemText": City E], ["itemCode": 6, "itemText": City F]])
I would like to reach value of a dictionary from first item in an Array:
As you know, here is the first item in an array:
["itemCode": 0, "itemText": please select city]
And here is the value in above dictionary:
"itemText": please select city
After that I would like to set above text ("please select city") as a button title if dataList count is not zero:
if dataList?.count != 0
{
**//need help in here!!!**
{
self.btnSelectMarkaOutlet.setTitle(tempBtnTitle, for: .normal)
}
}
I have tried below scope. But it crushed:
if let newTry = dataList![0]["itemText"] as? [String]
{
print("here is the output:: \(newTry[0])")
}
Here is the error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
let tempBtnTitle = must be "please select city"
I wanted to do this.
Let me share whole view controller file. Because dataList is nil.
import UIKit
class SinyalSeviyesineGoreAramaYapViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var btnSelectMarkaOutlet: UIButton!
#IBOutlet weak var pickerViewOutlet: UIPickerView!
var dataList : [[String:Any]]?
override func viewDidLoad() {
print("viewDidLoad")
super.viewDidLoad()
self.pickerViewOutlet.delegate = self
self.pickerViewOutlet.dataSource = self
self.loadSayacRecordsForTheUIPickerView()
print("Is the data list nil? : \(self.dataList)")
if let newTry = self.dataList![0]["itemText"] as? String
{
print("here is the output:: \(newTry)")
}
//I WANTED TO SET BUTTON TITLE !!
let tempButtonTitle = ""
self.btnSelectMarkaOutlet.setTitle(tempButtonTitle, for: .normal)
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let recordName = self.dataList![row]
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
//return self.cityList![row]
let info = self.dataList![row]
if let name = info["itemCode"] as? String,
let code = info["itemText"] as? String
{
let text = "\(name) - \(code)"
//print("am i here? \(text)")
return text
}
return nil
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.dataList == nil ? 0 : self.dataList!.count
}
func loadSayacRecordsForTheUIPickerView()
{
print("loadSayacRecordsForTheUIPickerView method is called")
ServiceManager.oServiceManager.loadSayacRecords()
{
(result) in
if let sayacRecords = try? JSONSerialization.jsonObject(with: result!, options: .mutableContainers) as? [String:Any]
{
if let resultCodeFromWebApi = sayacRecords?["resultCode"] as? String
{
DispatchQueue.main.async
{
print("loadSayacRecordsForTheUIPickerView resultCode: \(resultCodeFromWebApi)")
if resultCodeFromWebApi == "999"
{
if let myRecordList = sayacRecords?["recordList"] as? [[String:Any]]
{
//DispatchQueue.main.async
//{
self.dataList = myRecordList
print("DATA LIST:\n \(self.dataList)")
self.pickerViewOutlet.reloadAllComponents()
//}
}
} // resultCodeFromWebApi == 999 ENDS
else
{
if let resultMessageFromWebApi = sayacRecords?["resultMessage"] as? String
{
print("resultMessage: \(resultMessageFromWebApi)")
}
}
} // DispatchQueue.main.async ENDS
}
else
{
print("cant parse it")
} // if let resultCodeFromWebApi = loginResult?["resultCode"] as? Int ENDS
}
}
} // loadSayacRecordsForTheUIPickerView ENDS
} // class SinyalSeviyesineGoreAramaYapViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource ENDS
The problem is in the logic you implemented: the dataList is loaded asynchronously, which means that you don't know when it will be available, but you are using it like it's always available.
Example of this error is in the viewDidLoad:
self.loadSayacRecordsForTheUIPickerView()
print("Is the data list nil? : \(self.dataList)")
if let newTry = self.dataList![0]["itemText"] as? String
{
print("here is the output:: \(newTry)")
}
With the first line you start to load the dataList, but in the for loop you are force unwrapping, which means you are saying "Ehi I'm 100% sure it will be available" - which is not true.
A possible solution for your problem is to convert the dataList! to dataList? everywhere, so if it's not loaded then nothing happens and your app won't crash.
A better and quicker solution is to have a default value for dataList, so in case is not loaded you will just react to an empty data model:
var dataList : [[String:Any]] = [[String:Any]]()
A reason would be array is nil and also you are unwrapping with [String] which is String, which may give nil.
Try below to avoid crashes.
if dataList?.isEmpty == false,let firstDic = dataList?.first, let neededString = firstDic["itemText"] as? String {
}

Getting data out of a struct for use in a table view

I'm unable to get a struct containing vars loaded from JSON into a format suitable for a table view
I've been playing around with this code for a while, done plenty of reading but I'm just plain stuck. I can get data from the server fine and print it to the console. I notice that the data is printed as KEY: "value" rather than "KEY":"value" and that might have something to do with it. Also as the code is right now I can count the number of rows correctly and display the value of FIRST in the table view. What I can't figure out is how to access the other variables for display. Any help is greatly appreciated!
import Cocoa
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
static var globalPatientInstance: [Patient] = []
var gotJSON: ([Patient], Error?) -> Void = { (patients, error) in
print("patients from gotJSON: \(patients)")
globalPatientInstance = patients
// this returns an error: "Type 'Patient' does not conform to protocol 'Sequence'"
/*
for (key, value) in patients[0] {
println("\(key) -> \(value)")
tableArray.append(Objects(sectionName: key, sectionObjects: value))
}
*/
print("patients from gotJSON: \(globalPatientInstance[0])")
}
#IBOutlet var tableView: NSTableView!
#IBAction func button(_ sender: Any) {
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.delegate = self as NSTableViewDelegate
self.tableView.dataSource = self
let url = URL(string: "http://172.16.1.25/backend/returnA")
let returnA = URLRequest(url: url!)
retrieveJSON(with: returnA, completionHandler: gotJSON)
self.tableView.reloadData()
}
func retrieveJSON(with request: URLRequest, completionHandler: #escaping ([Patient], Error?) -> Void) {
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: request as URLRequest) {
// completion handler argument
(data, response, error) in
// completion handler
guard let data = data else {
print("Did not recieve data")
completionHandler([], error)
return
}
do {
let decoder = JSONDecoder()
let patients = try decoder.decode(Array<Patient>.self, from: data)
// print(Patient)
completionHandler(patients, error)
}
catch let err {
print("Err", err)
completionHandler([], error)
}
}
task.resume()
}
func numberOfRows(in tableView: NSTableView) -> Int {
return ViewController.globalPatientInstance.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var result: NSTableCellView
result = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) as! NSTableCellView
result.textField?.stringValue = ViewController.globalPatientInstance[row].FIRST
return result
}
}
This code runs fine. The array contains three structs and the value of FIRST is successfully displayed in the table view when the button is pressed.

Accessing information from another viewController

I am new to swift and I am trying to repopulate a pickerview using an array of names from another class. I have this class Notes that creates notes and each note has a name/title, I am trying to access those names/title and put them in pickerview, can anyone help me.
import UIKit
var allNotes: [Note] = []
var currentNoteindex: Int = -1
var noteTable: UITableView?
let kAllNotes:String = "notes"
class Note: NSObject
{
var date:String
var note:String
var name:String
override init()
{
date = NSDate().description
name = ""
note = ""
}
func dictionary() -> NSDictionary
{
return ["note": note, "date": date, "name": name]
}
I have two different pickerview and one works it's just the one with the names doesn't work.
override func viewDidLoad()
{
super.viewDidLoad()
fileNamePicker.dataSource = self
fileNamePicker.delegate = self
gradePicker.dataSource = self
gradePicker.delegate = self
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int
{
if pickerView == gradePicker
{
return grades.count
}
else
{
return allNotes.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?
{
if pickerView == gradePicker
{
return grades[row]
}
else
{
//This is where the Problem is, but I don't know how to fix it.
let nameFiles = allNotes[currentNoteindex].name
return nameFiles[row]
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int
{
return 1
}
nameFiles is accessing a name from a note.
nameFiles[row] doesn't work because nameFiles is not an array.
If you want to return a string, simply do return nameFiles. Check first to make sure that nameFiles actually has a value, though!
Looks like your allNotes array is empty. You have initilaize but does not seems to append the object. Create initiliaze method inside Note class
init(_ date: String, note: String, name: String) {
self.date = date
self.note = note
self.name = name
}
In your viewcontroller class try appending like this. Now below you can access inside picker view method:-
//You can append by doinf iteration based on your data
allNotes.append(Note(date: "value1", note: "Value", name: "name"))

issues loading items from firebase to a pickerview

I have successfully created a QuestionModel class which retrieves items from firebase such, as a Question, string of answers and a correct question. I am however now having difficulty in terms of getting these items to go into a picker view from another class. The class called QuestionsViewController is where I am having an issue in terms of using the questions class to retrieve data from. The QuestionModel class contains retrieving data from firebase. I am having bad execution errors throughout the code in the QuestionsViewController class. This mainly occurs when trying to set the itemlabel text before the pickverview and code for the pickerview functions.
import Foundation
import Firebase
import FirebaseDatabase
import FirebaseAuth
import CoreData
class QuestionList
{
//properties
public static var Username: String = ""
private static var quiz = [Question]()
static func getDummyQuestions()->[Question]
{
//create some dummy data for the model
var ref: FIRDatabaseReference!
var refHandle: UInt!
ref = FIRDatabase.database().reference() //reference
refHandle = ref.child("Questions").child("Q1").observe(.value, with: { (snapshot)in
if let dataDict = snapshot.value as? [String: Any] {
if let quest = dataDict["Question"] as? String,
let Answers = dataDict["Answers"] as? [String],
let Correct = dataDict["Correct"] as? Int {
quiz.append(Question(q: quest, a: Answers, c: Correct))
}
print (dataDict)
}
})
return quiz
}
}
class Question {
var quest:String
var answers:[String]
var correct:Int
init(q: String, a:[String], c:Int)
{
quest = q
answers = a
correct = c
}
func isCorrectQuestion(itemSelected: String)->Bool {
if (itemSelected == answers[correct]) {
return true
} else {
return false
}
}
}
import UIKit
import Firebase
import FirebaseAuth
class QuestionsViewController: UIViewController, UIPickerViewDelegate {
#IBOutlet weak var usernamelabel: UILabel! //sets username label
#IBOutlet weak var Next: UIButton! //next button
#IBOutlet weak var itemLabel: UILabel! //item user has selected
#IBOutlet weak var Question: UILabel! //sets question label
#IBOutlet weak var pickerview: UIPickerView! //sets picker view
public var totalQuestions: Int = 0 //sets total question to 0
public var currentQuestion = 0 //sets current question to 0
public var totalCorrect: Int = 0 //sets totalcorrect to 0
var itemSelected: String = "" //item selected
var LabelText = String()
let Exam = QuestionList() //uses the questions class for instances
var Questions = QuestionList.getDummyQuestions()
var ref: FIRDatabaseReference!
var refHandle: UInt!
override func viewDidLoad() {
super.viewDidLoad() //when the app is loaded
ref = FIRDatabase.database().reference() //reference
refHandle = ref.child("Questions").observe(.value, with: { (snapshot)in
let dataDict = snapshot.value as! [String: AnyObject]
print (dataDict)
})
usernamelabel.text = LabelText //username
pickerview.delegate = self
itemLabel.text = "" //loads the item label of whats selected
itemSelected = QuestionList.getDummyQuestions()[currentQuestion].answers[0] //initially when loaded first item is selected
Question.text = QuestionList.getDummyQuestions()[currentQuestion].quest
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1 //return one component from the picker
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return QuestionList.getDummyQuestions()[currentQuestion].answers.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?{
return QuestionList.getDummyQuestions(). [currentQuestion].answers[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){
itemSelected = QuestionList.getDummyQuestions(). [currentQuestion].answers[row]
}
#IBAction func NextAction(_ sender: Any){
currentQuestion = currentQuestion + 1 //moves onto next question and increments
if (QuestionList.getDummyQuestions()[currentQuestion].isCorrectQuestion(itemSelected: itemSelected)) {
totalCorrect += 1
itemLabel.text = String(totalCorrect) + "/" + String(totalQuestions)
}
if(currentQuestion < QuestionList.getDummyQuestions().count) {
pickerview.reloadAllComponents()
itemSelected = QuestionList.getDummyQuestions()[currentQuestion].answers[1]
Question.text = QuestionList.getDummyQuestions() [currentQuestion].quest
} else {
pickerview.isHidden = true
Question.text = "You have finished"
Next.isHidden = true
}
}
}
Firebase functions do not (and should not) return values as they are asynchronous.
So the return quiz line will fail most of the time as it will try to return data before Firebase has had time to retrieve it from the server.
When coding with Firebase, data is only valid inside the closure following the function. So for example, this what NOT to do:
func someFunc() {
ref.child("Questions").child("Q1").observe(.value, with: { snapshot in
print(snap)
})
print(snap) //this will not print the snap as this line executes *before* the closure
}
So doing it the right way; retrieve the data from Firebase, populate the array and refresh the tableview all within the closure.
static func populateArrayAndRefreshTableView()
{
var ref: FIRDatabaseReference!= FIRDatabase.database().reference()
let questionsRef = ref.child("Questions")
questionsRef.child("Q1").observeSingleEvent(of: .value, with: { snapshot in
if let dataDict = snapshot.value as? [String: Any] {
let quest = dataDict["Question"] as? String,
let Answers = dataDict["Answers"] as? [String],
let Correct = dataDict["Correct"] as? Int {
self.quizArray.append(Question(q: quest, a: Answers, c: Correct))
self.tableView.reloadData()
}
})
}
}
Also note that the original code was using observe(.value). That will leave an observer attached to the ref and if the question changes, the code will be called. It doesn't look like that should be the behavior so using observeSingleEvent will call it once without adding an observer.
Finally - you may want to re-consider how the nodes are named in your structure. It's often best practice to disassociate node name keys from the data they contain.
questions
-UYiuokoksokda
question: "What significant contribution to bioengineering was made on the Loonkerian outpost on Klendth?"
correct_answer: answer_1
answers:
answer_0: "Left handed smoke shifter"
answer_1: "The universal atmospheric element compensator"
answer_2: "Warp coil nullification amplifier"
answer_3: "H.A.L. 9000"
-YY8jioijasdjd
question: "What is Kiri-kin-tha's first law of metaphysics?"
correct_answer: answer_2
answers:
answer_0: "No matter where you go, there you are"
answer_1: "Only people with sunroofs use them"
answer_2: "Nothing unreal exists"
answer_3: "Gravity is heavy"
The keys, UYiuokoksokda, are created with childByAutoId().
If you need to query answers you may want to even denormalize them into their own node and use the question key as the node key for the answers or keep a child node with the question key.

Json parsing into swift 3 currency exchange values from bank accounts

Is it possible to fetch currency exchange rates from some banks websites in my country, and is it legal ?
making an app specified to my country and have been trying for a week now but couldn't find an answer this is the code could you tell a solution?
import UIKit
class ViewController: UIViewController,UIPickerViewDelegate,UIPickerViewDataSource {
#IBOutlet weak var lblDisplay: UILabel!
#IBOutlet weak var txtField: UITextField!
#IBOutlet weak var pkrView: UIPickerView!
var myCurrency:[String] = []
var myValue:[Double] = []
var activeCurrency:Double = 0
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://ca-egypt.com")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error)in
if error != nil{
print("error is here!")
}else{
if let content = data{
do{
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let rates = myJson["rates"] as? NSDictionary{
var firstOne = true
for (key,value)in rates{
self.myCurrency.append((key as? String)!)
self.myValue.append((value as? Double)!)
}
}
}catch{
}
}
}
self.pkrView.reloadAllComponents()
}
task.resume()
}
#IBAction func btnCalculateAction(_ sender: UIButton) {
lblDisplay.text = String(Double(txtField.text!)! * activeCurrency)
}
func numberOfComponents(in pickerView: UIPickerView,umberOfRowsInComponent component: Int) -> Int {
return myCurrency.count
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return myCurrency.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return myCurrency[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
activeCurrency = myValue[row]
}
}
Image of the Error:
Hello #Ali instead of fetching currency exchange rates from some banks websites, you can make use of some API, which are freely available or you can make use of paid API's. And off-course fetching currency exchange rates is illegal. They won't allow some-one to get values.
To get various countries currency-exchange rates use Fixer.io , which are updating on daily basis, and yes it is freely available.
Fixer.io is a free JSON API for current and historical foreign exchange rates, published by the European Central Bank. The rates are updated daily around 4PM CET.
Example :
API Link : http://api.fixer.io/latest?base=USD
Alamofire.request("http://api.fixer.io/latest?base=USD") .responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
let usd_val = (arr["rates"]?["INR"] as? NSNumber)!
print(usd_val)
}
}
By this way you can parse JSON API and use this currency exchange values, and if you want to get your country currency exchange value, then pass value.
example : To get current currency exchange value of Japan, then pass value of it. Like this. http://api.fixer.io/latest?base=JPY
Hope it helps you.