I'm trying to populate a UIPickerView using the response from an API, I can successfully do this with basic JSON responses but I am really struggling with the example below where the JSON returns a value as an array.
The code below is from a Playground and has been simplified to only show the relevant code, I am confident that if I can create a variable with the array in the correct format then I can use it to populate the UIPickerView but I've included the UIPickerView code in case I am going about this the wrong way.
I have searched for and tried to adapt various examples from both this forum and others but for some reason, I just can't get it to work, I am new to Swift so I'm on a learning curve!
The closest I have got is account(data: ["account1", "account2", "account3"]) but I need to get ["account1", "account2", "account3"]
I hope someone can help and hopefully not downvote the question.
import UIKit
let json = """
{
"returnResult":1,
"data":["account1", "account2", "account3"]
}
"""
struct account: Codable {
var data: [String]
}
let accounts: account = try JSONDecoder().decode(account.self, from:json.data(using: .utf8)!)
print(accounts)
/*
RESULT RETURNED
account(data: ["account1", "account2", "account3"])
RESULT REQUIRED
["account1", "account2", "account3"]
PICKERVIEW CODE FROM WORKING VIEW
// picker view methods
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return accs.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> account? {
return accs[row]
}
// delegate method
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let selectedDriver = accs[row]
displayLbl.text = selectedDriver
}
*/
I finally got it sorted using the following code as the basis for the solution
import UIKit
struct Account: Codable {
var data: [String]
}
class ViewController: UIViewController {
// MARK: - IBOutlets
#IBOutlet private var displayLabel: UILabel!
#IBOutlet private var pickerView: UIPickerView!
// MARK: - Internal Variables'
var account: Account?
// MARK: - IBActions
#IBAction private func showPicker(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
pickerView.isHidden = !sender.isSelected
}
private func createAccountList() {
let json = """
{
"returnResult":1,
"data":["account1", "account2", "account3","account4", "account5", "account6"]
}
"""
do {
account = try JSONDecoder().decode(Account.self, from: json.data(using: .utf8)!)
} catch {
print(error.localizedDescription)
}
}
private func initUI() {
pickerView.delegate = self
pickerView.dataSource = self
}
override func viewDidLoad() {
super.viewDidLoad()
createAccountList()
initUI()
pickerView.reloadAllComponents()
// Do any additional setup after loading the view, typically from a nib.
}
}
extension ViewController: UIPickerViewDataSource, UIPickerViewDelegate {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return account?.data.count ?? 0
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return account?.data[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
displayLabel.text = account?.data[row]
}
}
The project can be found on Github https://github.com/techmehra/uipicker-demo
I did have to amend the code slightly to make it work with the live API response.
Related
i am building a COVID-19 app tracker on IOS.
In order to display the data by country, I have built a pickerView that will contain all the country names.
thanks to an HTTP cal, I have managed to get the JSON data i.e the name of each country. ideally I wish to append each value to an array that in turn will populate the pickerView.
Is that possible ? If yes, how would I do that ?
I am also open to other ways to do it. Here is my code :
#IBOutlet weak var confirmedCasesLabel: UILabel!
#IBOutlet weak var deathsLabel: UILabel!
#IBOutlet weak var recoveriesLabel: UILabel!
//MARK: - Relevant variables
private let covidUrl: String = "https://corona-api.com/countries"
var countryArray: [String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
countryPickerView.delegate = self
countryPickerView.dataSource = self
//
httpCall()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
//MARK: - Functions that handles picker view delegates and data source
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return countryArray.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return countryArray[row]
}
//MARK: - HTTP CALL - GET COUNTRY DATA
func httpCall() {
request(covidUrl, method: .get).responseJSON { (response) in
if response.result.isSuccess {
//test just print some data
let dataJSON: JSON = JSON(response.result.value)
//print(dataJSON)
//on va identifier chaque pays + l'ajouter au tableau des pays
// let countryNameJSON = dataJSON["data"][99]["name"]
// print(countryNameJSON)
for country in 0...99 {
let countryNameJSON = dataJSON["data"][country]["name"].stringValue
print(countryNameJSON)
//on ajoute ce nom au tabeleau de pays
//self.countryArray.append(countryNameJSON)
}
}
}
}
}
Create a struct which conforms to Decodable protocol and add the required attributes
struct Country: Decodable {
var name: String?
}
Create an Country object type array named countryArray in your class
Perform the HTTP call
Fetch the data from the server
Parse the and load into countryArray object using JSONDecoder
Reload the countryPickerView after parsing
Please follow the sample below
class YourClass {
#IBOutlet weak var countryPickerView: UIPickerView!
var countryArray = [Country]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
countryPickerView.delegate = self
countryPickerView.dataSource = self
httpCall()
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return countryArray.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return countryArray[row].name
}
//MARK: - HTTP CALL - GET COUNTRY DATA
func httpCall() {
request(covidUrl, method: .get).responseJSON { (response) in
if response.result.isSuccess {
let countryArray = try JSONDecoder().decode([Country].self, from: #yourJsonData#)
countryPickerView.reloadAllComponents()
}
}
}
}
Since the Country object conforms to Decodable protocol the parsing will be done with no loops given that the Struct matches with your JSON schema.
I am calling API to fill the data in UIPickerView using Swift 5 and Alamofire 4.8.2.
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
var my_array = [[String : AnyObject]]()
#IBOutlet weak var pickerArray: UIPickerView!
#IBOutlet weak var labelName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string:"https://restcountries.eu/rest/v2/all") else {
return
}
Alamofire.request(url, method: .get, encoding: JSONEncoding.default, headers: nil).responseJSON { (resData) -> Void in
let swiftyJsonVar = JSON(resData.result.value!)
print("Full Output", swiftyJsonVar)
let finalData = swiftyJsonVar[0]["callingCodes"].arrayValue
print("final data", finalData)
let finalData2 = swiftyJsonVar[0]["borders"].arrayValue
print("single dimensional array output: ", finalData2)
let sampData = JSON(resData.result.value!)
self.my_array = finalData2 as [AnyObject] as! [[String : AnyObject]]
self.pickerArray.reloadAllComponents()
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return my_array.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return my_array[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
labelName.text = "You Selected : " + my_array[row]
}
}
Finally, I need that
* "finalData2" result into my picker view
* also any data I need to take from that nested JSON data and pass to picker view
How to load all data into picker view, now I can load first data only
this is my data from JSON response
**jsonResponse ==> Optional([["PlanDate": 18/01/2019, "PlanDateFullFormat": 20190118], ["PlanDateFullFormat": 20190119, "PlanDate": 19/01/2019]])
jsonArray ==>[["PlanDate": 18/01/2019, "PlanDateFullFormat": 20190118], ["PlanDateFullFormat": 20190119, "PlanDate": 19/01/2019]]
jsonDictionary ==>["PlanDate": 18/01/2019, "PlanDateFullFormat": 20190118]
planDate ==> 18/01/2019. ==> I want load all plant date into picker view
Loop json ==> (key: "PlanDateFullFormat", value: 20190118)
Loop json ==> (key: "PlanDate", value: 18/01/2019)**
I cannot load all data into picker view
func getPlanDatetoPickerview(ptruckID: String)-> Void {
.....
//check data shipment for json Dictionary
let planDate: String = jsonDictionary["PlanDate"] as! String
print("planDate ==> \(planDate)")
//show on pickerView
for myplanDate in jsonDictionary{
print("Loop json ==> \(myplanDate)")
}//for
self.getpPlandate = [jsonDictionary["PlanDate"] as! String]
.....
}catch let myerror{
print(myerror)
// check display plandate in database
....
}//DispatchQueue
}//catch
}//task
task.resume()
}//getPlanDatetoPickerview
I'm assuming your pickerView is set up properly but you are only seeing one row? If that's the case that's probably because of this line of code:
//change array to dictionary
guard let jsonDictionary:Dictionary = jsonArray.first else{
return
}//guard
print("jsonDictionary ==>\(jsonDictionary)")
You are only getting the first element of your array.
What I would do instead is just use jsonResponse directly like this:
var planDates = [Any]()
if let jsonResponse = jsonResponse {
for dictionary in jsonResponse {
planDates.append(dictionary["PlanDate"])
}
}
Then you can use planDates to populate your pickerView.
Or maybe, you are trying to load a pickerView with data from a dictionary?
First, your ViewController has to subclass UIPickerViewDataSource, UIPickerViewDelegate.
Then in ViewDidLoad, set your ViewController as the delegate/datasource for your UIPickerView:
repeatPicker.delegate = self
repeatPicker.dataSource = self
Then implement your Delegate/Datasource methods:
// The number of components of your UIPickerView
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
// The number of rows of data
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
// Return number of planDate entries
}
// The data to return for the row and component (column) that's being passed in
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
}
Actually its pretty easy to load data to a picker view. It's like UITableView.
class YourViewController {
var yourArray: [YourObject] = []
var yourPicker = UIPicker()
override func viewDidLoad() {
super.viewDidLoad()
yourPicker.dataSource = self
yourPicker.delegate = self
}
}
// MARK: - UIPickerViewDataSource
extension YourViewController: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return yourArray.count
}
}
// MARK: - UIPickerViewDelegate
extension YourViewController: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return yourArray[row].title
}
}
I have these JSON output using PHP.
[
{"number":"001","name":"MIKE"},
{"number":"002","name":"JOSH"}
]
In Swift, I managed to select "name" value and display it into UIPickerView like below.
DropdownJSON.swift
import UIKit
class DropdownJSON: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
#IBOutlet var dropdownTxt: UITextField!
#IBOutlet var dropdownPV: UIPickerView!
#IBOutlet var numberLbl: UILabel!
#IBOutlet var nameLbl: UILabel!
var persons = [Person]()
struct Person {
var number:String
var name: String
init?(dict: [String:Any]) {
guard let number = dict["number"] as? String, let name = dict["name"] as? String else {
return nil
}
self.number = number
self.name = name
}
}
override func viewDidLoad() {
super.viewDidLoad()
getDropdownJSON()
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.persons.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return persons[row].name
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.numberLbl.text = self.persons[row].number
self.nameLbl.text = self.persons[row].name
self.dropdownPV.isHidden = true
self.dropdownTxt.resignFirstResponder()
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == self.dropdownTxt{
self.dropdownPV.isHidden = false
}
}
func getDropdownJSON() {
let url = URL(string: "http://localhost/DropdownJSON.json")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data, error != nil else {
print(error?.localizedDescription)
return
}
if let array = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [[String:Any]] {
self.persons = array.flatMap(Person.init)
DispatchQueue.main.async {
self.dropdownPV.reloadAllComponents()
}
}
}
task.resume()
}
}
But my goal is to display JSON output "number" into numberLbl and "name" into nameLbl in function pickerView (didSelectRow).
The reason is I want to post the numberLbl only later on. nameLbl is just to display on the screen page.
Is it possible ? Appreciate if someone can help on this matters.
UPDATE
Thanks.
First of all you need to use URLSession.dataTask to get response from URL instead of NSData(contentsOf:). Also in Swift 3 use native URL and Data instead of NSURL and NSData.
Now the problem is you are not storing the number value form dictionary. The simplest way to manage this situation is to create struct and store both number and name value with it. After that create Array of that struct instead of Array of AnyObject.
struct Person {
var number:String
var name: String
init?(dict: [String:Any]) {
guard let number = dict["number"] as? String, let name = dict["name"] as? String else {
return nil
}
self.number = number
self.name = name
}
}
Now declare one array of struct Person and with your getDropdownJSON method add data with in it.
var persons = [Person]()
func getDropdownJSON() {
let url = URL(string: "http://localhost/getDropdownJSON.json")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data, error == nil else {
print(error?.localizedDescription)
return
}
if let array = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [[String:Any]] {
self.persons = array.flatMap(Person.init)
DispatchQueue.main.async {
dropdownPV.reloadAllComponents()
}
}
}
task.resume()
}
Now in PickerViewDelegate method use this array to fill its component.
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.persons.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return persons[row].name
}
Now in didSelectRow you need to simply access array object and you will be get the number and name both.
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if row >= self.persons.count {
return
}
self.numberLbl.text = self.persons[row].number
self.nameLbl.text = self.persons[row].name
self.dropdownPV.isHidden = true
self.dropdownTxt.resignFirstResponder()
}
My Current View Controller is like this
import UIKit
import Alamofire
class ViewController: UIViewController , UIPickerViewDelegate, UIPickerViewDataSource{
#IBOutlet var venuePicker : UIPickerView?
var result = [String:String]()
var resultArray = [String]()
override func viewDidLoad() {
self.venuePicker?.delegate = self
Alamofire.request(.POST, "http://example.com/xxx/xx/xx").responseJSON() {
(request, response, jsonData, error) in
var venues = JSON(jsonData!)
let d = venues.dictionaryValue
for (k, v) in venues {
self.result[k] = v.arrayValue[0].stringValue
}
self.resultArray = self.result.values.array
self.venuePicker?.reloadAllComponents()
}
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return resultArray.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return resultArray[row]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I don't know how to show value to UIPickerView from my json dictionary.i don't want key to show at UIPickerView.I am now stuck at "?????" statement.
This is my output for result
[C2517: ORIX Kobe Nyusatsu, C2510: NPS Sendai Nyusatsu, C2033: JU Gunma, C2053: KAA Kyoto, C2035: JU Ibaraki, C2077: USS Gunma, C2024: ISUZU Kobe, C2505: NAA Osaka Nyusatsu, C2529: SMAP Sapporo Nyusatsu, C2502: L-Up PKobeNyusatsu, C2005: ARAI Sendai, C2072: TAA Minami Kyushu]
Please help!!!
If you wanted to show data to a picker view, use declare 2 arrays as properties instead.
var resultKeys = [String]()
var resultValues = [String]()
Inside your viewDidLoad function
var venues = JSON(jsonData!)
let d = venues.dictionaryValue
for (k, v) in venues {
resultKeys.append(k)
resultValues.append(v)
}
venuePicker.dataSource = self
venuePicker.reloadAllComponents()
Then inside titleForRow
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return resultValues[row]
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return resultValues.count
}
This way resultKeys[pickerView.selectedRowInComponent(0)] will return the key for the selected value.
Alamofire is totally async. It means that the block code will be executed when the data is ready. By then, the PickerView is already loaded, with an empty Array. That's the reason why the PickerView is empty. So in order to display the data we want, we need to force the PickerView to reload:
override func viewDidLoad() {
self.venuePicker?.delegate = self // This should be outside the block, my bad.
//...
Alamofire.request(...) {in
//...
venuePicker?.reloadAllComponents() // hopefully this will work
}
If you're not familliar with the concept of async programming, you can also choose to use sync way. Using this code I wrote https://github.com/skyline75489/swift-playground/blob/master/swift-playground%2FRequests.swift, we can easily send sync requests.
if let jsonData = Requests.get("YOUR_URL") {
let venues = JSON(jsonData)
//...
}
The reason why we have async is sometimes the data can be big and using sync way will cause the viewDidLoad to take forever to load. That's awful for user experience. However, if the data is small. Sync way can finish the task fast enough that users won't feel it. It's totally OK to use sync ways.