On the last question I asked about the code error in my animal table view project, now I finished the initial coding, but my UI turned really strange. It is missing the first letter of each animal name and the table view prototype cell.
For example, amel should be camel and hinoceros should be rhinoceros.
Is this a bug from the code here?
import UIKit
class AnimalTableViewController: UITableViewController {
var animalsDict = [String: [String]] ()
var animalSelectionTitles = [String] ()
let animals = ["Bear", "Black Swan", "Buffalo", "Camel", "Cockatoo", "Dog", "Donkey", "Emu", "Giraffe", "Greater Rhea", "Hippopotamus", "Horse", "Koala", "Lion", "Llama", "Manatus", "Meerkat", "Panda", "Peacock", "Pig", "Platypus", "Polar Bear", "Rhinoceros", "Seagull", "Tasmania Devil", "Whale", "Whale Shark", "Wombat"]
func createAnimalDict() {
for animal in animals {
let animalKey = animal.substringFromIndex(advance(animal.startIndex, 1))
if var animalValues = animalsDict[animalKey] {
animalValues.append(animal)
animalsDict[animalKey] = animalValues
} else {
animalsDict[animalKey] = [animal]
}
}
animalSelectionTitles = [String] (animalsDict.keys)
animalSelectionTitles.sort({ $0 < $1})
animalSelectionTitles.sort( { (s1:String, s2:String) -> Bool in
return s1 < s2
})
}
override func viewDidLoad() {
super.viewDidLoad()
createAnimalDict()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return animalSelectionTitles.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section:Int) -> String? {
// Return the number of rows in the section.
return animalSelectionTitles[section]
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
let animalKey = animalSelectionTitles[indexPath.section]
if let animalValues = animalsDict[animalKey] {
cell.textLabel?.text = animalValues[indexPath.row]
let imageFileName = animalValues[indexPath.row].lowercaseString.stringByReplacingOccurrencesOfString("", withString: "_", options: nil, range: nil)
cell.imageView?.image = UIImage(named:imageFileName)
}
return cell
}
}
So far I can say the error is in your createAnimalDict() method. In the line
let animalKey = animal.substringFromIndex(advance(animal.startIndex, 1))
exchange the second parameter in advance to 0 so it be:
let animalKey = animal.substringFromIndex(advance(animal.startIndex, 0))
In fact I don't really know what you are trying to do.
In this method:
override func tableView(tableView: UITableView, titleForHeaderInSection section:Int) -> String? {
// Return the number of rows in the section. (THIS COMMENT IS INCORRECT)
return animalSelectionTitles[section]
}
You are returning the title for each section. However your animalSelectionTitles[index] contains the animal name without the first letter due to its construction in createAnimalDict
Use the animal array instead for providing the full animal names:
override func tableView(tableView: UITableView, titleForHeaderInSection section:Int) -> String? {
return animals[section]
}
However note that due to the removal of the first letter you might have two animals mapping to the same key so if not necessary use the entire animal name as the key.
Related
How to add section headers and index list to UITableView in this use case?
#IBOutlet var tableView: UITableView!
var detail: Detail? = nil
var list = [tabledata]()
let search = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
list = [
tabledata(name:"something".localized, sort:"sort.something".localized, id:"something.html"),
tabledata(name:"somethingelse".localized, sort:"sort.somethingelse".localized, id:"somethingelse.html"),
...
]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "library", for: indexPath)
var data: tabledata
data = list[indexPath.row]
cell.textLabel!.text = data.name
return cell
}
Now the point is that table data are going to be translated.
Note that
name: is the actual cell name going to be .localized
sort: has to help characters like á é etc. in cell name to sort properly (avoiding them to show in the end of alphabet)
id: calls the html file location to display in detailViewController ('cause name has to be translated and we want a static text here)
A usual implementation of section headers and index list will result in something like
T // section header
translation // cell names
transmission
...
T // table in
Übersetzung // another language
Getriebe
...
What's the correct model for UILocalizedIndexedCollation?
.xcodeproj on my github. More info on demand.
Thanks for help!
Edit: it turns out that "getting the first letter of each row to use as the index" is much more complicated than I thought when accounting for multiple languages, especially non-Latin ones. I'm making use of UILocalizedIndexedCollation to simplify this task.
I think that UILocalizedIndexedCollation is more confusing than rolling your own data models. You need to 2 models: one to represent the row and one to represent the section:
// UILocalizedIndexedCollation uses a selector on the `name` property
// so we have to turn this data type in to a class.
class TableRow: NSObject {
#objc var name: String
var id: String
var sectionTitle = ""
init(name: String, id: String) {
self.name = name
self.id = id
}
}
// TableSection does not have to be a class but we are making it so
// that it's consistent with `TableRow`
class TableSection: NSObject {
var title: String
var rows: [TableRow]
init(title: String, rows: [TableRow]) {
self.title = title
self.rows = rows
}
}
After that, populating and filtering the table is very easy:
class Library: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
var detail: Detail? = nil
var list = [TableSection]()
var filter = [TableSection]()
let search = UISearchController(searchResultsController: nil)
let collation = UILocalizedIndexedCollation.current()
override func viewDidLoad() {
super.viewDidLoad()
// search
search.searchResultsUpdater = self as UISearchResultsUpdating
search.obscuresBackgroundDuringPresentation = false
search.searchBar.placeholder = "search".localized
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.searchController = search
definesPresentationContext = true
// Set the color of the index on the right of the table.
// It's settable from Interface Builder as well
tableView.sectionIndexColor = UIColor(red: 0, green: 122.0 / 255.0, blue: 1, alpha: 1)
// I took the liberty to add a few more items to the array
let rows = ["something", "somethingelse", "apple", "orange", "apricot", "strawberry"].map {
TableRow(name: $0.localized, id: $0)
}
list = organizeIntoSections(rows: rows)
tableView.reloadData()
}
// Organize rows into sections with titles
func organizeIntoSections(rows: [TableRow]) -> [TableSection] {
// Organize the rows into sections based on their `name` property
let selector: Selector = #selector(getter: TableRow.name)
// Sort the rows by `name`
let sortedRows = collation.sortedArray(from: rows, collationStringSelector: selector) as! [TableRow]
// Allocate rows into sections
var sections = collation.sectionTitles.map { TableSection(title: $0, rows: []) }
for row in sortedRows {
let sectionNumber = collation.section(for: row, collationStringSelector: selector)
sections[sectionNumber].rows.append(row)
}
// Remove empty sections
sections.removeAll(where: { $0.rows.isEmpty })
return sections
}
override func viewWillAppear(_ animated: Bool) {
if let selection = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: selection, animated: animated)
}
super.viewWillAppear(animated)
}
// MARK: - Table View
func numberOfSections(in tableView: UITableView) -> Int {
return filtering() ? filter.count : list.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
// If a section has no row, don't show its header
let data = filtering() ? filter[section] : list[section]
return data.rows.isEmpty ? nil : data.title
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filtering() ? filter[section].rows.count : list[section].rows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "library", for: indexPath)
let data = filtering() ? filter[indexPath.section].rows[indexPath.row]
: list[indexPath.section].rows[indexPath.row]
cell.textLabel!.text = data.name
return cell
}
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return filtering() ? filter.map { $0.title } : list.map { $0.title }
}
// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "transporter" {
if let indexPath = tableView.indexPathForSelectedRow {
let selected = filtering() ? filter[indexPath.section].rows[indexPath.row]
: list[indexPath.section].rows[indexPath.row]
let controller = (segue.destination as! Detail)
// This assumes you change `controller.result` to have type TableRow
controller.result = selected
}
}
}
// search filter
func filterContent(_ searchText: String) {
let query = searchText.lowercased()
filter = list.compactMap { section in
let matchingRows = section.rows.filter { $0.name.lowercased().contains(query) }
return matchingRows.isEmpty ? nil : TableSection(title: section.title, rows: matchingRows)
}
tableView.reloadData()
}
func searchEmpty() -> Bool {
return search.searchBar.text?.isEmpty ?? true
}
func filtering() -> Bool {
return search.isActive && (!searchEmpty())
}
}
Results:
English:
Italian:
Slovak:
(I got the translation from Google Translate so my apology in advance if any word is out of whack -- I cannot speak Italian or Slovak)
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)")
}
}
}
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)
I'm writing a test case for UIViewController that has UITableView. I want to ask how can I get number of rows in UITableView
func testloadingDataIntoUiTableView()
{
var countRow:Int = viewController.formListTableView.numberOfRowsInSection
XCTAssert(countRow == 4)
}
Introduction
Please keep in mind that the data model generates the UI. But you should not query the UI to retrieve your data model (unless we are talking about user input).
Lets look at this example
class Controller:UITableViewController {
let animals = ["Tiger", "Leopard", "Snow Leopard", "Lion", "Mountain Lion"]
let places = ["Maveriks", "Yosemite", "El Capitan"];
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0: return animals.count
case 1: return places.count
default: fatalError()
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID") else { fatalError("Who took the MyCellID cell???") }
switch indexPath.section {
case 0: cell.textLabel?.text = animals[indexPath.row]
case 1: cell.textLabel?.text = places[indexPath.row]
default: fatalError()
}
return cell
}
}
The ugly solution
In this case to get the total number of rows into the table we should query the model (the animals and places properties), so
let controller: Controller = ...
let rows = controller.animals.count + controller.places.count
The nice solution
Or even better we could make the animals and places properties private and add a computed property like this
class Controller:UITableViewController {
private let animals = ["Tiger", "Leopard", "Snow Leopard", "Lion", "Mountain Lion"]
private let places = ["Maveriks", "Yosemite", "El Capitan"];
var totalNumberOfRows: Int { return animals.count + places.count }
...
Now you can use this
let controller: Controller = ...
let rows = controller.totalNumberOfRows
Here's my code. While following multiple tutorials on how to implement search in Swift I've had no luck.
import UIKit
class DataTableExercisesTableViewController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
var exercises = ["Abs", "Arms", "Back", "Chest", "Legs", "Shoulders", "Triceps"]
var searchActive : Bool = false
#IBOutlet weak var searchBar: UISearchBar!
var filteredTableData = [String]()
var resultSearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
// Reload the table
self.tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return exercises.count;
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")! as UITableViewCell;
if (self.resultSearchController.active) {
cell.textLabel?.text = filteredTableData[indexPath.row]
return cell
}
else {
cell.textLabel?.text = exercises[indexPath.row]
return cell
}
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (exercises as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
I've been having trouble implementing search from different tutorials and just doesn't seem to be working out too well. Any insight is much appreciated.
Your numberOfRowsInSection is always returning exercises.count. But when you are filtering, you are not using exercises, but a smaller array, filteredTableData. So, just as in cellForRowAtIndexPath, you need to change your answer if you are filtering.
The best solution is before access the array value just the check the total count should less the the index you want fetch from the array or use below way to iterate the array
EX :
let arrayOfInts: [Int] = [1, 2, 3];
for i in arrayOfInts {
print(i);
}
In your case you could change the code :
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rowCount = 0
if(self.resultSearchController.active){
rowCount = filteredTableData.count
}
else{
rowCount = exercises.count
}
return rowCount;
}