Programmatically altering NSIndexPath in Swift - ios

I'm implementing AdMob in a UITableView by putting banner ad in the first row of a section. I'm most of the way there implementing it, however I'm having a tough time getting cellForRowAtIndexPath to work as desired.
This is what my numberOfRowsInSection looks like:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count = Int()
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
count = currentSection.numberOfObjects
count = count + 1 // add another row for an ad
}
return count
}
My cellForRowAtIndexPath looks like this:
override func tableView(tableView: UITableView, var cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let adCell: BannerAdTableViewCell = tableView.dequeueReusableCellWithIdentifier(BannerAdTableViewCell.reuseIdentifier(), forIndexPath: indexPath) as! BannerAdTableViewCell
// customization
return adCell
} else {
// Cell for vanilla item to display
// TODO: fix indexpath here. need to add 1
let newIndexPath = indexPath.indexPathByAddingIndex(indexPath.row+1)
indexPath = newIndexPath
// Cell for a Routine
let customCell = tableView.dequeueReusableCellWithIdentifier(RoutineSelectionTableViewCell.reuseIdentifier(), forIndexPath: indexPath) as! RoutineSelectionTableViewCell
let routine = fetchedResultsController.objectAtIndexPath(indexPath) as! SavedRoutines
customCell.routineNameLabel.text = routine.routineTitle
return customCell
}
}
I know I need to adjust the value of the indexPath to account for an extra row in the indexPathSection, but everything I've tried triggers out of bounds exceptions of some sort. Any feedback would be greatly appreciated.

indexPathByAddingIndex adds a new index, it does not increment a value of an index but adds one. If you previously had two indices / dimensions (section and row) you now have 3 indices / dimension: section, row and "the newly added one".
Provides an index path containing the indexes in the receiving index path and another index.
What you should do instead is either create a new NSIndexPath by hand. And I do not think you need to add one, but subtract one, since the item at index 1 should actually be the element in your result at index 0:
let customIndexPath = NSIndexPath(forRow: indexPath.row - 1, inSection: indexPath.section)
which you then use to access the "correct" routine at the right index:
let routine = fetchedResultsController.objectAtIndexPath(customIndexPath) as! SavedRoutines
Your call to tableView.dequeueReusableCellWithIdentifier should stay the same and still pass in the default indexPath.

Related

Access to array's indexPath in a function Swift

I am trying to access an array's indexPath inside a function to update this array's data but I don't know how to pass the indexPath as a parameter (espacially what to pass when calling) to the function or if this is even the solution.
I included cellForRowAt to illustrate how this function access indexPath.
var cryptosArray: [Cryptos] = []
extension WalletTableViewController: UITableViewDelegate, UITableViewDataSource, CryptoCellDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let crypto = cryptosArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! WalletTableViewCell
cell.setCrypto(crypto: crypto)
cell.delegate = self
cell.amountTextField.delegate = self
return cell
}
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let crypto = cryptosArray[indexPath.row] //<---- How to do that?
crypto.amount = walletTableViewCell.amountTextField.text
//Then update array's amount value at correct index
walletTableViewCell.amountTextField.text = ""
}
}
Instead of hacking something, simply ask the tableView to tell you the indexPath of the given cell:
// use indexPath(for:) on tableView
let indexPath = tableView.indexPath(for: walletTableViewCell)
// then you can simply use it
let crypto = cryptosArray[indexPath.row]
UITableView.indexPath(for:) documentation says:
Returns an index path representing the row and section of a given table-view cell.
And this is exactly what you want, you don't want to hack the indexPath to the cell. indexPath should be taken care of by the tableView, not the cell. Ideally, cell should be completely oblivious of its indexPath.
Always try to use the standard way to solve your problems. In general, when you are trying to solve something, I would recommend you to first look at the documentation of the UITableView, there are many useful methods there.
if you want to get index path.row when user clicked on cell , you should get index path.row when user clicked and then use this to your func
for ex :
var indexrow : int = 0
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// table cell clicked
indexrow = indexPath.row
}
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let crypto = cryptosArray[indexrow]
crypto.amount = walletTableViewCell.amountTextField.text
//Then update array's amount value at correct index
walletTableViewCell.amountTextField.text = ""
}

Variable use of multiple custom cells

I'm using a unclickable tableView to display different information of one object.
For this informations I have different custom cell types one where I placed a map, if my object have locations, one have a list with links, and another a multiple line label for a little description...for example.
I manage this cells with:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell: mapCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! MapCell
return cell
} else if indexPath.row == 1 {
let cell: textCell = tableView.dequeueReusableCellWithIdentifier("textCell") as! TextCell
return cell
} else if indexPath.row == 2 {
let cell: listCell = tableView.dequeueReusableCellWithIdentifier("listCell") as! ListCell
return cell
}
}
So far so good, everything working fine. My problem is, not every object needs a map, some of them just need some text and a list, other objects need a map and a list, other all of them. I want my tableView to skip some cells if there is a condition.
I know, I can make an symbolic array for changing the number of cells of my tableView, but that deleting just from the end of my tableView, not specific cells.
One of my ideas is to generate a empty cell, maybe with a height of 0 or 1 so that I can do something like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
if mapCellNeeded {
let cell: mapCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! mapCell
} else {
let cell: emptyCell = tableView.dequeueReusableCellWithIdentifier("emptyCell") as! EmptyCell
}
return cell
} else if indexPath.row == 1 {
...
}...
}
put I don't know if there isn't an efficient way. Hope you guys can help me.
Your solution would work. Another approach (very nice and swifty) would be not to hardcode row numbers, but rather use enum instead:
enum InfoCellType {
case Map
case Text
case Links
}
...
var rows = [InfoCellType]()
...
// when you know what should be there or not
func constructRows() {
if (mapCellNeeded) {
rows.append(InfoCellType.Map)
}
rows.append(InfoCellType.Text)
... etc
}
Then in the table view methods just see what's the type for current indexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellType: InfoCellType = self.rows[indexPath.row]
switch cellType {
case .Map:
let cell: mapCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! mapCell
return cell
case .Text:
...
case.Links:
...
}
}
This solution also allows to easily change order of rows - just change the order of items in rows array.

When UITableView Scrolling Returned Wrong IndexPath.Row Value

This is my codes;
// MARK: - Table View Delegate && Data Source Methods
// **************************************************
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let index = indexPath.row
print(index)
if index == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("HeaderCell", forIndexPath: indexPath)
cell.backgroundColor = ColorHelper.getCellBackgroundColor()
return cell
}
else {
if let cell = tableView.dequeueReusableCellWithIdentifier("GradeCell", forIndexPath: indexPath) as? GradeCell {
cell.activeViewController = self;
cell.gradeButton.tag = index
cell.creditButton.tag = index
cell.lessonNameTextField.tag = index
cell.lessonNameTextField.delegate = self
cell.backgroundColor = ColorHelper.getCellBackgroundColor()
return cell
}
return UITableViewCell()
}
}
I have 11 cells and someone are missing, When i scrolled table view index returns like this;
0-1-2-3-4-5-6-7-8-0-1-2..
After reload process, my values are confused. Wrong value in wrong cell, how can i fix this ?
Your problem could possible be how many cells you are returning. Especially, if you are having problems with the last one or two. From what you said, it sounds like you have 11 cells, make sure you return 12. The cells in a UITableView always start counting with 0 being the first cell and 10 being that last cell, in your case.
func tableView(tableView: UITableView, numberOfRowsInSection section:Int) -> Int {
return 12
}

How to use multiple custom cells (>1) in UITableView

I've searched for an answer to this question all over Stack Overflow and have found some useful answers but my situation is different as the number of rows in the section are to be determined from the number of items listed in an array. I'm trying to create a table that uses two custom cells. The first cell displays profile information while the second displays the news feed.
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myProfileDM.profileArray.count
//return myProfileFeedDM.profileFeedArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->
UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("bio", forIndexPath:indexPath) as! ProfileTableViewCell
cell.followerNumber!.text = myProfileDM.profileArray[indexPath.row].followerNumberInterface
cell.followers!.text = myProfileDM.profileArray[indexPath.row].followersInterface
cell.following!.text = myProfileDM.profileArray[indexPath.row].followingInterface
cell.followingNumber!.text = myProfileDM.profileArray[indexPath.row].followingNumberInterface
return cell
}
else{
let cell = tableView.dequeueReusableCellWithIdentifier("feed", forIndexPath:indexPath) as! FeedTableViewCell
//let cell: FeedTableViewCell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "feed")
cell.profileFeedLabel!.text = myProfileFeedDM.profileFeedArray[indexPath.row].profileFeed
cell.profileDateLabel!.text = myProfileFeedDM.profileFeedArray[indexPath.row].profileDate
return cell
}
}
}
when I run the program, the first cell (with identifier-bio) is the only one that loads/shows up.
I suppose the number of rows in the section is wrong. From your variable names I suspect it should be
myProfileFeedDM.profileFeedArray.count + 1
Note that in the feed array you would have to use indexPath.row - 1 to get to the right index of your array because the first row is for the profile.
I don't see any reason from the code why it doesn't work.
Try to debug cellForRowAtIndexPath method to see what is the value of the indexPath on each call
(or just put println ("IndexPath: \(indexPath)") to your cellForIndexPath method)
PS: But as long as you need your profile cell only once - I would suggest to move ProfileCell into table's or Section's header
it would be a bit more logical I think.

Swift - How to use dequeueReusableCellWithIdentifier with two cell types? (identifiers)

I am trying to build a table view for events, like so:
I have two cell prototypes:
An event cell with identifier "event"
A separator cell with identifier "seperator"
Also, I have this class to represent a date:
class Event{
var name:String = ""
var date:NSDate? = nil
}
And this is the table controller:
class EventsController: UITableViewController {
//...
var eventsToday = [Event]()
var eventsTomorrow = [Event]()
var eventsNextWeek = [Event]()
override func viewDidLoad() {
super.viewDidLoad()
//...
self.fetchEvents()//Fetch events from server and put each event in the right property (today, tomorrow, next week)
//...
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let event = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
let seperator = tableView.dequeueReusableCellWithIdentifier("seperator", forIndexPath: indexPath) as SeperatorTableViewCell
//...
return cell
}
}
I have all the information I need at hand, but I can't figure out the right way to put it all together. The mechanics behind the dequeue func are unclear to me regrading multiple cell types.
I know the question's scope might seem a little too broad, but some lines of code to point out the right direction will be much appreciated. Also I think it will benefit a lot of users since I didn't found any Swift examples of this.
Thanks in advance!
The basic approach is that you must implement numberOfRowsInSection and cellForRowAtIndexPath (and if your table has multiple sections, numberOfSectionsInTableView, too). But each call to the cellForRowAtIndexPath will create only one cell, so you have to do this programmatically, looking at the indexPath to determine what type of cell it is. For example, to implement it like you suggested, it might look like:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventsToday.count + eventsTomorrow.count + eventsNextWeek.count + 3 // sum of the three array counts, plus 3 (one for each header)
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var index = indexPath.row
// see if we're the "today" header
if index == 0 {
let separator = tableView.dequeueReusableCellWithIdentifier("separator", forIndexPath: indexPath) as SeparatorTableViewCell
// configure "today" header cell
return separator
}
// if not, adjust index and now see if we're one of the `eventsToday` items
index--
if index < eventsToday.count {
let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
let event = eventsToday[index]
// configure "today" `eventCell` cell using `event`
return eventCell
}
// if not, adjust index and see if we're the "tomorrow" header
index -= eventsToday.count
if index == 0 {
let separator = tableView.dequeueReusableCellWithIdentifier("separator", forIndexPath: indexPath) as SeparatorTableViewCell
// configure "tomorrow" header cell
return separator
}
// if not, adjust index and now see if we're one of the `eventsTomorrow` items
index--
if index < eventsTomorrow.count {
let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
let event = eventsTomorrow[index]
// configure "tomorrow" `eventCell` cell using `event`
return eventCell
}
// if not, adjust index and see if we're the "next week" header
index -= eventsTomorrow.count
if index == 0 {
let separator = tableView.dequeueReusableCellWithIdentifier("separator", forIndexPath: indexPath) as SeparatorTableViewCell
// configure "next week" header cell
return separator
}
// if not, adjust index and now see if we're one of the `eventsToday` items
index--
assert (index < eventsNextWeek.count, "Whoops; something wrong; `indexPath.row` is too large")
let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
let event = eventsNextWeek[index]
// configure "next week" `eventCell` cell using `event`
return eventCell
}
Having said that, I really don't like that logic. I'd rather represent the "today", "tomorrow" and "next week" separator cells as headers, and use the section logic that table views have.
For example, rather than representing your table as a single table with 8 rows in it, you could implement that as a table with three sections, with 2, 1, and 2 items in each, respectively. That would look like:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case 0:
return "Today"
case 1:
return "Tomorrow"
case 2:
return "Next week"
default:
return nil
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return eventsToday.count
case 1:
return eventsTomorrow.count
case 2:
return eventsNextWeek.count
default:
return 0
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
var event: Event!
switch indexPath.section {
case 0:
event = eventsToday[indexPath.row]
case 1:
event = eventsTomorrow[indexPath.row]
case 2:
event = eventsNextWeek[indexPath.row]
default:
event = nil
}
// populate eventCell on the basis of `event` here
return eventCell
}
The multiple section approach maps more logically from the table view to your underlying model, so I'd to adopt that pattern, but you have both approaches and you can decide.

Resources