refreshing table while scrolling ios swift - ios

I have tableView with custom cell. I load some information from the internet and add it to the table if user scroll table down. Problem: after loading information new cells sometimes rewrite old cells, and sometimes blank cells appears in this table while scrolling. There is my code:
var Bool canEdit = true
#IBOutlet var table : UITableView!
var eventList : [EventCell] = [] // EventCell - custom cell class
func addEventsAndRefresh() {
if (canEdit){
canEdit = false
HTTPManager.getEvents(isFuture: true, offset: eventList.count, didLoad: getEvents) //it creates url and call function "getEvents" after ending async request
}
}
func getEvents(response : NSURLResponse!, data : NSData!, error : NSError!) {
if ((error) != nil) {
HTTPManager.showErrorAlert()
} else {
var result = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSArray
for elementOfArray in result {
var cell : EventCell = table.dequeueReusableCellWithIdentifier("EventCell") as EventCell
... // parsing result to cell
eventList.append(cell)
}
table.reloadData()
canEdit = true
}
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return eventList.count
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
return eventList[indexPath.row]
}
func scrollViewDidEndDragging(scrollView: UIScrollView!, willDecelerate decelerate: Bool) {
var currentOffset = scrollView.contentOffset.y;
var maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
if (maximumOffset - currentOffset <= -40.0 && canEdit) {
addEventsAndRefresh()
}
}

I got the same problem when I reused the cell variable as you do.
Try to not put your cells in a list but instead save your result list as an instance variable and get the objects by in index in cellForRowAtIndexPath function as shown below.
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell : EventCell = table.dequeueReusableCellWithIdentifier("EventCell") as EventCell
var element = result[indexPath.row] as YourObject
//Do something with cell
return cell
}

Related

UITableViewAutomaticDimension Cells resize after scroll or pull to refresh

I have added a table view, and I am display image in the cells. I have also added this code:
tableView.estimatedRowHeight = 175
tableView.rowHeight = UITableViewAutomaticDimension
So that the cells resize depending on the image.
When I launch my app though, I get this :
And the images do not load untill I start scrolling...If I scroll down half the page then go back to the top, I get this(which is correct):
Also if I remove the like button and just has the image alone in a cell, when I launch my app if I wait 3 seconds without touching anything the cells resize on they're own..?!
Any ideas? I have researched on google and tried the odd solution for the older versions of Xcode, But nothing seems to work!
Here is the rest of my code from the TableViewController:
extension TimelineViewController: UITableViewDataSource {
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 46
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return timelineComponent.content.count
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("PostHeader") as! PostHeaderTableViewCell
let post = self.timelineComponent.content[section]
headerCell.post = post
return headerCell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PostCell") as! PostTableViewCell
//cell.postImageView?.image = UIImage(named: "Background.png")
let post = timelineComponent.content[indexPath.section]
post.downloadImage()
post.fetchLikes()
cell.post = post
cell.layoutIfNeeded()
return cell
}
}
extension TimelineViewController: UITableViewDelegate {
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
timelineComponent.targetWillDisplayEntry(indexPath.section)
}
Download image code:
func downloadImage() {
// 1
image.value = Post.imageCache[self.imageFile!.name]
if image is not downloaded yet, get it
if (image.value == nil) {
imageFile?.getDataInBackgroundWithBlock { (data: NSData?, error: NSError?) -> Void in
if let data = data {
let image = UIImage(data: data, scale: 2.0)!
self.image.value = image
// 2
Post.imageCache[self.imageFile!.name] = image
}
}
}
}
// MARK: PFSubclassing
extension Post: PFSubclassing {
static func parseClassName() -> String {
return "Post"
}
override class func initialize() {
var onceToken : dispatch_once_t = 0;
dispatch_once(&onceToken) {
// inform Parse about this subclass
self.registerSubclass()
// 1
Post.imageCache = NSCacheSwift<String, UIImage>()
}
}
}
And here is my TableViewCell:
var post: Post? {
didSet {
postDisposable?.dispose()
likeDisposable?.dispose()
if let oldValue = oldValue where oldValue != post {
oldValue.image.value = nil
}
if let post = post {
postDisposable = post.image
.bindTo(postImageView.bnd_image)
likeDisposable = post.likes
.observe { (value: [PFUser]?) -> () in
if let value = value {
//self.likesLabel.text = self.stringFromUserList(value)
self.likeButton.selected = value.contains(PFUser.currentUser()!)
// self.likesIconImageView.hidden = (value.count == 0)
} else {
//self.likesLabel.text = ""
self.likeButton.selected = false
//self.likesIconImageView.hidden = true
}
}
}
}
}
Any help is really appreciated!
I guess, you need to reload the cell when the image is finally loaded, because tableView needs to recalculate cell height (and the whole contentHeight) when image with new size arrives
post.downloadImage { _ in
if tableView.indexPathForCell(cell) == indexPath {
tableView.reloadRowsAtIndexPaths([indexPath], animation: .None)
}
}
and downloadImage method needs to call completion closure. Something like that.
func downloadImage(completion: ((UIImage?) -> Void)?) {
if let imageValue = Post.imageCache[self.imageFile!.name] {
image.value = imageValue
completion?(imageValue)
return
}
//if image is not downloaded yet, get it
imageFile?.getDataInBackgroundWithBlock { (data: NSData?, error: NSError?) -> Void in
if let data = data {
let image = UIImage(data: data, scale: 2.0)!
self.image.value = image
// 2
Post.imageCache[self.imageFile!.name] = image
completion?(image)
} else {
completion?(nil)
}
}
}

Load more options with UITableView

Trying to load my data in page using pagination, I've already seen many examples but all are in Objective-C and some questions are unanswered.
My code:
class testTableViewController: UITableViewController {
//MARK: - Properties -
var allObjectArray: NSMutableArray = []
var elements: NSMutableArray = []
var currentPage = 0 //number of current page
var nextpage = 0
var selectedRow = Int()
//MARK: - View Life Cycle -
override func viewDidLoad() {
super.viewDidLoad()
for var i = 1; i < 500; i++ {
allObjectArray.addObject(i)
}
elements.addObjectsFromArray(allObjectArray.subarrayWithRange(NSMakeRange(0, 30)))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source -
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return elements.count + 1
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedRow = (tableView.indexPathForSelectedRow?.row)!
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var customCell = tableView.dequeueReusableCellWithIdentifier("cell")
customCell = UITableViewCell(style: .Default, reuseIdentifier: "cell")
customCell!.textLabel!.text = "cell - \(allObjectArray[indexPath.row])"
if indexPath.row == elements.count {
customCell?.textLabel?.textColor = UIColor.blueColor()
customCell?.textLabel?.text = "Load more..."
}
return customCell!
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
nextpage = elements.count
if indexPath.row == nextpage {
if indexPath.row == selectedRow {
currentPage++
nextpage = elements.count - 5
elements.addObjectsFromArray(allObjectArray.subarrayWithRange(NSMakeRange(currentPage, 30)))
tableView.reloadData()
}
}
}
}
I want this kind of output:
Tried to fetch selected index but it will return nil.
Create Outlets in Interface Builder for tableview and make two dynamic prototype cells give them identifiers make one cell with a button(your load more cell button)
Then create action with that button that will contain the logic to load more cells!!!
now see the snipet below for reference...
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tblDemo: UITableView!
var arForCells:NSMutableArray = NSMutableArray()
let simpleCellID = "SimpleCell"
let loadMoreCell = "LoadMoreCell"
override func viewDidLoad() {
super.viewDidLoad()
tblDemo.delegate = self
tblDemo.dataSource = self
arForCells = NSMutableArray(objects: "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arForCells.count + 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if (indexPath.row == arForCells.count){
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier(loadMoreCell, forIndexPath: indexPath)
return cell
}else {
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier(simpleCellID, forIndexPath: indexPath)
let lblCounter = cell.viewWithTag(111) as! UILabel
lblCounter.text = arForCells.objectAtIndex(indexPath.row) as! String
return cell
}
}
#IBAction func loadMoreCells(sender: AnyObject) {
let newAr:NSArray = NSArray(objects: "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
arForCells.addObjectsFromArray(newAr as [AnyObject])
tblDemo.reloadData()
}}
as I have checked, It should give you the desired results.
You could also do the same in TableViewFooter.
To set "Load More.." text add a label with this text in a view having the same size of your tableViewCell. Then add this in your tableView footer. Add a (custom/transparent)button in footer view so when that touched it will load your main array with more data and then reload your tableView.
Hope this helps!
First of all, You don't have to use this:
selectedRow = (tableView.indexPathForSelectedRow?.row)!
in the didSelectRowAtIndexPath. You can use simply
selectedRow = indexPath.row
next, your logic for willDisplayCellAtIndexPath seems redundant if you want to have cell to tap on it. You can simply put the following in enter code here:
nextpage = elements.count
if indexPath.row == nextpage {
currentPage++
nextpage = elements.count - 5
elements.addObjectsFromArray(allObjectArray.subarrayWithRange(NSMakeRange(currentPage, 30)))
tableView.reloadData()
}
also, I am not sure why do you need nextpage = elements.count - 5 but I assume that you have reason behind this.
done it my self
import UIKit
class LoadMoreTableVC: UITableViewController {
//MARK: - Properties -
var allObjectArray: NSMutableArray = []
var elements: NSMutableArray = []
var currentPage = 0 //number of current page
var nextpage = 0
var totalElements = 500 //total elements
var elementAtOnePage = 30 //at one page
//MARK: - View Life Cycle -
override func viewDidLoad() {
super.viewDidLoad()
for i in 0 ..< totalElements {
allObjectArray.addObject(i+1)
}
elements.addObjectsFromArray(allObjectArray.subarrayWithRange(NSMakeRange(0, elementAtOnePage)))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source -
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let count = elements.count + 1
if count < allObjectArray.count
{
return count
}
else
{
return allObjectArray.count
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == elements.count {
loadDataDelayed()
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel!.text = "\(allObjectArray[indexPath.row])"
if indexPath.row == elements.count {
cell.textLabel?.text = "Load more..."
}
return cell
}
func loadDataDelayed(){
currentPage += 1
elements.addObjectsFromArray(allObjectArray.subarrayWithRange(NSMakeRange(currentPage, elementAtOnePage)))
tableView.reloadData()
}
}
I have already done this is in swift.
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
if loading == true {
var endScrolling:CGFloat = scrollView.contentOffset.y + scrollView.frame.size.height
if(endScrolling >= scrollView.contentSize.height){
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "loadDataDelayed", userInfo: nil, repeats: false)
}
}
}
Please try this code.
And share your response.
Happy coding :)

UITableView going out of view

i have a UITableView with multiple selection enabled with checkmarks. When i make selection that are all visible in the view, i don't run into any errors. However, if i scroll down further and place a selected item out of view, i get errors and even though the row stays selected, the checkmark goes away.
import Foundation
import Parse
import UIKit
class customerMenuVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var menuTV: UITableView!
var menuItems: [String] = ["Hello"]
var menuPrices: [Double] = [0.0]
var orderSelection: [String] = []
var priceSelection: [Double] = []
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return menuItems.count
}
func tableView(tableView: UITableView, numberOfColumnsInSection section: Int) -> Int
{
return 1;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell:UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "mycell")
cell.textLabel!.text = "\(menuItems[indexPath.row])\t $\(menuPrices[indexPath.row])"
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
//tableView.deselectRowAtIndexPath(indexPath, animated: true)
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell!.accessoryType = .Checkmark
orderSelection.append(cell!.textLabel!.text!)
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath)
{
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell!.accessoryType = .None
}
override func viewDidLoad() {
super.viewDidLoad()
menuTV.allowsMultipleSelection = true
let resMenu = resUser.sharedInstance
var resName = resMenu.nameStr
var resID = resMenu.idStr
var menuQ = PFQuery(className: "menu")
menuQ.getObjectInBackgroundWithId(resID){
(menus: PFObject?, error: NSError?) -> Void in
if error == nil && menus != nil {
let items: [String] = menus?.objectForKey("menuItems") as! Array
let prices: [Double] = menus?.objectForKey("menuPrices") as! Array
self.menuItems = items
self.menuPrices = prices
self.menuTV.reloadData()
}
}
}
#IBAction func continueButton(sender: AnyObject) {
let selections = menuTV.indexPathsForSelectedRows() as! [NSIndexPath]
var indexCount = selections.count
println(indexCount)
var x = 0
while x < indexCount
{
println(x)
let currentCell = menuTV.cellForRowAtIndexPath(selections[x]) as? UITableViewCell?;
println(x)
println(selections[x].row.description)
orderSelection.append(currentCell!!.textLabel!.text!)
println(orderSelection[x])
x++
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
This is how table views work.
When a cells scrolls off-screen, it gets tossed into the recycle queue and then used again to display data for a different indexPath in your data.
Any time the user makes any changes to the data for a cell you should save it to your data model (usually an array of information, or maybe an array of arrays if you're using a sectioned table view.) Then you should tell the table view to redisplay the changed cell. The cellForRowAtIndexPath method picks up the changed data and shows the changes to the cell. If the cell scrolls off-screen and then scrolls back on-screen, it gets displayed with the correct settings.
This applies to keeping track of which cells are selected as well.

Swfit TableView load next page after scrolling to the last cell

I have tableview controller. It gets data from the api and loads page 1.
I want to go the next page when I scroll the bottom of the current page.
Is there a way to implement this?
var currentPage = 1
func loadArticles(){
let url = "http://yazar.io/api/article/feed/" + String(currentPage)
Alamofire.request(.GET, url).responseJSON { (Request, response, json, error) -> Void in
if (json != nil){
var jsonObj = JSON(json!)
if let data = jsonObj["articles"].arrayValue as [JSON]?{
self.articles = data
self.tableView.reloadData()
self.view.hideLoading()
self.refreshControl?.endRefreshing()
}
}
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("MakaleCell") as MakaleTableViewCell
if (indexPath.row == self.articles!.count - 5) {
currentPage += 1
println(currentPage)
loadArticles(currentPage: currentPage)
}
cell.article = self.articles?[indexPath.row]
return cell
}
I have tryied this, it looks ok. Is that work for you ?
#IBOutlet weak var tableview: UITableView!
var datasource = Array<Int>()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0...21
{
datasource.append(i)
}
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
{
if indexPath.row == datasource.count-1
{
var max = datasource.count;
for i in max...max+15
{
datasource.append(i)
}
self.tableview.reloadData()
}
}
Thanks everyone, extending data solved my problem...
self.articles?.extend(data)

Swift: Change button image in table view on click

I created an Xcode project for iPhone using Swift, with Parse for the backend. My current problem is with creating a todo list application as one tab of a larger application.
Inside of a custom prototype cell, I want to have a checkbox button that changes its image when clicked as well as changing the isChecked:Bool variable for that cell. I've gotten most of the way there, but I've run into a brick wall regarding setting and accessing the appropriate variables for this button.
Edit: Thanks to the answer below and other resources, I have finally written working code for this checkbox functionality. Essentially, the button's tag property is set equal to the indexPath.row of the PFObject. As my original question was a bit broad, I am updating my code below so that it might help other new developers who are working on similar functionality. There may be better ways, but this seems to work.
// TasksVC.swift
var taskObjects:NSMutableArray! = NSMutableArray()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.fetchAllObjects()
}
func fetchAllObjects() {
var query:PFQuery = PFQuery(className: "Task")
query.whereKey("username", equalTo: PFUser.currentUser()!)
query.orderByAscending("dueDate")
query.addAscendingOrder("desc")
//fetches values within pointer object
query.includeKey("deal")
query.findObjectsInBackgroundWithBlock { (tasks: [AnyObject]!, error:NSError!) -> Void in
if (error == nil) {
var temp:NSArray = tasks! as NSArray
self.taskObjects = temp.mutableCopy() as NSMutableArray
self.tableView.reloadData()
println("Fetched objects from server")
} else {
println(error?.userInfo)
}
}
}
//MARK: - Tasks table view
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.taskObjects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as TaskCell
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "M/dd/yy"
var task:PFObject = self.taskObjects.objectAtIndex(indexPath.row) as PFObject
cell.desc_Lbl?.text = task["desc"] as? String
cell.date_Lbl.text = dateFormatter.stringFromDate(task["dueDate"] as NSDate)
//value of client within Deal pointer object
if let deal = task["deal"] as? PFObject {
// deal column has data
if let client = deal["client"] as? String {
// client has data
cell.client_Lbl?.text = client
}
}
//set the tag for the cell's UIButton equal to the indexPath of the cell
cell.checkbox_Btn.tag = indexPath.row
cell.checkbox_Btn.addTarget(self, action: "checkCheckbox:", forControlEvents: UIControlEvents.TouchUpInside)
cell.checkbox_Btn.selected = task["isCompleted"] as Bool
if (task["isCompleted"] != nil) {
cell.checkbox_Btn.setBackgroundImage(UIImage(named:(cell.checkbox_Btn.selected ? "CheckedCheckbox" : "UncheckedCheckbox")), forState:UIControlState.Normal)
}
cell.selectionStyle = .None
return cell
}
func checkCheckbox(sender:UIButton!) {
var senderBtn:UIButton = sender as UIButton
println("current row: \(senderBtn.tag)")
//retrieve the PFObject for the row we have selected
var task:PFObject = self.taskObjects.objectAtIndex(senderBtn.tag) as PFObject
println("objectID: \(task.objectId)")
if task["isCompleted"] as NSObject == true {
task["isCompleted"] = false
} else {
task["isCompleted"] = true
}
task.saveInBackgroundWithBlock { (success, error) -> Void in
if (error == nil) {
println("saved checkbox object in background")
} else {
println(error!.userInfo)
}
}
self.tableView.reloadData()
}
override func tableView(tableView: UITableView?, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView?, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
var task:PFObject = self.taskObjects.objectAtIndex(indexPath.row) as PFObject
task.deleteInBackgroundWithBlock({ (success, error) -> Void in
self.fetchAllObjects()
self.taskObjects.removeObjectAtIndex(indexPath.row)
})
} else if editingStyle == .Insert {
}
}
When working with tables and collection views, all the objects you have in a custom cell can be easily accessed in cellForRowAtIndexPath (for UITables)
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("ActionCell", forIndexPath: indexPath) as ActionCell
var action = actions[indexPath.row] as Action
cell.nameLabel?.text = action.name
cell.listLabel?.text = action.list
cell.dateLabel?.text = action.date
cell.checkboxButton = action.isChecked
cell.checkBoxButton.setImage(UIImage(named:"checkedImage"), forState:UIControlState.Normal)
return cell
}
more over I would suggest to change constants to variables. I'm new to Swift too and "let" declares a static variable.
I find very cool the use of the conditional operator (?:) in these cases:
cell.checkBoxButton.setImage(UIImage(named:(any_boolean_condition ? "checkedImage" : "uncheckedImage")), forState:UIControlState.Normal)
so it can return one image name for the condition True and another name for the condition False.

Resources