Separate array from core-data in tableView Sections - ios

I'm trying to sort array/names from my first TableViewController using core data to pass the data to my second UITableViewController, I have successfully been able to implement the number of sections and the correct number of rows for each section that the user needs by dividing the number of players with the number of sections.
However, I've been having problems separating the array of players continuously in each section. Cannot add the image of what the simulator looks like so I'll try to explain.
User types 10 names inside the first tableView, then selects the number of teams using a stepper(2 teams), lastly, they click on the team-up UIButton which divides 10(# players) by 2(number of teams) so on the second tableView two Sections will appear with 5 players but both Sections repeat the first 5 names.
I would like for the first Section to display the first 5 names of the array and the second Section to display the last 5 names instead of repeating the first 5 names on every Section regardless of how many players per Section the user chooses. I've been stuck for 4 days and have tried loops and Extensions and I cannot find a way for the rest of the sections to tap in the middle of the array of names, Please Help!! and thank you.
Here is the code of my secondTableView where I think the problems are either inside my tableView cellForRowAt or my loadedShuffledPlayers() function,
Note: Players comes from core data
import UIKit
import CoreData
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
loadedShuffledPlayers()
tableView.reloadData()
}
var players2 = [Players]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var numberOfTeams = Int()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return players2.count / numberOfTeams
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "shuffledNames", for: indexPath)
cell.textLabel?.text = players2[indexPath.row].names
return cell
}
func loadedShuffledPlayers(){
let request: NSFetchRequest<Players> = Players.fetchRequest()
do{
players2 = try context.fetch(request).shuffled()
}catch{
print("Error fetching data .\(error)")
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return numberOfTeams
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Team # \(section + 1)"
}
}

The problem is here
cell.textLabel?.text = players2[indexPath.row].names
You are only looking at the row number and are ignoring the section number. So with your example of 10 and 2 the row will always be between 0 and 4.
So you need to do something like (not tested):
let rowsPerSection = players2.count / numberOfTeams
let rowInSection = indexPath.row + rowsPerSection * indexPath.section
cell.textLabel?.text = players2[rowInSection].names

Related

How can I divide my table view data in sections alphabetically using Swift? (rewritten)

I have a data source in this form:
struct Country {
let name: String
}
The other properties won't come into play in this stage so let's keep it simple.
I have separated ViewController and TableViewDataSource in two separate files. Here is the Data source code:
class CountryDataSource: NSObject, UITableViewDataSource {
var countries = [Country]()
var filteredCountries = [Country]()
var dataChanged: (() -> Void)?
var tableView: UITableView!
let searchController = UISearchController(searchResultsController: nil)
var filterText: String? {
didSet {
filteredCountries = countries.matching(filterText)
self.dataChanged?()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredCountries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let country: Country
country = filteredCountries[indexPath.row]
cell.textLabel?.text = country.name
return cell
}
}
As you can see there is already a filtering mechanism in place.
Here is the most relevant part of the view controller:
class ViewController: UITableViewController, URLSessionDataDelegate {
let dataSource = CountryDataSource()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.tableView = self.tableView
dataSource.dataChanged = { [weak self] in
self?.tableView.reloadData()
}
tableView.dataSource = dataSource
// Setup the Search Controller
dataSource.searchController.searchResultsUpdater = self
dataSource.searchController.obscuresBackgroundDuringPresentation = false
dataSource.searchController.searchBar.placeholder = "Search countries..."
navigationItem.searchController = dataSource.searchController
definesPresentationContext = true
performSelector(inBackground: #selector(loadCountries), with: nil)
}
The loadCountries is what fetches the JSON and load the table view inside the dataSource.countries and dataSource.filteredCountries array.
Now, how can I get the indexed collation like the Contacts app has without breaking all this?
I tried several tutorials, no one worked because they were needing a class data model or everything inside the view controller.
All solutions tried either crash (worst case) or don't load the correct data or don't recognise it...
Please I need some help here.
Thank you
I recommend you to work with CellViewModels instead of model data.
Steps:
1) Create an array per word with your cell view models sorted alphabetically. If you have data for A, C, F, L, Y and Z you are going to have 6 arrays with cell view models. I'm going to call them as "sectionArray".
2) Create another array and add the sectionArrays sorted alphabetically, the "cellModelsData". So, The cellModelsData is an array of sectionArrays.
3) On numberOfSections return the count of cellModelsData.
4) On numberOfRowsInSection get the sectionArray inside the cellModelsData according to the section number (cellModelsData[section]) and return the count of that sectionArray.
5) On cellForRowAtindexPath get the sectionArray (cellModelsData[indexPath.section]) and then get the "cellModel" (sectionArray[indexPath.row]). Dequeue the cell and set the cell model to the cell.
I think that this approach should resolve your problem.
I made a sample project in BitBucket that could help you: https://bitbucket.org/gastonmontes/reutilizablecellssampleproject
Example:
You have the following words:
Does.
Any.
Visa.
Count.
Refused.
Add.
Country.
1)
SectionArrayA: [Add, Any]
SectionArrayC: [Count, Country]
SectionArrayR: [Refused]
SectionArrayV: [Visa]
2)
cellModelsData = [ [SectionArrayA], [SectionArrayC], [SectionArrayR], [SectionArrayV] ]
3)
func numberOfSections(in tableView: UITableView) -> Int {
return self.cellModelsData.count
}
4)
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionModels = self.cellModelsData[section]
return sectionModels.count
}
5)
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionModels = self.cellModelsData[indexPath.section]
let cellModel = sectionModels[indexPath.row]
let cell = self.sampleCellsTableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier",
for: indexPath) as! YourCell
cell.cellSetModel(cellModel)
return cell
}

Returning new Cell when new data is received in Swift

So I have come to a tiny stop in my app. I am currently working with a tableview to display some data that is being sent from a Arduino. Now I am manually sending it one byte array at a time to simulate, but it will eventually send a lot. Currently the app displays the data just fine, like I want it too, but I can't make it display the data in a new cell, each time I click send from the Arduino.
So in the numberOfRowsInSection it will return 100 cells of the same data. I want it to return 1 cell every time I send it from the Arduino. So if I click send 10 times, I want to display 10 cells, of the data that was sent.
Currently I have used: return recievedBytes.count, but that only counts each byte in the array. But I want a new cell, EVERYTIME a new byte array is received.
Do anyone know what I would need to return in order to do that?
Shoutout if anything is unclear.
Thanks guys
Here is the tableview code:
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100 //THIS IS WHERE I NEED HELP :)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RecieveCell", for: indexPath) as! RecieveTableCell
cell.rowNumber.text = "\(indexPath.row + 1)."
cell.modeLabel.text = "\(recievedModeType)"
cell.timeLabel.text = "\(String(message))μs"
return cell
}
EDIT:
OK guys, I think I should write in some more, since I think I mislead you a bit. I've tried out what you suggested but its not quite what I was thinking. I see now I wrote it a bit misleading.
For example. I am sending from the Arduino this: [0x11,0x22,0x33,0x44,0x55,0x66,0x77]
In this I can display a MODE(recievedModeType) and a TIME(message) in the table view.
Doing what you guys suggested, I am now returning 7 cells, with one element in each cell. Because of recievedBytes.count. Its not quite what I was thinking.
What I want is to display Mode and Time in one cell, by sending that array. And it will continue to display in more cells, as long as its being sent. So in a sense, if 50 of these arrays are being sent, then I would like to have 50 cells representing the MODE and TIME.
But I will continue to look more on this now..
My apologies for the confusion.
Thanks!
If you want to keep track of the arrays you are are receiving you can use another array
var receivedArrays: [[UInt8]] = []
var receivedBytes: [UInt8] = [] {
didSet {
receivedArrays.append(receivedBytes)
tableView.reloadData()
}
}
Then you can return receivedCount as numberOfRows and use your array of receivedBytes as you wish in your cellForRowAt function.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return receivedArrays.count
}
basically, if you are aiming to fill the table view with dynamically, I would recommend to keep following the approach of: return recievedBytes.count, which means keep using recievedBytes as the data resource for filling the table view.
but that only counts each byte in the array. But I want a new cell,
EVERYTIME a new byte array is received.
What you could do to resolve it is:
update recievedBytes array.
call the reloadData() method.
Although I am unaware of what is the exact type of recievedBytes, let's consider that is it [Int] to review an example:
class ViewController: UIViewController {
// ...
var recievedBytes: [Int] = [] {
didSet {
tableView.reloadData()
}
}
// ...
}
extension ViewController: UITableViewDataSource {
// If the number of section should be 1, you don't have to implement numberOfSections
// and let it return 1, it is the default value for it.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recievedBytes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RecieveCell", for: indexPath) as! RecieveTableCell
let currentByte = recievedBytes[indexPath.row]
cell.rowNumber.text = "\(indexPath.row + 1)."
cell.modeLabel.text = "\(currentByte)"
cell.timeLabel.text = "\(String(message))μs"
return cell
}
}
Note that:
As a good practice, for such a case it is recommended to declare recievedBytes as a property observer, each time recievedBytes gets updated tableView.reloadData() will get called.
recievedBytes should be also reliable when dealing with cellForRowAt method, currentByte should be the byte in recievedBytes based on the current row, therefore you could display its value.

How to Paginate when the table data is not pulled from server?

I searched around for pagination but everything seems to point to pulling data from a server:
Pagination
Pagination
In my situation my data arrays are filled with static items wherein as I already know the count of what's inside of each array. My tabBar has three tabs and each tab has a tableVIew with different amounts of data
tableDataForTabOne = [DataModel]() // the array has 1000 items in it
tableDataForTabTwo = [DataModel]() // the array has 690 items in it
tableDataForTabThree = [DataModel]() // the array has 7 items in it
How do I paginate the arrays for the tableView into different pages? For example the first 10 items is 1 page, the next 10 items is another page, etc etc?
The question has nothing to do with the tabs. I don't know how to paginate on a tableView without pulling data from a server.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak fileprivate var tableView: UITableView!
let tableDataForTabTwo = [DataModel]() //has 690 items in it
var pageNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
...
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableDataForTabTwo.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
cell.titleLabel.text = tableDataForTabTwo[indexPath.row]."some static title"
cell.imageView.image = tableDataForTabTwo[indexPath.row].UIImage(named: "some static image")
return cell
}
}

Filling UITableView cell from remote database

I am facing an issue with UITableView.
I want to dynamically fill its cells with data fetched from a remote database, so it takes some times before the data arrived.
Here is the code:
class MailBoxViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var users: [NSDictionary] = []
override func viewDidLoad() {
super.viewDidLoad()
// call to rest API code to get all users in [NSDictionary]
(...)
// set table view delegate and data source
self.tableView.delegate = self
self.tableView.dataSource = self
}
// set number of sections within table view
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
// set number of rows for each section
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return self.users.count
}
return 0
}
// set header title for each section
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Users"
}
}
// set cell content for each row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// deque reusable cell
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as UITableViewCell
// set item title
if indexPath.section == 0 {
cell.textLabel?.text = self.users[indexPath.row]["firstname"] as? String
}
return cell
}
}
The problem is that when tableView functions are called to set number of rows for each section and to set cell content for each row, my [NSDictionary] users is still empty.
How could I do to set rows and cells only after my call to rest API code to get all users in [NSDictionary] is finished?
Thank you for any feedback.
Regards
When you get the response from the API, you should set self.users = arrayOfUsersYouReceivedFromServer and then call self.tableView.reloadData(). This
After users is populated, call tableView.reloadData(). This will call your data source methods again.
When you're done fetching the users call tableView.reloadData().

UITableView Section is Repeating Data

This has been killing me for a few hours now. I have a UITableViewController that has multiple data sections. My data source is simply an Array.
The problem I'm running into is that each section is repeating data from the array starting from the first index instead of "slicing" it as I expect it should.
Simplified example:
let sections = ["Section A", "Section B"]
let counts = [3, 5]
let source = ["a","b",c","d","e","f","g","h"]
// Output in simulator:
# Section A
- a
- b
- c
# Section B
- a
- b
- c
- d
- e
- and so on...
I would expect that "Section B" would be the next 5 results starting at "d" and not restart from the first index.
The relevant code is pretty standard stuff:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count // returns 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return counts[section] // returns correct data
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let data = source[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! MyTableViewCell
// some cell formatting, populate UILabels, etc
cell.testLabel.text = data["test"].string
return cell
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("Header") as! MyTableViewHeaderCell
headerCell.backgroundColor = UIColor.grayColor()
headerCell.testHeaderLabel.text = sections[section]
return headerCell
}
Initial searching of SO led me to believe it's a cell reuse issue but after overriding prepareForReuse in my cell class, I don't think thats it.
Expected Results
# Section A
- a
- b
- c
# Section B
- d
- e
- f
- g
- h
Like I said, I'm expecting that dividing the TableView data in to sections would keep a reference to the array pointer and continue where it left off instead of starting back at 0 for each section.
Any thoughts or suggestions?
indexPath.row always returns the row-number inside a section.
In your second section, you need to add the number of rows displayed in all sections before.
Change let data = source[indexPath.row] to something like this:
let data = source[indexPath.row+counts[0]]
If you add more sections, this will be a bit more complicated to calculate.
Other idea:
If it is possible, you could rearrange your array. You could make a two-dimensional array. The main array would include arrays with the data for each section.
To display it, you' need to use indexPath.section, too.
dataArray[indexPath.section][indexPath.row]
Using the idea of FelixSFD, but with a little logical modification, so you can work dynamically:
Change this:
let data = source[indexPath.row]
for this:
var countIndex = indexPath.row
for section in 0...indexPath.section {
countIndex += counts[section]
}
let data = source[countIndex]
Be careful with this approach because you may have some performance issues on large tableViews.
If you can rearrange your array:
change
let source = ["a","b",c","d","e","f","g","h"]
into
let source = [["a","b","c"],["d","e","f","g","h"]]
and change
let data = source[indexPath.row]
into
let data = source[indexPath.section][indexPath.row]
I had the same problem, but with more complex situation, and i needed more dynamically way of doing it. Sure i could rearrange my data, to use two-dimensional array, but i don't want to handle it later. So i did it like this.
I am pulling my data from firebase, so i never know, how many sections/arrays i will have.
Creating an array, to insert amount of items in array.
var counterTableView = [Int]()
Filling array with 0, without doing it, i was getting errors later. (Index out of range)
override func numberOfSections(in tableView: UITableView) -> Int {
for i in 0...Array(Set(self.sections)).count {
counterTableView.insert(0, at: i)
}
counterTableView.removeLast(counterTableView.count-Array(Set(self.sections)).count-1)
return Array(Set(self.sections)).count
}
Next step, is to fill the amount of items in one section in array
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
counterTableView[section+1] = counts[section] + counterTableView[section]}
Last step, showing the data in cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.textLabel.text = source[indexPath.row+counterTableView[indexPath.section]]}

Resources