How to iterate through Swift array and use data in iOS Chart? - ios

I have a bar chart, which I am trying to populate through data pulled in from an array.
I am trying to iterate through this array in order to populate my chart.
Please note: in this example below there are 3 winningStreakDM variables hardcoded for testing purposes, but in 'real life' the number could be different each API call as it depends on how many 'users' are in that league.
This is why I need to iterate through the array and structure the data to suit.
let winningStreakDM1 : [String : Any] = [
"id" : 2,
"user_name" : "Dicky",
"winning_streak" : 5
]
let winningStreakDM2 : [String : Any] = [
"id" : 6,
"user_name" : "G",
"winning_streak" : 2
]
let winningStreakDM3 : [String : Any] = [
"id" : 5,
"user_name" : "Sultan",
"winning_streak" : 0
]
My issue is that I don't know how to iterate through the initial array to structure my data for it to work with the above code.
This is my full script:
import UIKit
import Charts
class CommunityLeagueStatsVC: UIViewController {
// GRAPHS *********
#IBOutlet weak var chartView: BarChartView!
var values = [BarChartDataEntry]()
// ****************
//********CHART VARIABLES**************//
//WINS LOSSES DRAWS
var winStreak: Double = 0.0
#IBOutlet weak var leagueStatsScrollView: UIScrollView!
var noDefeats: Bool?
var noWins: Bool?
var biggestWin: String?
var biggestWinTeams: String?
var passedCommunityName: String?
#IBOutlet weak var communityName: UILabel!
var playerId2: String?
var communityId2: String?
var eMail2: String?
override func viewDidLoad() {
super.viewDidLoad()
let winningStreak = ["Wins"]
let gamesWon = [winStreak]
setWinStreakChart(dataPoints: winningStreak, values: gamesWon)
let defaults = UserDefaults.standard
let Email = defaults.string(forKey: "userEmail")
let playerId = defaults.string(forKey: "playerId")
let commsId = defaults.string(forKey: "communityId")
self.playerId2 = playerId
self.communityId2 = commsId
self.eMail2 = Email
}
func setWinStreakChart(dataPoints: [String], values: [BarChartDataEntry]){
xAxis.valueFormatter = WinningStreakFormatter(chartView: self.chartView)
let barChartDataSet = BarChartDataSet(values: values, label: "Winning Streak")
barChartDataSet.colors = ChartColorTemplates.material()
let barChartData = BarChartData(dataSet: barChartDataSet)
barChartData.setValueFont(UIFont.systemFont(ofSize: 12.0))
self.chartView.data = barChartData
}
override func viewDidAppear(_ animated: Bool) {
let myUrl = URL(string: "http://www.xxx.uk/xxx/getLeagueStats.php")
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"
let postString = "player_id=\(self.playerId2!)&community_id=\(communityId2!)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
DispatchQueue.main.async
{
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:AnyObject]
print (json!)
if let dict = json?["leagueStats"] as? [String:AnyObject] {
//WINNING STREAK
var values = [BarChartDataEntry]()
if let dataWinStreak = dict["winningStreak"] as? [[String : Any]] {
print ("test one")
for (index,item) in dataWinStreak.enumerated() {
if let yValue = item["winning_streak"] as? Int, let userName = item["user_name"] as? String {
print ("test two")
let barChartDataEntry = BarChartDataEntry(x: Double(index), y: Double(yValue), data: userName as AnyObject?)
values.append(barChartDataEntry)
}
}
self.setWinStreakChart(dataPoints: ["wins"], values: values)
}
catch{
print(error)
}
}
}
task.resume()
}
}
UPDATE:
These are the errors that I am currently receiving:
With a bit of playing around and commenting out the line containing the first error I have got to this stage which correctly shows a graph with values, but no xAxis containing the userNames of each player.

You can create array of BarChartDataEntry this way.
var values = [BarChartDataEntry]()
if let dataWinStreak = dict["winningStreak"] as? [[String : Any]] {
for (index,item) in dataWinStreak.enumerated() {
if let yValue = item["winning_streak"] as? Int, let userName = item["user_name"] as? String {
let barChartDataEntry = BarChartDataEntry(x: index, y: yValue, data: userName)
values.append(barChartDataEntry)
}
}
}
//Now use values array
Edit: You just need to change setWinStreakChart function because now you are working with dynamic data not static one.
func setWinStreakChart(dataPoints: [String], values: [BarChartDataEntry]){
let xAxis : XAxis = self.chartView.xAxis;
xAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: 10.0)!
xAxis.labelTextColor = UIColor.black
xAxis.drawAxisLineEnabled = false
xAxis.drawGridLinesEnabled = true
xAxis.granularity = 1;
xAxis.labelPosition = .bottom
xAxis.valueFormatter = WinningStreakFormatter(chartView: self.chartView)
let barChartDataSet = BarChartDataSet(values: values, label: "Winning Streak")
barChartDataSet.colors = ChartColorTemplates.material()
let barChartData = BarChartData(dataSet: barChartDataSet)
barChartData.setValueFont(UIFont.systemFont(ofSize: 12.0))
self.chartView.data = barChartData
}
And now call this function after the for loop where we are creating array of BarChartDataEntry.
self.setWinStreakChart(dataPoints: ["wins"], values: values)

Related

Problem with MapBox PointAnnotations with data from Alamofire

In my app I need to get annotations coordinates from server, so im getting is with Alamofire request. And when I try to add annotations to map from Alamofire pid im getting unknown error
class MapViewController : UIViewController, MGLMapViewDelegate, FloatingPanelControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
....
....
getDataFromServer()
}
func getDataFromServer() {
let coffeeShopsJSON = AF.request("http://194.6*******")
coffeeShopsJSON.responseJSON { response in
switch response.result {
case .success(let value):
let jsonFile = JSON(value)
let arrayCount = jsonFile.array?.count ?? 0
var id = [Int]()
var name = [String]()
var lat = [Double]()
var lon = [Double]()
var img = [UIImage(named : "cofShopIcon"), UIImage(named : "cofShopIcon"), UIImage(named : "cofShopIcon"), UIImage(named : "cofShopIcon"), UIImage(named : "cofShopIcon")] //test images
for i in 0..<arrayCount {
let j_id = jsonFile[i]["id"].string ?? "-1"
id.append(Int(j_id) ?? -1)
let j_name = jsonFile[i]["name"].string ?? "N/A"
name.append(j_name)
let j_lat = jsonFile[i]["coordX"].string ?? "-1.0"
lat.append(Double(j_lat) ?? -1.0)
let j_lon = jsonFile[i]["coordY"].string ?? "-1.0"
lon.append(Double(j_lon) ?? -1.0)
}
for i in 0..<id.count {
mapData.append(coffeeShopsData(shopId : id[i], name : name[i], icon : img[i], latitude : lat[i], longitude : lon[i]))
}
for i in 0..<mapData.count {
let point = MGLPointAnnotation()
point.coordinate = CLLocationCoordinate2D(latitude : mapData[i].latitude ?? 0, longitude : mapData[i].longitude ?? 0)
point.title = mapData[i].name ?? "*Data Error*"
markers.append(point)
}
self.mapView.addAnnotations(markers)
case .failure(let error):
print(error)
}
}
}
}
Error:
libc++abi.dylib: terminating with uncaught exception of type std::domain_error
I'm new in swift, so any advice will be useful

class_copyPropertyList not working in Swift 5. Whats the reason?

class_copyPropertyList is giving empty properties in Swift 5 and it was working correct in Swift 3
extension NSObject {
func toDictionary(from classType: NSObject.Type) -> [String: Any] {
var propertiesCount : CUnsignedInt = 0
let propertiesInAClass = class_copyPropertyList(classType, &propertiesCount)
let propertiesDictionary : NSMutableDictionary = NSMutableDictionary()
for i in 0 ..< Int(propertiesCount) {
let property = propertiesInAClass?[i]
let strKey = NSString(utf8String: property_getName(property)) as String?
if let key = strKey {
propertiesDictionary.setValue(self.value(forKey: key), forKey: key)
}
}
return propertiesDictionary as! [String : Any]
}
}
// call this for NSObject subclass
let product = Product()
let dict = product.toDictionary(from: Product.self)
print(dict)

String interpretation in Swift

I have a struct that looks like this:
struct colorShapeSize{
let color: String!
let shape: String!
let size: UIImage!
}
and I have a string that looks something like this:
"color:{Blue}shape:{round}size:{medium}"
All of the strings will be in the same format (i.e. color will always come first, shape second, and size third).
How would I extract the data from the string and put it into a colorShapeSize struct?
How about this?
struct ColorShapeSize {
let color: String
let shape: String
let size: String
init(rawValue: String) {
var dictionary: [String: String] = [:]
var sorted = rawValue.components(separatedBy: "}").filter({ return $0.components(separatedBy: ":{").count == 2 })
for s in sorted {
let kv = s.components(separatedBy: ":{")
let key = kv[0]
let value = kv[1]
dictionary[key] = value
}
color = dictionary["color"] ?? ""
shape = dictionary["shape"] ?? ""
size = dictionary["size"] ?? ""
}
}
let str = "color:{Blue}shape:{round}size:{medium}"
let css = ColorShapeSize(rawValue: str)
print(css.color, css.shape, css.size)
try this, it will extract the string in array, then you can do what you want with the value
func test() {
let givenString = "color:{Blue}shape:{round}size:{medium}"
var results = [String]()
do {
let regex = try NSRegularExpression(pattern: "\\{(.*?)\\}", options: [])
let tempString = givenString as NSString
regex.enumerateMatches(in: givenString, options: [], range: NSMakeRange(0, givenString.characters.count), using: { (result, flag, stop) in
if let range = result?.rangeAt(1) {
let number = tempString.substring(with: range)
results.append(number)
}
})
print(results) //["Blue", "round", "medium"] (Here you can initialize your struct with the values)
}
catch(let error) {
print("Unable to extract string : \(error.localizedDescription)")
}
}

Force casts should be avoided

I am getting "Force cast violation : Force casts should be avoided warning"
on my code :
daysCombinedFinal = daysCombined[0] as! [Any?]
The screenshot is attached below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "location", for: indexPath) as? TravelShopCustomCell {
if !isFirstTime && self.nameArray.count != 0 {
var daysCombined = [Any]()
var daysCombinedFinal = [Any?]()
daysCombined = [self.combinedArray[0]]
daysCombinedFinal = daysCombined[0] as? [Any?] ?? []
let str = daysCombinedFinal.flatMap { $0 as? String }.joined(separator:" ")
var startAMCombined = [Any]()
var startAMCombinedFinal = [Any?]()
startAMCombined = [self.combinedStartAMArray[0]]
startAMCombinedFinal = startAMCombined[0] as? [Any?] ?? []
var endPMCombined = [Any]()
var endPMCombinedFinal = [Any?]()
endPMCombined = [self.combinedEndPMArray[0]]
endPMCombinedFinal = endPMCombined[0] as? [Any?] ?? []
cell.operatingDaysLabel.text = str
let isAMEqual = checkArrayStatus(testArray: [startAMCombinedFinal as Any])
let isPMEqual = checkArrayStatus(testArray: [endPMCombinedFinal as Any])
if isAMEqual && isPMEqual {
self.mergedArray = arrayMerger(array1: startAMCombinedFinal, array2: endPMCombinedFinal)
}
let startTimeString = self.mergedArray[0] as? String
let endTimeString = self.mergedArray[1] as? String
cell.operatingTimeLabel.text = startTimeString! + " - " + endTimeString!
return cell
} else if isFirstTime && self.nameArray.count != 0 {
var daysCombined = [Any]()
var daysCombinedFinal = [Any?]()
daysCombined = [self.combinedArray[indexPath.row]]
daysCombinedFinal = daysCombined[0] as! [Any?]
let str = daysCombinedFinal.flatMap { $0 as? String }.joined(separator:" ")
var startAMCombined = [Any]()
var startAMCombinedFinal = [Any?]()
startAMCombined = [self.combinedStartAMArray[indexPath.row]]
startAMCombinedFinal = startAMCombined[0] as! [Any?]
var endPMCombined = [Any]()
var endPMCombinedFinal = [Any?]()
endPMCombined = [self.combinedEndPMArray[indexPath.row]]
endPMCombinedFinal = endPMCombined[0] as! [Any?]
cell.operatingDaysLabel.text = str
let isAMEqual = checkArrayStatus(testArray: [startAMCombinedFinal as Any])
let isPMEqual = checkArrayStatus(testArray: [endPMCombinedFinal as Any])
if isAMEqual && isPMEqual {
self.mergedArray = arrayMerger(array1: startAMCombinedFinal, array2: endPMCombinedFinal)
}
let startTimeString = self.mergedArray[0] as? String
let endTimeString = self.mergedArray[1] as? String
cell.operatingTimeLabel.text = startTimeString! + " - " + endTimeString!
return cell
}
return cell
} else {
fatalError("Dequeueing SomeCell failed")
}
}
The array declaration is:
var dateArray = [Any]()
var endAmTimeArray = [Any]()
var endPmTimeArray = [Any]()
var startAmTimeArray = [Any]()
var startPmTimeArray = [Any]()
var combinedArray = [Any]()
var combinedStartAMArray = [Any]()
var combinedEndPMArray = [Any]()
var mergedArray = [Any?]()
Your problem has relation with concept of 'optional' & 'unwrapper'. Here is brief about both and difference between them: How to understand ! and ? in swift?
? (Optional) indicates your variable may contain a nil value while ! (unwrapper) indicates your variable must have a memory (or value) when it is used (tried to get a value from it) at runtime.
In your case, you are trying to get value from array using index number. Now type of elements of your array is 'Any'
So, there may be any kind of value/information contained by element of array. It will result into app crash, if you try to force unwrap a value of element, when it won't return a value or value with type that you're casting with unwrapper.
Here is basic tutorial in detail, by Apple Developer Committee.
This warning is indicating you, that your app may crash on force unwrapping optional value.
As a solution you should use ? (optional) with if-let block, to avoid force unwrapping and safe execution of your code, like:
if let daysC = daysCombined[0] as? [Any] {
daysCombinedFinal = daysC
}
Share your full source code, to get better solution of your query as you have not shared declaration of your array in your question. Because I've confusion about optional array [Any?] elements. Swift not allows array elements as optional.
Update : By considering elements of all arrays as 'Dictionary < String : Any >', forced unwraps from array assignments are removed here.
var daysCombined = [Any]()
var daysCombinedFinal = [Any?]()
daysCombined = [self.combinedArray[indexPath.row]]
// Update 1
// if combinedArray is an array of array
if let arrayElement = daysCombined[0] as? [Any] {
daysCombinedFinal = arrayElement
}
let str = daysCombinedFinal.flatMap { $0 as? String }.joined(separator:" ")
var startAMCombined = [Any]()
var startAMCombinedFinal = [Any?]()
startAMCombined = [self.combinedStartAMArray[indexPath.row]]
// Update 2
if let arrayElement = startAMCombined[0] as? [Any] {
startAMCombinedFinal = arrayElement
}
var endPMCombined = [Any]()
var endPMCombinedFinal = [Any?]()
endPMCombined = [self.combinedEndPMArray[indexPath.row]]
// Update 3
if let arrayElement = endPMCombined[0] as? [Any] {
endPMCombinedFinal = arrayElement
}
cell.operatingDaysLabel.text = str

While parsing JSON data, I get different result, from same url

Problem Statement: Every time whenever I refresh viewController, then I get different values.
Required Solution: Whenever I refresh, it should fetch same values.
Coding Stuff:
Below I have get values from JSON parsing via url. After fetching values, it pass to the 'setChart() function as a parameter.'
override func viewDidLoad()
{
super.viewDidLoad()
var completedCalls = 0
for i in 0..<strDates.count {
Alamofire.request("http://api.fixer.io/\(strDates[i])?base=USD").responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
completedCalls += 1
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
if completedCalls = strDates.count {
DispatchQueue.main {
setChart(dataPoints: strDates, values: sValues)
}
}
}
}
}
setChart() Function
func setChart(dataPoints: [String], values: [Double]) {
barChartView.noDataText = "You need to provide data for the chart."
for i in 0..<dataPoints.count {
let dataEntry = BarChartDataEntry(x: Double(i), yValues: [values[i]])
dataEntries.append(dataEntry)
}
let chartDataSet = BarChartDataSet(values: dataEntries, label: "INR Rates(₹)/$")
let chartData = BarChartData(dataSet: chartDataSet)
barChartView.data = chartData
barChartView.leftAxis.axisMinimum = 65
barChartView.leftAxis.axisMaximum = 70
barChartView.xAxis.labelPosition = .bottom
barChartView.rightAxis.enabled = false
barChartView.data?.setDrawValues(false)
}
}
You're issuing a sequence of asynchronous requests, but you have no assurances the order in which they'll complete.
I would suggest capturing the results in a dictionary, with the strDates as the key. Then you can extract them in the correct order when presenting the results.
So, I'm assuming that assuming that sValues is defined as:
var sValues = [Double]()
Change that to:
var sValues = [String: Double]()
And then, rather than:
self.sValues.append(inc)
You would do:
self.sValues[strDates[i]] = inc
Then, in your charting code, iterate through strDates array, and look up the sValues using those date strings.

Resources