I'm still beginner in iOS development. I'm using Alamofire 4 and SwiftyJSON in order to fetch the data. When I debug, I noticed that its execute return country_list first, and then only go to Alamofire body. I have do some search but still not found the answer.
Model
import UIKit
import SwiftyJSON
import Alamofire
class CountryList {
var ReturnOK: String?
var Countryid: String?
var Countryname: String?
init(ReturnOK: String, Countryid: String, Countryname: String) {
self.ReturnOK = ReturnOK
self.Countryid = Countryid
self.Countryname = Countryname
}
/*
* Fetch the country list
*/
class func fetchCountryList() -> [CountryList] {
var country_list = [CountryList]()
// Add parameters
let param: [String: String] = [
Constants.LIST_COUNTRY.strAppID: Constants.OTHERS.temp_strAppID
]
Alamofire.request(Constants.LIST_COUNTRY.URL, parameters:param, encoding: URLEncoding.default).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
for (_, subJson):(String, JSON) in json {
let item = CountryList(
ReturnOK: subJson["ReturnOK"].stringValue,
Countryid: subJson["Countryid"].stringValue,
Countryname: subJson["Countryname"].stringValue)
country_list.append(item)
}
case .failure(let error):
print(error)
}
}
return country_list
}
}
ViewController
class CreateAccountViewController: UIViewController, UITextViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
#IBOutlet weak var tf_country: UITextField!
#IBOutlet weak var picker_country: UIPickerView!
var countryL: [CountryList]?
override func viewDidLoad() {
super.viewDidLoad()
// Get the country list
countryL = CountryList.fetchCountryList()
print(countryL)
}
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Address"
textView.textColor = UIColor.lightGray
}
}
// Sets number of columns in picker view
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
// Sets the number of rows in the picker view
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if let countryL = countryL {
return countryL.count
} else {
return 0
}
}
// This function sets the text of the picker view to the content of the "salutations" array
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let titleRow = (countryL?[row] as? String)!
return titleRow
}
// When user selects an option, this function will set the text of the text field to reflect
// the selected option.
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if (countryL?.count)! > 0 && (countryL?.count)! >= 0 {
self.tf_country.text = self.countryL?[row] as! String
self.picker_country.isHidden = true
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
// When select th texfield, begin editing
if (textField == self.tf_country) {
self.picker_country.isHidden = false
self.view.endEditing(true)
}
}
}
On Model I can see data is append. But when I print, it was nil.
Any help is really appreciated.
you should create a function with Completion Handler
idea here is that your Alamofire function is asynchronous, which means that, when you call return country_list, Alamofire request is not yet finished.
you should create a function with handler like
class func fetchCountries(handler: #escaping ([CountryList]) -> Void)) {
//alamofire request here
...
case .success(let value):
let json = JSON(value)
for (_, subJson):(String, JSON) in json {
let item = CountryList(
ReturnOK: subJson["ReturnOK"].stringValue,
Countryid: subJson["Countryid"].stringValue,
Countryname: subJson["Countryname"].stringValue)
country_list.append(item)
}
handler(countryList) //this return countryList from this functions
}
and inside your UIViewController it will look like
override func viewDidLoad() {
super.viewDidLoad()
// Get the country list
CountryList.fetchCountryList { [weak self] countryList in
guard let `self` = self else { //this here to avoid reference cycle
return
}
self.countryL = countryList
//update your UI here
print(countryL)
}
}
Related
I am new in swift and I was thinking a way to populate data ideally with segment control which I did not attempt before.
VC has layout below
CategoryType Segment Control : to control CategoryType)
CategoryTextField with picker function : keyboard will show list of data from category
Expecting result of selected CategoryType Segment Control to show list of data based on CategoryType on pickerview
This code is in trial and error mode, as I did not have an exact idea on how to execute the result I wish to obtain.
func appendDefaultCategoryTypes() {
categories = realm.objects(Category.self)
if categories.count == 0 {
try! realm.write() {
let defaultCategories = [
Category(type: .Expense, name: "EXPENSE 1"),
Category(type: .Expense, name: "EXPENSE 2"),
Category(type: .Income, name: "INCOME 1"),
Category(type: .Income, name: "INCOME 2"),
]
realm.add(defaultCategories)
}
}
}
//MARK: - Transaction Section
class Transaction : Object {
//Child of Transaction
let parentAccount = LinkingObjects(fromType: Account.self, property: "ofTransactions")
#objc dynamic var categoryType : Category?
#objc dynamic var amount : String = ""
#objc dynamic var date : String = ""
}
//MARK: - Transaction Category Section
enum CategoryType : String, CaseIterable {
case Income = "Income"
case Expense = "Expense"
static let allValues = [Income, Expense]
init?(id : Int) {
switch id {
case 1:
self = .Income
case 2:
self = .Expense
default:
return nil
}
}
}
class Category : Object {
#objc dynamic var type : String = CategoryType.Income.rawValue
#objc dynamic var name : String = ""
convenience init(type:CategoryType, name: String) {
self.init()
self.type = type.rawValue
self.name = name
}
}
//VC
var categories : Results<Category>!
var picker = UIPickerView()
#IBAction func categoryTypeSC(_ sender: UISegmentedControl) {
guard let selectedCategoryType = CategoryType.(rawValue: sender.selectedSegmentIndex) else {
fatalError("no corresponding category type for the index selected by segment control")
}
switch selectedCategoryType {
case .income :
print("Income in SC selected")
case .expense :
print("Expense in SC selected")
}
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
// if categorySCoutlet.selectedSegmentIndex == 0 {
// return CategoryType.income.count
// } else if categorySCoutlet.selectedSegmentIndex == 1 {
// return CategoryType.expense.count
// }
return categories.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
// if categorySCoutlet.selectedSegmentIndex == 0 {
// return
// } else if categorySCoutlet.selectedSegmentIndex == 1 {
// return
// }
// return "None"
return categories[row].name
}
In your view controller you need to keep track of the categories that correspond to the type indicated by the segmented control. I call it currentCategories.
class ViewController: UIViewController {
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var textField: UITextField!
var categories: Results<Category>!
var currentCategories: Results<Category>!
lazy var pickerView: UIPickerView = UIPickerView()
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
categories = realm.objects(Category.self)
appendDefaultCategoryTypes()
currentCategories = categories.filter("type == %#", CategoryType.income.rawValue)
textField.text = currentCategories.first?.name
textField.inputView = pickerView
pickerView.delegate = self
pickerView.dataSource = self
segmentedControl.addTarget(self, action: #selector(onCategoryTypeChanged(_:)), for: .valueChanged)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if touches.first?.view == view {
textField.resignFirstResponder()
}
}
}
When the segmented control value changes you need to refresh the picker so that the contents reflect the selected category type.
extension ViewController {
#IBAction func onCategoryTypeChanged(_ sender: UISegmentedControl) {
guard let type = CategoryType(id: sender.selectedSegmentIndex) else {
fatalError("no corresponding category type for the index selected by segment control")
}
currentCategories = categories.filter("type == %#", type.rawValue)
textField.text = currentCategories.first?.name
pickerView.reloadAllComponents()
pickerView.selectRow(0, inComponent: 0, animated: true)
}
}
In your picker data source and delegate methods you need to reference data from the categories that reflect the current type.
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return currentCategories.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return currentCategories[row].name
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
textField.text = currentCategories[row].name
}
}
Note that I took the liberty of changing a couple of things in your CategoryType enum. Indexes should start at zero, and cases should be lowercased.
enum CategoryType : String, CaseIterable {
case income = "income"
case expense = "expense"
init?(id : Int) {
if id < CategoryType.allCases.count {
self = CategoryType.allCases[id]
} else {
return nil
}
}
}
CODE HAS BEEN UPDATED AND IS WORKING AS EXPECTED
I have a View Controller with a text field and a PickerView. I want to display the data i have stored in Firebase inside the PickerView. I'm able to retrieve and print the data from Firebase but I can't find a way to display it inside the PickerView. Here is my code:
import UIKit
import Firebase
class pickerVC: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
#IBOutlet weak var labelTxt: UITextField!
#IBOutlet weak var infoPickerViewer: UIPickerView!
var dbRef: CollectionReference!
var pickerView: UIPickerView?
var itemsClass = [ItemInfo]()
override func viewDidLoad() {
super.viewDidLoad()
let pickerView = UIPickerView()
infoPickerViewer.delegate = self
infoPickerViewer.dataSource = self
dbRef = Firestore.firestore().collection(ITEMS_REF)
labelTxt.inputView = pickerView
labelTxt.delegate = self
self.infoPickerViewer = pickerView
self.infoPickerViewer?.delegate = self
self.infoPickerViewer?.dataSource = self
self.infoPickerViewer.reloadAllComponents()
getItems()
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func textFieldDidBeginEditing(_ textField: UITextField) {
labelTxt = textField
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
self.pickerView?.reloadAllComponents()
return true
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if labelTxt.isFirstResponder {
return self.itemsClass.count
}
return 0
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if labelTxt.isFirstResponder {
return itemsClass[row].itemName
}
return nil
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if labelTxt.isFirstResponder {
let item = itemsClass[row].itemName
labelTxt.text = item
}
}
func getItems() {
dbRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("error fetching docs: \(err)")
} else {
self.infoPickerViewer.reloadAllComponents()
let snap = snapshot
for document in snap!.documents {
let data = document.data()
let itemCode = data[ITEMS_CODE] as? String ?? ""
let itemName = data[ITEMS_NAME] as? String ?? ""
let t = ItemInfo(itemCode: itemCode, itemName: itemName)
self.itemsClass.append(t)
print("ITEMS_CODE", itemCode as Any)
print("ITEMS_NAME", itemName as Any)
}
}
}
}
}
The Firebase DB is structured as follow:
collection/AutoID/itemCode: "item1"
itemName: "item2"
collection/AutoID/itemCode: "item3"
itemName: "item4"
I only need to display the itemName inside the PickerView, the itemCode I'm going to use it to run a query depending on the selection in the PickerView.
Any help with this is greatly appreciated.
I am trying to set my Button title from a selected row from uipickerview. However, I can't seem to find a valid solution.
I have set the variables to be global thus I can access the Selected Variable. However I cant seem to be able to pinpoint exactly when the user clicks the exit button and able to update my button title.
Things I have tried:
Global Functions ( couldn't work because you cant specify the button you want to change the name of )
Dispatchqueue.main.async - Did not update my title. The idea here is for it to constantly check for a string change.
The solution I found here is in Obj-C. I tried converting it to swift https://objectivec2swift.com/#/converter/code/ with no luck.
Set Title of UIButton from Selected Row of UIPickerView
#IBAction func CountryPickButton(_ sender: UIButton) {
Global.setPickerDataSource(data:["Test","test2","test3"])
Global.setPickerCompletionHandler(int:0)
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PickerViewPopUpId") as! PickerViewController
//CountryPickButtonDetector = sender as? UIButton
self.addChild(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParent: self)
popOverVC.callback { value in
//CountryPickButton.title.text = value
sender.setTitle(value, for: UIControl.State)
}
}
import UIKit
var test = ""
class PickerViewController: UIViewController {
#IBOutlet weak var PickerView: UIPickerView!
var callback : ((String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
//self.view.backgroundColor = UIColor.black.withAlphaComponent(0.8)
PickerView.dataSource = self
PickerView.delegate = self
// Do any additional setup after loading the view.
}
#IBAction func ExitButton(_ sender: Any) {
switch Global.pickerCompletionHandler {
case 0:
callback?(Global.pickerResult ?? "")
Global.setProfileCountry(string:
Global.pickerResult ?? "")
default:
print("nothing")
}
self.view.removeFromSuperview()
}
}
extension PickerViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1 // can be more than 1 component like time/date/year
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return Global.pickerDataSource?.count ?? 1
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
//Global.pickerResult = Global.pickerDataSource?[row]
Global.setPickerResult(result: Global.pickerDataSource?[row] ?? "Test" )
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return Global.pickerDataSource?[row]
}
}
Globals
import Foundation
struct Global {
static var pickerDataSource:[String]? = nil
static var pickerResult:String? = nil
static var pickerCompletionHandler:Int? = 0
static var profileCountry:String? = nil
static func setPickerDataSource(data:[String]) {
Global.pickerDataSource = data
}
static func setPickerResult(result:String) {
Global.pickerResult = result
}
static func setPickerCompletionHandler(int: Int) {
Global.pickerCompletionHandler = int
}
static func setProfileCountry(string: String)
{
Global.profileCountry = string
}
}
I don't know what all that Global stuff is, the easiest solution might be to extend pickerCompletionHandler, but here is another (easy) one:
In PickerViewController add a callback passing a String value and no return value
var callback : ((String) -> Void)?
and call it in exitButton (please, please variable and function names are supposed to start with a lowercase letter)
#IBAction func exitButton(_ sender: Any) {
switch Global.pickerCompletionHandler {
case 0:
callback?(Global.pickerResult ?? "")
Global.setProfileCountry(string:
Global.pickerResult ?? "")
default:
print("nothing")
}
self.view.removeFromSuperview()
}
In countryPickButton (again: lowercase letter) set the callback and set sender to the real type. The callback sets the title
#IBAction func countryPickButton(_ sender: UIButton) {
Global.setPickerDataSource(data:["Test","test2","test3"])
Global.setPickerCompletionHandler(int:0)
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PickerViewPopUpId") as! PickerViewController
popOverVC.callback = { value in
sender.setTitle(value, for: .normal)
}
...
From "var myValue:[Double]..." and "var activeCurrency:Double = 0;" to the third pickerview code, I am not sure what to do in order to fix the "self.myValues.append((Url as? Double)!)" so that my app displays the currency amount. I have searched everywhere, and have no luck in finding anything. You are my last hope, please help. I'm still fairly new to coding, so I may not know the certain jargon.
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
var myCurrency:[String] = []
var myValues:[Double] = []
var activeCurrency:Double = 0;
//OBJECTS
#IBOutlet weak var textbox: UITextField!
#IBOutlet weak var pickerView: UIPickerView!
#IBOutlet weak var output: UILabel!
//CREATING PICKER VIEW
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
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 = myValues[row]
}
//BUTTON
#IBAction func action(_ sender: AnyObject) {
if (textbox.text != "") {
output.text = String(Double(textbox.text!)! * activeCurrency)
}
}
override func viewDidLoad() {
super.viewDidLoad()
//DATA GRABBING
let url = URL(string: "https://min-api.cryptocompare.com/data/all/coinlist")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print ("ERROR")
}
else {
if let content = data {
do {
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let rates = myJson["Data"] as? NSDictionary {
for (Name, Url) in rates {
self.myCurrency.append((Name as? String)!)
self.myValues.append((Url as? Double)!)
}
}
}
catch {
}
}
}
if data != nil {
DispatchQueue.main.async { // Correct
self.pickerView.reloadAllComponents()
}
}
}
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
getDataAnswer()
}
func getDataAnswer() {
// Fetch answers
let answerFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Answer")
let answerFilter = questions[AnswerResultPickerView.selectedRow(inComponent: 0)] // **tell me the error**
let answerPredicate = NSPredicate(format: "question == %#", answerFilter)
answerFetch.predicate = answerPredicate
do {
answers = try context.fetch(answerFetch) as! [Answer]
} catch {}
error that appear on the line marked
Hi, I have a picker view (called AnswerResultPickerView) and I want that when the User select a row of the first component (where there are questions) in the second component (where are the answers) the answers are filtered based on the related question. All data are in core Data and there is a one to one relationship from answer to question called "question" and the reverse that is one to many
I attach all the code of the view controller, maybe can be helpful:
#IBOutlet weak var AnswerResultPickerView: UIPickerView!
#IBOutlet weak var WeightTextField: UITextField!
#IBOutlet weak var NameTextField: UITextField!
#IBAction func CreateButton(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
self.AnswerResultPickerView.delegate = self
self.AnswerResultPickerView.dataSource = self
}
var questions: [Question] = []
var answers: [Answer] = []
var results: [Result] = []
var data = [[Question](), [Answer](), [Result]()] as [[Any]]
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewWillAppear(_ animated: Bool) {
getDataAnswer()
getData()
AnswerResultPickerView.reloadAllComponents()
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 3
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
let numberOfRowsInComponent0: Int = questions.count
let numberOfRowsInComponent1: Int = answers.count
let numberOfRowsInComponent2: Int = results.count
let returnValue = [numberOfRowsInComponent0, numberOfRowsInComponent1, numberOfRowsInComponent2]
return returnValue[component]
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
var returnValue = [String]()
switch component {
case 0:
returnValue = [questions[row].text!]
case 1:
returnValue = [answers[row].text!]
case 2:
returnValue = [results[row].tip!]
default:
break
}
return returnValue[0]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
getDataAnswer()
}
func getDataAnswer() {
// Fetch answers
let answerFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Answer")
let answerFilter = questions[AnswerResultPickerView.selectedRow(inComponent: 0)] // here where the error appear
let answerPredicate = NSPredicate(format: "question == %#", answerFilter)
answerFetch.predicate = answerPredicate
do {
answers = try context.fetch(answerFetch) as! [Answer]
} catch {
}
}
func getData() {
// Fetch questions
let questionFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Question")
let questionFilter = addNewMe
let questionPredicate = NSPredicate(format: "me = %#", questionFilter)
questionFetch.predicate = questionPredicate
// Fetch results
let resultFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Result")
let resultFilter = addNewInterest
let resultPredicate = NSPredicate(format: "interest = %#", resultFilter)
resultFetch.predicate = resultPredicate
do {
questions = try context.fetch(questionFetch) as! [Question]
results = try context.fetch(resultFetch) as! [Result]
} catch {
}
}