Remove Optional and nil - json

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.]

Related

Making func to share text with shareButton in iOS

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! :)

Swift Table view Fail to Load the data

I am new in swift . I am trying the display the data into tableview controller from API . Here is the api link . https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=DEMO_KEY. I create model based on the json property . Here is the model code .
import Foundation
struct Welcome: Codable {
let photos: [Photo]
}
// MARK: - Photo
struct Photo: Codable {
let id, sol: Int
let camera: Camera
let imgSrc: String
let earthDate: String
let rover: Rover
enum CodingKeys: String, CodingKey {
case id, sol, camera
case imgSrc = "img_src"
case earthDate = "earth_date"
case rover
}
}
// MARK: - Camera
struct Camera: Codable {
let id: Int
let name: String
let roverID: Int
let fullName: String
enum CodingKeys: String, CodingKey {
case id, name
case roverID = "rover_id"
case fullName = "full_name"
}
}
// MARK: - Rover
struct Rover: Codable {
let id: Int
let name, landingDate, launchDate, status: String
enum CodingKeys: String, CodingKey {
case id, name
case landingDate = "landing_date"
case launchDate = "launch_date"
case status
}
}
From this model I want to user Rover struct fields like id and status and display those property into table view cell .
Here is the network Manager code .
class NetworkManager{
func fetchData(completion: #escaping ([Rover]) -> Void) {
if let url = URL(string: "https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=DEMO_KEY") {
URLSession.shared.dataTask(with: url) { data, urlResponse, error in
if let data = data {
do {
let result = try JSONDecoder().decode([Rover].self, from: data)
completion(result)
} catch let error {
print(error.localizedDescription)
}
}
}
.resume()
}
}
}
Here code for view controller .
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
private var posts = [Rover]()
private let networkManager = NetworkManager()
private var rowSelected = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setUpUI ()
fetchData()
}
private func setUpUI () {
tableView.dataSource = self
tableView.delegate = self
}
private func fetchData() {
networkManager.fetchData { [weak self] array in
self?.posts = array
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
rowSelected = indexPath.row
performSegue(withIdentifier: "cell", sender: nil)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = indexPath.row
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let post = posts[row]
cell.textLabel?.text = String(post.id)
cell.detailTextLabel?.text = post.status
return cell
}
}
Here is the console result when i run the code , it is saying The data couldn’t be read because it isn’t in the correct format.

How to decode custom type inside dictionary value with JSON?

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!
}

Can we return Two Arrays in one tableview numberOfRowsInSection without using section in swift?

I am displaying all contacts in tableview.. but here
two service calls for two conditions 1) tagged 2) filter
now i need to check these 2 conditions in my total contacts and display accordingly
i have designed cell for all contacts tableview like below:
here: if the contact is tagged then i need to show tagged in place of invite button
if contact is filtered then i need to show onFilter in place of invite button and remaining should be invite... how to do that all in one cell? please suggest me
Initially i have tried tableview with two prototype cells and displaying like below and working:
var jsonArrayFilter = [ContactJsonFilter]()
var jsonArrayTagged = [ContactDetailsJsonTagged]()
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return jsonArrayTagged.count
}
else{
return jsonArrayFilter.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
var cell: ContactsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "ContactsTableViewCell", for: indexPath) as! ContactsTableViewCell
let taggedContactJson = jsonArrayTagged[indexPath.row]
cell.nameLbl.text = taggedContactJson.Name
cell.empRoleLbl.text = taggedContactJson.phnum
let conTaggedStat: Bool = taggedContactJson.isTaggedReq!
if conTaggedStat == true{
cell.taggedBtn.setTitle("Tagged", for: .normal)
}
return cell
}
if indexPath.section == 1{
var cell1: ContactsTableViewCell2 = tableView.dequeueReusableCell(withIdentifier: "ContactsTableViewCell2", for: indexPath) as! ContactsTableViewCell2
let aJson = jsonArrayFilter[indexPath.row]
cell1.filterBtn.setTitle("Filter", for: .normal)
cell1.nameLbl.text = aJson.userName
cell1.empRoleLbl.text = aJson.phnum
return cell1
}
return UITableViewCell()
}
but i need to check two conditions in one cell not in two seperate cells. how is that possible?
EDIT: My two APIs struct... how to make them in single array
struct ContactJsonFilter {
var userId: String?
var userType: String?
var onItag: Bool?
var taggedStat: Bool?
var userName: String?
var phnum: String?
init(userId: String, oniTaag: Bool, tagged: Bool, userName: String, key: String) {
self.userId = userId
self.userType = userType
self.onItag = oniTaag
self.taggedStat = tagged
self.userName = userName
self.phnum = key
}
}
struct ContactDetailsJsonTagged {
var userId: String?
var userType: String?
var fName: String?
var lName: String?
var phnum: String?
var isTaggedReq: Bool?
var status: String?
init(userId: String, userType: String, firstName: String, lastName: String, phone: String, isTaggedWithRequestor: Bool, status: String) {
self.userId = userId
self.userType = userType
self.fName = firstName
self.lName = lastName
self.phnum = phone
self.isTaggedReq = isTaggedWithRequestor
self.status = status
}
}

UITableView with Sections as Date from Json Data

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