How to Load TableView Cells in Batches - ios

I am looking for a way to smoothen my tableview. Since I have cells that need to download images from the URL, loading tableview cells one by one is very slow and makes the tableview lag. Moreover, the tableview will reload every time when I go back to previous cells. So I hope I could load the cells in batches of 15 with animation while loading. Any ideas of how I can do this? Any help is appreciated.
PS: Is my download function appropriate? Is there any other faster or better download function?
My code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let post = posts[indexPath.row] // "post" are the firebase documents
if post.posttype == 1{
let cell = tableView.dequeueReusableCell(withIdentifier: TextPostCell.identifier, for: indexPath) as! TextPostCell
cell.titleText.text = post.title
cell.contentLable.text = post.content
cell.userPhoto.downloadImage(from: post.userphoto, placeHolder: UIImage(named: "notfound")!)
cell.username.text = post.sender
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdentifier: ImageTextPostCell.identifier, for: indexPath) as! ImageTextPostCell
cell.titleText.text = post.title
cell.photoImage.downloadImage(from: post.photo![0], placeHolder: UIImage(named: "notfound")!)
print("success")
cell.userphoto.downloadImage(from: post.userphoto, placeHolder: UIImage(named: "notfound")!)
cell.username.text = post.sender
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// tableview selected
}
extension UIImageView{
func downloadImage(from url: String?, placeHolder placeholder: UIImage) {
print("Download Started")
if let safeString = url{
if let safeURL = URL(string: safeString){
getData(from: safeURL) { data, response, error in
if error != nil{
print("Tony's Notes: Problem retrieving data")
print(error!)
self.image = placeholder
}
// always update the UI from the main thread
DispatchQueue.main.async() { [weak self] in
if let safeData = data{
self?.image = UIImage(data: safeData)
return
}
}
}
}else{
self.image = placeholder
}
}else{
self.image = placeholder
}
}
func getData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
}

You may be looking for preloading: prefetch rows, and precancel rows.
https://developer.apple.com/documentation/uikit/uitableviewdatasourceprefetching

Related

How to display JSON image on custom cell of tableview in Swift?

I'm new into coding, learning how to parse JSON image into table view
able to display the labels but not able to display the image file. How to display it? I used the code given below please check it.
import UIKit
class ViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
var dataArray = [[String:AnyObject]]()
#IBOutlet weak var myTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://jsonplaceholder.typicode.com/photos")! //change the url
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "GET" //set http method as POST
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String:Any]] {
self.dataArray = json as [[String : AnyObject]]
DispatchQueue.main.async {
self.myTable.reloadData()
}
print(json)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 250
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 250
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id") as! ModTableViewCell
cell.labout.text = String(describing: dataArray[indexPath.row]["id"]!)
cell.imagethum.image = UIImage(named :dataArray[indexPath.row]["thumbnailUrl"]! as! String)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "story") as? FinalViewController
var selectedindex = indexPath.row
vc?.jarray = dataArray
vc?.selectedindex1 = selectedindex
self.navigationController?.pushViewController(vc!, animated: true)
}
}
You need to download your image at first.
The basic solution is:
if let url = URL(string: "YOUR_URL") {
if let data = try? Data(contentsOf: url) {
cell.imagethum.image = UIImage(data: data)
}
}
For more advanced solution take a look on SDWebImage framework ( for example ) - it's beginner-friendly.
You need to download the image using thumbnailUrl String that you're getting from JSON response.
Replace the implementation of cellForRowAt method with the following code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id") as! ModTableViewCell
cell.labout.text = String(describing: dataArray[indexPath.row]["id"] ?? "")
if let urlString = dataArray[indexPath.row]["thumbnailUrl"] as? String, let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
if let data = data {
cell.imagethum.image = UIImage(data: data)
}
}.resume()
}
return cell
}
Also, don't use forced unwrapping (!) so extensively. It might result in crashing the app unnecessarily.
You can try this
We use this SDWebImage pod to load images from URL. This pod provides an async image downloader with cache support.
Example Of SDWebImage as below
let img1 = savedInspofflineData[indexPath.row].image1
if img1 == ""{
//Error
}else{
let imageUrl = URL(string: img1!)!
cell.img1.sd_setImage(with: imageUrl, placeholderImage: UIImage(named: "logo_grey"), options: .refreshCached, completed: nil)
}

On cell click display image of particular logged in id

I need to print image in my next view controller after comparing ID of a table containing user details after comparing the ID I am successfully getting the name but the respective image is unable to fetch
if the user has posted anything then I am getting name for particular posted job now what I want is image of from respective user (that image which user uploaded while registration), (which identifies the posted job is posted via which user).
Below is my code:
func getJOBData()
{
let jobUrl = URL(string: "http://172.16.1.22/Get-Job-API/get-jobs/")
URLSession.shared.dataTask(with: jobUrl!) { (data, response, error) in
do
{
if error == nil
{
self.jobArray = try JSONDecoder().decode([JobData].self, from: data!)
for mainJobArr in self.jobArray
{
DispatchQueue.main.async {
self.jobPostTblView.reloadData()
}
}
print("Job Data****\(self.jobArray)")
}
}
catch
{
// print("my data=\(self.mainCellData)")
print("Error in get JSON Data\(error)")
}
}.resume()
}
numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jobFilteredArray.count
}
cellForRowAtIndexPath Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("JobTableViewCell", owner: self, options: nil)?.first as! JobTableViewCell
let data = jobArray[indexPath.row]
cell.jobTitle.text = data.job_desig
cell.expDetails.text = data.job_exp_to
cell.locationDetails.text = data.job_location
cell.dateDetails.text = data.job_skills
cell.companyName.text = companyArray.first { $0.company_id == data.company_id }?.company_name
return cell
}
didSelectRowAtIndexPath
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath)!
selectedCell.contentView.backgroundColor = UIColor.white
let rows = indexPath.row
print("Rows=\(rows)")
let jobDetail = WorkerJobDetailsViewController(nibName: "WorkerJobDetailsViewController", bundle: nil)
let jdata = jobFilteredArray[indexPath.row]
jobDetail.gender = jobArray[indexPath.row].job_emp_gender
jobDetail.location = jobArray[indexPath.row].job_location
jobDetail.companyName = (companyArray.first { $0.company_id == jdata.company_id }?.company_name)!
jobDetail.profile = jobImgPath
jobImgPath = (companyArray.first { $0.company_id == jdata.company_id }?.company_logo)!
jobDetail.skills = jobArray[indexPath.row].job_skills
jobDetail.descriptionValue = jobArray[indexPath.row].job_desc
jobDetail.jobDesignation = jobArray[indexPath.row].job_desig
self.present(jobDetail, animated: true, completion: nil)
}
Can anyone please help me to fetch images for respective user of posted job??
just use a pod like SDWebImage and fetch the link with the url that you are mapping the problem that you will face is that the link is a local ip and it will not work from a remote network but if the link changes at the json in the future you will be fine
Without pod you can do this
func getDataFromUrl(url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, response, error)
}.resume()
}
func downloadImage(url: URL) {
print("Download Started")
getDataFromUrl(url: url) { data, response, error in
guard let data = data, error == nil else { return }
print(response?.suggestedFilename ?? url.lastPathComponent)
print("Download Finished")
DispatchQueue.main.async() {
self.imageView.image = UIImage(data: data)
}
}
}

TableView assigning image to cell in TVC

Having a problem with this code. Basically i'm trying to populate a table cell using an image im pulling from twitter. The url field here has the value http://pbs.twimg.com/profile_images/796924570150301696/35nSG5nN_normal.jpg but for some reason the print("REACHED") is never printed. Any help/suggestions appreciated!
code snippet:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: tIdentifier, for: indexPath) as! TweetCell
let tweet = tweets[indexPath.section][indexPath.row]
let url = tweet.user.profileImageURL!
print(url.absoluteString)
let data = try? Data(contentsOf: url)
if (data == nil){
} else {
print("REACHED")
cell.avatarImage = UIImage(data: data!)
}
cell.tweet = tweets[indexPath.section][indexPath.row]
return cell
}
This worked for me:
func example() {
let cell = UITableViewCell()
let url = URL(string: "http://pbs.twimg.com/profile_images/796924570150301696/35nSG5nN_normal.jpg")
do {
let data = try Data(contentsOf: url!)
print("REACHED")
cell.imageView?.image = UIImage(data: data)
} catch {
print("received this error:\n\(error.localizedDescription)")
}
}
If it doesn't work right away, at least you'll have an error message to help you figure it out. Good luck!
Edit:
You should make sure you have updated your Info.plist to include an entry for:
App Transport Security Settings
Without this you will not have access to other sites.
Transport security has blocked a cleartext HTTP
Some tips for an easy lifeā€¦
Don't force unwrap
Don't download on the main queue
Don't expose your cell's IBOutlets
let imageQueue = DispatchQueue(label: "imageQueue", qos: DispatchQoS.background)
class TweetCell: UITableViewCell {
#IBOutlet fileprivate var avatarImage: UIImageView!
var tweet: Tweet {
didSet {
guard let url = tweet.user.profileImageURL else { return }
loadImage(url: url)
}
}
fileprivate func loadImage(url: URL) {
imageQueue.async {
do {
let data = try Data(contentsOf: url)
DispatchQueue.main.async {
self.avatarImage.image = UIImage(data: data)
}
} catch {
// Handle error
}
}
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: tIdentifier, for: indexPath) as! TweetCell
cell.tweet = tweets[indexPath.section][indexPath.row]
return cell
}

passing NSArray into tableview swift 3.0

I'm trying to connect my swift ios app to mysql with php... and the upon receiving the JSON from the php.. i converted it into nsarray and tried to populate my tableview with it.. however it doesnt seem to show anything in the tableview when i run it.... the data is successful in passing into the NSArray as i see my result when i print(values).. it just cant seem to show up on my tableview and i dont know why
#IBOutlet weak var tableView: UITableView!
var values:NSArray = []
#IBAction func php(_ sender: Any) {
let url = NSURL(string: "http://localhost/try.php")
let data = NSData(contentsOf: url! as URL)
values = try! JSONSerialization.jsonObject(with: data! as Data, options:JSONSerialization.ReadingOptions.mutableContainers) as! NSArray
print (values)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
cell.descriptionView.text = (values[indexPath.row] as AnyObject) as? String
return cell
}
That's the recommended way to load data over the network.
As mentioned in the comment do not use NSArray, NSData and NSURL in Swift 3. URLSession loads the data asynchronously and in the background. The table view is reloaded on the main thread.
var values = [[String:String]]()
#IBAction func php(_ sender: AnyObject) {
let url = URL(string: "http://localhost/try.php")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
do {
self.values = try JSONSerialization.jsonObject(with: data!, options:[]) as! [[String:String]]
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
task.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
let item = values[indexPath.row]
cell.descriptionView.text = item["title"]
return cell
}

Swift UITableView didSelectRowAtIndexPath in multipleSection and multiple prototype cells

I have a question about UITableView. I want to build a table with the first row is static cell where I use UIWebView to play video. The other rows below will be a list of videos. When I click on the video in the list, I want to play it at the first row.
Can anyone help me to set up the function didSelectRowAtIndexPath for this situation? Thank you very much.
Here is my code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell2 = tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! staticCell
let htmlString = "<video width='100%' webkit-playsinline controls autoplay ><source src= 'http://203.162.121.235:1935/live/tranhieuapt.rqv3-1d9w-hx44-5y3m/playlist.m3u8'></video>"
cell2.videoView.loadHTMLString(htmlString, baseURL: nil)
return cell2
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCellWithIdentifier("dynamicCell", forIndexPath: indexPath) as! dynamicCell
// Get data function
func getDataFromUrl(url:NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) {
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
completion(data: data, response: response, error: error)
}.resume()
}
// Download Image Function
func downloadImage(url: NSURL){
getDataFromUrl(url) { (data, response, error) in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
guard let data = data where error == nil else { return }
cell.thumnail.image = UIImage(data:data)
}
}
}
let vid = videoArray[indexPath.row]
cell.titleLb.text = vid.vidTitle
cell.viewLb.text = vid.vidView
cell.dateLb.text = vid.vidDate
let encodedUrl = vid.vidThumb.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
if let checkedUrl = NSURL(string: encodedUrl!) {
cell.thumnail.contentMode = .ScaleAspectFit
downloadImage(checkedUrl)
}
return cell
}
let cell = UITableViewCell()
return cell
}
I think need to restructure your architecture.
You need to have a static view at the top (that has the player) and a tableView below it and on didSelectRowAtIndexPath just update the player.
You can try to create custom view for tableView header:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = YourWebView()
return headerView
}
Thus you`ll be able to scroll everything

Resources