I'm new at programming at all, trying making app with country's info (capital, language, currencies etc.).
For now all working except one thing. I want make button to share text info about country. And at this place start's troubles, I can't write method to share my currencies and language info. I write func to capture one currency from each country, but I don't understand how to iterate through my currencies and languages, to get all values if country have more than 1 currency and language. I understand how to do it in my tableView method, using indexPath for this, but can't understand how do it this at another function. Sorry for my English :) It's not my native language.
I parse JSON from RestCountries. This is my struct to parse JSON:
struct Country: Codable {
let name: Name
let cca2: String
let capital: [String]?
let population: Int
let currencies: [String: Currency]?
let languages: [String: String]?
}
struct Name: Codable {
let common: String
let official: String
}
struct Currency: Codable {
let name: String?
let symbol: String?
}
This is my DetailViewController:
import UIKit
class DetailViewController: UITableViewController {
var country: Country!
let flag = "Flag"
let general = "General"
let currency = "Currency"
let language = "Languages"
var currencyText = ""
lazy var languages = country.languages?.sorted { $0.0 < $1.0 }
lazy var sectionTitles = [flag, general, currency, language]
lazy var currencies = country.currencies?.sorted { $0.0 < $1.0 }
override func viewDidLoad() {
super.viewDidLoad()
title = country.name.common
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareTapped))
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch sectionTitles[section] {
case flag:
return 1
case general:
return 4
case currency:
// How make to return proper number's of rows??
return currencies?.count ?? 0
case language:
return country.languages?.count ?? 0
default:
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch sectionTitles[indexPath.section] {
case flag:
let cell = tableView.dequeueReusableCell(withIdentifier: "Flag", for: indexPath)
if let cell = cell as? FlagCell {
cell.flagImageView.image = UIImage(named: country.cca2.lowercased())
}
return cell
case general:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text", for: indexPath)
cell.textLabel?.numberOfLines = 0
switch indexPath.row {
case 0:
cell.textLabel?.text = "Common country name: \(country.name.common)"
case 1:
cell.textLabel?.text = "Official country name: \(country.name.official)"
case 2:
cell.textLabel?.text = "Capital: \(country.capital?[0] ?? "Unknown")"
case 3:
cell.textLabel?.text = "Population: \(country.population) people"
default:
return cell
}
return cell
case currency:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text", for: indexPath)
cell.textLabel?.numberOfLines = 0
if let (code, currency) = currencies?[indexPath.row] {
let currencyCode = code
let currencyName = currency.name ?? ""
let currencySymbol = currency.symbol ?? ""
cell.textLabel?.text = "Code: \(currencyCode), Currency: \(currencyName), Symbol: \(currencySymbol)"
}
return cell
case language:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text", for: indexPath)
cell.textLabel?.numberOfLines = 0
if let (_, language) = languages?[indexPath.row] {
cell.textLabel?.text = "Language: \(language)"
}
return cell
default:
break
}
return UITableViewCell ()
}
#objc func shareTapped () {
currenciesText()
let shareFlag = UIImage(named: country.cca2.lowercased())
let shareText = """
General
\(country.name.common)
\(country.name.official)
\(country.capital?[0] ?? "")
\(country.population)
Currencies
\(currencyText)
"""
let vc = UIActivityViewController(activityItems: [shareFlag!, shareText], applicationActivities: [])
vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
present(vc, animated: true)
}
func currenciesText () {
// How to make this work if country have more than 1 currency?
if let (code, currency) = currencies?[0] {
let currencyCode = code
let currencyName = currency.name ?? ""
let currencySymbol = currency.symbol ?? ""
currencyText = "\(currencyName) (\(currencyCode), \(currencySymbol))"
}
}
}
I'm totally lost in all this loops, this must be the way use for-in, but all things I try Xcode don't like :))) Please help! :)
Related
my JSON:
https://www.cbr-xml-daily.ru/daily_json.js
my code:
struct CoinData: Decodable {
let Valute: [String: CoinInfo]
}
struct CoinInfo: Decodable {
let Name: String
let Value: Double
}
if let safeData = data {
if let coinData = self.parseJSON(safeData) {
print(coinData)
}
}
func parseJSON(_ data: Data) -> [String: CoinInfo]? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CoinData.self, from: data)
return decodedData.Valute
} catch {
delegate?.didFailWithError(error: error)
return nil
}
}
In debug console following gets printed:
["PLN": CurrencyConverter.CoinInfo(Name: "X", Value: 19.6678), ...]
This way I can't reach Name and Value properties of a coin. What's wrong?
I am going to do for-loop to check if a key contains certain symbols. If it does - I will need to be able to access to both Name and Value
You don't actually need a for loop. Since coinData is a dictionary, you can use its subscript, together with optional binding to do this. For example, to check if the key "PLN" exists, and access its name and value:
if let coinInfo = coinData["PLN"] {
print(coinInfo.Name)
print(coinInfo.Value)
} else {
// "PLN" does not exist
}
StoyBoard
Code
import UIKit
import Alamofire
// MARK: - CoinData
struct CoinData: Codable {
let date, previousDate: String
let previousURL: String
let timestamp: String
let valute: [String: Valute]
enum CodingKeys: String, CodingKey {
case date = "Date"
case previousDate = "PreviousDate"
case previousURL = "PreviousURL"
case timestamp = "Timestamp"
case valute = "Valute"
}
}
// MARK: - Valute
struct Valute: Codable {
let id, numCode, charCode: String
let nominal: Int
let name: String
let value, previous: Double
enum CodingKeys: String, CodingKey {
case id = "ID"
case numCode = "NumCode"
case charCode = "CharCode"
case nominal = "Nominal"
case name = "Name"
case value = "Value"
case previous = "Previous"
}
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
var getCoinData = [CoinData]()
var coinNameArr = [String]()
var coinDataArr = [Valute]()
#IBOutlet weak var tblDataList: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
getData()
}
func getData()
{
let url = "https://www.cbr-xml-daily.ru/daily_json.js"
AF.request(url, method: .get, encoding: URLEncoding.default).responseJSON { response in
let json = response.data
do{
let decoder = JSONDecoder()
self.getCoinData = [try decoder.decode(CoinData.self, from: json!)]
let response = self.getCoinData[0]
if response.valute.count != 0 {
self.coinNameArr.removeAll()
self.coinDataArr.removeAll()
for (coinName, coinData) in response.valute {
self.coinNameArr.append(coinName)
self.coinDataArr.append(coinData)
}
self.tblDataList.reloadData()
} else {
}
}catch let err{
print(err)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coinDataArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:coinTblCell = tableView.dequeueReusableCell(withIdentifier: "CellID", for: indexPath as IndexPath) as! coinTblCell
cell.accessoryType = .disclosureIndicator
cell.tintColor = .black
let rowData = coinDataArr[indexPath.row]
cell.lblName.text = rowData.name
cell.lblValue.text = String(rowData.value)
return cell
}
}
class coinTblCell: UITableViewCell {
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var lblValue: UILabel!
}
My scenario, I am trying to load JSON data into Tableview. Here, Tableview custom cell I am maintaining. Whenever trying to upload data into tableview, I may have chance to add new data from server.
Here, while loading I am getting Index out of range error. Every time I am calling JSON function from viewWillAppear. I have enough data, Inside my array nothing uneven data. Below code I am using
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) // No need for semicolon
self.tableArray.removeAll()
self.cartname.removeAll()
self.parentid.removeAll()
self.name.removeAll()
self.year.removeAll()
parseJSON()
}
This is my JSON Process
if let content = json["content"] as? [[String:String]] {
print(json)
for category in content {
let cat_id = category["cat_id"]
let cat_name = category["cat_name"]
let cat_parentid = category["cat_parentid"]
let name = category["name"]
let year = category["year"]
self.tableArray.append(cat_id ?? "unknnown")
self.cartname.append(cat_name ?? "unknnown")
self.parentid.append(cat_parentid ?? "unknnown")
self.name.append(name ?? "unknnown")
self.year.append(year ?? "unknnown")
}
Tableview cell data load
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCustomCell
cell.cat_id.text = self.tableArray[indexPath.row]
cell.cat_name.text = self.cartname[indexPath.row]// Some time Here I am getting out of range error
cell.cat_parentid.text = self.parentid[indexPath.row]
cell.name.text = self.name[indexPath.row] // Here I am getting out of range error
cell.year.text = self.year[indexPath.row]
return cell
}
}
It's expected with multiple arrays when using the tableView ,First from OOP point you need to create 1 model like
struct Root: Codable {
let catID, catName, catParentid, year,name: String?
enum CodingKeys: String, CodingKey {
case catID = "cat_id"
case catName = "cat_name"
case catParentid = "cat_parentid"
case year, name
}
}
and use Codable to parse the json
var arr = [Root]()
do {
let content = json["content"] as! [[String:String]]
let staData = try JSONSerialization.data(withJSONObject:content,options:[])
arr = try JSONDecoder().decode([Root].self, from:staData)
}
catch {
print(error)
}
in numberOfRows
return arr.count
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCustomCell
let item = arr[indexPAth.row]
cell.cat_id.text = item.catID
cell.cat_name.text = item.catName
cell.cat_parentid.text = item.catParentid
cell.name.text = item.name
cell.year.text = item.year
return cell
}
I am working on table view to render some data received after JSON parsing. I want my table view to have sections based on different dates. Each record in JSON is an event and multiple events can take place on single date.
Here is my JSON data
https://get.rosterbuster.com/wp-content/uploads/dummy-response.json
I want to render my table view like this
Table View with Sections as Date
What I have done sofar:
I have parsed the data in following Structure
struct Roster : Codable {
let flightnr: String?
let date: String?
let aircraftType: String?
let tail: String?
let departure: String?
let destination: String?
let departTime: String?
let arrivalTime: String?
let dutyID: String?
let dutyCode: String?
let captain: String?
let firstOfficer: String?
let flightAttendant: String?
enum CodingKeys: String, CodingKey {
case flightnr = "Flightnr"
case date = "Date"
case aircraftType = "Aircraft Type"
case tail = "Tail"
case departure = "Departure"
case destination = "Destination"
case departTime = "Time_Depart"
case arrivalTime = "Time_Arrive"
case dutyID = "DutyID"
case dutyCode = "DutyCode"
case captain = "Captain"
case firstOfficer = "First Officer"
case flightAttendant = "Flight Attendant"
}
}
I have also setup basic table view but don't know how to group the retrieved data into different sections as per the image I have attached above.
Any help would be appreciated.
This is the approach I'd suggest:
1) get number of sections by mapping the API JSON response in a set based on the date property. Here's something you could use (maybe you don't need to cast it in Array as well and you want to check if date is not nil)
self.sections = Array(Set(self.dataModel.map({ (roster) -> String in
roster.date!
})))
2) set your rowsPerSection data model by creating an array of Roster for each section.
//first set the array of sections.count dimension and empty array for each item
self.sections.forEach({ (string) in
self.rowsPerSection.append([])
})
//then set each array
for index in 0..<self.sections.count {
self.dataModel.forEach({ (roster) in
if roster.date == self.sections[index] {
self.rowsPerSection[index].append(roster)
}
})
}
This is my dummy code, I tested it with your URL and it works:
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var dataModel = [Roster]()
var sections = [String]()
var rowsPerSection = [[Roster]]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
APICall { (rosters) in
DispatchQueue.main.async {
self.dataModel = rosters!
self.sections = Array(Set(self.dataModel.map({ (roster) -> String in
roster.date!
})))
//first set the array of sections.count dimension and empty array for each item
self.sections.forEach({ (string) in
self.rowsPerSection.append([])
})
//then set each array
for index in 0..<self.sections.count {
self.dataModel.forEach({ (roster) in
if roster.date == self.sections[index] {
self.rowsPerSection[index].append(roster)
}
})
}
self.tableView.reloadData()
}
}
}
func APICall(onSuccess: #escaping(_ response: [Roster]?) -> Void) {
let group = DispatchGroup()
group.enter()
DispatchQueue.global(qos: .default).async {
let url = URL(string: "https://get.rosterbuster.com/wp-content/uploads/dummy-response.json")!
let requestURL = URLRequest(url: url)
let session = URLSession.shared
session.dataTask(with: requestURL) { (data, response, error) in
let decoder = JSONDecoder()
let responseJson = try! decoder.decode([Roster].self, from: data!)
onSuccess(responseJson)
group.leave()
}.resume()
group.wait()
return
}
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
for index in 0..<sections.count {
if index == section {
return rowsPerSection[index].count
}
}
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = rowsPerSection[indexPath.section] [indexPath.row].destination
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
}
Here's the screenshot -> screenshot
cellForRow
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
guard let cell =
tableView.dequeueReusableCell(withIdentifier: "EventsCell") as? EventsCell
else { return UITableViewCell() }
cell.homeLabel.text = events[indexPath.row].homeTeamName
cell.awayLabel.text = events[indexPath.row].awayTeamName
cell.homeGoalLbl.text =
String (describing: events[indexPath.row].result.goalsHomeTeam)
cell.awayGoalLbl.text =
String (describing: events[indexPath.row].result.goalsAwayTeam)
return cell
}
Codables
class EventsFull: Codable {
let fixtures: [EventsData]
init(fixtures: [EventsData]) {
self.fixtures = fixtures
}
}
class ResultsData: Codable {
let goalsHomeTeam: Int?
let goalsAwayTeam: Int?
init(goalsHomeTeam: Int,goalsAwayTeam: Int) {
self.goalsHomeTeam = goalsHomeTeam
self.goalsAwayTeam = goalsAwayTeam
}
}
class EventsData: Codable {
let date: String
let status: String
let matchday: Int
let homeTeamName: String
let awayTeamName: String
let result: ResultsData
let odds: Double?
init(date: String, status: String, matchday: Int, homeTeamName: String, awayTeamName: String, result: ResultsData, odds: Double) {
self.date = date
self.status = status
self.matchday = matchday
self.homeTeamName = homeTeamName
self.awayTeamName = awayTeamName
self.result = result
self.odds = odds
}
}
console:
downloaded
Optional(3)
Optional(2)
How to remove Optional from the view and how to "nil" does not appear?
If we get nil, what string should appear in the goals labels? You need to specify that. Then you can write this:
let ifnil = "" // or whatever the desired string is
cell.homeGoalLbl.text =
events[indexPath.row].result.goalsHomeTeam.flatMap {String($0)} ?? ifnil
cell.awayGoalLbl.text =
events[indexPath.row].result.goalsAwayTeam.flatMap {String($0)} ?? ifnil
That will do both jobs at once — it eliminates both "Optional" and "nil" as possible label values.
[See https://stackoverflow.com/a/42960286/341994.]
I want to parse data into tableviewcontroller but it doesn't display anything
this is the webservice data:
I want to access the title, img_url and price_formatted inside the key "listings"
the user enters a city name and type of home which he is searching for and I save these values using userdefaults and receive them in PropertySearchViewController.
This is my code using almofire to display these values:
I have PropertySearchViewController which I display the values inside it, PropertyTableViewCell and public model Property
1-
class PropertySearchViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let URL_Get_Data = "https://api.nestoria.co.uk/api?"
#IBOutlet weak var tableViewProperty: UITableView!
var properties = [Property]()
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return properties.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PropertyTableViewCell
let property :Property
property = properties[indexPath.row]
cell.propertyTitle.text = property.title
cell.propertyPrice.text = property.price
if property.imageUrl != nil {
Alamofire.request(property.imageUrl!).responseImage { response in
debugPrint(response)
if let image = response.result.value {
cell.propertyImage.image = image
}
else{
print("no image")
}
}}
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
//fetching data from web api
//recieve data
let city :String = UserDefaults.standard.string(forKey: "city")!
let type :String = UserDefaults.standard.string(forKey: "typeP")!
print("search values are :",city,type)
let params: [String: String] = ["encoding": "json", "pretty": "1", "action": "search_listings", "country": "uk", "listing_type": type, "place_name": city]
//end
//
Alamofire.request(URL_Get_Data, method: .get, parameters: params, encoding: URLEncoding.default, headers: nil).validate(statusCode: 200..<600).responseJSON {
// Alamofire.request(URL_Get_Data).responseJSON {
response in
// response in
//getting json
if let json = response.result.value {
print(type(of: json))
//converting json to NSArray
let propertyArray = json as! NSDictionary
//traversing through all elements of the array
for i in 0..<propertyArray.count{
//adding hero values to the hero list
self.properties.append(Property(
title: (propertyArray[i] as AnyObject).value(forKey: "title") as? String,
price: (propertyArray[i] as AnyObject).value(forKey: "price_formatted") as? String,
imageUrl: (propertyArray[i] as AnyObject).value(forKey: "imageUrl") as? String
))
}
//displaying data in tableview
self.tableViewProperty.reloadData()
}
}
}}
//end of PropertySearchViewController
2-
class PropertyTableViewCell: UITableViewCell {
#IBOutlet weak var propertyImage: UIImageView!
#IBOutlet weak var propertyTitle: UILabel!
#IBOutlet weak var propertyPrice: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
//end
3-
class Property{
var title: String?
var price: String?
var imageUrl: String?
init(title: String?, price: String?, imageUrl: String?) {
self.title = title
self.price = price
self.imageUrl = imageUrl
}
}
Thanks in advance
(1) Install pod SwiftyJSON
(2) import SwiftyJSON in PropertySearchViewController
(3) Add let reuseIdentifierResultTable = "cell" below let URL_Get_Data = "url"
(4) Add in viewDidLoad() tableViewProperty.register(PropertyTableViewCell.self, forCellReuseIdentifier: reuseIdentifierResultTable)
tableViewProperty.delegate = self
tableViewProperty.dataSource = self
(5) Replace everyting in .responseJSON{} with
response in
if let data = response.data {
let json = String(data: data, encoding: String.Encoding.utf8)
//print(json)
if let dataFromString = json?.data(using: .utf8, allowLossyConversion: false) {
let json2 = JSON(data: dataFromString)
print("Response: \(json2)")
print("json status code: \(json2["response"]["status_code"])")
if json2["response"]["status_code"] == "200" && !(json2.null != nil){
print("json request count: \(json2["request"].count)")
print("json response count: \(json2["response"].count)")
print("json response listings count: \(json2["response"]["listings"].count)")
for i in 0...json2["response"]["listings"].count-1 {
print("\(i). \(json2["response"]["listings"][i]["title"])")
self.properties.append(Property(
title: json2["response"]["listings"][i]["title"].rawString()!,
price: json2["response"]["listings"][i]["price_formatted"].rawString()!,
imageUrl: json2["response"]["listings"][i]["img_url"].rawString()!
))
}
}
}
self.tableViewProperty.reloadData()
}
(6) Replacepublic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if properties.count < 1 {
return 0
}
return properties.count
}
(7) Replace
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PropertyTableViewCell
with
let cell = Bundle.main.loadNibNamed("PropertyTableViewCell", owner: self, options: nil)?.first as! PropertyTableViewCell
(8) Replace
cell.propertyTitle.text = property.title!
cell.propertyPrice.text = property.price!