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
}
Related
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()
I have tableView with one cell which contains one label.. i need to display different label text for different category.
for that i have created empty iteamArray and i am trying to append iteamArray for different category using if condition. in the same way i need to return array count in numberOfRowsInSection but here i am unable to check if condition, it only goes out side return if i give return iteamArray.count then it displays all category values if i give return 0 it display nothing in tableview.
if i click water i need to show only water related iteamsArray in tabelview.. but here if i click any category it shows all category related values in tableview.. please help me in the code.
here is my code:
import UIKit
class PaymentTableViewCell: UITableViewCell{
#IBOutlet weak var pamntTypeLabel: UILabel!
}
class AllMakePaymentViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var categoryName: String?
var iteamsArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
allPaymentService()
}
func allPaymentService(){
let urlStr = "https://dev.com/webservices/api.php?rquest"
let url = URL(string: urlStr)
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let respData = data else {
return
}
guard error == nil else {
print("error")
return
}
do{
let jsonObj = try JSONSerialization.jsonObject(with: respData, options: .allowFragments) as! [String: Any]
//print("the all make payment json is \(jsonObj)")
let billerdetailsArray = jsonObj["billerdetails"] as! [[String: Any]]
for billerdetail in billerdetailsArray {
self.categoryName = billerdetail["bcategoryname"] as? String
if self.categoryName == "Water"{
let bName = billerdetail["bname"] as? String
self.iteamsArray.append(bName ?? "")
print("water arry \(self.iteamsArray.append(bName ?? ""))")
}
if self.categoryName == "Landline Postpaid"{
let bName = billerdetail["bname"] as? String
self.iteamsArray.append(bName ?? "")
print("Landline arry \(self.iteamsArray.append(bName ?? ""))")
}
if self.categoryName == "DTH"{
let bName = billerdetail["bname"] as? String
self.iteamsArray.append(bName ?? "")
print("DTH arry \(self.iteamsArray.append(bName ?? ""))")
}
if self.categoryName == "Electricity"{
let bName = billerdetail["bname"] as? String
self.iteamsArray.append(bName ?? "")
print("Electricity arry \(self.iteamsArray.append(bName ?? ""))")
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print("catch error")
}
}).resume()
}
}
extension AllMakePaymentViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if categoryName == "Water"{
return iteamsArray.count
}
if categoryName == "Landline Postpaid"{
return iteamsArray.count
}
if categoryName == "DTH"{
return iteamsArray.count
}
if categoryName == "Electricity"{
return iteamsArray.count
}
return iteamsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! PaymentTableViewCell
cell.pamntTypeLabel.text = iteamsArray[indexPath.row]
self.tableView.separatorStyle = .none
return cell
}
}
Please help me in above code.
This as you embed all the items to the same array here
if self.categoryName == "Water"{
let bName = billerdetail["bname"] as? String
self.iteamsArray.append(bName ?? "")
print("water arry \(self.iteamsArray.append(bName ?? ""))")
}
if self.categoryName == "Landline Postpaid"{
let bName = billerdetail["bname"] as? String
self.iteamsArray.append(bName ?? "")
print("Landline arry \(self.iteamsArray.append(bName ?? ""))")
}
if self.categoryName == "DTH"{
let bName = billerdetail["bname"] as? String
self.iteamsArray.append(bName ?? "")
print("DTH arry \(self.iteamsArray.append(bName ?? ""))")
}
if self.categoryName == "Electricity"{
let bName = billerdetail["bname"] as? String
self.iteamsArray.append(bName ?? "")
print("Electricity arry \(self.iteamsArray.append(bName ?? ""))")
}
Even categoryName is different , instead you better need
var iteamsArray = [Category]()
var filteredArray = [Category]()
guard let res = try? JSONDecoder().decode(Root.self,from:data) else { return }
self.iteamsArray = res.billerdetails
struct Root {
let billerdetails:[Category]
}
struct Category {
let bcategoryname,bname:String
}
Then use filteredArray for the table and when you need to change category do
self.filteredArray = itemsArray.filter{ $0.bcategoryname == "Water" }
self.tableView.reloadData()
I currently have an array which I am appending options to. They are they displayed in a table with 3 sections. The first 2 sections have 1 row each, but the third section has a variable number of rows depending on what is appended to the array. I essentially take the third component of my initial array (allAlbums[0].markscheme) and break it down to create multiple new items in the array.
However, when I am trying to simulate this, I get a fatal array on 'cell.textData?.text = section[indexPath.row] as! String' and I'm not sure why?
final class CaseViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var titleText: UILabel!
#IBOutlet var tableView: UITableView!
private var allAlbums = [Case]()
let kHeaderSectionTag: Int = 6900;
var expandedSectionHeaderNumber: Int = -1
var expandedSectionHeader: UITableViewHeaderFooterView!
var sectionItems: Array<Any> = []
var sectionNames: Array<Any> = []
var markschemeRows: Array<Any> = []
override func viewDidLoad() {
super.viewDidLoad()
allAlbums = LibraryAPI.shared.getCases()
// Filter the main array to match a single case
allAlbums = allAlbums.filter { $0.title == title}
// Get data to fill in to table
sectionNames = [ "Trainee Information", "Patient Information", "Examiner Information" ];
sectionItems = [ [allAlbums[0].doctor], [allAlbums[0].patient], [allAlbums[0].markscheme]]
let text = allAlbums[0].markscheme
markschemeRows = text.components(separatedBy: " ")
sectionItems.append(contentsOf: markschemeRows)
// Autoresize rows
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 500
// Remove excess row seperators
tableView.tableFooterView = UIView()
tableView.separatorColor = UIColor.clear
titleText.text = title
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.expandedSectionHeaderNumber == section) {
// Header section
let header = self.sectionNames[section] as! String
// If markscheme, create the markscheme format
if (header == "Examiner Information")
{
print(self.markschemeRows.count)
return self.markschemeRows.count
}
else
{
let arrayOfItems = self.sectionItems[section] as! NSArray
print(arrayOfItems.count)
return arrayOfItems.count
}
} else {
return 0;
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if (self.sectionNames.count != 0) {
return self.sectionNames[section] as? String
}
return ""
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44.0;
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat{
return 0;
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
//recast your view as a UITableViewHeaderFooterView
let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.contentView.backgroundColor = UIColor.darkGray
header.textLabel?.textColor = UIColor.white
if let viewWithTag = self.view.viewWithTag(kHeaderSectionTag + section) {
viewWithTag.removeFromSuperview()
}
let headerFrame = self.view.frame.size
let theImageView = UIImageView(frame: CGRect(x: headerFrame.width - 32, y: 13, width: 18, height: 18));
theImageView.image = UIImage(named: "Chevron-Dn-Wht")
theImageView.tag = kHeaderSectionTag + section
header.addSubview(theImageView)
// make headers touchable
header.tag = section
let headerTapGesture = UITapGestureRecognizer()
headerTapGesture.addTarget(self, action: #selector(CaseViewController.sectionHeaderWasTouched(_:)))
header.addGestureRecognizer(headerTapGesture)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! CustomTableCell
let section = self.sectionItems[indexPath.section] as! NSArray
cell.textLabel?.textColor = UIColor.black
cell.textData?.text = section[indexPath.row] as! String
return cell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
// MARK: - Expand / Collapse Methods
#objc func sectionHeaderWasTouched(_ sender: UITapGestureRecognizer) {
let headerView = sender.view as! UITableViewHeaderFooterView
let section = headerView.tag
let eImageView = headerView.viewWithTag(kHeaderSectionTag + section) as? UIImageView
if (self.expandedSectionHeaderNumber == -1) {
self.expandedSectionHeaderNumber = section
tableViewExpandSection(section, imageView: eImageView!)
} else {
if (self.expandedSectionHeaderNumber == section) {
tableViewCollapeSection(section, imageView: eImageView!)
} else {
let cImageView = self.view.viewWithTag(kHeaderSectionTag + self.expandedSectionHeaderNumber) as? UIImageView
tableViewCollapeSection(self.expandedSectionHeaderNumber, imageView: cImageView!)
tableViewExpandSection(section, imageView: eImageView!)
}
}
}
func tableViewCollapeSection(_ section: Int, imageView: UIImageView) {
let sectionData = self.sectionItems[section] as! NSArray
self.expandedSectionHeaderNumber = -1;
if (sectionData.count == 0) {
return;
} else {
UIView.animate(withDuration: 0.4, animations: {
imageView.transform = CGAffineTransform(rotationAngle: (0.0 * CGFloat(Double.pi)) / 180.0)
})
var indexesPath = [IndexPath]()
for i in 0 ..< sectionData.count {
let index = IndexPath(row: i, section: section)
indexesPath.append(index)
}
self.tableView!.beginUpdates()
self.tableView!.deleteRows(at: indexesPath, with: UITableView.RowAnimation.fade)
self.tableView!.endUpdates()
}
}
func tableViewExpandSection(_ section: Int, imageView: UIImageView) {
let sectionData = self.sectionItems[section] as! NSArray
if (sectionData.count == 0) {
self.expandedSectionHeaderNumber = -1;
return;
} else {
UIView.animate(withDuration: 0.4, animations: {
imageView.transform = CGAffineTransform(rotationAngle: (180.0 * CGFloat(Double.pi)) / 180.0)
})
var indexesPath = [IndexPath]()
// Header section
let header = self.sectionNames[section] as! String
// If markscheme, create the markscheme format
if (header == "Examiner Information")
{
for i in 0 ..< markschemeRows.count {
let index = IndexPath(row: i, section: section)
indexesPath.append(index)
}
}
else
{
for i in 0 ..< sectionData.count {
let index = IndexPath(row: i, section: section)
indexesPath.append(index)
}
}
self.expandedSectionHeaderNumber = section
self.tableView!.beginUpdates()
self.tableView!.insertRows(at: indexesPath, with: UITableView.RowAnimation.fade)
self.tableView!.endUpdates()
}
}
}
The error is pretty clear.
In numberOfRows you return markschemeRows.count for section 2 which is the number of separated items in this line
markschemeRows = text.components(separatedBy: " ")
Then you must also get the item from markschemeRows rather than from section[indexPath.row] in cellForRow
var markschemeRows = [String]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! CustomTableCell
let section = self.sectionItems[indexPath.section] as! NSArray
cell.textLabel?.textColor = UIColor.black
if indexPath.section == 2 {
cell.textData?.text = markschemeRows[indexPath.row]
} else {
cell.textData?.text = section[indexPath.row] as! String
}
return cell
}
Your code is quite cumbersome. For example sectionNames and markschemeRows are clearly [String].Why do you declare the arrays as [Any]? This is Swift. Take care of the types. And don't use Foundation collection types like NSArray in Swift at all. Again take care of the types.
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"
}
}
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.
}
}