Swift - iOS - Multiple table view controllers sharing a single data source. - ios

I'd like to get started using swift to make a small list based application. I was planning on using two table view controllers to display the two lists, and was wondering if it were possible to have them share a common data source.
Essentially the data would just be an item name, and two integers representing the amount of the item owned vs needed. When one number increases, the other decreases, and vice versa.
I figured this might be easiest to do using a single data source utilized by both table view controllers.
I did some googling on shared data sources and didn't find anything too useful to help me implement this. If there are any good references for me to look at please point me in their direction!

You can create one data source class and use it in both view controllers:
class Item {
}
class ItemsDataSource: NSObject, UITableViewDataSource {
var items: [Item] = []
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
//setup cell
// ...
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
}
class FirstViewController : UITableViewController {
var dataSource = ItemsDataSource()
override func viewDidLoad() {
self.tableView.dataSource = dataSource
self.tableView.reloadData()
}
}
class SecondViewController : UITableViewController {
var dataSource = ItemsDataSource()
override func viewDidLoad() {
self.tableView.dataSource = dataSource
self.tableView.reloadData()
}
}

use singleton design pattern, it means both tables will get data source from the same instance
class sharedDataSource : NSObject,UITableViewDataSource{
static var sharedInstance = sharedDataSource();
override init(){
super.init()
}
//handle here data source
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
}
}
var tableOne = UITableView();
var tableTwo = UITableView();
tableOne.dataSource = sharedDataSource.sharedInstance;
tableTwo.dataSource = sharedDataSource.sharedInstance;

The first argument to the delegate method is:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
At that point, your one Datasource delegate can decide which table view is wanting a cell, for example, and return results accordingly.

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
}

Swift View Controller with UITableView sections

I've been searching for awhile without luck. I am trying to find an example of a View Controller with a UITableView that has sections. The examples I've see are all dealing with a Table View Controller which I cannot use as I have need of buttons in the same view which control the content of the table view. Anyone have an example, know of an example or have an idea about to implement such? Thanks.
Edit
I've got a table view in a view controller, get the data from an api call, separate the sections and data in an array of a struct. I then send this to be bound to the table view. Doing so throws
[UIView tableView:numberOfRowsInSection:]: unrecognized selector sent to instance
but I don't understand where the problem is.
Code for the tablview
//MARK: Tableview delegates
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let count = incidentDataSection?.count{
return count
}
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (incidentDataSection?.count)! > 0{
return incidentDataSection![section].incidents.count
}
return 0
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
/*
func tableView(tableView: UITableView, iconForHeaderInSection section: Int) -> UIImage? {
return incidentDataSection?[section].icon
}*/
//if clicked, will openn details view passing in the details
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//let incidentDetails = incidentData?[indexPath.row]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let section = incidentDataSection?[indexPath.section] {
let cell = tableView.dequeueReusableCell(withIdentifier: "IncidentTableViewCell") as! IncidentTableViewCell
cell.roadNameLabel.text = section.incidents[indexPath.row].RoadWay
cell.whenLabel.text = section.incidents[indexPath.row].DateCreated
cell.statusLabel.text = section.incidents[indexPath.row].DateCleared
return cell
}
return UITableViewCell()
}
incidentDataSection is an array of a struct which has the section title and the different items.
Answer
Though I received some fairly good feedback, the cause was actually a typo. Looking closely at
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
you'll notice the problem is that there is no underscore before tableView:. What was happening is that the datasource and delegate were skipping over the functions since with and without call different protocols in swift 3. Thanks to thislink I was able to figure out the cause. My bad for forgetting to mention this was in Swift 3. Might had saved everyone some time.
You need a tableview instance in your view controller.
Implement the protocols UITableViewDelegate, UITableViewDataSource in your view controller as a UITableViewController.
Don't forget bind the tableview in XIB with tableview in the class.
Look this sample:
class Sample01ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
tableView?.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.tableView?.reloadData()
}
// ...
You have the required methods implemented, however it sounds like you need to "subclass" or "subcribe" to the UITableView's delegate and dataSource. By using:
class MyViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView : UITableView!
}
Now that you have those protocols you will need to set your tableView's delegate and dataSource to your viewController. You can do this using storyboard by drag and drop, or inside of your viewDidLoad() which is what I always do because it is easy for other developers to see from the start of opening your code where your delegate and dataSources are assigned to. Using:
#override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
Then your delegate methods and dataSource methods in your viewcontroller will be called for that tableView. Then you can add the IBOutlets to UIButton/UILabel/UISwitch, etc... and do what you will with your ViewController without being limited to simply using a table view inside of that view controller. I Almost always use this methods when using UITableViews/UICollectionViews even if I set the tableView/collectionView to be the size of the whole view because I like the freedom of using a UIViewController over a UITableViewController/UICollectionViewController.
*Note numberOfRows() is not required but I always override it as well, just kind of a habit at this point. Also you sound new to iOS development, so if you aren't already, the next thing I would look into after getting your tableView up and running is pulling your data from your API on a background thread to keep your mainThread open for user response on your UI, DispatchQueue. This is really important if you are displaying images from the API.

Swift: How to load external array to my UITableView

I am learning Swift and I have pattern that I used to do in Objective C, but don't understand how to do it here.
I have UIViewController with TableView. I works fine when I put my array inside it. But according to MVC I want to move my array with data to another class. And I have no idea how to do it. Everything I tried doesn't work.
Thank you!
My code, how to move tableDS outside:
import UIKit
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
//temp table data
let tableDS = ["fdf", "dfd"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = self
tableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableDS.count
}
let textCellIdentifier = "TableViewCell"
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MyCell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as MyCell
let row = indexPath.row
cell.dayLabel.text = tableDS[row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
println(tableDS[row])
}
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel.text = tableDS[indexPath.row]
return cell
}
This should work.
If you want to use the MVC pattern, create a new singleton class, create the array there, then create a method returning the array.
First you need to initialize your table view with an empty array. When you load your MyViewController from another view controller in the code example below you can pass your data, and change your let tableDS = [“fdf”, “dfd”] to var tableDS = [“fdf”, "dfd"]. let is used for a constant variables.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "YourMyViewControllerSequeId" {
let myViewController = segue.destinationViewController as MyViewController
var myArrayToPass = ["learn swift", "or get a life"];
myViewController.tableDS = myArrayToPass
myViewController.tableView.reloadData()
}
}
In the MVC design pattern for a table view the table view is the view object. The controller is the view controller.
The model is whatever you use to store your data.
The controller object serves as an intermediary between the model and the view.
For a simple table view the model object can be a as simple as an array. The array is the model. Thus there is no reason to store the data in a separate object.
If you really want to make your model a completely different object, create a new class. Call it MyTableViewModel. Make your MyTableViewModel class contain an array of your data. Also make MyTableViewModel conform to the UITableViewDatasource protocol. To do that, you'll have to implement several methods - in particular, cellForRowAtIndexPath.
Now in your view controller, create a MyTableViewModel object as a strong property of your view controller, install the array in it, and make it the data source of the table view.
Done.
Again, though, it's quite common to just treat a simple array as your model, and let the view controller serve up cells by implementing cellForRowAtIndexPath in the view controller.

Swift - Array not being populated with values

I am trying to build a list within a table view controller and I have the proper setup, but for some reason my simulator crashes at the line where I set my array. It is not an error, but a Thread issue. I'm still learning the XCode warning system so I'm not sure what that means, but I noticed that in the Thread notifications that cityArray = ([String]) 0 values. Can anyone help?
import UIKit
class ListTableViewController: UITableViewController {
var cityArray: [String] = ["Portland","San Francisco","Cupertino"]
override func viewDidLoad() {
super.viewDidLoad()
}
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 cityArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = self.cityArray[indexPath.row]
return cell
}
}
UPDATE:
Images of the Thread message:
try this
let cityArray: [NSArray] = ["Portland","San Francisco","Cupertino"] as NSArray
Can you also provide the error you're receiving? Is it an issue with the array being mutated while being enumerated or something?
Also,
var cityArray: [String] = ["Portland","San Francisco","Cupertino"]
can change to
var cityArray = ["Portland", "San Francisco", "Cupertino"]
Since all your objects are of the same type, the Swift's type inference takes care of this for you.

Using UITableView with storyboard but separate datasource

EDIT Answer below in this post
I'm trying to set up a UITableView controller in storyboard, with a separate datasource, and I've hit a wall. The data source doesn't seem to respond to changes or push it's 'updates' to the table view. I've tried implementing the data source in the MainMenuTableViewController which worked fine.
This is my MainMenuTableViewController
override func viewDidLoad() {
super.viewDidLoad()
sharedLightsManager.delegate = self
sharedLightsManager.loadNetworkContext()
dataSource = MainMenuTableViewDataSource(sharedLightsManager: sharedLightsManager)
tableView.dataSource = dataSource
tableView.delegate = dataSource
title = "test"
}
//This method fires each time a change happens
func updateLights(){
lights = sharedLightsManager.localNetworkContext.allLightsCollection.lights
tableView.reloadData()
}
MainMenuDataSource:
class MainMenuTableViewDataSource: NSObject, UITableViewDataSource, UITableViewDelegate
{
let reuseIdentifier = "tableViewCell"
var sharedLightsManager: SharedLightsManager?
var lights = []
init(sharedLightsManager: SharedLightsManager)
{
self.sharedLightsManager = sharedLightsManager
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lights.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("tableViewCell", forIndexPath: indexPath) as UITableViewCell
var lights = sharedLightsManager!.localNetworkContext.allLightsCollection.lights
var light = LFXLight()
if lights.count == 0 {
println("Lights array still loading...")
} else {
light = lights[indexPath.row] as LFXLight
}
return cell
}
}
and here is my outlets:
I've just figured it out. A bit embarrassing. It was due to the lights array not having any objects in it, so obv. lights.count would return 0, therefore no rows...
The data source will not push updates unless the UITableView is told to reloadData. If you change the numberOfRows value, it will not update unless the tableView is notified through methods like insertRowAtIndexPath, reloadData, deleteRowAtIndexPath etc.

Resources