Dynamic viewForHeaderInSection and multiple cellForRowAtIndexPath - ios

I'm trying to make a custom cell to be the Section of my cells:
For this I'm overriding some functions like:
numberOfRowsInSection
viewForHeaderInSection
heightForHeaderInSection
And I'm working with multiple cellForRowAtIndexPath I'm using a if indexPath.row to identify the Cell and populate them in a dynamic way.
import UIKit
class TesteTableViewController: PFQueryTableViewController {
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return UIStatusBarStyle.LightContent
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
loadCollectionViewData()
}
func loadCollectionViewData() {
// Build a parse query object
let query = PFQuery(className:"Feed")
// Check to see if there is a search term
// Fetch data from the parse platform
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
// The find succeeded now rocess the found objects into the countries array
if error == nil {
print(objects!.count)
// reload our data into the collection view
} else {
// Log details of the failure
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return objects!.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
var header = tableView.dequeueReusableCellWithIdentifier("Cell2")! as! TesteTableViewCell
return header
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30.0
}
// Initialise the PFQueryTable tableview
override init(style: UITableViewStyle, className: String!) {
super.init(style: style, className: className)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
// Configure the PFQueryTableView
self.parseClassName = "Feed"
self.pullToRefreshEnabled = true
self.paginationEnabled = false
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: TesteTableViewCell!
let object = objects![indexPath.section]
if indexPath.row == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TesteTableViewCell
// Extract values from the PFObject to display in the table cell
if let nameEnglish = object["name"] as? String {
cell?.label1?.text = nameEnglish
}
}
else{
cell = tableView.dequeueReusableCellWithIdentifier("Cell2", forIndexPath: indexPath) as! TesteTableViewCell
// Extract values 2from the PFObject to display in the table cell
if let nameEnglish2 = object["brief"] as? String {
cell?.label2?.text = nameEnglish2
}
}
return cell
}
}
With my code I have this result:
I'm successfully populating both Cells with different Identifiers.
But look like this function is called before the cellForRowAtIndexPath and it return the content that I have in the storyBoard and not the content that I'm populating dynamically.
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
var header = tableView.dequeueReusableCellWithIdentifier("Cell2")! as! TesteTableViewCell
return header
}
And I really think that is my problem. (The order that things happen).
Any ideas?
Thanks.

You need to move the code which you have in cellForRowAtIndexPath for row != 0 because that block of code is never executed which is causing static data rendering from storyboard instead of dynamic data.
In this case you have to give dynamic data to cell in viewForHeaderInSection method so that you cell will populate that information on each reload.
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
var header = tableView.dequeueReusableCellWithIdentifier("Cell2")! as! TesteTableViewCell
let object = objects![section]
if let nameEnglish2 = object["brief"] as? String {
header.label2?.text = nameEnglish2
}
return header
}

Related

Custom xib cells are not appearing on UITableView swift

my cells are not appearing.
I did:
Checked if datasource and delegate were connected
Checked if my custom cells identifier name and class were correct
Things that I didn't:
I am struggling with auto layout, so I just decided not to do it.
My app is loading with the correct amount of cells, but the cells are not registered.
My code:
import UIKit
class WelcomeViewController: UITableViewController, NetworkManagerDelegate {
private var networkManager = NetworkManager()
private var infoForCells = [Result]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "ImageViewCell", bundle: nil), forCellReuseIdentifier: "imageCell")
networkManager.delegate = self
networkManager.fetchNews()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoForCells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? ImageViewCell else{
return UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let cellIndex = infoForCells[indexPath.row]
cell.titleForImage.text = cellIndex.alt_description
print(cell.titleForImage ?? "lol")
// if let image = cellIndex.urlToImage {
// cell.imageForArticle.load(url: image)
// }
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func didUpdateNews(root: Root) {
infoForCells = root.results
}
}
Reload the table
func didUpdateNews(root: Root) {
infoForCells = root.results
tableView.reloadData()
}
In addition to Sh_Khan answer you can also listen to updates of infoForCells property
private var infoForCells = [Result]() {
didSet {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
}

Adding TableViewCell Section Header everytime a new cell is created that displays month and year created - swift

I'm trying to display a section header with the month and year the cell was created as the text in the section header. This is my code but it only displays one section header like so. Any idea why and how I can get it to display the year and month the cell was created?
import UIKit
class PRViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tblTasks : UITableView!
//For persisting data
let defaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
self.tblTasks.reloadData()
tblTasks.registerNib(UINib(nibName: "PRTableViewCell", bundle: nil), forCellReuseIdentifier: "PRTableCell")
tblTasks.tableFooterView = UIView()
}
override func viewWillAppear(animated: Bool) {
self.tblTasks.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return 1
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Your PR's"
}
//Define how our cells look - 2 lines a heading and a subtitle
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let identifier = "PRTableCell"
var cell: PRTableViewCell! = tableView.dequeueReusableCellWithIdentifier(identifier) as? PRTableViewCell
if cell == nil {
tableView.registerNib(UINib(nibName: "PRTableViewCell", bundle: nil), forCellReuseIdentifier: identifier)
cell = tableView.dequeueReusableCellWithIdentifier(identifier) as? PRTableViewCell
}
// Assign the contents of our var "items" to the textLabel of each cell
// cell.textLabel!.text = taskMgr.tasks[indexPath.row].name
// cell.detailTextLabel!.text = taskMgr.tasks[indexPath.row].desc
cell.PRLabel.text = taskMgr.tasks[indexPath.row].name
cell.NotesLabel.text = taskMgr.tasks[indexPath.row].desc
cell.WeightLabel.text = taskMgr.tasks[indexPath.row].weight + "lb"
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath){
if (editingStyle == UITableViewCellEditingStyle.Delete){
taskMgr.removeTask(indexPath.row)
tblTasks.reloadData()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
let numberOfSections = taskMgr.tasks.count
return numberOfSections
}
}
Here's one way to do it. Note that this code assumes your cell data is in an array called "cellArray". It displays a date that starts with today and goes back one day for each section. Obviously you'll need to substitute your dates.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.cellArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCell", forIndexPath: indexPath)
// TODO: configure cell for display using self.cellArray[indexPath.section]
return cell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let df = NSDateFormatter()
df.dateStyle = .MediumStyle
df.timeStyle = .NoStyle
// TODO: determine the actual date
let displayDate = NSDate().dateByAddingTimeInterval(Double(section * -86400))
return df.stringFromDate(displayDate)
}
So example code is below (code not tested obviously). I have assumed that there is a dateCreated property containing an NSDate in the objects held in your tasks array.
Example code:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let formatter = NSDateFormatter() // This is slightly inefficient for a large number of rows because setting up NSDateFormatter is expensive. You could make this a property.
formatter.dateStyle = .ShortStyle
formatter.timeStyle = .NoStyle
let sectionHeaderDate = taskMgr.tasks[section].dateCreated
let dateString = formatter.stringFromDate(sectionHeaderDate)
return dateString
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let identifier = "PRTableCell"
var cell: PRTableViewCell! = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath:idx) as? PRTableViewCell // You must register a class or nib file using the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method before calling this method (http://stackoverflow.com/q/12737860/558933).
cell.PRLabel.text = taskMgr.tasks[indexPath.row].name
cell.NotesLabel.text = taskMgr.tasks[indexPath.row].desc
cell.WeightLabel.text = taskMgr.tasks[indexPath.row].weight + "lb" // Note iOS 9 allows you to localise weights rather than hard-coding "lb" or "kg". You should look at the documentation.
return cell
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
let numberOfSections = taskMgr.tasks.count
return numberOfSections
}
To add a new "section" to the UITableView you must add the new data to the array taskMgr.tasks and then either reload the table or update just the added rows. Wrap these lines of code in tblTasks.beginUpdates and tblTasks.endUpdates. Similarly for deleting.

Populate table with Parse database

I'm working on an iOS app, which pulls data from a Parse database to populate a table view. I can get data from the database to print to the log, but when I try to use the data to populate the table cells, it just does not work. The table is empty every time and I can't figure out why. Any thoughts?
import UIKit
import Parse
var promoNum = -1
class TableViewController: UITableViewController, CLLocationManagerDelegate {
var titles = [String]()
override func viewDidLoad() {
super.viewDidLoad()
var getPromosQuery = PFQuery(className: "Promotions")
getPromosQuery.whereKey("zip", equalTo: "85281")
getPromosQuery.findObjectsInBackgroundWithBlock { (objects, error) in
if let objects = objects {
for object in objects {
self.titles.append(object["title"] as! String)
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return titles.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let promoCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! Cell
promoCell.promotionTitle.text = titles[indexPath.row]
return promoCell
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
promoNum = indexPath.row
return indexPath
}
}
Fixed it! I just needed to add self.tableView.reloadData() after self.titles.append(object["title"] as! String).

Image added to UITableViewCell appears in wrong rows when scrolling

I'm adding an image to a table view row (actually, I seem to be adding it to the row's cell) when selecting it (and removing when selecting it again). The table view consists of prototype cells.
This works but when I scroll around and get back to the row I had previously selected, the image would be in another row. Also, the image appears in other rows as well.
My guess is this happens because the cells are re-used when scrolling.
Here's the code of a little sample project:
import UIKit
class MyTableViewController: UITableViewController {
// Using integers for simplicity, should work with strings, too.
var numbers = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<50 {
numbers.append(i)
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TestCell", forIndexPath: indexPath)
cell.textLabel?.text = "\(numbers[indexPath.row] + 1)"
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numbers.count
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
if let myImage = curCell.viewWithTag(10) as? MyImage {
myImage.removeFromSuperview()
} else {
let myImage = myImage()
myImage.tag = 10
cell.addSubview(myImage)
}
}
I need to have the image stay in the correct row, also when coming back to this view controller. What's the correct way to tackle this?
Any advice much appreciated!
EDIT: I've tried to implement matt's answer but I seem to be missing something, as the problem is still the same.
EDIT 2: Updated, working as intended now.
import UIKit
class ListItem {
var name: String!
var showsImage = false
init(name: String) {
self.name = name
}
}
class MyTableViewController: UITableViewController {
var listItems = [ListItem]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<50 {
let listItem = ListItem(name: "row \(i)")
listItems.append(listItem)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TestCell", forIndexPath: indexPath)
cell.textLabel?.text = "\(listItems[indexPath.row].name)"
if listItems[indexPath.row].showsImage {
let myImage = myImage
myImage.tag = 10
cell.addSubview(myImage)
} else {
if let myImage = cell.viewWithTag(10) as? myImage {
myImage.removeFromSuperview()
listItems[indexPath.row].showsImage = false
}
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listItems.count
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
if let myImage = cell.viewWithTag(10) as? myImage {
myImage.removeFromSuperview()
listItems[indexPath.row].showsImage = false
} else {
listItems[indexPath.row].showsImage = true
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
}
}
EDIT 3: As matt suggested, here's an alternative solution to the code above which subclasses UITableViewCell instead of using a tag for the image.
import UIKit
class MyTableViewCell: UITableViewCell {
var myImage = MyImage()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
myImage.hidden = true
addSubview(myImage)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ListItem {
var name: String!
var showsImage = false
init(name: String) {
self.name = name
}
}
class MyTableViewController: UITableViewController {
var listItems = [ListItem]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<50 {
let listItem = ListItem(name: "row \(i)")
listItems.append(listItem)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listItems.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = MyTableViewCell(style: .Default, reuseIdentifier: "TestCell")
cell.textLabel?.text = "\(listItems[indexPath.row].name)"
cell.myImage.hidden = !(listItems[indexPath.row].showsImage)
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! MyTableViewCell
listItems[indexPath.row].showsImage = cell.myImage.hidden
cell.myImage.hidden = !cell.myImage.hidden
}
}
The problem is that cells are reused in other rows. When they are, cellForRowAtIndexPath is called again. But when it is, you are supplying no information about the image for that row.
The solution: fix your model (i.e. the info you consult in cellForRowAtIndexPath) so that it knows about the image. In didSelect, do not modify the cell directly. Instead, modify the model and reload the cell.

UITableView programmatically in Swift Empty

I'm making a UITableView in swift programmatically. With the following code (I have UITableViewDelegate and UITableViewDataSource in there):
var tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tableView.frame = CGRectMake(25, 180, 220, 150)
tableView.delegate = self
tableView.delegate = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.view.addSubview(tableView)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = options[indexPath.row]
println("cell label is")
println(cell.textLabel!.text)
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 30
}
The table is coming blank, however. The cells don't show up. Any advice?
Why tableView.delegate initialized twice in viewDidLoad?One of them must be datasource isn't it?
override func viewDidLoad() {
super.viewDidLoad()
tableView.frame = CGRectMake(25, 180, 220, 150)
tableView.delegate = self
tableView.datasource = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.view.addSubview(tableView)
}
Call tableView.reloadData() after adding it to self.view in viewDidLoad.
Not only you are missing self.tableView.reloadData() . What do you have inside of the data structure options where are you getting the data from?
you said you are Querying from parse :
var query = PFQuery(className: "whatever class name")
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil {
if let newObjects = objects as? [PFObject]{
for one in newObjects {
var message = one["TextMessage"] as! String
// so add this data message data into your array
// then reload Data
}
}
}
else{
print("error")
}
}
}
And also change this
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
To
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
I noticed that you (unconventionally) use each element in options as i row in its own section. But, when you set the label, you base it on the row number, which will alway be zero. What you want is:
cell.textLabel?.text = options[indexPath.section]
As for the table being blank, I would think that either
Your first element is nothing
The array is empty
You are not setting up the sections properly

Resources