Bind UITableView with Combine DataSource - swift

I want to directly link a UITableView with a #Published attribute without using DiffableDataSouce.
If I make the person
struct Person {
let name: String
}
and create the data array:
#Published
var people = [Person(name: "Kim"), Person(name: "Charles")]
So I want to bind my UITableView directly, with something like:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return $people.count()
}
But this gives the error
Cannot convert return expression of type 'Publishers.Count<Published[Person]>.Publisher>' to return type 'Int'

The problem here is that the UITableViewDataSource is pull based (the framework pulls data from your code) but Publishers are push based (they push data to something.) That means that in order to make it work, you need a Mediator (a la the Mediator pattern.)
One option would be to bring in RxSwift/RxCocoa and the RxCombine project to translate between Combine and RxSwift and use the functionality where this already exists. That's a lot for this one ask, but maybe you have other areas where RxCocoa could streamline your code as well.
For just this ask, here is a Mediator that I think would work:
#available(iOS 13.0, *)
final class ViewController: UIViewController {
var tableView: UITableView = UITableView()
#Published var people = [Person(name: "Kim"), Person(name: "Charles")]
var cancellable: AnyCancellable?
override func viewDidLoad() {
super.viewDidLoad()
tableView.frame = view.bounds
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
view.addSubview(tableView)
cancellable = $people.sink(receiveValue: tableView.items { tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = item.name
return cell
})
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.people = [Person(name: "Mark"), Person(name: "Allison"), Person(name: "Harold")]
}
}
}
extension UITableView {
func items<Element>(_ builder: #escaping (UITableView, IndexPath, Element) -> UITableViewCell) -> ([Element]) -> Void {
let dataSource = CombineTableViewDataSource(builder: builder)
return { items in
dataSource.pushElements(items, to: self)
}
}
}
class CombineTableViewDataSource<Element>: NSObject, UITableViewDataSource {
let build: (UITableView, IndexPath, Element) -> UITableViewCell
var elements: [Element] = []
init(builder: #escaping (UITableView, IndexPath, Element) -> UITableViewCell) {
build = builder
super.init()
}
func pushElements(_ elements: [Element], to tableView: UITableView) {
tableView.dataSource = self
self.elements = elements
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
build(tableView, indexPath, elements[indexPath.row])
}
}

Related

Is this ok when implementing multiple table views in Swift5?

I'm implementing two table views on a single view controller in Swift 5. I realise this can be done with a single extension to the view controller by identifying the table views by name. However, I'd like to keep the code separate, and so I've done it as shown in this simple example here - this has involved looking at several examples on the web and so a general thank you to all who post these things! The main one is the answer by Girish Ghoda here:
Two tables on one view in swift
It all seems to work, but I'm wondering if I'm breaking any important rules...
There are two table views on the view controller, with simple constraints and outlets tableView1 and tableView2.
This is the ViewController.swift file
import UIKit
var array1 = ["one", "two", "three"]
var array2 = ["left", "right", "centre", "outside"]
class ViewController: UIViewController {
#IBOutlet weak var tableView1: UITableView!
#IBOutlet weak var tableView2: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
initTableViews()
}
var dataSource1: DataSource1!
var dataSource2: DataSource2!
func initTableViews() {
dataSource1 = DataSource1()
tableView1.dataSource = dataSource1
tableView1.delegate = dataSource1
dataSource2 = DataSource2()
tableView2.dataSource = dataSource2
tableView2.delegate = dataSource2
}
}
There are then two files:
TableViewClass1.swift
import UIKit
class DataSource1: NSObject, UITableViewDataSource, UITableViewDelegate {
override init(){
super.init()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array1.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = array1[indexPath.row]
return cell
}
}
TableViewClass2.swift:
import UIKit
class DataSource2: NSObject, UITableViewDataSource, UITableViewDelegate {
override init(){
super.init()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array2.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = array2[indexPath.row]
return cell
}
}
As I say, everything seems ok, so it may seem strange to ask about it, but while the approach seems to give simple code structure, I'm wondering if there is anything here that could lead to problems.
Many thanks,
Ian
It's fine, but if both classes are that redundant I would use one DataSource class with a convenience initializer and lazy instantiated dataSource properties
class DataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
var array : [String]
let cellIdentifier : String
init(array: [String], cellIdentifier : String )
{
self.array = array
self.cellIdentifier = cellIdentifier
super.init()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = array[indexPath.row]
return cell
}
}
lazy var dataSource1: DataSource = {
return DataSource(array: array1, cellIdentifier: "cell1")
}()
lazy var dataSource2: DataSource = {
return DataSource(array: array2, cellIdentifier: "cell2")
}()
func initTableViews() {
tableView1.dataSource = dataSource1
tableView1.delegate = dataSource1
tableView2.dataSource = dataSource2
tableView2.delegate = dataSource2
}

macOS fatal error: Index out of range NSTableView

I am developing a macOS application. i have a problem with a tableView that load an array of products from a web server. The products are correctly received by the app (i see them in console). But i have these problems:
1) i can't see my products text descriptions in my tableView cells;
2) when i click on a single cell the app crash with this error message:
fatal error: Index out of range
Here is my code:
import Cocoa
import AppKit
class Controller: NSViewController {
#IBOutlet var tableViewa: NSTableView!
var products = [Product]()
override func viewDidLoad() {
super.viewDidLoad()
title = "Table list"
tableViewa.reloadData()
}
override func viewDidAppear() {
reload()
tableViewa.backgroundColor = NSColor.red
tableViewa.allowsEmptySelection = true
tableViewa.gridColor = NSColor.red
}
func reload() {
products = []
tableViewa.reloadData()
tableViewa.backgroundColor = NSColor.red
Products.store.requestProducts{success, products in
if success {
self.products = products!
self.tableViewa.reloadData()
}else {
print("download failed")
}
self.tableViewa.reloadData()
}
}
extension MasterViewController: NSTableViewDataSource, NSTableViewDelegate {
func numberOfRows(in atableView: NSTableView) -> Int {
print("products number: \(products.count)")
return products.count
}
func tableViewSelectionDidChange(_ notification: Notification) {
let tas = IndexPath(item: products.count, section: 0)
let Cell = tableViewa.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"),owner: tas ) as! ProductCell
let product = products[(tas as NSIndexPath).item]//here crash my app
Cell.product = product
}
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
return true
}
func tableView(_ tableView: NSTableView, cellForRowAt indexPath: IndexPath) -> NSTableCellView{
let Cell = tableViewa.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"),owner: indexPath ) as! ProCell
let product = products[(indexPath as NSIndexPath).item]
Cell.product = product
Cell.textField?.stringValue = array.item.1
return Cell
}
}
How ya doing?
You can use the IndexPath init
init(row: Int, section: Int)
instead
init(item: Int, section: Int)
After that you recover the row that way
let product = products[(tas as NSIndexPath).row]
Explanation:
The item parameter is used to identify an item in a section from collection view and row to table view

Generics in Swift

I am learning about Generics in Swift. For me, this topic is quite hard to understand. In the book I am reading, there is 2 challenges on Generics:
1st challenge: it asks to write a function findAll(_:_:) that takes and array of any type T that conforms to the Equatable protocol and a single element (also of type T). findAll(_:_:) should return an array of integers corresponding to every location where the element was found in the array. For example, findAll([5,3,7,3,9], 3] should return [1,3].
2nd challenge: to modify findAll(_:_:) to accept a Collection instead of an array and it gives a hint "You will need to change the return type from [Int] to an array of an associated type of the Collection protocol"
This is what i have done for first challenge
func findAll<T:Equatable> (_ first: [T], _ second: T) -> [Int] {
var array = [Int]()
for i in 0..<first.count {
if first[i] == second {
array.append(i)
}
}
return array
}
For the second challenge, what i am thinking about is a generic function that I can pass a Collection (can be an Array, a Dictionary or a Set). But for Set type, as it does not have a defined ordering, how do you find location of an item in a Set?
Thank you.
The subscript method of collections is defined as
public subscript(position: Self.Index) -> Self.Iterator.Element { get }
which means that your function should take as arguments
a collection C, and
a value of the associated type C.Iterator.Element
and return an array of C.Index. In addition, the element type
should be Equatable:
func findAll<C: Collection> (_ collection: C, _ element: C.Iterator.Element) -> [C.Index]
where C.Iterator.Element: Equatable
{ ... }
Similar as in your solution for arrays, one can loop over the
collection's indices:
func findAll<C: Collection> (_ collection: C, _ element: C.Iterator.Element) -> [C.Index]
where C.Iterator.Element: Equatable
{
var result: [C.Index] = []
var idx = collection.startIndex
while idx != collection.endIndex {
if collection[idx] == element {
result.append(idx)
}
collection.formIndex(after: &idx)
}
return result
}
One would expect that something like
for idx in collection.startIndex ..< collection.endIndex
// or
for idx in collection.indices
works, but (in Swift 3) this requires an additional constraint
on the associated Indices type:
func findAll<C: Collection> (_ collection: C, _ element: C.Iterator.Element) -> [C.Index]
where C.Iterator.Element: Equatable, C.Indices.Iterator.Element == C.Index
{
var result: [C.Index] = []
for idx in collection.indices {
if collection[idx] == element {
result.append(idx)
}
}
return result
}
This is no longer necessary in Swift 4, see for example
Unable to use indices.contains() in a Collection extension in Swift 3 for a good explanation.
This can now be simplified using filter:
func findAll<C: Collection> (_ collection: C, _ element: C.Iterator.Element) -> [C.Index]
where C.Iterator.Element: Equatable, C.Indices.Iterator.Element == C.Index
{
return collection.indices.filter { collection[$0] == element }
}
Example (a collection of Character):
let chars = "abcdabcdabcd".characters
let indices = findAll(chars, "c")
for idx in indices {
print(chars[idx])
}
Set is a Collection as well, it has an associated Index
type and a subscript method. Example:
let set = Set([1, 2, 3, 4, 5, 6, 7, 8, 9])
let indices = findAll(set, 3)
for idx in indices {
print(set[idx])
}
Finally you might want to define the function as a method
on the Collection type:
extension Collection where Iterator.Element: Equatable, Indices.Iterator.Element == Index {
func allIndices(of element: Iterator.Element) -> [Index] {
return indices.filter { self[$0] == element }
}
}
// Example:
let indices = [1, 2, 3, 1, 2, 3].allIndices(of: 3)
In order to create a custom definition of a function, you should create an extension on the type which the function belongs to. From there, the override tag must be used so that you can create a custom implementation for the function. More specifically, to create a function that accepts a collection instead of an array, create an overridden version of the function that accepts a collection instead.
Also please show us what you've tried so far, instead of just saying I've tried several things.
Here are some links that may be useful:
Swift Documentation
Override function
Another simple example of an override is whenever a ViewContoller class is made, the viewDidLoad() method where view setup happens in is often overridden.
Generics was quite a headache for me when I started learning it in the beginning. Though after some dedicated research in this topic I came across [this] 1 nice tutorial which helped me understanding this topic little deeper. Here I'm sharing the demo code which I'd prepared while learning, hope that help someone.
Demo contains UITableview with different type of cells, each UITableview represents single UITableViewCell with associated Model. I've also added one Hybrid Tableview in order to mix different types of cell in single tableview.
Here is the code.
Creating Generic UITableViewCells first
protocol ProtocolCell {
associatedtype U
static var cellIdentifier:String { get }
func configure(item:U,indexPath:IndexPath)
}
class BaseCell<U>: UITableViewCell, ProtocolCell {
var item:U!
static var cellIdentifier: String {
return String(describing: self)
}
func configure(item:U,indexPath: IndexPath) {
textLabel?.text = "Row: \(indexPath.row), \tClass: \(U.self) OVERRIDE THIS !!"
backgroundColor = .red
}
//MARK:- INIT
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func commonInit(){
selectionStyle = .none
}
}
class StringCell: BaseCell<String> {
override func configure(item: String, indexPath: IndexPath) {
super.configure(item: item, indexPath: indexPath)
textLabel?.text = "Row: \(indexPath.row), \tClass: \(U.self), \tData: \(item)"
backgroundColor = UIColor.yellow.withAlphaComponent(0.3)
}
}
class DogCell: BaseCell<Dog> {
override func configure(item: Dog, indexPath: IndexPath) {
super.configure(item: item, indexPath: indexPath)
textLabel?.text = "Row: \(indexPath.row), \tClass: \(U.self), \tData: \(item.name)"
backgroundColor = UIColor.green.withAlphaComponent(0.3)
}
}
class CountryCell: BaseCell<Country> {
override func configure(item: Country, indexPath: IndexPath) {
super.configure(item: item, indexPath: indexPath)
textLabel?.text = "Row: \(indexPath.row), \tClass: \(U.self), \tData: \(item.name)"
backgroundColor = UIColor.magenta.withAlphaComponent(0.3)
}
}
Then creating Generic UITableViews
class BaseTableView<T_Cell:BaseCell<U_Model>,U_Model>: UITableView, UITableViewDelegate, UITableViewDataSource {
var arrDataSource = [U_Model]()
var blockDidSelectRowAt:((IndexPath) -> Void)?
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addIn(view:UIView) {
view.addSubview(self)
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
leftAnchor.constraint(equalTo: view.readableContentGuide.leftAnchor),
rightAnchor.constraint(equalTo: view.readableContentGuide.rightAnchor),
bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
}
func commonInit() {
delegate = self
dataSource = self
backgroundColor = .gray
layer.borderWidth = 2
layer.borderColor = UIColor.red.cgColor
register(T_Cell.self, forCellReuseIdentifier: T_Cell.cellIdentifier)
}
//MARK:- DATA SOURCE
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrDataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: T_Cell.cellIdentifier, for: indexPath) as! BaseCell<U_Model>
cell.configure(item: arrDataSource[indexPath.row], indexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
blockDidSelectRowAt?(indexPath)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
}
class DogTableView: BaseTableView<DogCell, Dog> {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
}
class StringTableView: BaseTableView<StringCell, String> {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
}
class CountryTableView: BaseTableView<CountryCell, Country> {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
class HybridTableView: BaseTableView<BaseCell<Any>, Any> {
// OVERRIDING DEFAULT BEHAVIOUR
override func commonInit() {
super.commonInit()
register(DogCell.self, forCellReuseIdentifier: DogCell.cellIdentifier)
register(StringCell.self, forCellReuseIdentifier: StringCell.cellIdentifier)
register(CountryCell.self, forCellReuseIdentifier: CountryCell.cellIdentifier)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let hybridItem = arrDataSource[indexPath.row]
switch hybridItem {
case is Dog:
let cell = tableView.dequeueReusableCell(withIdentifier: DogCell.cellIdentifier) as! DogCell
cell.configure(item: hybridItem as! Dog, indexPath: indexPath)
return cell
case is String:
let cell = tableView.dequeueReusableCell(withIdentifier: StringCell.cellIdentifier) as! StringCell
cell.configure(item: hybridItem as! String, indexPath: indexPath)
return cell
case is Country:
let cell = tableView.dequeueReusableCell(withIdentifier: CountryCell.cellIdentifier) as! CountryCell
cell.configure(item: hybridItem as! Country, indexPath: indexPath)
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: BaseCell<Any>.cellIdentifier) as! BaseCell<Any>
cell.configure(item: hybridItem, indexPath: indexPath)
return cell
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let hybridItem = arrDataSource[indexPath.row]
switch hybridItem {
case is Dog: return 70
case is String: return 140
case is Country: return 210
default: return 50
}
}
}
Models used in the tableview
struct Dog {
let name : String
}
struct Country {
let name : String
}
ViewController
class ViewController: UIViewController {
//MARK:- OUTLETS
#IBOutlet private weak var btn: UIButton!
private let tableViewDog = DogTableView()
private let tableViewString = StringTableView()
private let tableViewCountry = CountryTableView()
private let tableViewHybrid = HybridTableView()
//MARK:- VIEW LIFE CYCLE
override func viewDidLoad() {
super.viewDidLoad()
// doSetupUI()
doSetupUIHybrid()
}
//MARK:- UI SETUP
private func doSetupUI(){
tableViewDog.addIn(view: view)
tableViewDog.arrDataSource = [Dog(name: "Dog 1"),Dog(name: "Dog 2")]
// tableView.arrDataSource = ["First","Second"]
// tableView.arrDataSource = [Country(name: "India"),Country(name: "Nepal")]
tableViewDog.reloadData()
tableViewDog.blockDidSelectRowAt = { [unowned selff = self] indexPath in
print("DID SELECT ROW : \(indexPath.row), VALUE : \(selff.tableViewDog.arrDataSource[indexPath.row].name)")
}
}
private func doSetupUIHybrid(){
tableViewHybrid.addIn(view: view)
tableViewHybrid.arrDataSource = [Dog(name: "Dog1"),
"String1",
Country(name: "India"),
Dog(name: "Dog2"),
"String2",
Country(name: "Nepal")]
tableViewHybrid.reloadData()
tableViewHybrid.blockDidSelectRowAt = { [unowned selff = self] indexPath in
var itemToPrint = ""
let hybridItem = selff.tableViewHybrid.arrDataSource[indexPath.row]
switch hybridItem {
case is Dog: itemToPrint = (hybridItem as! Dog).name
case is Country: itemToPrint = (hybridItem as! Country).name
case is String: itemToPrint = (hybridItem as! String)
default: break
}
print("DID SELECT ROW : \(indexPath.row), VALUE : \(itemToPrint)")
}
}
}

Swift 3.0 Autocomplete Address For Search Bar

I am interested in using a tableView to list possible addresses based on inputs in the search bar. After selecting the cell that contains the address desired, the search bar text consists of the address, however I want the possible addresses (cells) to disappear. Does self.searchResultsTableView.reloadData() in didSelectRowAt clear all the cells or is there another command? I am not certain how to clear the cells after selecting the appropriate address without iterating and having the suggestion introduce more cells.
import UIKit
import MapKit
class SearchViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
var searchCompleter = MKLocalSearchCompleter()
var searchResults = [MKLocalSearchCompletion]()
var searchSource: [String]?
#IBOutlet weak var searchResultsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
searchCompleter.delegate = self
searchBar.delegate = self
}
}
extension SearchViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchCompleter.queryFragment = searchText
}
}
extension SearchViewController: MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
searchResults = completer.results
searchResultsTableView.reloadData()
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
// handle error
}
}
extension SearchViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let searchResult = searchResults[indexPath.row]
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = searchResult.title
cell.detailTextLabel?.text = searchResult.subtitle
return cell
}
}
extension SearchViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let completion = searchResults[indexPath.row]
let searchRequest = MKLocalSearchRequest(completion: completion)
let search = MKLocalSearch(request: searchRequest)
search.start { (response, error) in
let coordinate = response?.mapItems[0].placemark.coordinate
print(String(describing: coordinate))
print(response?.mapItems)
self.searchBar.text = response?.mapItems[0].name
}
self.searchResultsTableView.reloadData()
}
}
If you want to clear your tableView then you need to make your datasource array empty and then reload the tableView.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let completion = searchResults[indexPath.row]
let searchRequest = MKLocalSearchRequest(completion: completion)
let search = MKLocalSearch(request: searchRequest)
search.start { (response, error) in
let coordinate = response?.mapItems[0].placemark.coordinate
print(String(describing: coordinate))
print(response?.mapItems)
self.searchBar.text = response?.mapItems[0].name
}
//Make empty your array ant then reload tableView
searchResults = []
self.searchResultsTableView.reloadData()
}

Swift - when is data actually loaded in TableViewController

I'm very much a Swift beginner - am populating a table view from Firebase data.
In the table footer I want to display some calculated totals under the table columns. However when calling footerCell.configure(priceLines, isPortrait: isPortrait) the priceLines dictionary is still empty.
How to remedy this?
Thanks in advance, André Hartman, Belgium
import UIKit
import FirebaseDatabase
class ListTableViewController: UITableViewController {
var priceLines = [NSDictionary]()
var isPortrait = false
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ListTableViewController.rotated), name: UIDeviceOrientationDidChangeNotification, object: nil)
loadDataFromFirebase()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return priceLines.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("profileCell", forIndexPath: indexPath) as! PriceTableViewCell
cell.configure(priceLines, row: indexPath.row, isPortrait: isPortrait, source: "intraday")
return cell
}
override func tableView(tableView: UITableView,viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! CustomHeaderCell
headerCell.configure(isPortrait)
return headerCell
}
override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerCell = tableView.dequeueReusableCellWithIdentifier("FooterCell") as! CustomFooterCell
footerCell.configure(priceLines, isPortrait: isPortrait)
return footerCell
}
override func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 30.0
}
override func tableView (tableView:UITableView, heightForHeaderInSection section:Int) -> CGFloat
{
return 50.0;
}
// MARK:- Load data from Firebase
func loadDataFromFirebase() {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
refInter.observeEventType(.Value, withBlock: { snapshot in
var tempItems = [NSDictionary]()
for item in snapshot.children {
let child = item as! FIRDataSnapshot
let dict = child.value as! NSDictionary
tempItems.append(dict)
}
self.priceLines = tempItems
self.tableView.reloadData()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
})
}
func rotated()
{
let newDisplay = (UIDeviceOrientationIsLandscape(UIDevice.currentDevice().orientation))
if(newDisplay != isPortrait){
self.tableView.reloadData()
}
isPortrait = newDisplay
}
}
The documentation clearly says that
When the table view is about to appear the first time it’s loaded, the
table-view controller reloads the table view’s data.
So, it will reload the table automatically somewhere between viewDidLoad and viewWillAppear. Your priceLines is empty at this point and will be populated with data only when the closure in the method loadDataFromFirebase is fired. I'm not sure when it happens in your case, but as you call implicitly reloadData then you should have already priceLines nonempty (of course if the results in the closure have some data)