Trying to implement GCD and concurrency on a table view - ios

Currently using Alamofire synchronously within cellForRowAtIndexPath that queries a JSON array from Heroku, and within a for loop, creates a struct from each JSON object within the JSON array with image and text properties and then appends each struct in an array property within the table view controller. Not surprising that this is really slow. On app launch, the initial VC is a container VC that either shows a navigation controller or page VC based on if the user is "logged in." The initial VC in the page VC is a container VC that holds the table VC in question.
I'm totally new to GCD and the concept of concurrency. Was wondering how I can populate my array that serves as the foundational data for each of the table view cells.
Here's my current code - changing some variable names because I signed an NDA for this project:
import UIKit
import Alamofire
import Alamofire_Synchronous
final class PopularPearsTableViewController: UITableViewController {
let screenSize: CGRect = UIScreen.main.bounds
var pears: [Pear] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(PopularPearTableViewCell.self, forCellReuseIdentifier: "popularPear")
tableView.rowHeight = (screenSize.height) * 0.3
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: - Table View Data Source
extension PopularShopsTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// just a stub, will be replaced with dynamic code later on
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print(#function)
let cell = tableView.dequeueReusableCell(withIdentifier: "popularPear", for: indexPath) as! PopularPearTableViewCell
let userDefaults = UserDefaults.standard
guard let pearUUID = userDefaults.string(forKey: "pearUUID"),
let pearToken = userDefaults.string(forKey: "pearToken")
else {
return cell
}
if indexPath.row == 0 {
let header = createAuthenticatedHeader(user: pearUUID, password: pearToken)
let pearResponse = Alamofire.request("url", headers: header).responseJSON()
if let pearsFromResponse = (pearResponse.result.value! as! JSON)["data"] as? [JSON] {
for pear in pearsFromResponse {
let name = pear["name"] as! String
let pictureURL = pear["picture_url"] as! String
let imageURL = URL(string: pictureURL)
let imageData = NSData(contentsOf: imageURL!)
let image = UIImage(data: imageData as! Data)!
let newPear = Pear(name: name, image: image)
self.pears.append(newPear)
}
}
}
cell.giveCell(pearImage: pears[indexPath.row].image, pearName: pears[indexPath.row].name)
return cell
}
}

Related

Swift: How to Load JSON in different Table View

Hi everybody I'm new to Swift and I need help.
So I have three different JSON that will show in different moments, The first JSON has been loading perfectly, but when I clicking on the item to reload another JSON and show the detail nothing appears.
I'm confused about details:
Need I have three differents table Views for each JSON? or the only one is enough?
When I working with data (JSON) need I use a specific function to prepare the new JSON that will appear as "prepare"?
In my project I have:
Two view controllers: ViewController(default) and DetailViewController.
In my Main.Storyboard: Tab Bar Controller --> Navigation --> Table View
The code of the first View controller:
import UIKit
class ViewController: UITableViewController {
var categories = [Category]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let urlString = "https://www.themealdb.com/api/json/v1/1/categories.php"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
} else {
print("error connecting")
}
}
}
func parse(json: Data) {
let decoder = JSONDecoder()
print("parse called")
do {
let jsonCategories = try decoder.decode(Categories.self, from: json)
categories = jsonCategories.categories
tableView.reloadData()
} catch {
print("error parsin: \(error)")
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let category = categories[indexPath.row]
cell.textLabel?.text = category.idCategory
cell.detailTextLabel?.text = category.strCategoryDescription
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let newViewController = DetailViewController()
self.navigationController?.pushViewController(newViewController, animated: true)
}
}
The code of the second view controller:
import UIKit
import WebKit
class DetailViewController: UIViewController {
var webView: WKWebView!
var meals = [Meal]()
override func loadView() {
webView = WKWebView()
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://www.themealdb.com/api/json/v1/1/filter.php?c=Beef"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
} else {
print("error connecting")
}
}
}
func parse(json: Data) {
let decoder = JSONDecoder()
print("parse called")
do {
let jsonMeals = try decoder.decode(Meals.self, from: json)
meals = jsonMeals.meals
print(String(format:"read %d meals", meals.count))
} catch {
print("error parsin: \(error)")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return meals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let meal = meals[indexPath.row]
cell.textLabel?.text = meal.idMeal
cell.detailTextLabel?.text = meal.strMeal
return cell
}
}
The function parse(json: Data) on DetailViewController does not call tableView.reloadData() so the new data cannot be loaded to UITableView
Need I have three differents table Views for each JSON? or the only one is enough?
If you're loading different JSON files but want to show in the same view. You only need 1 UITableViewCell.
When you load & decode JSON, consider moving it to background queue to avoid blocking main thread.

Error setting UILabel text in custom UITableViewCell

I am quite new to Swift programming, but I am having trouble setting UILabel text in my UITableView class for individual UITableViewCell instances.
I have created a custom subclass of UITableViewCell called PizzaTableViewCell and a custom UITableView class called PizzaListTableViewController. I am trying to populate the UITableView instance with data from an array, which is being populated from an API call to my node.js server.
I have included my UITableView subclass, custom UITablveViewCell class, the struct for the data, and a link to a screenshot of the Simulator loading what I have done. Any help is greatly appreciated!
I have verified that the data is being put in the array with no issues, as I can print the contents after the call to fetchInventory method. I have been able to set a single textLabel with
cell.textLabel?.text = pizzas[indexPath.row].name
along with an image in the array with:
cell.imageView?.image = pizzas[indexPath.row].image
but I have 2 more labels that I need in each cell which I cannot set. I have checked my IBOutlets and Storyboard identifiers, and they match the code.
class PizzaListTableViewController: UITableViewController {
var pizzas: [Pizza] = []
override func viewDidLoad() {
super.viewDidLoad()
//title you will see on the app screen at the top of the table view
navigationItem.title = "Drink Selection"
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
//tableView.estimatedRowHeight = 134
//tableView.rowHeight = UITableViewAutomaticDimension
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
print(self.pizzas)
//self.tableView.reloadData()
//print(self.pizzas)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
} //end of viewDidLoad
private func fetchInventory(completion: #escaping ([Pizza]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/inventory", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.compactMap { pizzaDict -> Pizza? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
//print(data)
//print("CHECK")
print("Printing each item: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
completion(inventory)
}
}
#IBAction func ordersButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "orders", sender: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//PRINTING ROWS 0 TWICE in console
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ROWS", pizzas.count)
return self.pizzas.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Pizza", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
//cell.pizzaImageView?.image = pizzas[indexPath.row].image
//THESE WORK BUT ARE A STATIC WAY OF SETTING THE CELLS
//CAN ONLY SET THE SELL WITH A SINGLE TEXT LABEL FROM THE DATA ARRAY
cell.imageView?.image = pizzas[indexPath.row].image
cell.textLabel?.text = pizzas[indexPath.row].name
//cell.textLabel?.text = pizzas[indexPath.row].description
//cell.textLabel?.text = "$\(pizzas[indexPath.row].amount)"
// cell.name?.text = pizzas[indexPath.row].name
// cell.imageView?.image = pizzas[indexPath.row].image
// cell.amount?.text = "$\(pizzas[indexPath.row].amount)"
// cell.miscellaneousText?.text = pizzas[indexPath.row].description
//print(cell.name?.text! as Any)
print(cell.imageView as Any)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
} //END OF
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pizzaSegue", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizzaSegue" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}
class PizzaListTableViewController: UITableViewController {
var pizzas: [Pizza] = []
override func viewDidLoad() {
super.viewDidLoad()
//title you will see on the app screen at the top of the table view
navigationItem.title = "Drink Selection"
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
//tableView.estimatedRowHeight = 134
//tableView.rowHeight = UITableViewAutomaticDimension
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
print(self.pizzas)
//self.tableView.reloadData()
//print(self.pizzas)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
} //end of viewDidLoad
private func fetchInventory(completion: #escaping ([Pizza]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/inventory", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.compactMap { pizzaDict -> Pizza? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
//print(data)
//print("CHECK")
print("Printing each item: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
completion(inventory)
}
}
#IBAction func ordersButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "orders", sender: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//PRINTING ROWS 0 TWICE in console
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ROWS", pizzas.count)
return self.pizzas.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Pizza", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
//cell.pizzaImageView?.image = pizzas[indexPath.row].image
//THESE WORK BUT ARE A STATIC WAY OF SETTING THE CELLS
//CAN ONLY SET THE SELL WITH A SINGLE TEXT LABEL FROM THE DATA ARRAY
cell.imageView?.image = pizzas[indexPath.row].image
cell.textLabel?.text = pizzas[indexPath.row].name
//cell.textLabel?.text = pizzas[indexPath.row].description
//cell.textLabel?.text = "$\(pizzas[indexPath.row].amount)"
// cell.name?.text = pizzas[indexPath.row].name
// cell.imageView?.image = pizzas[indexPath.row].image
// cell.amount?.text = "$\(pizzas[indexPath.row].amount)"
// cell.miscellaneousText?.text = pizzas[indexPath.row].description
//print(cell.name?.text! as Any)
print(cell.imageView as Any)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
} //END OF
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pizzaSegue", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizzaSegue" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}
struct Pizza {
let id: String
let name: String
let description: String
let amount: Float
//let amount: String
let image: UIImage
init(data: [String: Any]) {
//print("CHECK:: pizza.swift")
self.id = data["id"] as! String
self.name = data["name"] as! String
// self.amount = data["amount"] as! Float
self.amount = ((data["amount"] as? NSNumber)?.floatValue)!
self.description = data["description"] as! String
self.image = data["image"] as! UIImage
}
}
As noted above, I have been able to print the contents of the data array with beer names, pictures, descriptions and etc. I have tried to print to console
print(cell.name?.text)
after setting
cell.name?.text = pizzas[indexPath.row].name
but it prints nil and this is a problem. I have been stuck with this for about 2 weeks!
IBOutlets screenshot:
I think i found your Problem, let me explain
What you are doing here is you have a custom UITableViewCell defined in the Storyboard in a Controller named "Root View Controller" which is not your PizzaListTableViewController to put it simply
And as you said you have absolutely no issue regarding the IBOutlets
Now when you say
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
In Your PizzaListTableViewController you are not linking it with the UI of the cell rather just the Code (This is only used when there is no xib of the cell)
Now what you can do to solve this
Solution # 1
Move/Copy your UI of the PizzaTableViewCell to PizzaListTableViewController in the storyboard from your "Root View Controller"
Make sure you add a Reuse Identifier in the Attribute Inspector of the cell in the storyboard
remove tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza") this wont give you an error this time as it will automatically get register
Make sure all the IBOutlets are connected
Solution # 2
create a separate Nib (xib) of the cell
and now you have to register the cell here like
tableView.register(UINib(nibName: "PizzaTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: "PizzaCell")
Hope this helps.
Try this
cell.name?.text = ...
cell.amount?.text = ...
cell.miscellaneousText?.text = ...
cell.pizzaImageView?.image = ...
If it still does not work then make sure your cell and your outlets are not null when setting its value. Hope it helps !
There is something definitely strange going on with your setup.
If you try to name the IBOutlets with the same name as the UITableViewCell default property it'll throw an error. The fact that you were able to set those names and build successfully is strange.
From the screenshot above you can see what happens when I attempted to do this.
Make sure your Table View Controller class is set in the storyboard.
Make sure your Table View Cell class is set in the storyboard.
Make sure that all your outlets are properly connected.
Make sure your Table View Cell Identifier is provided in the storyboard.
My Table View Controller Subclass
My Table View Cell Subclass
cell.imageView?.image and cell.textLabel?.text are optional properties of the table view itself. They are not the properties of the custom cell that you designed.
You use tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza") when you have designed a table view cell in XIB. But as you have designed the cell in the storyboard itself you should set the cell reuse identifier and cell class in the storyboard.
I hope this will help you out.

Issue with passing proper image to tableviewcell

This is my struct...
struct ProductImage {
let id : String
let url : URL
let isDefault : Bool
}
struct Product {
let name : String
let id : String
var images = [ProductImage]()
init(name : String, id: String) {
self.name = name
self.id = id
}
mutating func add(image: ProductImage) {
images.append(image)
}
}
Now I have an image loaded on the collectionview and on the click of a button, I want to pass this image to a tableviewcell. The collectionview does have a couple of labels with name and id which is passed successfully...But how the image can be passed that I'm not able to figure out. Below is what happens so far on the click of the sell button...
func SellBtnTapped(_ sender: UIButton) {
let indexPath = collectionView?.indexPath(for: ((sender.superview?.superview) as! RecipeCollectionViewCell))
let myVC = storyboard?.instantiateViewController(withIdentifier: "productSellIdentifier") as! sellTableViewController
let productObject = productData1[(indexPath?.row)!]
if selectedItems == nil {
//selectedItems is an array which will hold all struct items.
selectedItems = [Product(name:productObject.name, id: productObject.id)]
} else {
selectedItems?.append(productObject)
}
myVC.arrProduct = selectedItems
navigationController?.pushViewController(myVC, animated: true)
}
This is how I'm assigning the images and other data in the tableviewcell. This is the code of cellForRow..(of the tableview from where the cells are loaded..)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: sellTableViewCell = tableView.dequeueReusableCell(withIdentifier: "sellProductIdentifier") as! sellTableViewCell
//cell.prdImgView?.image =.... by doing this, the images are displayed in the tableviewcell in the same order as they are displayed in the collectionview cells irresoective of which cell was clicked. i.e clicking on btn on 1st collection view item shows the image on that collection view item on the tableviewcell.And when I click on the btn on the 4th collectionview item the image shown on the tableview cell will be that of the 2nd collectionview item...
cell.prdImgView?.image = self.appDelegate.commonArrayForURLImages[indexPath.row]
let product = arrProduct?[indexPath.row]
cell.produvtNameLabel.text = product?.name
cell.rateTextField.text = product?.theRate
return cell
}
This is how the array(which is passed to the tableview cell) gets the images...
var theProduct = Product(name: name, id: id, theRate: rate, quantity: qty, sku: skuCode, prdCateg: prodCat, prodDescr: description)
if let images1 = anItem["product_images"] as? [[String:String]] {
for image in images1 {
guard let imageId = image["id"],
let url1 = image["image"],
let isDefault = image["is_default"] else { continue }
let productImage = ProductImage(id: imageId, url: URL(string: url1)!, isDefault: isDefault == "1")
theProduct.add(image: productImage)
self.productData1.append(theProduct)
self.imgData.append(productImage)
let url = URL(string: url1)
if let data = try? Data(contentsOf: url!) {
let img = UIImage(data: data)
print(img!)
self.arrayOfURLImages.append(img!)
}
self.appDelegate.commonArrayForURLImages = self.arrayOfURLImages
}
}
Structs provide you with member wise initialiser, so in most cases you don't need one of your own.In your code your product initialiser is only holding name and id, and not array of productImage, You seem to be having a separate function for holding that data, which i think is not needed here.So what I did is just created a array type for [ProductImages] and sticked with default initialiser.
import Foundation
struct ProductImage {
let id : String?
let url : String? // Keep this string
let isDefault : Bool?
}
struct Product {
let name : String?
let id. : String?
var images : [ProductImage]?
}
ControllerClass(with collection view getting initial data)-:
In your controller class I created 2 arrays -:
1) That holds data for images .
2) That holds data for entire product information.
For saving data I am just passing constant values for now. In viewDidLoad I called initialiser for each object -:
1) Holding images object data.
2) ProductObject data .
3) Append both object to appropriate arrays.
import UIKit
class MyViewController: UIViewController {
#IBOutlet weak var mainCollectionView: UICollectionView!
// ARRAY OBJECT OF TYPE PRODUCT AND PRODUCT IMAGE
var imageData = [ProductImage]()
var productData = [Product]()
//viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
modelDataForCollectionView()
}
func modelDataForCollectionView(){
// GET IMAGE DATA
let imageObject = ProductImage(id: "1", url: "your url", isDefault: true)
imageData.append(imageObject)
// MODEL FOR PRODUCTS
let productObject = Product(name: "", id: "", images: imageData)
productData.append(productObject)
}
//didReceiveMemoryWarning
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MyViewController extending collection view
extension MyViewController :UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
//numberOfItemsInSection
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return productData.count
}
//dequeueReusableCell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionCell
cell.sendButton.addTarget(self, action: #selector(sendDataToTable), for: UIControlEvents.touchUpInside)
return cell
}
//numberOfSections
func numberOfSections(in collectionView: UICollectionView) -> Int{
return 1
}
// sizeForItemAt for each row
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
return CGSize(width: view.frame.width, height: 200)
}
func sendDataToTable(sender:UIButton){
let index = mainCollectionView.indexPath(for: sender.superview?.superview as! CollectionCell)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let Controller = storyboard.instantiateViewController(withIdentifier: "tableData") as! ViewController1
Controller.dataForTableView = productData[(index?.row)!].images
self.navigationController?.pushViewController(Controller, animated: true)
}
}
Now when you tap on a button in UICollectionViewCell , get the tapped index , and read product object present at that index from Product array.After that you can easily pass required data to table view(Second class).
Second controller class-:
import UIKit
class ViewController1: UIViewController {
// ARRAY TO HOLD IMAGE DATA FOR TAPPED COLLECTION CELL
var dataForTableView:[ProductImage]?
var name : String?
var id : String?
#IBOutlet weak var secondTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// CHECK FOR DATA
print(dataForTableView?[0].url as Any) // Optional("your url")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController1 : UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1") as! testingCell2
return cell
}
// Number of sections in table
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}// Default is 1 if not implemented
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat{
return 50
}
}
Once you get image URL and any other required information in second class, you can present that on table easily. To get images make api call to server. I hope that helps you.
Code for parsing-:
var imageUrl:String?
var imageId:String?
var isDefaults:String?
var productId:String?
var productIdTitle:String?
var productIdImageWithPath:String?
//MARK : Call Back Delegate Methods
func apiSuccessResponse(_ response: Dictionary<String, AnyObject>) {
print(response)
if let actualStyleData = response["Productdata"] as? [Dictionary<String, AnyObject>]{
for object in actualStyleData{
if let id = object["product_id"] as? String{
productId = id
}else{
productId = ""
}
if let title = object["product_name"] as? String{
productIdTitle = title
}
if let imageDetails = object["product_images"] as? [Dictionary<String, AnyObject>]{
for details in imageDetails{
if let id = details["id"] as? String{
imageId = id
}
if let url = details["image"] as? String{
imageUrl = url
}
if let isDefault = details["is_default"] as? String{
isDefaults = isDefault
}
let saveImageObject = ProductImage(id: imageId, url: imageUrl, isDefault: isDefaults)
imageData.append(saveImageObject)
}
}
let saveProductObject = Product(name: productIdTitle, id: productId, images: imageData)
productData.append(saveProductObject)
}
}
}

Populate the data saved on core data on a tableView

The code of my first UIViewController looks like this and basically I want to save the data whatever I type on the UITextField and retrieve it and populate on a UITableView. code as below. (I think my issue is in the cellForRow method)
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var textField1: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func nxtbuttonpressed(_ sender: AnyObject) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
//Lets save data
let newUser = NSEntityDescription.insertNewObject(forEntityName: "Expenses", into: context)
newUser.setValue(textField1.text, forKey: "expenseName")
do {
try context.save ()
print("data saved successfully")
}catch{
print("There was an error")
}
performSegue(withIdentifier: "ShowNxtVC", sender: nil)
}
}
This is where I retrieve the data that I saved in the above VC (as the code bellow). However even though I can print all the data on the console, only the last data item I saved will populate to the UITableViewCell. How can i overcome this as to populate all the data that I've saved before instead the one that i saved last.
import UIKit
import CoreData
class TableViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var myArray : Array? = []
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "Expenses")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request)
// check data existance
if results.count>0 {
print(results.count)
for resultGot in results as! [NSManagedObject]{
if let expName = resultGot.value(forKey:"expenseName") as? String{
myArray = [expName]
print("my array is : \(myArray)")
}
}
}
}catch{
print("No Data to load")
}
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(myArray?.count)
return myArray!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell ()
cell.textLabel?.text = myArray? [indexPath.row] as! String?
return cell
}
}
first off all you have to change this:
myArray = [newValue] because that resets your array with just one value every time it finds a new expense in core data.
var array = [String]()
array = ["hello"]
array = ["joe"]
print(array)
// returns: ["joe"]
use:
myArray.append(newExpense)
and you get:
var array2 = [String]()
array2.append("hello")
array2.append("joe")
print(array2)
// returns: ["hello", "joe"]
After the FOR loop you add:
tableView.reloadData()
Now you should be up and running
There is another thing you should do:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExpCell", for: indexPath)
cell.textLabel?.text = myArray?[indexPath.row] as! String
return cell
}
In your Storyboard you need to put ExpCell as reuse identifier into the TableViewCell. The dequeReusableCell command loads only the cells you can see on your device and reuses those cells after you scrolled them out of sight. This way your app uses much less memory and will be faster.
update table view with data, after myArray = [expName] use
DispatchQueue.main.async { [unowned self] in
self.tableView.reloadData()
}
change the tableview cell for row at index path function that will solve your problem
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier identifier: String,
for indexPath: IndexPath)
if(!cell){
cell = tableView.register(tableViewCellClass,forCellReuseIdentifier identifier: String)
}
return cell
}

TableViewController does not appear for a few seconds after Transition

I have a tabbarcontroller with four tableviewcontrollers that are connected by navigation controllers. The tableviews are popualted by images and text download from the internet by a XMLParser. When the app loads, after the splash screen, the screen goes black for a few seconds, then the first table view appears. Tab clicks on the other tableviews also lag. How can I display something in place of a black screen or unresponsive interface while the tableview controller's data is downlaoded?
The code of one of the tableviews:
import UIKit
class TopicsTableViewController: UITableViewController, XMLParserDelegate {
var xmlParser : XMLParser!
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "http://sharontalon.com/feed")
xmlParser = XMLParser()
xmlParser.delegate = self
xmlParser.startParsingWithContentsOfURL(url!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: XMLParserDelegate method implementation
func parsingWasFinished() {
self.tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return xmlParser.arrParsedData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("idCell", forIndexPath: indexPath)
let currentDictionary = xmlParser.arrParsedData[indexPath.row] as Dictionary<String, String>
let url = currentDictionary["enclosure"]
let data = NSData(contentsOfURL: url!.asNSURL) //make sure your image in this url does exist, otherwise unwrap in a if let check
let description = currentDictionary["description"]
cell.selectionStyle = UITableViewCellSelectionStyle.None
cell.textLabel?.text = currentDictionary["title"]
cell.detailTextLabel?.text = String(htmlEncodedString: description!)
cell.detailTextLabel?.numberOfLines = 3;
cell.textLabel?.numberOfLines = 2;
cell.imageView?.image = UIImage(data: data!)
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 80
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let dictionary = xmlParser.arrParsedData[indexPath.row] as Dictionary<String, String>
let tutorialLink = dictionary["link"]
let publishDate = dictionary["pubDate"]
let tutorialViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("idTutorialViewController") as! TutorialViewController
tutorialViewController.tutorialURL = NSURL(string: tutorialLink!)
tutorialViewController.publishDate = publishDate
showDetailViewController(tutorialViewController, sender: self)
}
This may be caused by a simple threading issue, give this a shot and if it doesn't work I'll try to help you further:
First move your resource heavy operation to a background thread:
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "http://sharontalon.com/feed")
xmlParser = XMLParser()
xmlParser.delegate = self
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {
self.xmlParser.startParsingWithContentsOfURL(url!)
})
}
Next, move any code that will update the user interface to the foreground thread:
func parsingWasFinished() {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
If this doesn't resolve your issue, let me know any I'll remove this answer and rethink your problem.
Reloading table data has to be on the main thread of the table to be renewed immediately.
func parsingWasFinished() {
self.tableView.reloadData()
}
Would be:
func parsingWasFinished() {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}

Resources