oops
oops

Reputation: 205

In swift - UITableview gets jerky while scrolling

Am make a tableview listing, data loading dynamically and a image contain in cell and some text. When it scroll gets jerking and irritating the users

Any idea for improving the table view performance?

class MainViewController: UIViewController,UITableViewDataSource, UITableViewDelegate {
    @IBOutlet weak var listTable: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.listTable.delegate = self
        self.listTable.dataSource = self
        self.listTable.separatorStyle = .None
        self.listTable.registerNib(UINib(nibName: "cellOne", bundle: nil), forCellReuseIdentifier: "cellOne")
        self.listTable.registerNib(UINib(nibName: "cellTwo", bundle: nil), forCellReuseIdentifier: "cellTwo")
        self.listTable.registerNib(UINib(nibName: "cellThree", bundle: nil), forCellReuseIdentifier: "cellThree")
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int{
        return 1
    }

    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        if pageIndex == 7 {
            return 120
        } else if indexPath.row == 0  {
            return 280
        } else if (indexPath.row == 5 || indexPath.row == 4) {
            return 200
        } else if (indexPath.row == 8 || indexPath.row == 11) {
            return 160
        }
        return 80
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
        return 50
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        let listArray = self.listDataArray[indexPath.row]

        if indexPath.row == 0 && pageIndex != 7 {
            var cell = tableView.dequeueReusabl eCellWithIdentifier("cellOne") as! cellOne!

            if cell == nil {
                tableView.registerClass(cellOne.classForCoder(), forCellReuseIdentifier: "cellOne")
                cell = cellOne(style: UITableViewCellStyle.Default, reuseIdentifier: "cellOne")
            }

            cell.headLbl.text = "\(listArray["title"]!)".html2String
            cell.addImageView.userInteractionEnabled = true
            let guster = UITapGestureRecognizer(target: self, action: "addTarget:")
            cell.addImageView.addGestureRecognizer(guster)
            cell.addImageView.tag = 1

            let qualityOfServiceClass = QOS_CLASS_BACKGROUND
            let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
            dispatch_async(backgroundQueue, {
                if listArray["image"] != nil && self.imagesLocalDictionary[indexPath.row] == nil {
                    let u = listArray["image"] as! String
                    let url = NSURL(string: u)
                    if url != nil {
                        let data = NSData(contentsOfURL: url!)

                        if data != nil {
                            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                                cell.imageViews.image = UIImage(data: data!)
                            })
                            self.imagesLocalDictionary.setObject(data!, forKey: indexPath.row)
                        }
                    }
                } else {
                    dispatch_async(dispatch_get_main_queue(), { () -> Void in
                        cell.imageViews.image = UIImage(data: self.imagesLocalDictionary[indexPath.row] as! NSData)

                    })
                }
            })


            if Constants.sharedInstance.addData["1"] != nil {
                cell.addImageView.image = UIImage(data: Constants.sharedInstance.addData["1"] as! NSData)
            }

            return cell

        } else if indexPath.row == 4 && pageIndex != 7 {
            var cell = tableView.dequeueReusableCellWithIdentifier("cellTwo") as! cellTwo!

            if cell == nil {
                tableView.registerClass(cellTwo.classForCoder(), forCellReuseIdentifier: "cellTwo")
                cell = cellTwo(style: UITableViewCellStyle.Default, reuseIdentifier: "cellTwo")
            }

            cell.headLbl.text = "\(listArray["title"]!)".html2String
            cell.addImageView.userInteractionEnabled = true
            let guster = UITapGestureRecognizer(target: self, action: "addTarget:")
            cell.addImageView.addGestureRecognizer(guster)
            cell.addImageView.tag = 2

            let qualityOfServiceClass = QOS_CLASS_BACKGROUND
            let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
            dispatch_async(backgroundQueue, {
                if listArray["image"] != nil && self.imagesLocalDictionary[indexPath.row] == nil {
                    let u = listArray["image"] as! String
                    let url = NSURL(string: u)
                    if url != nil {
                        let data = NSData(contentsOfURL: url!)

                        if data != nil {
                            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                                cell.imageViews.image = UIImage(data: data!)
                            })
                            self.imagesLocalDictionary.setObject(data!, forKey: indexPath.row)
                        }

                    }
                } else {
                    dispatch_async(dispatch_get_main_queue(), { () -> Void in
                        cell.imageViews.image = UIImage(data: self.imagesLocalDictionary[indexPath.row] as! NSData)
                    })
                }
            })

            if Constants.sharedInstance.addData["2"] != nil {
                cell.addImageView.image = UIImage(data: Constants.sharedInstance.addData["2"] as! NSData)
            }

            return cell

        } else if indexPath.row == 5 && pageIndex != 7 {
            var cell = tableView.dequeueReusableCellWithIdentifier("cellThree") as! cellThree!

            if cell == nil {
                tableView.registerClass(cellThree.classForCoder(), forCellReuseIdentifier: "cellThree")
                cell = cellThree(style: UITableViewCellStyle.Default, reuseIdentifier: "cellThree")
            }

            cell.headLbl.text = "\(listArray["title"]!)".html2String
            cell.addImageView.userInteractionEnabled = true
            let guster = UITapGestureRecognizer(target: self, action: "addTarget:")
            cell.addImageView.addGestureRecognizer(guster)
            cell.addImageView.tag = 3

            let qualityOfServiceClass = QOS_CLASS_BACKGROUND
            let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)

            dispatch_async(backgroundQueue, {
                if listArray["image"] != nil && self.imagesLocalDictionary[indexPath.row] == nil {
                    let u = listArray["image"] as! String
                    let url = NSURL(string: u)
                    if url != nil {
                        let data = NSData(contentsOfURL: url!)

                        if data != nil {
                            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                                cell.imageViews.image = UIImage(data: data!)
                            })
                            self.imagesLocalDictionary.setObject(data!, forKey: indexPath.row)
                        }
                    }
                } else {
                    dispatch_async(dispatch_get_main_queue(), { () -> Void in
                        cell.imageViews.image = UIImage(data: self.imagesLocalDictionary[indexPath.row] as! NSData)
                    })
                }
            })

            if Constants.sharedInstance.addData["3"] != nil {
                cell.addImageView.image = UIImage(data: Constants.sharedInstance.addData["3"] as! NSData)
            }

            return cell
        } else {
            cell.backgroundColor = UIColor.groupTableViewBackgroundColor()

            let container = UIView()
            container.frame = CGRectMake(2, 1, tableView.frame.width-4, 78)
            container.backgroundColor = UIColor.whiteColor()
            cell.addSubview(container)

            let headerLbl = UILabel()
            headerLbl.backgroundColor = UIColor.clearColor()

            headerLbl.frame = CGRectMake(120, 1, self.view.frame.width-130, 76)
            headerLbl.numberOfLines = 0
            headerLbl.font = UIFont(name: "TelegramHead", size: 18)

            headerLbl.text = "\(listArray["title"]!)".html2String
            headerLbl.lineBreakMode = NSLineBreakMode.ByCharWrapping

            let imageView = UIImageView()
            imageView.frame = CGRectMake(5, 5, 100, 68)
            imageView.image = UIImage(named: "place_holder.jpg")
            imageView.contentMode = UIViewContentMode.ScaleAspectFill
            imageView.clipsToBounds = true

            let blackLayerView = UIView();
            blackLayerView.frame = CGRectMake(0, 0, 0, 0);
            blackLayerView.backgroundColor = UIColor.blackColor();
            blackLayerView.alpha = 0.4;

            container.addSubview(imageView)
            container.addSubview(blackLayerView);
            container.addSubview(headerLbl)

            if pageIndex == 7 {
                container.frame = CGRectMake(2, 1, tableView.frame.width-4, 118)
                imageView.frame = CGRectMake(5, 5, container.frame.width-10, 108)
                blackLayerView.frame = imageView.frame;
                headerLbl.frame = imageView.frame//CGRectMake(10, 5, self.view.frame.width-20, 100)
                headerLbl.textColor = UIColor.whiteColor()
                headerLbl.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.2)

            } else if (indexPath.row == 8 || indexPath.row == 11) && pageIndex != 7 {
                let add = UIImageView()
                add.frame = CGRectMake(2, 82, tableView.frame.width-4, 76)
                //add.contentMode = UIViewContentMode.ScaleAspectFit;
                cell.addSubview(add)
                add.userInteractionEnabled = true
                let guster = UITapGestureRecognizer(target: self, action: "addTarget:")
                add.addGestureRecognizer(guster)

                if Constants.sharedInstance.addData["4"] != nil && indexPath.row == 8{
                    add.image = UIImage(data: Constants.sharedInstance.addData["4"] as! NSData)
                    add.tag = 4
                }

                if Constants.sharedInstance.addData["5"] != nil && indexPath.row == 11{
                    add.image = UIImage(data: Constants.sharedInstance.addData["5"] as! NSData)
                    add.tag = 5
                }
            }

            let qualityOfServiceClass = QOS_CLASS_BACKGROUND
            let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)

            dispatch_async(backgroundQueue, {
                if listArray["image"] != nil && self.imagesLocalDictionary[indexPath.row] == nil {
                    let u = listArray["image"] as! String
                    let url = NSURL(string: u)
                    if url != nil {
                        let data = NSData(contentsOfURL: url!)


                        if data != nil {
                            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                                imageView.image = UIImage(data: data!)
                                self.imagesLocalDictionary.setObject(data!, forKey: indexPath.row)
                            })
                        }
                    }

                } else {
                    dispatch_async(dispatch_get_main_queue(), { () -> Void in
                        imageView.image = UIImage(data: self.imagesLocalDictionary[indexPath.row] as! NSData)
                    })
                }
            })
        }
        return cell
    }
}

I do get the required functionality. But there is a very unnatural jerk on the TableView which results in a bad User experience.

Upvotes: 2

Views: 1854

Answers (1)

Scriptable
Scriptable

Reputation: 19750

There are a number of improvements you could make to this code to improve performance.

  1. Don't register cells in cellForRow
  2. Define specific custom classes for each cell and register them for reuse in viewDidLoad
  3. Reduce the logic used in cellForRowAt
  4. At the start of cellForRowAt you create a new instance of a cell and later add subviews to it if none of the other cases match. there is no reuse for this cell. Always reuse. you'll use less resources

I have built a complex table view before and created functions that configure and return reusble cells for the particular indexPath.

class LargeImageCell: UITableViewCell {
   var imageView: UIImageView()

   // in init, init the cell, add imageview as subview, setup constraints

   func setContent(imageURL: URL) {
      // load image from cache or fetch from network in background
   }
}

// in tableview/view controller
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
   switch indexPath.row {
     case 0:
        return self.getLargeImageCell(indexPath: indexPath)
     // all other cases
   }
}

This is just roughly how I implemented mine, just try to reduce the amount of logic in the cell and I've alwayss found it best to create custom cells rather than adding them in cellForRowAt, let the cell configure itself

func getLargeImageCell(indexPath: NSIndexPath) -> UITableViewCell {
    let data = self.data[indexPath.row] // model from db

    var cell: UITableViewCell
    if let c = self.tableView.dequeueReusableCellWithIdentifier("largeImageCell") as? LargeImageCell {
       cell = c
    } else {
       cell = LargeImageCell(style: UITableViewCellStyle.Default, reuseIdentifier: "largeImageCell")
    }
    cell.setContent(imageURL: data.imageURL)
    return cell
} 

Upvotes: 2

Related Questions