Reputation: 67
When I run the application I can see a blank table like the below screenshot loaded for certain milliseconds and then loading the table with actual data.As the items array is having 0 elements at the beginning, numberOfRowsInSection
returns 0 and the blank table view is loading. Is it like that? Please help me on this.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
I changed above code to the one below, but same issue exists and in debug mode I found out that the print("Item array is empty")
is executing twice, then the blank table view is displaying for a fraction of seconds, after that the actual API call is happening and data is correctly displayed in the tableview.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if items.isEmpty{
print("Item array is empty")
return 0
} else {
return self.items.count
}
}
import UIKit
class MainVC: UIViewController,UITableViewDelegate,UITableViewDataSource {
@IBOutlet weak var bookslideShow: UIView!
@IBOutlet weak var bookTableView: UITableView!
var items : [Items] = []
override func viewDidLoad() {
super.viewDidLoad()
bookTableView.dataSource = self
bookTableView.delegate = self
self.view.backgroundColor = UIColor.lightGray
bookTableView.rowHeight = 150
// self.view.backgroundColor = UIColor(patternImage: UIImage(named: "background.jpeg")!)
self.fetchBooks { data in
self.items.self = data
DispatchQueue.main.async {
self.bookTableView.reloadData()
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if items.isEmpty{
print("Item array is empty")
return 0
} else {
return self.items.count
//bookTableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BookCell",for:indexPath) as! BookCell
//cell.backgroundColor = UIColor(red: 180, green: 254, blue: 232, alpha: 1.00)
let info = items[indexPath.row].volumeInfo
cell.bookTitle.text = info.title
cell.bookCategory.text = info.categories?.joined(separator: ",")
cell.bookAuthor.text = info.authors?.joined(separator: ", ")
let imageString = (info.imageLinks?.thumbnail)!
if let data = try? Data(contentsOf: imageString) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
cell.bookImage.image = image
}
}
}
return cell
}
func fetchBooks(comp : @escaping ([Items])->()){
let urlString = "https://www.googleapis.com/books/v1/volumes?q=quilting"
let url = URL(string: urlString)
guard url != nil else {
return
}
let session = URLSession.shared
let dataTask = session.dataTask(with: url!) { [self] (data, response, error) in
//check for errors
if error == nil && data != nil{
//parse json
do {
let result = try JSONDecoder().decode(Book.self, from: data!)
comp(result.items)
}
catch {
print("Error in json parcing\(error)")
}
}
}
//make api call
dataTask.resume()
}
}
Upvotes: 0
Views: 837
Reputation: 1
After making sure that you have the reloadData() called, make sure your constraints for labels/images are correct. This makes sure that you're labels/images can be seen within the cell.
Upvotes: 0
Reputation: 2078
Normally I will do data task as below code show, please see the comments in code.
// show a spinner to users when data is loading
self.showSpinner()
DispatchQueue.global().async { [weak self] in
// Put your heavy lifting task here,
// get data from some completion handler or whatever
loadData()
// After data is fetched OK, push back to main queue for UI update
DispatchQueue.main.async {
self?.tableView.reloadData()
// remove spinner when data loading is complete
self?.removeSpinner()
}
}
Upvotes: 0
Reputation: 758
You are crossing the network for the data. That can take a long time especially if the connection is slow. An empty tableview isn't necessarily bad if you are waiting on the network as long as the user understands what's going on. Couple of solutions,
2)Start fetching it early even before the segue. In your code you need it for the table view but you're waiting all the way until view did load. This is pretty late in the lifecycle.
3)If you have to have the user wait on a resource let them know you're loading. Table View has a refresh control that you can call while you are waiting on the network or use a progress indicator or spinner. You can even hide your whole view and present a view so the user knows what's going on.
Also tableview is calling the datasource when it loads automatically and you're calling it when you say reloadData() in your code, that's why you get two calls.
So to answer your question this can be accomplished any number of ways, you could create a protocol or a local copy of the objects instance ie: MainVC in your presentingViewController then move your fetch code to there and set items on the local copy when the fetch comes back. And just add a didset to items variable to reload the tableview when the variable gets set. Or you could in theory at least perform the fetch block in the segue passing the MainVC items in the block.
For instance
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as MainVC
self.fetchBooks { data in
vc.items.self = data // not sure what the extra self is?
DispatchQueue.main.async {
vc.bookTableView.reloadData()
}
}
}
Since the closure captures a strong pointer you can do it this way.
Upvotes: 0
Reputation: 3750
The delegates methods may be called multiple times. If you want to remove those empty cells initially. You can add this in viewDidLoad:
bookTableView.tableFooterView = UIView()
Upvotes: 0