I'm really new to multithreading. It has produced a result I wasn't expecting, but it makes sense when I think about it. Now I need to find a way to make it work right.
Here is the scenario....
I have routine that calls a web page with a requested date. Let's call it getPage(inDate: String). The results are of unknown, but different sizes, depending on the date submitted. (Today could have 100 items, tomorrow 2 items, the next day 10 items).
I have a loop that calls getPage, in date sequence.
Each return is a section in a tableview.
PSEUDO CODE
func viewDidLoad() {
...
for cntr in 1...4 {
getPage(calculatedDate)
}
...
}
getPage(inDate: String) {
let task = NSURLSession ( ....) {
...
// create tableview section data using self. arrays of string
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.dateSection.count // array of sections
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataItem1[section].count // an array of data for the section
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let mycell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! calCell
self.navigationItem.title = "Calendar Data"
// Configure the cell...
mycell.dataItem1.text = self.data1[indexPath.section][indexPath.row]
mycell.dataItem2.text = self.data2[indexPath.section][indexPath.row]
mycell.dataItem3.text = self.data3[indexPath.section][indexPath.row]
mycell.dataItem4.text = self.data4[indexPath.section][indexPath.row]
return mycell
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
// print("In titleForHederInSection: Section \(section)")
// print("Title: \(self.dateSection[section])")
return self.dateSection[section]
}
It doesn't seem to matter where I put the reload(), or if I use dispatch_sync or dispatch_async, the table is out of order...giving me the quickest returns first.
My initial attempts with semaphores has been a dismal failure...locking up the application forever.
So the question is, how do I get the viewtable to be built in the order that I called the web page (in this case, date order)?
Related
I have a messaging function in an app and I'm trying to sort the cells of a tableView based off a timestamp included with every message. I have two strings that are used to populate two different custom cells. Due to this, I can't just rearrange the contents of the string, I need to rearrange the rows themselves.
class messageThreadViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return finalItems.count
} else {
return finalItems2.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "message1") as! MyTableViewCellThread
cell.messagelabel?.text = finalItems[indexPath.row]
return cell
} else {
let cell2 = tableView.dequeueReusableCell(withIdentifier: "message2") as! MyTableViewCellThread2
cell2.messageLabel2?.text = finalItems2[indexPath.row]
return cell2
}
}
the two different strings are finalItems and finalitems2. I have attached an image of the app running to help get a better idea of what I'm trying to do. Let me know if I can explain it better. Thanks in advance!
In general, when sorting cells in tableviews, you simply sort the arrays (datasource) themselves and then call the tableView.reloadData() method.
To sort arrays, the best practice is
let sortedArray = array.filter { $0.timestamp > $1.timestamp }
// This sort with descending order, just replace '>' with '<'
I want to print out a list of events in one day selected on a calendar made using FSCalendar. So far I have written this in my app:
while counter2 < noOfEvents{
print("counter[counter2]: ", counter[counter2])
cell.eventName.text = eventsArray[counter[counter2]].nameOfEvent
cell.eventDescription.text = eventsArray[counter[counter2]].descriptionEvent
cell.countryName.text = eventsArray[counter[counter2]].country
cell.eventDate.text = eventsArray[counter[counter2]].dateEvent
cell.eventTime.text = eventsArray[counter[counter2]].timeEvent
print("eventsArray[counter[counter2]].nameOfEvent: ", eventsArray[counter[counter2]].nameOfEvent)
counter2 += 1
}
My question is how to make the list display both events and not just the latest one. In the logs both are clearly there but in the actual list on the app only the latest one is displayed. Any helpp is appreciated
// MARK: - Table view data source
// Set the number of sections in the table view (assumes 1 if simple list of events)
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// Set the number of events
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventsArray.count
}
// Provide each cell when requested
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CalendarCell2", for: indexPath) as! CalendarCell2
// Configure the cell...
if let event = eventsArray[indexPath.row] {
// Set the appropriate elements in the cell
}
return cell
}
Note that if the number of events changes you'll want to update the table view by the reloadData() function.
I am currently using a tableview to display a individual "Posts." In each tableview cell is a unique Post. I would like to add a collection of "comments" into each cell. The only way I can think about collecting comments is to add another TableView inside the Post's cell. What is some preferred methods to accomplishing this? It seems pretty complicated to use a TableView inside another TableViews Cell.
I don't think it's a good idea.
Create N sections in your table view, one section for every post.
For every section
the cell at row 0 will be populated with the post data
every cell in the following rows will be populated with the comments data.
Example
struct PostModel {
let title: String
let comments: [String]
}
class Posts: UITableViewController {
private var posts: [PostModel] = ... // TODO
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return posts.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts[section].comments.count + 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = posts[indexPath.section]
let cell = tableView.dequeueReusableCellWithIdentifier("StandardCell") ?? UITableViewCell(style: .Default, reuseIdentifier: "StandardCell")
if indexPath.row == 0 {
cell.textLabel?.text = post.title
return cell
} else {
let comment = post.comments[indexPath.row-1]
cell.textLabel?.text = comment
return cell
}
}
}
Is there a way to reset tableview height so that no empty rows are showed. For an example, a tableview displays 3 rows but there is only one row having real data. I'd like the tableview shrinks it size so there is only one row display
I guess you have a View Controller like this
class Controller: UITableViewController {
private var data: [String?] = ["One", nil, "Three", nil, nil]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID")!
cell.textLabel?.text = data[indexPath.row]
return cell
}
}
Since your model (data in my example) contains nil values you are getting a table view like this
One
_
Three
_
_
Removing the empty rows
Now you want instead a table view like this right?
One
Three
Filtering your model
Since the UI (the table view) is just a representation of your model you need to change your model.
It's pretty easy
class Controller: UITableViewController {
private var data: [String?] = ["One", nil, "Three", nil, nil]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID")!
cell.textLabel?.text = data[indexPath.row]
return cell
}
override func viewDidLoad() {
data = data.filter { $0 != nil }
super.viewDidLoad()
}
}
As you can see, inside viewDidLoad() I removed the bill values from the data array. Now you'll get only cells for real values.
I think you need remove the empty data before execute func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int.
//EDITED
I give you this possible solution, maybe there are more ways to do it, you can try with this.
class MyViewController: UITableViewDataSource, UITableViewDelegate {
private var realHeight = 0
private var data : [String] = []
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID")!
cell.textLabel?.text = data[indexPath.row]
self.realHeight += self.tableView.rowHeight //Increase real height value.
return cell
}
func loadData() { //This function reload the data
self.realHeight = 0
self.tableView.reloadData()
//Update tableViewHeight here, use self.realHeight. You can use constraints or update directly the frame.
}
}
The solution is have a counter that represents the sum of all visibles rows height. e.g If you have n cells then your height will be self.tableView.rowHeight * n, then you ever will have an effective height.
I recommend you to create a constraint for the table height and when the cells change, you only need change the constant of the constraint, is more easy than change the frame.
Imagine a table view controller ExtraRowTableViewController,
which always inserts an extra row, after (let's say) the third row.
So in this example ...
class SomeList:ExtraRowTableViewController
override func numberOfSectionsInTableView(tableView: UITableView)->Int
{
return yourData.count ... say, 50 items
}
override func tableView
(tableView:UITableView, cellForRowAtIndexPath indexPath:NSIndexPath)
-> UITableViewCell
{
return yourData.cell ... for that row number
}
ExtraRowTableViewController would "take over" and actually return 51.
For cellForRowAtIndexPath, it would "take over" and return its own cell at row four, it would return your cell row N from 0 to 3, and it would return your cell row minus one for rows above four.
How can this be achieved in ExtraRowTableViewController ?
So that the programmer of SomeList need make no change at all.
Would you be subclassing UITableView, or the data source delegate .. or??
To clarify, an example use case might be, let's say, adding an ad, editing field, or some special news, at the fourth row. It would be appropriate that the programmer of SomeList need do absolutely nothing to achieve this, ie it is achieved in a completely OO manner.
Note that it's, of course, easy to just add new "substitute" calls, which your table view would "just know" to use instead of the normal calls. (RMenke has provide a useful full example of this below.) So,
class SpecialTableViewController:UITableViewController
func tableView(tableView: UITableView, specialNumberOfRowsInSection section: Int) -> Int
{
print ("You forgot to supply an override for specialNumberOfRowsInSection")
}
func tableView
(tableView:UITableView, specialCellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell
{
print ("You forgot to supply an override for specialCellForRowAtIndexPath")
}
override final func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.specialNumberOfRowsInSection(section) + 1
}
override final func tableView
(tableView:UITableView, cellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell
{
if indexPath.row == 4
{ return ... the special advertisement cell ... }
if indexPath.row < 4
{ return self.specialCellForRowAtIndexPath( indexPath )
if indexPath.row > 4
{ return self.specialCellForRowAtIndexPath( [indexPath.row - 1] )
}
In the example your table view programmer would have to "just know" that they must use specialNumberOfRowsInSection and specialCellForRowAtIndexPath in SpecialTableViewController rather than the usual calls ... it's not a clean, drop-in, OO solution.
Note: I appreciate you could probably subclass NSObject in some way to override the signals (such as discussed here), but that is not a language solution.
github link -> might contain more updated code
To answer the question: It is not possible to override the standard flow of the functions between the UITableViewController and the UITableViewDataSource in the form of a subclass.
The UIKit source code is like a big black box which we can not see or alter. (apps will be rejected if you do.) To do exactly what you want you would need to override the functions that call on the functions from the UITableViewDataSource so they point to a third function instead of to the protocol functions. This third function would alter the basic behaviour and trigger the function from the UITableViewDataSource. This way it would all stay the same for other devs.
Hack : Subclass the entire UITableviewController -> you need stored properties. This way other people can subclass your custom class and they won't see any of the magic/mess under the hood.
The class below uses the same style as the regular UITableViewController. Users override the methods they wish to alter. Because those methods are used inside the existing function you get an altered functionality.
Unfortunately it is not possible to mark those functions as private.
The adapter for the indexPath stores a Bool and the original indexPath. -> This will correspond to your data.
The new inserted cells will get an indexPath based on the section they are created in and a counter. -> Could be useful.
Update: Add x extra rows after y rows
class IATableViewController: UITableViewController {
private var adapters : [[cellAdapter]] = []
private struct cellAdapter {
var isDataCell : Bool = true
var indexPath : NSIndexPath = NSIndexPath()
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func cellIdentifier(tableView: UITableView, isDataCell: Bool) -> String {
return "Cell"
}
func numberOfSections(tableView: UITableView) -> Int {
return 0
}
func numberOfRowsInSection(tableView: UITableView, section: Int) -> Int {
return 0
}
func insertXRowsEveryYRows(tableView: UITableView, section: Int) -> (numberOfRows:Int, everyYRows:Int)? {
//(numberOfRows:0, everyYRows:0)
return nil
}
func insertXRowsAfterYRows(tableView: UITableView, section: Int) -> (numberOfRows:Int, afterYRows:Int)? {
//(numberOfRows:0, afterYRows:0)
return nil
}
internal override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
let sections = numberOfSections(tableView)
adapters = []
for _ in 0..<sections {
adapters.append([])
}
return sections
}
internal override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
let rows = numberOfRowsInSection(tableView, section: section)
adapters[section] = []
for row in 0..<rows {
var adapter = cellAdapter()
adapter.indexPath = NSIndexPath(forRow: row, inSection: section)
adapter.isDataCell = true
adapters[section].append(adapter)
}
insertion(tableView, section: section)
return adapters[section].count
}
private func insertion(tableView: UITableView, section: Int) {
if let insertRowEvery = insertXRowsEveryYRows(tableView, section: section) {
let insertionPoint = insertRowEvery.everyYRows
let insertionTimes = insertRowEvery.numberOfRows
var counter = 0
var startArray = adapters[section]
var insertionArray: [cellAdapter] = []
while !startArray.isEmpty {
if startArray.count > (insertionPoint - 1) {
for _ in 0..<insertionPoint {
insertionArray.append(startArray.removeFirst())
}
for _ in 0..<insertionTimes {
var adapter = cellAdapter()
adapter.indexPath = NSIndexPath(forRow: counter, inSection: section)
adapter.isDataCell = false
insertionArray.append(adapter)
counter += 1
}
} else {
insertionArray += startArray
startArray = []
}
}
adapters[section] = insertionArray
}
else if let insertRowAfter = insertXRowsAfterYRows(tableView, section: section) {
let insertionPoint = insertRowAfter.afterYRows
let insertionTimes = insertRowAfter.numberOfRows
if adapters[section].count > (insertionPoint - 1) {
for i in 0..<insertionTimes {
var adapter = cellAdapter()
adapter.indexPath = NSIndexPath(forRow: i, inSection: section)
adapter.isDataCell = false
adapters[section].insert(adapter, atIndex: insertionPoint)
}
}
}
}
func insertionCellForRowAtIndexPath(tableView: UITableView, cell: UITableViewCell, indexPath: NSIndexPath) -> UITableViewCell {
return cell
}
func dataCellForRowAtIndexPath(tableView: UITableView, cell: UITableViewCell, indexPath: NSIndexPath) -> UITableViewCell {
return cell
}
internal override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let adapter = adapters[indexPath.section][indexPath.row]
let identifier = cellIdentifier(tableView, isDataCell: adapter.isDataCell)
let cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath)
switch adapter.isDataCell {
case true :
return dataCellForRowAtIndexPath(tableView, cell: cell, indexPath: adapter.indexPath)
case false :
return insertionCellForRowAtIndexPath(tableView, cell: cell, indexPath: adapter.indexPath)
}
}
}