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.
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.
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'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.
Xcode 9 - Swift 4
No Permissions set on Firebase Data - read/write everyone
I imported json data into firebase and my data looks like this..
I am trying to get to to the title of the jobs listed in FireBase Database, place the list of titles in an array and into a tableView and it will not return anything
My swift code looks like this..
import UIKit
import FirebaseDatabase
class PostViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var jobPostsTableView: UITableView!
var ref: DatabaseReference?
var databaseHandle: DatabaseHandle = 0
var searchJSONQuery : String = ""
var jobsData = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
jobPostsTableView.delegate = self
jobPostsTableView.dataSource = self
//Set the Firebase Reference
ref = Database.database().reference()
// Retreive the posts and listen for changes
databaseHandle = (ref?.child("positions/title").observe(.childAdded, with: { (snapshot) in
//Code to execute when a child is added under "positions"
//Take the value from the snapshot and add it to the jobsData array
let list = snapshot.value as? String
if let actualList = list {
self.jobsData.append(actualList)
self.jobPostsTableView.reloadData()
}
}))!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jobsData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell")
cell?.textLabel?.text = jobsData[indexPath.row]
return cell!
}
}
When using child() you only go one level down the tree at the time. Because you have a lot of positions you can not simply access the titles by using child("title").
When calling the observeSingleEvent you're looking for the values of the key you have stated in you database-reference.
The way written below you get a snapshot of all the values beneath your "positions" key. Therefore you use the for-loop to access the "title" value of every single object.
You should write it as a separate function and call it from viewDidLoad() rather than write the firebase code inside viewDidLoad itself.
func retrieveJobTitles(){
let positionRef = ref.child("positions")
positionsRef.observeSingleEvent(of: .value, with: { (snapshot) in
// Iterate through all of your positions
for child in snapshot.children.allObjects as! [DataSnapshot] {
let position = child as! DataSnapshot
let positionsInfo = position.value as! [String: Any]
let jobTitle = positionsInfo["title"] as! String
if jobTitle != nil {
self.jobsData.append(jobTitle)
}
}
self.jobPostsTableView.reloadData()
})
}
}
I want to show the JSON data grabbed from a server on a Table View. The problem is, I can't get it to show up on it. I have tried several different methods and searched a lot to find a solution, but I can't.
My code (all of it) is shown below and I hope somebody can help me out. Thanks in advance.
import UIKit
import Alamofire
import SwiftyJSON
class MasterViewController: UITableViewController {
var tableTitle = [String]()
var tableBody = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
getJSON()
}
func getJSON(){
Alamofire.request(.GET, "http://announcement.vassy.net/api/AnnouncementAPI/Get/").responseJSON { (Response) -> Void in
// checking if result has value
if let value = Response.result.value {
let json = JSON(value)
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableBody.append(body!)
}
}
}
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Table View Stuff
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableTitle.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TableViewCell
// cell config
cell.title!.text = tableTitle[indexPath.row]
cell.body!.text = tableBody[indexPath.row]
return cell
}
}
The Alamofire network request is asynchronous, meaning you can't know when the result will come back.
The problem here is that you reload the tableView outside the scope of the Alamofire request, so it is executed before the data comes back.
The reload should happen in the same scope, and on the main thread, for example:
func getJSON(){
Alamofire.request(.GET, "http://announcement.vassy.net/api/AnnouncementAPI/Get/").responseJSON { (Response) -> Void in
// checking if result has value
if let value = Response.result.value {
let json = JSON(value)
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableBody.append(body!)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
}
I think #Eric said almost everything in his answer, nevertheless, not it's a good decision in design keep the code for make the network request in your same UITableViewController this keep a couple between two things that are independents and change for differents reasons.
My advice is separate the two parts of the code decoupling the dependency between your two layers. In this way when you need to change anything related with your networking request handler you don't need to change it in any place where you make the same request, it's an advice!!!.
In case you want to do it, you can use closures to hanlde the async behaviour of Alamofire passgin the completionHandlerinside the wrapper you make to handle the networking requests, for example, let's define a simple wrapper using the singleton pattern (it's just for the purpose of explain the sample, you can handle it as you want).
import AlamofireImage
import SwiftyJSON
class NetworkHandler {
/// The shared instance to define the singleton.
static let sharedInstance = RequestManager()
/**
Private initializer to create the singleton instance.
*/
private init() { }
func getJSON(completionHandler: (json: JSON?, error: NSError?) -> Void) {
Alamofire.request(.GET, http://announcement.vassy.net/api/AnnouncementAPI/Get/).responseJSON { response in
switch(response.result) {
case .Success(let value):
let json = JSON(value)
completionHandler(json: json, error: nil)
case .Failure(let error):
completionHandler(json: nil, error: error)
}
}
}
}
Then in your UITableViewController you can call the new wrapper to Alamofire in this way:
class MasterViewController: UITableViewController {
var tableTitle = [String]()
var tableBody = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NetworkHandler.sharedInstance.getJSON { [weak self] (json, error) -> Void in
// request was made successful
if error == nil {
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableBody.append(body!)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
}
// rest of your code
}
In the above way you keep the code decoupled, it's a good design.
I hope this help you