Reusable UITableViewCell duplicating on scroll in Swift - ios

Revisiting some old code I once did to start learning how code works when developing iOS applications so I apologize for the godlike viewController. The problem when running the app is that once a user begins to scroll the tableview, the labels are beginning to duplicate on top of each other and I'm pretty sure the webviews are most likely stacking as well. I've tried changing the code where I add the subviews in the cell by using the cell.contentview.addsubview() method but that doesn't change the stacking behavior when scrolling. I'm using the dequeueReusableCellWithIdentifier() method as well which I thought clears out cells all together once it goes off screen.
Any help here would be greatly appreciated as I'm still learning. Thank you in advance.
import UIKit
class VimeoFeedViewController: UIViewController, NSXMLParserDelegate, UITableViewDataSource, UIWebViewDelegate, UITableViewDelegate
{
#IBOutlet var tbData: UITableView?
var parser = NSXMLParser()
var posts = NSMutableArray()
var elements = NSMutableDictionary()
var element = NSString()
var title1 = NSMutableString()
var date = NSMutableString()
var link = NSMutableString()
var webView = UIWebView()
var boxView = UIView()
var selectedCell = NSIndexPath()
var valueToPass:String!
var viewToPass: UIView!
var customWebView = UIWebView()
var url = NSURL()
//custom code for webviews to show up
var postTitle: String = String()
var postLink: String = String()
var ename: String = String()
//end of custom code for webviews to show up
override func viewDidLoad() {
super.viewDidLoad()
self.beginParsing()
self.tbData?.backgroundColor = UIColor(patternImage: UIImage(named: "home-page-background.png")!)
webView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func beginParsing()
{
posts = []
parser = NSXMLParser(contentsOfURL:(NSURL(string:"https://vimeo.com/channels/1000464/videos/rss"))!)!
parser.delegate = self
parser.parse()
tbData!.reloadData()
}
//XMLParser Methods
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String])
{
element = elementName
if (elementName as NSString).isEqualToString("item")
{
elements = NSMutableDictionary()
elements = [:]
title1 = NSMutableString()
title1 = ""
date = NSMutableString()
date = ""
link = NSMutableString()
link = ""
postTitle = String()
postLink = String()
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
{
if (elementName as NSString).isEqualToString("item") {
if !title1.isEqual(nil) {
elements.setObject(title1, forKey: "title")
}
if !date.isEqual(nil) {
elements.setObject(date, forKey: "date")
}
if !link.isEqual(nil) {
elements.setObject(link, forKey: "link")
}
posts.addObject(elements)
}
}
func parser(parser: NSXMLParser, foundCharacters string: String)
{
if element.isEqualToString("title") {
title1.appendString(string)
}
if element.isEqualToString("pubDate") {
date.appendString(string)
}
if element.isEqualToString("link") {
link.appendString(string)
}
}
//Tableview Methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 100.0
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
let imageName = "broughtToYouAG.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: 0, y: 0, width: 420, height: 91)
headerView.addSubview(imageView)
headerView.backgroundColor = UIColor(patternImage: UIImage(named: "home-page-background.png")!)
return headerView
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return posts.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell")!
if(cell.isEqual(NSNull)) {
cell = NSBundle.mainBundle().loadNibNamed("Cell", owner: self, options: nil)![0] as! UITableViewCell;
}
//start of customization
let textLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 91))
let detailTextLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 20))
let customWebView:UIWebView = UIWebView(frame: CGRectMake(245, 5, 149, 80))
let url: NSURL? = NSURL(string: posts.objectAtIndex(indexPath.row).valueForKey("link") as! NSString as String)
let request: NSURLRequest = NSURLRequest(URL: url!)
customWebView.loadRequest(request)
customWebView.delegate = self
customWebView.scalesPageToFit = true
cell.contentView.addSubview(customWebView)
cell.contentView.addSubview(boxView)
//cell.addSubview(customWebView)
//cell.addSubview(boxView)
textLabelCustom.text = posts.objectAtIndex(indexPath.row).valueForKey("title") as! NSString as String
textLabelCustom.numberOfLines = 4
detailTextLabelCustom.numberOfLines = 2
textLabelCustom.textColor = UIColor.blackColor()
detailTextLabelCustom.font = UIFont(name: "AmericanTypewriter", size: 15)
textLabelCustom.font = UIFont(name: "HelveticaNeue", size: 18)
//cell.addSubview(textLabelCustom)
cell.contentView.addSubview(textLabelCustom)
//cell.addSubview(detailTextLabelCustom)
cell.contentView.addSubview(detailTextLabelCustom)
cell.backgroundColor = UIColor(patternImage: UIImage(named: "home-page-background.png")!)
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor(patternImage: UIImage(named: "tableselectedimage.png")!)
cell.selectedBackgroundView = backgroundView
return cell as UITableViewCell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You selected cell #\(indexPath.row)!")
let indexPath = tbData!.indexPathForSelectedRow;
let url: NSURL? = NSURL(string: posts.objectAtIndex(indexPath!.row).valueForKey("link") as! NSString as String)
print("Video url selected: \(url)!")
}
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
if (segue.identifier == "vimeoWebSegue") {
//get a reference to the destination view controller
let destinationVC: playingThatVideoViewController = segue.destinationViewController as! playingThatVideoViewController
let indexPath = tbData!.indexPathForSelectedRow;
let url: NSURL? = NSURL(string: posts.objectAtIndex(indexPath!.row).valueForKey("link") as! NSString as String)
let titlegoing = posts.objectAtIndex(indexPath!.row).valueForKey("title") as! NSString as String
//set properties on the destination view controller
destinationVC.videoWebView = customWebView
destinationVC.urlbrought = url!
destinationVC.titlebrought = titlegoing
}
}
}

You should create a custom UITableViewCell with desirable views (in your case 2 UILabels and UIWebView), load this cell from nib in cellForRowAtIndexPath and set needed values from arrays at indexPath.row appropriately. Don't create and add views in cellForRowAtIndexPath, just set values.

I'm using the dequeueReusableCellWithIdentifier() method as well which
I thought clears out cells all together once it goes off screen
It does not clear the cells.
It reuses cells that go off screen to display the cells that come on screen.
You should be removing the views before adding them again.
One way to accomplish this would be setting a tag for the view (view.tag = 10), and when dequeing a cell you cand do cell.contentView.viewWithTag(10)?.removeFromSuperview().

As the other answers suggest that you remove and re-add the views. Id rather suggest you add them once, and reuse the views. Subclass your cell and add the subviews once. Then use prepareForReuse to clear the text label.text = nil, for example.
For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state.

There are Two Possible Reason of this bug.
Reason 1
in your cellForRowAtIndexPath method
you are creating two labels on the same position which is overlapping one on other.
//Here is your code
//Check the x-axis and y-axis of both textLabelCustom and detailTextLabelCustom are same.
//It means both label will place on same axis but with different heights.
let textLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 91))
let detailTextLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 20))
let customWebView:UIWebView = UIWebView(frame: CGRectMake(245, 5, 149, 80))
Change it like this. And increase you cell size as well.
let textLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 90))
let detailTextLabelCustom = UILabel(frame: CGRectMake(20, 90, 200, 20))
let customWebView:UIWebView = UIWebView(frame: CGRectMake(245, 5, 149, 80))
Reason 2
Implement another delegate method didEndDisplayingCell and write below line
cell.contentView.removeAllSubviews()

In cellForRowAtIndexPath, since the cells for reused (hence the method name 'reusableCell'), you'll have to remove all the cell's subviews before you add more subviews to the cell.
for subview in cell.contentView.subviews {
subview.contentView.removeFromSuperview()
}

Related

Plus and minus buttons for a shopping app in swift

I've got a problem. I can't find a way to make these buttons work properly and I need some help.
I'm trying to make a app for a restaurant and I don't know how to make the plus and the minus buttons work. I extract data from Firebase (the name and the price for the product). I have a label for amount. Click plus amount increase, click minus amount decrease.
Here is the code from my viewController:
import UIKit
import Firebase
import FirebaseDatabase
class foodListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var plus: UIButton!
#IBOutlet weak var foodTableView: UITableView!
var ref:DatabaseReference?
var databaseHandle: DatabaseHandle?
var foodData = [food]()
var stats = [Buy]()
override func viewDidLoad() {
super.viewDidLoad()
foodTableView.delegate = self
foodTableView.dataSource = self
foodTableView.backgroundView = UIImageView(image: UIImage(named: "bg-general.png"))
foodTableView.allowsSelection = false
foodTableView.separatorStyle = .none
//Set the firebase reference
ref = Database.database().reference()
//Retrieve the data and listen for changes
ref?.child("inventory").child("food").observe(.childAdded, with: { (snapshot) in
/* if let dict = snapshot.value as? [String: AnyObject] {
let foods = food()
foods.setValuesForKeys(dict)
print(foods.FirstName!)
//self.foodTableView.reloadData()
}*/
print(snapshot)
if let dictionary = snapshot.value as? [String: AnyObject] {
let foods = food()
// foods.setValuesForKeys(dictionary)
foods.title = dictionary["title"] as? String
foods.amount = dictionary["amount"] as? String
foods.price = dictionary["price"] as? Double
foods.category = dictionary["category"] as? String
foods.id = dictionary["id"] as? String
self.foodData.append(foods)
DispatchQueue.main.async {
print("BlaBlaBla")
self.foodTableView.reloadData()
}
// print(foods.title!,foods.amount!,foods.price!,foods.category!,foods.id!)
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return foodData.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 140;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FoodCell", for: indexPath)
let food = foodData[indexPath.row]
// Food and price
let titleLabel = cell.viewWithTag(1) as! UILabel
let priceLabel = cell.viewWithTag(2) as! UILabel
let cantitateLabel = cell.viewWithTag(3) as! UILabel
//Labels text size
// titleLabel.font = UIFont(name: "Times New Roman-Bold", size: 30)
// priceLabel.font = UIFont(name: "Times New Roman-Bold", size: 17.0)
// cantitateLabel.font = UIFont(name: "Times New Roman-Bold", size: 17.0)
titleLabel.text = food.title
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
priceLabel.text = numberFormatter.string(from: food.price! as NSNumber)! + " $"
// Design for table
cell.backgroundColor = UIColor.clear
cell.contentView.backgroundColor = UIColor.clear
let whiteRoundedView : UIView = UIView(frame: CGRect(x: 0, y: 10, width: self.view.frame.size.width, height: 70))
whiteRoundedView.backgroundColor = UIColor.greenColor
whiteRoundedView.layer.masksToBounds = false
whiteRoundedView.layer.cornerRadius = 3.0
whiteRoundedView.layer.shadowOffset = CGSize(width: -1, height: 1)
whiteRoundedView.layer.shadowOpacity = 0.5
cell.contentView.addSubview(whiteRoundedView)
cell.contentView.sendSubview(toBack: whiteRoundedView)
//Plus - Minus
let pluss = cell.viewWithTag(4) as! UIButton
let minuss = cell.viewWithTag(5) as! UIButton
//pluss.addTarget(self, action: #selector(plusss(cantitate: amount)), for: .touchUpInside)
// minuss.addTarget(self, action: #selector(minusss(cantitate: amount)), for:. touchUpInside)
return cell
}
}
Also, pressing plus will add my product to another viewcontroller which is for my order list. Pressing minus will decrease the amount and, when the amount is 0, it will delete my product from order list.
Thanks in advance.
You need an IBAction for such buttons... I recommend you to have a prototype tableViewCell with the buttons and labels and handle the actions in the corresponding view controller.
This is how I have managed my two different button to act as stepper
My Array That stores the values which need to be updated
var meanValuesArray : [String] = ["","","0","0","0","1","1","",""]
Enum Function used to justify which task is to be Performed
//MARK: math Function Enum
/**
This Enum is used to Detect do Add Math Operation or Subtract action is to be Performed
*/
enum mathFunction {
/// When Addition is to done
case Add
/// When Subtraction is to be Done
case Subtract
}
Cell For Row Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.addPropertyTableView.dequeueReusableCell(withIdentifier: "APStepperSwitch", for: indexPath) as! addPropertyWithStepperSwitch
cell.staticImageView.image = self.leftImagesArray[indexPath.row]
cell.propertyDetailLabel.text = self.leftValueArray[indexPath.row]
cell.propertyStepperLabel.text = self.meanValuesArray[indexPath.row]
/// Adding Button Handlers
cell.propertyPlusButton.addTarget(self, action: #selector(AddingPropertyVC.plusButtonHandler(sender:)), for: UIControlEvents.touchUpInside)
cell.propertyMinusButton.addTarget(self, action: #selector(AddingPropertyVC.minusButtonHandler(sender:)), for: UIControlEvents.touchUpInside)
return cell
}
Button Actions
#objc func minusButtonHandler(sender: UIButton) {
/// Getting The Button Position Which is clicked
let buttonPosition : CGPoint = sender.convert(CGPoint.zero, to: self.addPropertyTableView)
/// Getting Index Path From Button Location
let indexPath : IndexPath = self.addPropertyTableView.indexPathForRow(at: buttonPosition)!
/// Extracting and Updating my current Order Value Either + or -
self.meanValuesArray[indexPath.row] = AFWrapperClass.compareStringValue(currentValue: self.meanValuesArray[indexPath.row], limit: 20, toDo: .Subtract)
/// Reloading Table
self.addPropertyTableView.reloadData()
}
#objc func plusButtonHandler(sender: UIButton) {
/// Getting The Button Position Which is clicked
let buttonPosition : CGPoint = sender.convert(CGPoint.zero, to: self.addPropertyTableView)
/// Getting Index Path From Button Location
let indexPath : IndexPath = self.addPropertyTableView.indexPathForRow(at: buttonPosition)!
/// Extracting and Updating my current Order Value Either + or -
self.meanValuesArray[indexPath.row] = AFWrapperClass.compareStringValue(currentValue: self.meanValuesArray[indexPath.row], limit: 20, toDo: .Add)
/// Reloading Table
self.addPropertyTableView.reloadData()
}
Main Stepper Function
class AFWrapperClass : NSObject {
//MARK: Fucntion used to comapre and update value
/**
This function is used to update stepper values
- parameter currentValue : Current Value in Array
- parameter limit : Maximum Value that can be used as stepper+1
- parameter toDo : tells need to perform Add or subtract
*/
class func compareStringValue(currentValue:String, limit:Int, toDo : mathFunction) -> String {
var current : Int = Int(currentValue)!
if (current <= limit) && (current >= 0) {
if toDo == .Add {
if current == limit {
return String(current)
}
else{
current += 1
return String(current)
}
}
else {
if current == 0 {
return String(current)
}
else {
current -= 1
return String(current)
}
}
}
else {
return String(current)
}
}
}

Swift, loading more data into table view causes scroll lags

I have a UITableView with data, that parsed from url.
UITableView will load more data, when scrolled right to the bottom (or got some more space to scroll, but close to the end - did both, same results)
When more data is loaded - i simple append it to array of my class, that contains data for TableView and then list scrolls back more than half of list (e.g. got 40 items, loading 10 more -> scrolling back to 20-25).
Calling TableView.reloadData() after append is complete.
Is there some mistakes in plan of doing it?
I can share code, but it's pretty common.
class TabAllTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, XMLParserDelegate {
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
#IBOutlet weak var BlogAllTableView: UITableView!
var loadMoreStatus = false
var maxPage = 1.0
var currentPageLoad = 1.0
var blogList: [DetailAndRenderBlogObject] = []
var eName: String = String()
var category = String()
var full_text = String()
var short_text = String()
var blog_title = String()
var avatar = String()
var full_name = String()
var url = String()
var created_at = String()
private let CATEGORY = ConfigData.CATEGORY_ALL
let cellIdentifier = "BlogTableViewCell"
override func viewDidLoad() {
super.viewDidLoad()
setupNavMenuButtons()
self.BlogAllTableView.insertSubview(refreshControl, at: 0)
self.BlogAllTableView.tableFooterView?.isHidden = true
downloadBlogData(1, true, true)
BlogAllTableView.delegate = self
BlogAllTableView.dataSource = self
}
var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action:
#selector(TabAllTableViewController.handleRefresh(_:)),
for: UIControlEvents.valueChanged)
refreshControl.tintColor = Colors.ColorLoadingIndicator
return refreshControl
}()
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
downloadBlogData(1, true, false)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let currentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
let deltaOffset = maximumOffset - currentOffset
if deltaOffset <= 0 && currentPageLoad < maxPage {
loadMore()
}
}
func loadMore() {
if ( !loadMoreStatus ) {
self.loadMoreStatus = true
self.activityIndicator.startAnimating()
self.BlogAllTableView.tableFooterView?.isHidden = false
downloadBlogData(currentPageLoad, false, false)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.sideMenuController?.isLeftViewEnabled = true
AppDelegate.tabBarReference.isHidden = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.navigationBar.topItem?.title = "all".localized()
}
private func setupNavMenuButtons() {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(handleMenuRefresh))
let image = UIImage(named:"menu_ham.png")
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.contentMode = .scaleAspectFit
imageView.isUserInteractionEnabled = true
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
imageView.image = image
let imageHeight = navigationController?.navigationBar.frame.size.height
let wrapperView = UIView(frame: CGRect(x: 0, y: 0, width: imageHeight!, height: imageHeight!))
wrapperView.addSubview(imageView)
imageView.center = CGPoint(x: imageView.frame.size.width / 2, y: wrapperView.frame.size.height / 2)
let tap = UITapGestureRecognizer(target: self, action: #selector(TabAllTableViewController.menuButtonClick))
wrapperView.addGestureRecognizer(tap)
let btnHamburgerMenu: UIBarButtonItem = UIBarButtonItem(customView: wrapperView)
navigationItem.setLeftBarButton(btnHamburgerMenu, animated: false)
}
#objc private func menuButtonClick()
{
self.sideMenuController?.showLeftViewAnimated()
}
#objc private func handleMenuRefresh() {
downloadBlogData(1, true, true)
}
func numberOfSections(in tableView: UITableView) -> Int {
return blogList.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if (section != 0) {
return 12
} else {
return CGFloat.leastNonzeroMagnitude
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.0)
return view
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(ConfigData.CELL_HEIGHT)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Configure the cell...
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BlogTableViewCell else {
fatalError("The dequeued cell is not an instance of BlogTableViewCell.")
}
cell.layer.cornerRadius = 10
let blogItem = blogList[indexPath.section]
cell.avatarBackground.contentMode = .scaleAspectFill
cell.avatarBackground.layer.cornerRadius = cell.avatarBackground.frame.size.width / 2;
cell.avatarBackground.clipsToBounds = true;
if let authorImgUrl = URL(string: blogItem.authorImg) {
cell.authorRoundImage.contentMode = .scaleAspectFill
cell.authorRoundImage.layer.cornerRadius = cell.authorRoundImage.frame.size.width / 2;
cell.authorRoundImage.clipsToBounds = true;
//Helper.downloadImage(url: authorImgUrl, imageview: cell.authorRoundImage)
cell.authorRoundImage.sd_setImage(with: authorImgUrl)
}
if let headerImageUrl = URL(string: blogItem.image) {
cell.bigImage.contentMode = .scaleToFill
//Helper.downloadImage(url: headerImageUrl, imageview: cell.bigImage)
cell.bigImage.sd_setImage(with: headerImageUrl)
}
cell.authorLabel.text = blogItem.author
cell.dateLabel.text = blogItem.date
cell.title.text = blogItem.title
cell.titleDescription.text = blogItem.shortDescription
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let secondViewController = self.storyboard!.instantiateViewController(withIdentifier: "ShowArticleViewController") as! ShowArticleViewController
secondViewController.blogItem = blogList[indexPath.section]
self.navigationController!.pushViewController(secondViewController, animated: true)
}
private func downloadBlogData(_ page : Double, _ refresh : Bool, _ showOverlay : Bool) {
// print(InAppProperties.sharedInstance.getDefaulLang())
if showOverlay {
LoadingOverlay.shared.showOverlay(view: self.view)
}
let req = NSMutableURLRequest(url: NSURL(string: Helper.getBlogUrl(CATEGORY, Int(page)))! as URL)
req.httpMethod = "GET"
req.httpBody = "key=\"value\"".data(using: String.Encoding.utf8) //This isn't for GET requests, but for POST requests so you would
URLSession.shared.dataTask(with: req as URLRequest) { data, response, error in
if error != nil {
//Your HTTP request failed.
print(error?.localizedDescription)
} else {
//Your HTTP request succeeded
if refresh {
self.currentPageLoad = 1
} else {
self.currentPageLoad += 1
}
let stringData = String(data: data!, encoding: String.Encoding.utf8)!
//print(stringData)
let xml = SWXMLHash.parse(stringData)
if page == 1 {
self.blogList = [DetailAndRenderBlogObject]()
}
for elem in xml["response"]["provider"].all {
let itemList = elem["item"]
for article in itemList.all {
let blogItem = DetailAndRenderBlogObject()
blogItem.articleUrl = article["url"].element!.text
blogItem.author = article["full_name"].element!.text
blogItem.authorImg = article["avatar"].element!.text
blogItem.text = article["full_text"].element!.text
blogItem.shortDescription = article["short_text"].element!.text
blogItem.title = article["title"].element!.text
blogItem.categoryUrl = article["category"].element!.text
blogItem.image = self.repairLink(article["thumbnail"].element!.text)
blogItem.date = self.formatDate(article["created_at"].element!.text)
if (blogItem.categoryUrl.lowercased().range(of:"video") == nil &&
blogItem.title.lowercased().range(of: "видео") == nil) {
self.blogList.append(blogItem)
}
}
if let totalItemsCount = xml["response"]["pagination"]["totalCount"].element?.text {
self.maxPage = (Double(totalItemsCount)! / 10).rounded(.up)
}
}
DispatchQueue.main.async {
self.BlogAllTableView.reloadData()
self.refreshControl.endRefreshing()
self.loadMoreStatus = false
self.activityIndicator.stopAnimating()
self.BlogAllTableView.tableFooterView?.isHidden = true
if showOverlay {
LoadingOverlay.shared.hideOverlayView()
}
if (page == 1) {
let indexPath = NSIndexPath(row: 0, section: 0)
self.BlogAllTableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
}
}.resume()
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
eName = elementName
if elementName == "item" {
category = String()
full_text = String()
short_text = String()
blog_title = String()
avatar = String()
full_name = String()
url = String()
created_at = String()
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "item" {
let blogListItem = DetailAndRenderBlogObject()
blogListItem.categoryUrl = category
blogListItem.text = full_text
blogListItem.shortDescription = short_text
blogListItem.title = blog_title
blogListItem.authorImg = avatar
blogListItem.author = full_name
blogListItem.articleUrl = url
blogListItem.date = created_at
print("WRITING DATA")
blogList.append(blogListItem)
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
//print("ENAME: " + eName)
switch eName {
case "category":
print("writing category: " + string)
category = string;
case "full_text":
full_text = string;
case "short_text":
short_text = string;
case "title":
title = string;
case "thumbnail":
avatar = string;
case "avatar":
avatar = string;
case "full_name":
full_name = string;
case "url":
url = string;
case "created_at":
created_at = string;
default:
()
}
}
func parserDidEndDocument(_ parser: XMLParser) {
}
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print(parseError)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func repairLink(_ link : String) -> String { //sometimes links looks like https://asdasd.comhttps://asdasd.com/...
let newLink = link.suffix(from: link.index(link.startIndex, offsetBy: 1))
if let range = newLink.range(of: "http") {
let substring = newLink[range.lowerBound...]
return String(substring)
}
return link
}
func formatDate(_ date : String) -> String { //date String "2018-01-22 08:59:43"
if let dividerIndex = date.index(of: " ") {
return String(date[..<dividerIndex])
}
return date
}
}
Reloading the entire table is quite an intensive operation. The better way might be to use the tableView.insertRows(at:with:) method. An example would be something like -
func didFinishLoadingData(newBlogs: [DetailAndRenderBlogObject]) {
tableView.beginUpdates()
var indexPaths: [IndexPath] = []
for row in (blogList.count..<(blogList.count + newBlogs.count)) {
indexPaths.append(IndexPath(row: row, section: 0))
}
blogList.append(contentsOf: newBlogs)
tableView.insertRows(at: indexPaths, with: .fade)
tableView.endUpdates()
}
I had the same issue and found another solution, after long time search.
It seems that whenever you call the endRefreshing() function of an UIRefreshControl that is attached to a UITableView scrollview, it stops the decelerating process of the scroll view.
So, if your code, that fetches the next page, is calling endRefreshing() function (with no reason at all) and the user started the scrolling and lifted his/her finger from the device's screen (scrollview is decelerating) this will have as a result to stop the scrolling.
The solution is simple, replace this line in your code:
self.refreshControl.endRefreshing()
with these lines:
if self.refreshControl.isRefreshing {
self.refreshControl.endRefreshing()
}
I even created a handy extension that does this check for me, to call it whenever I need it:
extension UIRefreshControl {
func endIfRefreshing() {
if isRefreshing { endRefreshing() }
}
}

swift table section header duplicating on scroll

I am admitting defeat with these custom headers in Swift. I have tried for days to prevent the labels and images inside of the section headers from duplicating. Basically on scroll the labels/images inside the headers duplicate and lay on top of each other.
Would someone please for the love of god explain to me why this is happening and how to fix it.
The circular image keeps creating images and laying them on top of the previous, the same with the name and date labels!!
Here is my ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var navigationBar: UINavigationItem!
#IBOutlet weak var tableView: UITableView!
var list = []
var audioPlayer:AVAudioPlayer!
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func getListBro() -> NSArray {
return list
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.dataSource = self;
tableView.delegate = self;
self.tableView.rowHeight = UITableViewAutomaticDimension
let streamURL = NSURL(string: "http://192.241.174.8:8000/beat-stream-all/")!
let stuff = GetBeatStream(url:streamURL)
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 50.0; //
stuff.downloadJSONFromURL {
(let jsonDictionary) in
if let jsonList = jsonDictionary["results"] as? NSArray {
self.list = jsonList
}
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.tableView.reloadData()
}
}
}
}//end view did load
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("streamCell", forIndexPath: indexPath) as! StreamTableViewCell
cell.beatCover.image = UIImage(named: "imgres")
if let id = list[indexPath.section] as? NSDictionary {
if let beatID = id["id"] as? NSInteger {
cell.setID(beatID)
}
}
if let beat_cover = list[indexPath.section] as? NSDictionary {
if let beat_cover_image = beat_cover["beat_cover"] as? String {
cell.beatCover.downloadImageFrom(link: beat_cover_image, contentMode: UIViewContentMode.ScaleToFill) //set your image from link array.
}
}
if let audio = list[indexPath.section] as? NSDictionary {
if let audio_url = audio["audio"] as? String {
cell.url = audio_url
cell.buildPlayer()
}
}
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return list.count
}
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let beatAuthorLabel = UILabel(frame: CGRectMake(55, 5, 200, 40))
//USER PIC
let profilePictureImageView = UIImageView(frame: CGRectMake(5, 5, 40, 40));
profilePictureImageView.layer.borderWidth = 0
profilePictureImageView.layer.masksToBounds = false
profilePictureImageView.layer.borderColor = UIColor.blackColor().CGColor
profilePictureImageView.layer.cornerRadius = profilePictureImageView.frame.height/2
profilePictureImageView.clipsToBounds = true
profilePictureImageView.layer.masksToBounds = true
profilePictureImageView.image = UIImage(named: "imgres") //set placeholder image first.
if let userPicSection = list[section] as? NSDictionary {
if let artist = userPicSection["artist"] as? NSDictionary {
if let profilePic = artist["profile_pic"] as? String {
profilePictureImageView.downloadImageFrom(link: profilePic, contentMode: UIViewContentMode.ScaleAspectFit)
}
}
}
if let nameSection = list[section] as? NSDictionary {
if let name = nameSection["artist"] as? NSDictionary {
if let adminName = name["admin_name"] as? NSString {
print(adminName)
beatAuthorLabel.text = adminName as String
beatAuthorLabel.font = UIFont(name: beatAuthorLabel.font.fontName, size: 14)
}
}
}
var dateLabel = UILabel(frame: CGRectMake(225, 5, 200, 40))
if let created = list[section] as? NSDictionary {
if let date = created["created_at"] as? String {
dateLabel.text = date as String
dateLabel.font = UIFont(name: dateLabel.font.fontName, size: 8)
}
}
let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.contentView.addSubview(beatAuthorLabel)
header.contentView.addSubview(dateLabel)
header.contentView.addSubview(dateLabel)
header.contentView.addSubview(profilePictureImageView)
header.contentView.backgroundColor = UIColor(red: 179/255, green: 194/255, blue: 191/255, alpha:1)
header.textLabel!.textColor = UIColor.whiteColor()
header.alpha = 1
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow
let currentCell = tableView.cellForRowAtIndexPath(indexPath!)! as! StreamTableViewCell
currentCell.player.play()
}
func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let leavingCell = cell as! StreamTableViewCell
leavingCell.player.pause()
}
}
Header views, like cells, are reused. So, when the table view sends you tableView(_:willDisplayHeaderView:), it might not be the first time you've received that message for that header view. Yet every time you receive the message, you add four subviews to it.
Don't implement tableView(_:willDisplayHeaderView:forSection:) at all.
Instead, make a subclass of UITableViewHeaderFooterView with properties for the different subviews. This is just like how you created a subclass of UITableViewCell named StreamTableViewCell. Maybe you could call your header subview class StreamSectionHeaderView. Then you have two options for setting up the header view's subviews.
Option 1: In StreamSectionHeaderView.initWithFrame(_:), create the subviews of the header view (and store them in the instance properties and add them as subviews). This is essentially what you're doing now in tableView(_:willDisplayHeaderView:forSection:), but you would move most of the code into the StreamSectionHeaderView class. Register the StreamSectionHeaderView class with the table view using UITableView.registerClass(_:forHeaderFooterViewReuseIdentifier:).
Option 2: Design the header view and its subviews in a XIB (you can't do it in a storyboard), connect the subviews to the StreamSectionHeaderView properties (which must be IBOutlets in this case), and register the XIB in the table view with UITableView.registerNib(_:forHeaderFooterViewReuseIdentifier:).
To produce section, implement tableView(_:viewForHeaderInSection:) by calling tableView.
dequeueReusableHeaderFooterViewWithIdentifier(_:) and then configuring the header view's subviews, which already exist by the time dequeueReusableHeaderFooterViewWithIdentifier(_:) returns.
UPDATE
Here's your StreamSectionHeaderView, assuming you want to set up its subviews in code:
class StreamSectionHeaderView: UITableViewHeaderFooterView {
// Make these IBOutlets if you design StreamSectionHeaderView in a XIB.
var beatAuthorLabel = UILabel(frame: CGRectMake(55, 5, 200, 40))
var profilePictureImageView = UIImageView(frame: CGRectMake(5, 5, 40, 40))
var dateLabel = UILabel(frame: CGRectMake(225, 5, 200, 40))
init(frame: CGRect) {
profilePictureImageView.layer.borderWidth = 0
profilePictureImageView.layer.masksToBounds = false
profilePictureImageView.layer.borderColor = UIColor.blackColor().CGColor
profilePictureImageView.layer.cornerRadius = profilePictureImageView.frame.height/2
profilePictureImageView.clipsToBounds = true
profilePictureImageView.layer.masksToBounds = true
profilePictureImageView.image = UIImage(named: "imgres")
beatAuthorLabel.font = UIFont(name: beatAuthorLabel.font.fontName, size: 14)
dateLabel.font = UIFont(name: dateLabel.font.fontName, size: 8)
contentView.addSubview(beatAuthorLabel)
contentView.addSubview(dateLabel)
contentView.addSubview(profilePictureImageView)
contentView.backgroundColor = UIColor(red: 179/255, green: 194/255, blue: 191/255, alpha:1)
textLabel!.textColor = UIColor.whiteColor()
alpha = 1
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Then, in your table view controller:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
// blah blah blah other stuff
tableView.registerClass(StreamSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: "Header")
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterViewWithIdentifier("Header") as! StreamSectionHeaderView
if let userPicSection = list[section] as? NSDictionary {
if let artist = userPicSection["artist"] as? NSDictionary {
if let profilePic = artist["profile_pic"] as? String {
header.profilePictureImageView.downloadImageFrom(link: profilePic, contentMode: UIViewContentMode.ScaleAspectFit)
}
}
}
if let nameSection = list[section] as? NSDictionary {
if let name = nameSection["artist"] as? NSDictionary {
if let adminName = name["admin_name"] as? NSString {
print(adminName)
header.beatAuthorLabel.text = adminName as String
}
}
}
if let created = list[section] as? NSDictionary {
if let date = created["created_at"] as? String {
header.dateLabel.text = date as String
}
}
return header
}
}

The text in the textview inside the table view cell is not formatted

I am trying to build a Table View in which each cell contains a UITextView with formatted text. I got everything together, and the the table view gets populated by text. The problem is that when I test it in the simulator, the text is not formatted. It gets formatted only after scrolling, when it gets refreshed.
Here I am pasting below the view controller, which calls for the tableview and for the text (from a SQLite database), and the class I use to insert the text in the UITextView and format it. I have tried to force the layout of the container, but it does not work.
The View Controller
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var myTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.myTable.estimatedRowHeight = 44.0
self.myTable.rowHeight = UITableViewAutomaticDimension
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 200
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("verseCell") as! VerseTextView
let row : Int = indexPath.row
var getVerse = databaseDB()
var verse = databaseVars()
(verse.book, verse.chapter, verse.verseText) = getVerse.getVerseBy(row, database: databasePath as String)
if verse.verseNumber == 1 {
chapterToBePassed = String(verse.chapter)
}
else {
chapterToBePassed = ""
}
cell.configure(verseChapter: chapterToBePassed, verseText: "\(verse.verseText)")
return cell
}
}
the VerseTextView class
class VerseTextView: UITableViewCell {
#IBOutlet weak var verse: UITextView!
func configure(#verseChapter: String?, verseText: String?) {
// Formats
var style = NSMutableParagraphStyle()
style.lineSpacing = 15
let font = UIFont(name: "Georgia", size: 18.0) ?? UIFont.systemFontOfSize(18.0)
let textFont = [NSFontAttributeName:font, NSParagraphStyleAttributeName : style]
let fontChapter = UIFont(name: "Georgia-Bold", size: 22.0) ?? UIFont.systemFontOfSize(22.0)
let chapterFont = [NSFontAttributeName:fontChapter]
let myText = NSMutableAttributedString()
var versettoId : String = ""
if verseChapter != ""{
let myTextChapter = "\n"+verseChapter!+" "
let capitolo = NSAttributedString(string: myTextChapter, attributes:chapterFont)
myText.appendAttributedString(capitolo)
}
let testoVerso = verseText!+" "
let verso = NSAttributedString(string: testoVerso, attributes:textFont)
myText.appendAttributedString(verso)
verse.attributedText = myText
// verse.layoutManager.ensureLayoutForTextContainer(verse.textContainer)
// verse.layoutIfNeeded()
}
}
Thanks,
S
try this ,
in cellForRowAtIndexpath
let cell = tableView.dequeueReusableCellWithIdentifier("verseCell") as? VerseTextView
if cell == nil {
VerseTextView(style: .Default, reuseIdentifier: "verseCell")
}
and implement VerseTextView(style: Style, reuseIdentifier: ReuseIdentifire)
in ur custom cell class

Dismiss UITableView Cell upon selection in Swift?

I have a UITableView Cell in a View Controller serving as a display for autocompletion for a text field. However, selecting one of the autocomplete suggestions does not dismiss the table view cell. I have tried "self.dismissViewControllerAnimated(True, completion: nil)" inside my didSelectRowAtIndexPath function for UITableView. Any thoughts?
class ViewController: UIViewController, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var RecName: UITextField!
#IBOutlet var Body: UITextField!
#IBOutlet var RecEmail: UITextField!
var emailArray = ""
var emailNSArray = [""]
var autocomplete = [String]()
var tycard2 = ""
var addressBook: ABAddressBookRef?
#IBOutlet var autocompleteTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//email
RecEmail.delegate = self
//autocomplete
autocompleteTableView!.delegate = self
autocompleteTableView!.dataSource = self
autocompleteTableView!.scrollEnabled = true
autocompleteTableView!.hidden = true
println(tycard2)
}
func createAddressBook(){
var error: Unmanaged<CFError>?
addressBook = ABAddressBookCreateWithOptions(nil, &error).takeRetainedValue()
}
func textToImage(drawText: NSString, inImage: UIImage, atPoint:CGPoint)->UIImage{
// Setup the font specific variables
var textColor: UIColor = UIColor.whiteColor()
var textFont: UIFont = UIFont(name: "Helvetica Bold", size: 20)!
//Setup the image context using the passed image.
UIGraphicsBeginImageContext(inImage.size)
//Setups up the font attributes that will be later used to dictate how the text should be drawn
let textFontAttributes = [
NSFontAttributeName: textFont,
NSForegroundColorAttributeName: textColor,
]
//Put the image into a rectangle as large as the original image.
inImage.drawInRect(CGRectMake(0, 0, inImage.size.width, inImage.size.height))
// Creating a point within the space that is as bit as the image.
var rect: CGRect = CGRectMake(atPoint.x, atPoint.y, inImage.size.width, inImage.size.height)
//Now Draw the text into an image.
drawText.drawInRect(rect, withAttributes: textFontAttributes)
// Create a new image out of the images we have created
var newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
// End the context now that we have the image we need
UIGraphicsEndImageContext()
//And pass it back up to the caller.
return newImage
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
autocompleteTableView!.hidden = false
var substring = (self.RecEmail.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
searchAutocompleteEntriesWithSubstring(substring)
self.dismissViewControllerAnimated(true, completion: {})
return true
}
func searchAutocompleteEntriesWithSubstring(substring: String)
{
autocomplete.removeAll(keepCapacity: false)
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
let emailArray = delegate.emailArray
var emailNSArray = emailArray.componentsSeparatedByString(",")
for curString in emailNSArray
{
println(curString)
var myString: NSString! = curString as NSString
var substringRange: NSRange! = myString.rangeOfString(substring)
if (substringRange.location == 0)
{
autocomplete.append(curString)
}
}
autocompleteTableView!.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return autocomplete.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let autoCompleteRowIdentifier = "AutoCompleteRowIdentifier"
var cell = tableView.dequeueReusableCellWithIdentifier(autoCompleteRowIdentifier) as? UITableViewCell
if let tempo1 = cell
{
let index = indexPath.row as Int
cell!.textLabel!.text = autocomplete[index]
} else
{
cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: autoCompleteRowIdentifier)
}
return cell!
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedCell : UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
self.dismissViewControllerAnimated(true, completion: nil)
println("dismiss")
RecEmail.text = selectedCell.textLabel!.text
}
Thanks!
You are trying to dismiss a ViewController not a table view cell. If you want to remove a cell, just delete current row index in your data source (here to be your [String] array autocomplete), and do reload data.

Resources