Swift 3 JSON to array of objects is very slow - arrays

I am upgrading my app from swift 2 to swift 3 and have a significant performance loss when building an array of custom objects. In swift 2 this took a few seconds while in swift 3 it takes around 30 seconds. I am using alamofire to return swiftyJSON, returning about 3000 rows of data. the alamofire return is quick, its looping through this json to build array of custom objects thats slow. These objects greatly simplify the code written when building table cells and passing data to new views.
Code:
class Customer {
var ID: String!
var sysName: String!
var address: String!
var contactID: String!
required init(_name:String?, _id: String?, _address:String?, _contactID:String?) {
//print(json)
if _id != nil {
self.ID = _id
}else{
self.ID = ""
}
if _name != nil {
self.sysName = _name
}else{
self.sysName = ""
}
if _address != nil {
self.address = _address
}else{
self.address = "No Address on File"
}
if _contactID != nil {
self.contactID = _contactID
}else{
self.contactID = ""
}
}
}
Alamofire.request(API.Router.customerList()).responseJSON() {
response in
print(response.request ?? "") // original URL request
print(response.response ?? "") // URL response
print(response.data ?? "") // server data
print(response.result) // result of response serialization
if let json = response.result.value {
print("JSON: \(json)")
self.customers = JSON(json)
self.parseJSON()
}
}
func parseJSON(){
let jsonCount = self.customers["customers"].count
self.totalCustomers = jsonCount
for i in 0 ..< jsonCount {
self.loadedCustomers = i
print("customer = \(self.customers["customers"][i] ["sysName"].string!)")
//VERY SLOW
//create a customer object
let customer = Customer( _name: self.customers["customers"][i]["sysName"].string!, _id: self.customers["customers"][i]["ID"].string!, _address: self.customers["customers"][i]["mainAddr"].string!, _contactID: self.customers["customers"][i]["contactID"].string!)
//add customer to customer array
self.customersArray.append(customer)
}
self.layoutViews() //build view, call all table methods
}
Thanks
Improved code:
import Foundation
import UIKit
import Alamofire
import SwiftyJSON
enum SearchMode{
case name
case address
}
class CustomerListViewController: ViewControllerWithMenu, UITableViewDelegate, UITableViewDataSource, UISearchControllerDelegate, UISearchBarDelegate, UISearchDisplayDelegate, UISearchResultsUpdating{
var indicator: SDevIndicator!
var totalCustomers:Int!
//data arrays
var ids = [String]()
var names = [String]()
var addresses = [String]()
var searchController:UISearchController!
var currentSearchMode = SearchMode.name
var customerTableView:TableView = TableView()
var layoutVars:LayoutVars = LayoutVars()
var sections : [(index: Int, length :Int, title: String)] = Array()
var customersSearchResults:[String] = []
var shouldShowSearchResults:Bool = false
let viewsConstraint_V:NSArray = []
let viewsConstraint_V2:NSArray = []
override func viewDidLoad() {
super.viewDidLoad()
title = "Customer List"
view.backgroundColor = layoutVars.backgroundColor
getCustomerList()
}
func getCustomerList() {
//remove any added views (needed for table refresh
for view in self.view.subviews{
view.removeFromSuperview()
}
// Show Indicator
indicator = SDevIndicator.generate(self.view)!
Alamofire.request(API.Router.customerList()).responseJSON() {
response in
//print(response.request ?? "") // original URL request
//print(response.response ?? "") // URL response
//print(response.data ?? "") // server data
//print(response.result) // result of response serialization
do {
if let data = response.data,
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let customers = json["customers"] as? [[String: Any]] {
for customer in customers {
if let id = customer["ID"] as? String {
self.ids.append(id)
}
if let name = customer["sysName"] as? String {
self.names.append(name)
}
if let address = customer["mainAddr"] as? String {
self.addresses.append(address)
}
}
}
} catch {
print("Error deserializing JSON: \(error)")
}
// build sections based on first letter(json is already sorted alphabetically)
var index = 0;
var firstCharacterArray:[String] = [" "]
for i in 0 ..< self.names.count {
let stringToTest = self.names[i].uppercased()
let firstCharacter = String(stringToTest[stringToTest.startIndex])
if(i == 0){
firstCharacterArray.append(firstCharacter)
}
if !firstCharacterArray.contains(firstCharacter) {
let title = firstCharacterArray[firstCharacterArray.count - 1]
firstCharacterArray.append(firstCharacter)
let newSection = (index: index, length: i - index, title: title)
self.sections.append(newSection)
index = i;
}
if(i == self.names.count - 1){
let title = firstCharacterArray[firstCharacterArray.count - 1]
let newSection = (index: index, length: i - index, title: title)
self.sections.append(newSection)
}
}
self.layoutViews()
}
}
func layoutViews(){
indicator.dismissIndicator()
searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.placeholder = "Search Customers"
searchController.searchResultsUpdater = self
searchController.delegate = self
searchController.searchBar.delegate = self
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
navigationItem.titleView = searchController.searchBar
let items = ["Name","Address"]
let customSC = SegmentedControl(items: items)
customSC.selectedSegmentIndex = 0
customSC.addTarget(self, action: #selector(self.changeSearchOptions(sender:)), for: .valueChanged)
self.view.addSubview(customSC)
self.customerTableView.delegate = self
self.customerTableView.dataSource = self
self.customerTableView.register(CustomerTableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(self.customerTableView)
//auto layout group
let viewsDictionary = [
"view2":customSC,
"view3":self.customerTableView
]as [String:AnyObject]
let sizeVals = ["fullWidth": layoutVars.fullWidth,"width": layoutVars.fullWidth - 30,"navBottom":layoutVars.navAndStatusBarHeight,"height": self.view.frame.size.height - 100] as [String:Any]
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view2(fullWidth)]", options: [], metrics: sizeVals, views: viewsDictionary))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view3(fullWidth)]", options: [], metrics: sizeVals, views: viewsDictionary))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-navBottom-[view2(40)][view3(height)]", options: [], metrics: sizeVals, views: viewsDictionary))
}
/////////////// Search Methods ///////////////////////
func changeSearchOptions(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
currentSearchMode = .name
break
case 1:
currentSearchMode = .address
break
default:
currentSearchMode = .name
break
}
filterSearchResults()
}
func updateSearchResults(for searchController: UISearchController) {
filterSearchResults()
}
func filterSearchResults(){
customersSearchResults = []
switch currentSearchMode {
case .name:
self.customersSearchResults = self.names.filter({( aCustomer: String ) -> Bool in
return (aCustomer.lowercased().range(of: self.searchController.searchBar.text!.lowercased()) != nil) })
break
case .address:
self.customersSearchResults = self.addresses.filter({( aCustomer: String) -> Bool in
return (aCustomer.lowercased().range(of: self.searchController.searchBar.text!.lowercased()) != nil)
})
break
}
self.customerTableView.reloadData()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
//print("searchBarTextDidBeginEditing")
shouldShowSearchResults = true
self.customerTableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
shouldShowSearchResults = false
self.customerTableView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if !shouldShowSearchResults {
shouldShowSearchResults = true
self.customerTableView.reloadData()
}
searchController.searchBar.resignFirstResponder()
}
/////////////// TableView Delegate Methods ///////////////////////
func numberOfSections(in tableView: UITableView) -> Int {
if shouldShowSearchResults{
return 1
}else{
return sections.count
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
//print("titleForHeaderInSection")
if shouldShowSearchResults{
return nil
}else{
if(sections[section].title == "#"){
return " # \(self.totalCustomers) Customers Found"
}else{
return " " + sections[section].title //hack way of indenting section text
}
}
}
func sectionIndexTitles(for tableView: UITableView) -> [String]?{
print("sectionIndexTitlesForTableView 1")
if shouldShowSearchResults{
return nil
}else{
//print("sectionIndexTitlesForTableView \(sections.map { $0.title })")
return sections.map { $0.title }
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
//print("heightForHeaderInSection")
if shouldShowSearchResults{
return 0
}else{
return 50
}
}
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return index
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//print("numberOfRowsInSection")
if shouldShowSearchResults{
return self.customersSearchResults.count
} else {
return sections[section].length
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = customerTableView.dequeueReusableCell(withIdentifier: "cell") as! CustomerTableViewCell
customerTableView.rowHeight = 50.0
if shouldShowSearchResults{
let searchString = self.searchController.searchBar.text!.lowercased()
if(currentSearchMode == .name){
cell.nameLbl.text = self.customersSearchResults[indexPath.row]
cell.name = self.customersSearchResults[indexPath.row]
if let i = self.names.index(of: cell.nameLbl.text!) {
//print("\(cell.nameLbl.text!) is at index \(i)")
cell.addressLbl.text = self.addresses[i]
cell.address = self.addresses[i]
cell.id = self.ids[i]
} else {
cell.addressLbl.text = ""
cell.address = ""
cell.id = ""
}
//text highlighting
let baseString:NSString = cell.name as NSString
let highlightedText = NSMutableAttributedString(string: cell.name)
var error: NSError?
let regex: NSRegularExpression?
do {
regex = try NSRegularExpression(pattern: searchString, options: .caseInsensitive)
} catch let error1 as NSError {
error = error1
regex = nil
}
if let regexError = error {
print("Oh no! \(regexError)")
} else {
for match in (regex?.matches(in: baseString as String, options: NSRegularExpression.MatchingOptions(), range: NSRange(location: 0, length: baseString.length)))! as [NSTextCheckingResult] {
highlightedText.addAttribute(NSBackgroundColorAttributeName, value: UIColor.yellow, range: match.range)
}
}
cell.nameLbl.attributedText = highlightedText
}else{//address search mode
cell.addressLbl.text = self.customersSearchResults[indexPath.row]
cell.address = self.customersSearchResults[indexPath.row]
if let i = self.addresses.index(of: cell.addressLbl.text!) {
cell.nameLbl.text = self.names[i]
cell.name = self.names[i]
cell.id = self.ids[i]
} else {
cell.nameLbl.text = ""
cell.name = ""
cell.id = ""
}
//text highlighting
let baseString:NSString = cell.address as NSString
let highlightedText = NSMutableAttributedString(string: cell.address)
var error: NSError?
let regex: NSRegularExpression?
do {
regex = try NSRegularExpression(pattern: searchString, options: .caseInsensitive)
} catch let error1 as NSError {
error = error1
regex = nil
}
if let regexError = error {
print("Oh no! \(regexError)")
} else {
for match in (regex?.matches(in: baseString as String, options: NSRegularExpression.MatchingOptions(), range: NSRange(location: 0, length: baseString.length)))! as [NSTextCheckingResult] {
highlightedText.addAttribute(NSBackgroundColorAttributeName, value: UIColor.yellow, range: match.range)
}
}
cell.addressLbl.attributedText = highlightedText
}
} else {
//print("make cell")
cell.id = self.ids[sections[indexPath.section].index + indexPath.row]
cell.name = self.names[sections[indexPath.section].index + indexPath.row]
cell.address = self.addresses[sections[indexPath.section].index + indexPath.row]
cell.nameLbl.text = self.names[sections[indexPath.section].index + indexPath.row]
cell.addressLbl.text = self.addresses[sections[indexPath.section].index + indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRow(at: indexPath!) as! CustomerTableViewCell
let customerViewController = CustomerViewController(_customerID: currentCell.id)
navigationController?.pushViewController(customerViewController, animated: false )
tableView.deselectRow(at: indexPath!, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Related

create a function that finds the closest object base on Lat long in an array

I have created the following code for getting the closest point in an array to a users location. This is not working. I can get locations but not closest. My goal is to be able to gat the closest point and print out the pm2 values of the item.
import UIKit
import CoreLocation
extension Point {
var location: CLLocation { .init(latitude: latitude ?? 5, longitude: longitude ?? 5) }
}
class popAirViewController: UITableViewController, CLLocationManagerDelegate {
private let popAirURL = "https://www.purpleair.com/json"
private var results = [Point]()
var locationManager: CLLocationManager!
var PM5Value: Int = 5
var points: [Point] = []
var destination: CLLocation?
//Temporary background image
var backgroundImage: String = "Green"
// sets PM5 text Label
#IBOutlet weak var latLabel: UILabel!
//#IBOutlet weak var latLabel: UILabel!
func PM5Text(){
latLabel.text = "56.5"
latLabel.textAlignment = .center
latLabel.numberOfLines = 10
latLabel.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height+300)
}
override func viewDidLoad() {
self.tableView.backgroundView = UIImageView(image: UIImage(named: backgroundImage))
self.tableView.backgroundColor = .clear
super.viewDidLoad()
//sets background images
if 0 ... 50 ~= Int(PM5Value) {
self.tableView.backgroundView = UIImageView(image: UIImage(named: "Green"))}
else if 51 ... 100 ~= Int(PM5Value) {
self.tableView.backgroundView = UIImageView(image: UIImage(named: "Yellow"))}
else if 101 ... 150 ~= Int(PM5Value) {
self.tableView.backgroundView = UIImageView(image: UIImage(named: "Orange"))}
else if 151 ... 1000 ~= Int(PM5Value) {
self.tableView.backgroundView = UIImageView(image: UIImage(named: "Purple"))}
print(PM5Value)
self.navigationController?.navigationBar.isHidden = true;
//let fullImageView = UIImageView(frame: self.view.frame)
//view.addSubview(fullImageView)
if (CLLocationManager.locationServicesEnabled())
{
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
tableView.estimatedRowHeight = 92.0
tableView.rowHeight = UITableView.automaticDimension
getLatestLocations()
}
// MARK: - Table view data source
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
PM5Text()
}
override func numberOfSections(in tableView: UITableView) -> Int {
// Return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows
return results.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! KivaLoanTableViewCell
// Configure the cell...
cell.amountLabel.text = results[indexPath.row].pm2
return cell
}
// MARK: - Helper methods
func getLatestLocations() {
guard let pointUrl = URL(string: popAirURL) else {
return
}
let request = URLRequest(url: pointUrl)
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
if let error = error {
print(error)
return
}
// Parse JSON data
if let data = data {
self.results = self.parseJsonData(data: data)
// Reload table view
OperationQueue.main.addOperation({
self.tableView.reloadData()
})
}
})
task.resume()
}
func parseJsonData(data: Data) -> [Point] {
var points = [Point]()
do {
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
// Parse JSON data
let jsonPoints = jsonResult?["results"] as! [AnyObject] as [AnyObject]
for jsonPoint in jsonPoints {
var point = Point()
point.latitude = jsonPoint["Lat"] as? Double
point.longitude = jsonPoint["Lon"] as? Double
point.pm2 = jsonPoint["PM2_5Value"] as? String
let latq = point.latitude
let lonq = point.longitude
//let destination = CLLocationCoordinate2DMake(latq ?? 5, lonq ?? 5)
self.destination = CLLocation(latitude: latq ?? 5, longitude: lonq ?? 5)
points.append(point)
//print(destination)
print("Destination=: \(destination!)")
}
} catch {
print(error)
}
return points
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
var userLocation:CLLocation = locations[0]
let long = userLocation.coordinate.longitude;
let lat = userLocation.coordinate.latitude;
if let closestPoint = points.min(by:{
$0.location.distance(from: userLocation) < $1.location.distance(from: userLocation)
}) {
print("closest point:", closestPoint)
}
if destination == nil{
return
}
userLocation = CLLocation(latitude: lat , longitude: long )
}
}
You can use CLLocation distance(from: CLLocation) method to get the distance from the current location and use min(by: ) method to get the minimum distance from your points:
var points: [Point] = []
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
if let closestPoint = points.min(by: {
$0.location.distance(from: location) < $1.location.distance(from: location)
}) {
print("closest point:", closestPoint)
}
}
Add this helper to construct a location from your point:
extension Point {
var location: CLLocation { .init(latitude: latitude, longitude: longitude) }
}
Btw it is Swift naming convention to name your properties starting with a lower case letter:
struct Point: Equatable {
let pm2: String
let latitude: Double
let longitude: Double
}
edit/update: Not related to your question but you should use Codable protocol to parse the json response from the API:
Playground testing:
import UIKit
import CoreLocation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
struct Root: Codable {
let results: [Point]
}
struct Point: Codable {
let pm2: String?
let latitude, longitude: Double?
enum CodingKeys: String, CodingKey {
case pm2 = "PM2_5Value", latitude = "Lat", longitude = "Lon"
}
}
extension Point {
var location: CLLocation { .init(latitude: latitude ?? .zero, longitude: longitude ?? .zero) }
}
let popAirURL = "https://www.purpleair.com/json"
URLSession.shared.dataTask(with: URL(string: popAirURL)!) { data, response, error in
guard let data = data else {
print("error", error!)
return
}
do {
let points = try JSONDecoder().decode(Root.self, from: data).results
let location = CLLocation(latitude: 38, longitude: -121)
if let closestPoint = points.min(by: {
$0.location.distance(from: location) < $1.location.distance(from: location)
}) {
print("closest point:", closestPoint) // closest point: Point(pm2: Optional("107.47"), latitude: Optional(38.100134), longitude: Optional(-120.860454))
}
} catch {
print(error)
}
}.resume()

Display List of data when button is clicked

I have 2 different JSON and I want to display list of data when the image from the collectionview is pressed to my another view controller.
Those list of data should be clickable also.
It would be one SkillsName(TSCSkillName) to many Training's (TSCTtopicName)
SAMPLE JSON
{
"TSCskillID": 1,
"TSCProficiency": "Product",
"TSCLevel": "Fundamental",
"TSCSkillName": "Product Usability",
"TSCskillLOGO": "images\/TSCskillLogo\/Product Usability.jpg",
"TSCskillDescription": "SKILLS Test Description"
}
For the second JSON
{
"TSCProficiency": "Product",
"TSCSkillName": "Product Usability",
"TSCskillDescription": "SKILLS Test Description",
"TSCLevel": "Fundamental",
"TSCskillLOGO": "images\/TSCskillLogo\/Product Usability.jpg",
"TSCTtopicID": 1,
"TSCTtopicName": "Trend Micro Security",
"TSCTtopicDescription": "TOPIC Test Description",
"Status": "Done",
"TSCTMemorabilia": "images\/Memorabilia"
},
{
"TSCProficiency": "Product",
"TSCSkillName": "Product Usability",
"TSCskillDescription": "SKILLS Test Description",
"TSCLevel": "Fundamental",
"TSCskillLOGO": "images\/TSCskillLogo\/Product Usability.jpg",
"TSCTtopicID": 2,
"TSCTtopicName": "Trend Micro Antivirus for Mac",
"TSCTtopicDescription": "TOPIC Test Description",
"Status": "Done",
"TSCTMemorabilia": "images\/Memorabilia"
}
// I used this data to display the images to my collectionview
struct getSkills: Codable {
let TSCProficiency, TSCSkillName, TSCskillDescription, TSCLevel: String
let TSCskillLOGO: String
let TSCTtopicID: Int?
let TSCTtopicName, TSCTtopicDescription, status, TSCTMemorabilia: String
enum CodingKeys: String, CodingKey {
case TSCProficiency = "TSCProficiency"
case TSCSkillName = "TSCSkillName"
case TSCskillDescription = "TSCskillDescription"
case TSCLevel = "TSCLevel"
case TSCskillLOGO = "TSCskillLOGO"
case TSCTtopicID = "TSCTtopicID"
case TSCTtopicName = "TSCTtopicName"
case TSCTtopicDescription = "TSCTtopicDescription"
case status = "Status"
case TSCTMemorabilia = "TSCTMemorabilia"
}
}
struct skills {
static var TSCskillLOGO : String = "Photo Location"
static var TSCskillID: String = "1"
static var TSCProficiency: String = "Skill Proficiency"
static var TSCSkillName: String = "Skill Name"
static var TSCskillDescription: String = "Skill Description"
static var TSCLevel: String = "Skill Level"
static var TSCTtopicID: String = "Trainings ID"
static var TSCTtopicName: String = "Training Name"
static var TSCTtopicDescription: String = "Training Description"
static var Status: String = "Status"
static var TSCTMemorabilia: String = "Memorabilia"
}
struct testSkills: Codable {
let TSCskillID: Int
let TSCProficiency, TSCLevel,TSCSkillName, TSCskillLOGO: String
let TSCskillDescription: String
enum CodingKeys: String, CodingKey {
case TSCskillID = "TSCskillID"
case TSCProficiency = "TSCProficiency"
case TSCLevel = "TSCLevel"
case TSCSkillName = "TSCSkillName"
case TSCskillLOGO = "TSCskillLOGO"
case TSCskillDescription = "TSCskillDescription"
}
}
class SkillsTreeViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var skillsCollectionView: UICollectionView!
#IBOutlet weak var skillsCatLbl: UILabel!
var getSkillsInfo = [getSkills]()
var testSkillsInfo = [testSkills]()
var badges: [UIImage] = []
var isBadgeLoaded = false
var retryTimes = 0
// You may check my code below
override func viewDidLoad() {
super.viewDidLoad()
skillsCollectionView.layer.cornerRadius = 10
skillsCollectionView.layer.masksToBounds = true
skillsCatLbl.text = "Network"
loadData()
}
func loadData() {
handleCollectionViewLayout(collectionView: skillsCollectionView)
getTestSkillsList {
self.addValuesToVariables {
self.handleLoading(view: self.skillsCollectionView, isDoneLoading: !self.isBadgeLoaded)
self.loadImages()
}
}
// getSkillsDetails {}
}
func addValuesToVariables(completed: #escaping () -> ()) {
skills.TSCskillLOGO = testSkillsInfo[0].TSCskillLOGO
skills.TSCProficiency = testSkillsInfo[0].TSCProficiency
skills.TSCSkillName = testSkillsInfo[0].TSCSkillName
skills.TSCskillDescription = testSkillsInfo[0].TSCskillDescription
skills.TSCLevel = testSkillsInfo[0].TSCLevel
skills.TSCskillID = String(testSkillsInfo[0].TSCskillID)
completed()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return getSkillsInfo.count
return testSkillsInfo.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let collectionviewcell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionviewcell", for: indexPath) as! FAMECell
collectionviewcell.skillsBadgePic?.addImageShadow(shadowOffset: CGSize(width: 0, height: 0), shadowColor: UIColor.black.cgColor, shadowRadius: 0.5, shadowOpacity: 5.0)
collectionviewcell.skillsBadgePic?.contentMode = .scaleAspectFit
if isBadgeLoaded {
activityIndicator.stopAnimating()
collectionviewcell.skillsBadgePic?.image = badges[indexPath.item]
}
return collectionviewcell
//collectionviewcell.skillsBadgePic?.image = getSkillsInfo[indexPath.item].image
//}
// print(getSkillsInfo[indexPath.item].image!)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("User Tapped: \(indexPath.item)")
let selectedCell = skillsCollectionView.cellForItem(at: indexPath) as! FAMECell
let mainStoryboard:UIStoryboard = UIStoryboard (name: "Main", bundle:nil)
let desVC = mainStoryboard.instantiateViewController(withIdentifier: "SkillsTreePopUpViewController") as! SkillsTreePopUpViewController
desVC.badgeTitle = testSkillsInfo[indexPath.item].TSCSkillName
desVC.badgeImage = selectedCell.skillsBadgePic?.image
desVC.badgeDescription = testSkillsInfo[indexPath.item].TSCskillDescription
desVC.skillID = testSkillsInfo[indexPath.item].TSCskillID
desVC.trainingsBtn = getSkillsInfo[indexPath.item].TSCTtopicName //getting an error on this line [array is nil]
self.present(desVC, animated: true, completion: nil)
}
func getTestSkillsList(completed: #escaping () -> ()) {
let fcat = self.skillsCatLbl.text
let siebelid = engineerProfileInfo.siebelID
let rootLink = "https://skills.dasq.com/iOS/testskills.php?"
let url = URL (string: rootLink + "id=" + siebelid + "&fcat=" + fcat!)
URLSession.shared.dataTask(with: url!) {(data, response, error) in
if error == nil {
do {
self.testSkillsInfo = try JSONDecoder().decode([testSkills].self, from: data!)
print(self.testSkillsInfo)
DispatchQueue.main.async {
completed()
}
} catch {
print("JSON Error: Skills Tree")
self.handleJSONErrorAlert()
}
} else {
self.handleNoNetAlert()
}
}.resume()
}
func getSkillsDetails(completed: #escaping () -> ()) {
let tid = skills.TSCskillID
let siebelid = engineerProfileInfo.siebelID
let rootLink = "https://skills.dasq.com/iOS/getskillspage.php"
let url = URL (string: rootLink + "sid=" + siebelid + "&tid=" + tid )
URLSession.shared.dataTask(with: url!) {(data, response, error) in
if error == nil { // getting an error here that array is nil
do {
self.getSkillsInfo = try JSONDecoder().decode([getSkills].self, from: data!)
DispatchQueue.main.async {
completed()
}
} catch {
print("JSON Error: Trainings Details")
print (error)
self.handleJSONErrorAlert()
}
} else {
self.handleNoNetAlert()
}
}.resume()
}
func loadImages(){
for index in 1...testSkillsInfo.count {
let badgeTitle = testSkillsInfo[index-1].TSCSkillName
let completeLink = "https://skills.dasq.com/" + "images/TSCskillLogo/\(badgeTitle).jpg"
let imageUrlString = completeLink.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)
let url = URL(string: imageUrlString!)
let data = try? Data(contentsOf: url!)
let coloredPic = UIImage(data: data!)
badges.append(coloredPic!)
}
isBadgeLoaded = !isBadgeLoaded
print(badges.count)
self.skillsCollectionView.reloadData()
}
Please check you decodable struct. I have generated one for you. The error shows that
JSON text did not start with array.
So, try to decode "TopicName" which is an array of "TopicNameElement"
SkillName
struct SkillName: Codable {
let tsCskillID: Int
let tscProficiency, tscLevel, tscSkillName, tsCskillLOGO: String
let tsCskillDescription: String
enum CodingKeys: String, CodingKey {
case tsCskillID = "TSCskillID"
case tscProficiency = "TSCProficiency"
case tscLevel = "TSCLevel"
case tscSkillName = "TSCSkillName"
case tsCskillLOGO = "TSCskillLOGO"
case tsCskillDescription = "TSCskillDescription"
}
}
TopicName
typealias TopicName = [TopicNameElement]
struct TopicNameElement: Codable {
let tscProficiency, tscSkillName, tsCskillDescription, tscLevel: String
let tsCskillLOGO: String
let tscTtopicID: Int
let tscTtopicName, tscTtopicDescription, status, tsctMemorabilia: String
enum CodingKeys: String, CodingKey {
case tscProficiency = "TSCProficiency"
case tscSkillName = "TSCSkillName"
case tsCskillDescription = "TSCskillDescription"
case tscLevel = "TSCLevel"
case tsCskillLOGO = "TSCskillLOGO"
case tscTtopicID = "TSCTtopicID"
case tscTtopicName = "TSCTtopicName"
case tscTtopicDescription = "TSCTtopicDescription"
case status = "Status"
case tsctMemorabilia = "TSCTMemorabilia"
}
}

for loop only shows first item in array swift

I have a function called loadPosts that returns an array of Int values. Upon running it is used within the UITableView which has a function called setCell. Only the first item in the array is being used and then it repeats that value for the length of the array.
UPDATE 2:
Here are the parameters within the hhmessages array:
1. senderusername
2. recipient
3. message text
4. ava image
UPDATED: now includes additional code in loadPosts function
func loadPosts()->[Int] {
let me = user!["username"] as! String
let uuid = messages["uuid"] as! String
let url = URL(string: "http://localhost/message.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let body = "username=\(me)&uuid=\(uuid)"
request.httpBody = body.data(using: String.Encoding.utf8)
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async(execute: {
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
self.hhmessages.removeAll(keepingCapacity: false)
self.tableView.reloadData()
// declare new parseJSON to store json
guard let parseJSON = json else {
print("Error while parsing")
return
}
guard let messages = parseJSON["messages"] as? [AnyObject] else {
print("Error while parseJSONing")
return
}
self.hhmessages = messages
//print(self.hhmessages)
for i in 0 ..< self.hhmessages.count {
if me == self.hhmessages[i]["senderusername"]!! as! String {
self.incoming = [0]
}
if me == self.hhmessages[i]["recipient"]!! as! String {
self.incoming = [1]
}
}
self.tableView.reloadData()
return [Int()]
}
// UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ConversationCell
func setCell(incoming: [Int]) {
var layoutAttribute: NSLayoutAttribute
var layoutConstant: CGFloat
for i in 0 ..< self.incoming.count {
if (self.incoming[i] == 1) {
cell.bubbleImageView.image=#imageLiteral(resourceName: "chat_bubble_received")
cell.messageLbl.textColor = UIColor.black
layoutAttribute = .left
layoutConstant = 10
cell.contentView.addConstraint(NSLayoutConstraint(item: cell.bubbleImageView, attribute: layoutAttribute, relatedBy: .equal, toItem: cell.contentView, attribute: layoutAttribute, multiplier: 1, constant: layoutConstant))
}
if (self.incoming[i] == 0) {
cell.bubbleImageView.image = #imageLiteral(resourceName: "chat_bubble_sent")
cell.messageLbl.textColor = UIColor.white
layoutAttribute = .right
layoutConstant = -10
cell.contentView.addConstraint(NSLayoutConstraint(item: cell.bubbleImageView, attribute: layoutAttribute, relatedBy: .equal, toItem: cell.contentView, attribute: layoutAttribute, multiplier: 1, constant: layoutConstant))
}
}
}
// get main queue to this block of code to communicate back
DispatchQueue.main.async {
tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
setCell(incoming: self.incoming)
}
return cell
}
func loadPosts()->[Int] {
let me = user!["username"] as! String
let uuid = messages["uuid"] as! String
let url = URL(string: "http://localhost/message.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let body = "username=\(me)&uuid=\(uuid)"
request.httpBody = body.data(using: String.Encoding.utf8)
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async(execute: {
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
self.hhmessages.removeAll(keepingCapacity: false)
self.tableView.reloadData()
// declare new parseJSON to store json
guard let parseJSON = json else {
print("Error while parsing")
return
}
guard let messages = parseJSON["messages"] as? [AnyObject] else {
print("Error while parseJSONing")
return
}
self.hhmessages = messages
//print(self.hhmessages)
/// This is the part I edited
for i in 0 ..< self.hhmessages.count {
if me == self.hhmessages[i]["senderusername"]!! as! String {
self.incoming.append(0)
}
if me == self.hhmessages[i]["recipient"]!! as! String {
self.incoming.append(1)
}
}
self.tableView.reloadData()
return [Int()]
}
Change your cellForRowAt to use the indexPath.row as the index for self.incoming :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ConversationCell
func setCell(incoming: [Int]) {
var layoutAttribute: NSLayoutAttribute
var layoutConstant: CGFloat
if (self.incoming[indexPath.row] == 1) {
cell.bubbleImageView.image=#imageLiteral(resourceName: "chat_bubble_received")
cell.messageLbl.textColor = UIColor.black
layoutAttribute = .left
layoutConstant = 10
cell.contentView.addConstraint(NSLayoutConstraint(item: cell.bubbleImageView, attribute: layoutAttribute, relatedBy: .equal, toItem: cell.contentView, attribute: layoutAttribute, multiplier: 1, constant: layoutConstant))
}
if (self.incoming[indexPath.row] == 0) {
cell.bubbleImageView.image = #imageLiteral(resourceName: "chat_bubble_sent")
cell.messageLbl.textColor = UIColor.white
layoutAttribute = .right
layoutConstant = -10
cell.contentView.addConstraint(NSLayoutConstraint(item: cell.bubbleImageView, attribute: layoutAttribute, relatedBy: .equal, toItem: cell.contentView, attribute: layoutAttribute, multiplier: 1, constant: layoutConstant))
}
}
// get main queue to this block of code to communicate back
DispatchQueue.main.async {
tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
setCell(incoming: self.incoming)
}
return cell
}

My loop to grab number of results within an array in swift is not working

Greetings dear fans and programmers of the Swift language. I have tried to formulate an algorithm number of times an element shows up within an array with a for loop, but it doesn't seem to be working.
My code is as follows:
else if indexPath.section == 1 {
cell = tableView.dequeueReusableCellWithIdentifier("GreenCell", forIndexPath: indexPath)
for item in theModel.userAnswer {
numOfCoincidences[item] = (numOfCoincidences[item] ?? 0) + 1
}
for (numLangs, value) in numOfCoincidences {
txtSummary = "#languages spoken: \(numLangs)"
txtSummary2 = "# of People: \(value)"
}
cell.textLabel?.text = txtSummary
cell.detailTextLabel?.text = txtSummary2
I am trying to display this information in a table Cell but it is not working. I thought that my algorithm was spot on. Any suggestions?
I'm getting the following output: I have a navigation controller in effect where I input data on one screen and it outputs the data on a table. It's a bit of a survey where I prompt the user to enter their name and the number of languages spoken. I'm using an MVC programming methodology.
so in the model, here is the code:
import Foundation
class Model {
var userName = [String]()
var userAnswer = [String]()
var userInfo = [String]()
var name:String
var answer:String
init(){
self.name = ""
self.answer = ""
}
func addUserInfo(name:String, answer:String) -> Void {
userName.append(name)
userAnswer.append(answer)
}
}
In the input screen, I have 2 text boxes that prompt for username and number of languages spoken. So on the output screen, if 2 people speak 4 languages, the output should reflect that, but it's not. If 1 person speaks 3 languages, it should display that and so on. The output is coming out completely incorrectly. Here is the for the data entry code:
import UIKit
class ViewController: UIViewController {
var model = Model()
#IBOutlet var txtName: UITextField!
#IBOutlet var lblStatus: UILabel!
#IBOutlet var txtAnswer: UITextField!
#IBAction func btnAnswer(sender: UIButton) {
model.answer = txtAnswer.text!
model.name = txtName.text!
if ((txtName.text)! == "" || (txtAnswer.text)! == "") {
lblStatus.text = "Name and answer are both required"
}else if model.userName.contains(model.name) {
lblStatus.text = "Answer already recorded for \(model.name)"
} else {
model.addUserInfo(model.name, answer: model.answer)
lblStatus.text = "Ok, \(model.name) answered \(model.answer)"
}
txtAnswer.text = ""
txtName.text = ""
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toReesultsController" {
let vc = segue.destinationViewController as! TableViewController
vc.theModel = self.model
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
The code for the table to display the data inputted from the data entry screen is presented in a table as shown below:
import UIKit
class TableViewController: UITableViewController {
var theModel = Model()
var numOfCoincidences:[String:Int] = [:]
var txtSummary:String = ""
var txtSummary2:String = ""
var greatest:Int = 0
/* override func viewDidLoad() {
for index in 0..<theModel.userName.count {
}
}*/
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return theModel.userAnswer.count
} else if section == 1 {
return theModel.userAnswer.count
} else if section == 2{
return theModel.userAnswer.count
} else {
return 0
}
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Answer Log"
} else if section == 1 {
return "Summary"
} else if section == 2 {
return "Top Answers"
} else {
return nil
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell! = nil
if indexPath.section == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("BlueCell", forIndexPath: indexPath)
cell.textLabel?.text = "\(theModel.userName[indexPath.row]):\(theModel.userAnswer[indexPath.row])"
} else if indexPath.section == 1 {
cell = tableView.dequeueReusableCellWithIdentifier("GreenCell", forIndexPath: indexPath)
for item in theModel.userAnswer {
numOfCoincidences[item] = (numOfCoincidences[item] ?? 0) + 1
}
for (numLangs, value) in numOfCoincidences {
txtSummary = "#languages spoken: \(numLangs)"
txtSummary2 = "# of People: \(value)"
}
cell.textLabel?.text = txtSummary
cell.detailTextLabel?.text = txtSummary2
} else if indexPath.section == 2 {
cell = tableView.dequeueReusableCellWithIdentifier("OrangeCell", forIndexPath: indexPath)
greatest = 0
for index in 0..<theModel.userAnswer.count {
if Int(theModel.userAnswer[index])! > greatest {
greatest = Int(theModel.userAnswer[index])!
}
}
cell.textLabel?.text = "Answer with most votes is \(greatest) languages spoken."
}
return cell
}
}
Based on what you have shared, and making some assumptions on your input this seems to do what you want.
If each item is a dictionary with the keys shown below. Then you would walk through the array and get the number of languages spoken by the person. I chose to define that as a number(Int) since that is what it really is.
This is almost the same as your original code so I have to conclude that your idea was correct, but your implementation was missing something.
Assuming this is your input:
let a = [["name":"carla", "langs" : 3], ["name":"scott", "langs" : 3], ["name":"brad", "langs" : 1], ["name":"cynthia", "langs":2]]
var numOfCoincidences = [Int: Int]()
for item in a {
let numLangs = item["langs"] as! Int
numOfCoincidences[numLangs] = (numOfCoincidences[numLangs] ?? 0) + 1
}
var txtSummary = ""
var txtSummary2 = ""
for (numLangs, value) in numOfCoincidences {
// You should also note that you are overwriting values here.
txtSummary = "#languages spoken: \(numLangs)"
txtSummary2 = "# of People: \(value)"
print(txtSummary)
print(txtSummary2)
}
numOfCoincidences
Output:
// key := number of languages spoken
// value := number of people who speak that many languages
[2: 1, 3: 2, 1: 1]
You do have a problem in your cellForRowAtIndexPath.
After you loop through all the data, you do this:
cell.textLabel?.text = txtSummary
cell.detailTextLabel?.text = txtSummary2
Therefore, every cell is going to have the last values from the loop.

getting PickerData data from a Rest WebService

i get data from a web service and my pickerData Array don't save values when i want to use it outside of the Json Parsing bloc.
here's my code
var pickerData: [String] = [String]()
var mag : String!
override func viewDidLoad() {
super.viewDidLoad()
NomMAG.alpha = 0
// \(detectionString)
let str = "http://vps43623.ovh.net/yamoinscher/api/getAllMag"
let url = NSURL(string: str)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data {
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(urlContent, options: [])
if let jsonResult = jsonObject as? [String:AnyObject] {
if let Pick = jsonResult["magasin"] as? [[String:String]] {
for categorie in Pick {
self.mag = categorie["libelle"]!
self.pickerData.append(magasin)
//self.pickerData = [(self.produits[0].magasin)]
}
print(self.pickerData)
dispatch_async(dispatch_get_main_queue()) {
self.picker1.reloadInputViews()
// print(self.produits.count)
}
}
}
} catch {
print("JSON serialization failed", error)
}
} else if let connectionError = error {
print("connection error", connectionError)
}
}
task.resume()
//print(produits.count)
//pickerData = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "autre"]
//print(self.pickerData)
self.picker1.delegate = self
self.picker1.dataSource = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// The number of columns of data
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
// The number of rows of data
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.pickerData.count
}
// 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? {
return self.pickerData[row]
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerData[row] == "autre"
{
//print(row)
NomMAG.alpha = 1
}
else
{
//print(row.description)
NomMAG.alpha = 0
}
}
I want to get my PickerView full with the data i gained from the JsonParsing and the PickerData Array is null outside the block of code of the Json
Connect delegate and dataSource of the picker view in Interface Builder and replace viewDidLoad with
var pickerData = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let str = "http://vps43623.ovh.net/yamoinscher/api/getAllMag"
let url = NSURL(string: str)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data {
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(urlContent, options: [])
if let jsonResult = jsonObject as? [String:AnyObject],
magasin = jsonResult["magasin"] as? [[String:String]] {
// filter valid items, map them to an array and filter empty strings
self.pickerData = magasin.filter { $0["libelle"] != nil }.map { $0["libelle"]! }.filter { !$0.isEmpty}
}
dispatch_async(dispatch_get_main_queue()) {
self.picker1.reloadAllComponents()
}
} catch {
print("JSON serialization failed", error)
}
} else if let connectionError = error {
print("connection error", connectionError)
}
}
task.resume()
}
You have to add the NomMAG line

Resources