Columns added programmatically to NSTableView not recognised in Delegate - swift

I may be lost in a glass of water but I can't seem to be able to add columns to a NSTableView that are then recognised in the NSTableViewDelegate. I create a table in IB with one column and give the column a string identifier. The I add the other columns in the View Controller:
override func viewDidLoad() {
super.viewDidLoad()
for columnIndex in 0..<blotter!.singleOutput[0].parameter.count {
let tmpParam = blotter!.singleOutput[0].parameter[columnIndex]
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: tmpParam.columnID))
column.title = tmpParam.label
column.width = CGFloat(80)
column.minWidth = CGFloat(40)
column.maxWidth = CGFloat(120)
blotterOutputTable.addTableColumn(column)
}
blotterOutputTable.delegate = self
blotterOutputTable.dataSource = self
blotterOutputTable.target = self
blotterOutputTable.reloadData()
}
The NSTableViewDataSource returns the correct number of rows. The problem I have is in the NSTableViewDelegate:
extension OutputsViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var text: String = ""
var cellIdentifier: String = ""
guard let item = blotter?.singleOutput[row] else { return nil }
// 1. LABELS COLUMN
// ================
if tableColumn?.identifier.rawValue == "dealColumn" {
let myParameter = item.parameter.index(where: {$0.columnID == "BBTickColumn"})
text = item.parameter[myParameter!].value as! String
cellIdentifier = "dealColumn"
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
return cell
}
else { return nil }
} // END OF LABLES COLUMN (FIRST ONE)
else { // THIS IS WHERE THE PROBLEM IS
let myParameter = item.parameter.index(where: {$0.columnID == tableColumn?.identifier.rawValue } )
let (_, valueAsText) = item.parameter[myParameter!].interfaceItems()
text = valueAsText
cellIdentifier = item.parameter[myParameter!].columnID
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
return cell
}
else { return nil } // DEBUGGER PARAMETER ARE FROM HERE
}
}
}
The first column is the one I created in IB with its identifier. That works. The problem I have is in the else statement (which does not check for a column identifier). Below are the parameters as I see them in the debugger window when I stop the program after the cell creation failed
tableView NSTableView 0x000000010ebf9df0
tableColumn NSTableColumn? 0x0000600000895770
row Int 0
self DataBaseManager.OutputsViewController 0x0000600000102b50
text String "FLAT"
cellIdentifier String "directionColumn"
item DataBaseManager.BlotterOutputs 0x000060000002c240
myParameter Array.Index? 0
valueAsText String "FLAT"
cell (null) (null) (null)
tableColumn NSTableColumn? 0x0000600000895770
tableColumn?.identifier NSUserInterfaceItemIdentifier? some
_rawValue _NSContiguousString "directionColumn" 0x000060000104d200
Swift._SwiftNativeNSString _SwiftNativeNSString
_core _StringCore
You can see that cellIdentifier and the tableColumn?.identifier.rawvalue are the same string (as it should be). I cannot understand then why the cell is not created. Any help is mostly welcome and let me know if this is not clear. Thanks

must register nibs identifiers as in this code:
import Cocoa
class MultiColumnTable: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
var list = [[String]](), header=[String]()
var tableView : NSTableView? = nil
var nColumns : Int = 0
func genID(col : Int) -> NSUserInterfaceItemIdentifier { // generate column ID
return NSUserInterfaceItemIdentifier(rawValue: String(format: "Col%d", col))
}
func setContent(header: [String], list : [[String]]) {
self.header = header
self.list = list
self.nColumns = list[0].count
if tableView != nil {
tableView?.reloadData()
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
func createColumns() {
func addColumn(col:Int, header:String) {
let tableColumn = NSTableColumn(identifier: genID(col: col))
tableColumn.headerCell.title = header
self.tableView!.addTableColumn(tableColumn)
}
// create columns and register them in NIB
// IB: tableColumn[0] identifier ( NSTableColumn to "Col0" )
if let myCellViewNib = tableView.registeredNibsByIdentifier![NSUserInterfaceItemIdentifier(rawValue: "Col0")] {
for col in 0..<nColumns { // table should have 1 col in IB w/Ident 'Col0'
addColumn(col: col, header: header[col])
tableView.register(myCellViewNib, forIdentifier: genID(col: col)) // register the above Nib for the newly added tableColumn
}
tableView.removeTableColumn(tableView.tableColumns[0]) // remove the original Col0
}
}
self.tableView = tableView
createColumns()
return list.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let column = tableView.tableColumns.firstIndex(of: tableColumn!)!
tableColumn?.headerCell.title=header[column];
if let cell = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) as? NSTableCellView {
cell.textField?.stringValue = list[row][column]
cell.textField?.textColor = NSColor.blue
return cell
}
return nil
}
func tableViewSelectionDidChange(_ notification: Notification) {
}
}

Related

Why is my NSTableView displaying my data wrong?

My NSTableView seems to be mirroring all content which draws a String.
I have never seen something like this before and hope somebody has a tip on how to solve this Problem. I already looked it up, but couldn't find anything. I also filed a bug report, but apple didn't respond.
First idea, that I have: It must have something to do with the NSTextField and NSPopUpButton being disabled at start. They are only enabled as soon as you click one cell. And when they are enabled the text gets displayed the right way. But I don't want to enable them at start to prevent changing values by accidentally clicking one cell.
My Code seems to be fine and compiled without problems.
The Program is a simple Database Program which takes an own created file type and reads its content. From the content it creates Database, Table, Column and cell objects at runtime to display the database content.
Here is my NSTableView Code:
import Cocoa
class TableContentViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
#IBOutlet weak var tableContent: NSTableView!
var columnDragFrom:Int = -1
override func viewDidLoad() {
super.viewDidLoad()
tableContent.dataSource = self
tableContent.delegate = self
// Do view setup here.
tableContent.backgroundColor = NSColor(named: "darkColor")!
NotificationCenter.default.addObserver(self, selector: #selector(reloadData(_:)), name: .tableUpdated, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(cellSelection(_:)), name: .cellSelection, object: nil)
tableContent.selectionHighlightStyle = .none
}
#objc func cellSelection(_ notification: Notification) {
if let cell = notification.object as? NSTableCellView {
nxSelectionHandler.currentRow = tableContent.row(for: cell)
nxSelectionHandler.currentColumn = tableContent.column(for: cell)
nxSelectionHandler.highlightCell(sender: tableContent)
}
}
#objc func reloadData(_ notification: Notification) {
setupTable()
tableContent.reloadData()
}
func setupTable() {
tableContent.rowHeight = 30
while(tableContent.tableColumns.count > 0) {
tableContent.removeTableColumn(tableContent.tableColumns.last!)
}
if nxSelectionHandler.currentTable != nil {
for column in (nxSelectionHandler.currentTable?.nxColumns)! {
let newColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: column.title))
newColumn.title = column.title
tableContent.addTableColumn(newColumn)
}
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
if nxSelectionHandler.currentTable != nil {
var rowCounts:[Int] = []
for column in (nxSelectionHandler.currentTable?.nxColumns)! {
rowCounts.append(column.nxCells.count)
}
return rowCounts.max()!
}
return 0
}
func tableView(_ tableView: NSTableView, mouseDownInHeaderOf tableColumn: NSTableColumn) {
self.columnDragFrom = tableView.tableColumns.firstIndex(of: tableColumn)!
}
func tableView(_ tableView: NSTableView, didDrag tableColumn: NSTableColumn) {
nxSelectionHandler.currentTable?.nxColumns.swapAt(columnDragFrom, tableView.tableColumns.firstIndex(of: tableColumn)!)
tableView.reloadData()
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let column = tableView.tableColumns.firstIndex(of: tableColumn!)
if nxSelectionHandler.currentTable != nil {
let nxCell = nxSelectionHandler.currentTable?.nxColumns[column!].nxCells[row]
switch nxCell! {
case .nxString(let value):
var StringCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "nxString"), owner: self) as? StringCell
if StringCellView == nil {
tableView.register(NSNib(nibNamed: "StringCellNib", bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: "nxString"))
StringCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "nxString"), owner: self) as? StringCell
}
StringCellView?.textField?.stringValue = value
return StringCellView
case .nxCheckbox(let state):
var CheckboxCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "nxCheckbox"), owner: self) as? CheckboxCell
if CheckboxCellView == nil {
tableView.register(NSNib(nibNamed: "CheckboxCellNib", bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier("nxCheckbox"))
CheckboxCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "nxCheckbox"), owner: self) as? CheckboxCell
}
CheckboxCellView?.column = column!
CheckboxCellView?.row = row
CheckboxCellView?.checkbox.state = state
return CheckboxCellView
case .nxSelection(let selection, let options):
var SelectionCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "nxSelection"), owner: self) as? SelectionCell
if SelectionCellView == nil {
tableView.register(NSNib(nibNamed: "SelectionCellNib", bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: "nxSelection"))
SelectionCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "nxSelection"), owner: self) as? SelectionCell
}
SelectionCellView?.column = column!
SelectionCellView?.row = row
for option in options {
SelectionCellView?.selection.addItem(withTitle: option)
}
SelectionCellView?.selection.selectItem(at: selection)
return SelectionCellView
}
}
return nil
}
}
The objects used in the Code are all class types and cells are loaded from Nibs, where the cells all have constraints and are displayed the right way. A Screenshot of the NSTableView displaying the content wrong can be seen below.
Code of one of the custom cells:
import Cocoa
class StringCell: NSTableCellView, NSTextFieldDelegate {
var isSelected: Bool = false {
didSet {
self.needsDisplay = true
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
self.textField?.focusRingType = .none
self.textField?.textColor = NSColor.white
self.textField?.delegate = self
self.wantsLayer = true
self.layer?.borderWidth = 2
self.layer?.cornerRadius = 2
if isSelected {
self.layer?.borderColor = NSColor.systemBlue.cgColor
} else {
self.layer?.borderColor = NSColor.clear.cgColor
self.textField?.isEnabled = false
self.textField?.isEditable = false
}
}
override func mouseDown(with event: NSEvent) {
if self.isSelected {
self.textField?.isEditable = true
self.textField?.isEnabled = true
self.textField?.selectText(self)
} else {
self.isSelected = true
NotificationCenter.default.post(name: .cellSelection, object: self)
}
}
func controlTextDidEndEditing(_ obj: Notification) {
if let textField = obj.object as? NSTextField {
nxSelectionHandler.currentCell = nxCell.nxString(textField.stringValue)
}
}
}
Yeah, that's weird. Did you check if you have any active content filters in the View Effects inspector?
View Effects inspector

NSTableView not updating on initial loading

I have an NSView with a NSTableView called personTableView. In the ViewController class, I have the following code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
personTableView.delegate = self
personTableView.dataSource = self
personTableView.reloadData()
}
and have extended the class to with NSTableViewDelegate and NSTableViewDataSource
However, when the view appears, the table shows the following (there are only 2 entries that the table should display):
On my window, I have a button which invokes the following action:
#IBAction func refreshButton(_ sender: NSButton) {
let result = CoreDataHandler.fetchCount()
print("Row Count:\(result)")
personTableView.reloadData()
codeTableView.reloadData()
}
which when pressed, populates my TableView. I don't understand why it won't load automatically?
I have also tried putting the personTableView.reloadData() into viewWillAppear and viewDidAppear to no avail.
Update:
This is the fetchCount():
static func fetchCount() -> Int {
let context = getContext()
do {
let count = try context.count(for: Person.fetchRequest())
NSLog("Count from fetchCount: %d", count)
return count
} catch {
return 0
}
}
For information, this is the Table Delegate and DataSource functions:
extension ViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
if tableView == self.personTableView {
let result = CoreDataHandler.fetchCount()
//NSLog("Rows in Ext: %#",result)
return result
}
if tableView == self.codeTableView {
let row = personTableView.selectedRow
if row > -1 {
let person = CoreDataHandler.fetchPerson()?[row]
print("Person= \(String(describing: person?.first))")
//let result = CoreDataHandler.fetchCodes(person: person!)
let result = person?.codes
//print("Person from result: \(String(describing: result?.first.whosAccount?.ibAccount))")
let count = result!.count
print("Rows in Codes from viewController dataSource: \(count)")
return count
} else {
return 0
}
}
return 0
}
}
extension ViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
if tableView == self.personTableView {
guard let person = CoreDataHandler.fetchPerson()?[row] else {
return nil
}
if let cell = tableView.makeView(withIdentifier: (tableColumn!.identifier), owner: nil) as? NSTableCellView {
if tableColumn == tableView.tableColumns[0] {
cell.textField?.stringValue = (person.first ?? nil) ?? ""
} else if tableColumn == tableView.tableColumns[1] {
cell.textField?.stringValue = (person.last ?? nil) ?? ""
} else {
cell.textField?.stringValue = (person.ibAccount ?? nil) ?? ""
}
return cell
} else {
return nil
}
}
if tableView == self.codeTableView {
let personRow = personTableView.selectedRow
if personRow > -1 {
let person = CoreDataHandler.fetchPerson()?[personRow]
guard let code = CoreDataHandler.fetchCodes(person: person!)?[row] else {
return nil
}
if let cell = tableView.makeView(withIdentifier: (tableColumn!.identifier), owner: nil) as? NSTableCellView {
if tableColumn == tableView.tableColumns[0] {
cell.textField?.stringValue = (String(code.number) )
//cell.textField?.stringValue = person?.codes?.allObjects[row] as! String
} else if tableColumn == tableView.tableColumns[1] {
cell.textField?.stringValue = code.code!
} else if tableColumn == tableView.tableColumns[2] {
cell.textField?.stringValue = (code.whosAccount?.ibAccount ?? "")
}
return cell
} else {
return nil
}
}
}
return nil
}
}
You've mixed up tableView(_:objectValueFor:row:) of NSTableViewDataSource and tableView(_:viewFor:row:) of NSTableViewDelegate. Replace
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any?
by
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
or return strings from tableView(_:objectValueFor:row:)
It seems like the table view is being populated based on data stored in the result variable. Try making the call to let result = CoreDataHandler.fetchCount() in viewDidLoad

Uitableview infinite scroll hang lag

view the video of problem here
Im trying to make my infinite scroll work smoothly, before i had it working in a way where when the user reachs a certain item count in the table, it would load the others but while doing the load a Footer Activity indicator would appear, then dissapear and add the next batch of rows... which i kinda found annoying later on.. so i wanned to opmitized it. i want the tableview to initially load 100 items, then when the user is just around row 10 it would start to load the next 100 items and rows... the thing is.. everytime it makes the call.. the uitableview kinda hangs...
I did eliminate some trivial code here like the segmented control and letter buttons.. etc,
//
// VCrestlist.swift
// AsyncUITableview
import UIKit
import Foundation
import Alamofire
import SwiftyJSON
import Parse
class VCrestlist: UITableViewController{
let PageSize = 80
var items:[RestsModel] = []
var isLoading = false
var letter = "ALL"
var online = false
var loaded = false
var bigviewforact:UIView = UIView(frame: UIScreen.main.bounds)
#IBOutlet var MyFooterView : UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.MyFooterView.isHidden = true
createButtons()
// Load custom Xib RestCell
let nib = UINib(nibName: "RestCell", bundle: nil)
// register cell identifier with custom Xib RestCell
self.tableView.register(nib, forCellReuseIdentifier: "Cell")
tableView.dataSource = self
loadSegment(0, size: self.PageSize,argletter:"ALL",online:self.online)
self.ButtonsScrollView.isScrollEnabled = true
}
class DataManager {
func requestData(_ offset:Int, size:Int,letter:String,online:Bool, listener:#escaping ([RestsModel]) -> ()) {
DispatchQueue.global(qos: .background).async {
var letra = ""
print("el offest es \(offset) y el size es \(size)")
if (letter != "") {
letra = letter
} else {
letra = "ALL"
}
let cart = Cart.sharedInstance
Alamofire.request("https://www.getmedata.com/rests.php", parameters: ["offset": "\(offset)","size":"\(size)","letra":"\(letra)","online":"\(online)"]).authenticate(usingCredential: cart.credential).responseJSON() {
response in
if(response.result.value != nil) {
let jsonObj = JSON(response.result.value!);
let rests = jsonObj as JSON
// print(jsonObj);
//generate items
if let restsarray = rests["rests"].arrayValue as [JSON]? {
var arr = [RestsModel]()
//3
for restDict in restsarray {
let restName: String? = restDict["nombre"].stringValue
let restLOGO: String? = restDict["logo"].stringValue
let detURL: String? = restDict["url"].stringValue
let detProvincia: String? = restDict["provincia"].stringValue
let detHorario: String? = restDict["horario"].stringValue
let detDireccion: String? = restDict["direccion"].stringValue
let detTipoComida: String? = restDict["tipo_comida"].stringValue
let detTelefono: String? = restDict["telefono"].stringValue
let detidRest: String? = restDict["id"].stringValue
let detalleDelivery: String? = restDict["alldelivery"].stringValue
let detalleOrderOnline: String? = restDict["orderonline"].stringValue
let detalleReservaOnline: String? = restDict["reservasonline"].stringValue
let dethasSchedule: Bool? = restDict["hasSchedule"].boolValue
let detopenNow: Bool? = restDict["opennow"].boolValue
let detscheduleToday: String? = restDict["scheduleToday"].stringValue
let detscheduleTodayLiteral: String? = restDict["scheduleTodayLiteral"].stringValue
let emp_instagram: String? = restDict["emp_instagram"].stringValue
let emp_e_orderonline: String? = restDict["emp_e_orderonline"].stringValue
let emp_e_reservasonline: String? = restDict["emp_e_reservasonline"].stringValue
let emp_e_kids: String? = restDict["emp_e_kids"].stringValue
let emp_e_pickup: String? = restDict["emp_e_pickup"].stringValue
let emp_e_delivery: String? = restDict["emp_e_delivery"].stringValue
let emp_e_wifi: String? = restDict["emp_e_wifi"].stringValue
let emp_e_valet: String? = restDict["emp_e_valet"].stringValue
let emp_e_exterior: String? = restDict["emp_e_exterior"].stringValue
let emp_e_happyhour: String? = restDict["emp_e_happyhour"].stringValue
let emp_e_desayuno: String? = restDict["emp_e_desayuno"].stringValue
let emp_e_fumar: String? = restDict["emp_e_fumar"].stringValue
let emp_e_vinos: String? = restDict["emp_e_vinos"].stringValue
let emp_e_bar: String? = restDict["emp_e_bar"].stringValue
let emp_e_games: String? = restDict["emp_e_games"].stringValue
let rest = RestsModel(name: restName!,image:restLOGO!,detailURL:detURL!,provincia:detProvincia!,tipocomida:detTipoComida!,idRest:detidRest!,hasSchedule: dethasSchedule!,scheduleToday: detscheduleToday!,scheduleTodayLiteral: detscheduleTodayLiteral!,openNow: detopenNow!,allDelivery: detalleDelivery!,orderonline: detalleOrderOnline!,reservaonline: detalleReservaOnline!, instagram: emp_instagram!, emp_e_kids: emp_e_kids!, emp_e_pickup: emp_e_pickup!, emp_e_delivery: emp_e_delivery!, emp_e_wifi: emp_e_wifi!, emp_e_valet: emp_e_valet!, emp_e_exterior: emp_e_exterior!, emp_e_happyhour: emp_e_happyhour!, emp_e_desayuno: emp_e_desayuno!, emp_e_fumar: emp_e_fumar!, emp_e_vinos: emp_e_vinos!, emp_e_bar: emp_e_bar!, emp_e_games: emp_e_games!, horario: detHorario!, direccion: detDireccion! ,telefono: detTelefono!)
arr.append(rest)
}
print(arr)
//call listener in main thread
DispatchQueue.main.async {
listener(arr)
}
}
}
}
}
///
}
}
func loadSegment(_ offset:Int, size:Int,argletter:String,online:Bool) {
if (self.loaded == false) {
let act:UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
bigviewforact.addSubview(act)
act.center = bigviewforact.center
self.view.addSubview(bigviewforact)
self.view.bringSubview(toFront: bigviewforact)
act.hidesWhenStopped = true
act.startAnimating()
//self.bigviewforact.removeFromSuperview()
self.loaded = true
}
//if (!self.isLoading) {
// self.isLoading = true
//self.MyFooterView.isHidden = (offset==0) ? true : false
let manager = DataManager()
manager.requestData(offset, size: size,letter:argletter,online:online,
listener: {(items:[RestsModel]) -> () in
/*
Add Rows at indexpath
*/
for item in items {
self.tableView.beginUpdates()
let row = self.items.count
let indexPath = IndexPath(row:row,section:0)
self.items += [item]
self.tableView?.insertRows(at: [indexPath], with: UITableViewRowAnimation.fade)
self.tableView.endUpdates()
}
self.bigviewforact.removeFromSuperview()
self.isLoading = false
self.MyFooterView.isHidden = true
}
)
// }
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == items.count-70 {
loadSegment(items.count, size: PageSize,argletter:self.letter,online:self.online)
}
}
// MARK: Table view data source
override func numberOfSections(in tableView: UITableView?) -> Int {
return 1
}
override func tableView(_ tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView?, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 85;
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : RestCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! RestCell
// Pass data to Cell :) clean the mess at the View Controller ;)
cell.restData = items[indexPath.row]
cell.tag = indexPath.row
return cell
}
}
Don't insert each item into UITableView. Insert all of them together.
var indexPathes: [IndexPath] = []
var newItems: [RestsModel] = []
for item in items {
let row = self.items.count
let indexPath = IndexPath(row:row,section:0)
newItems.append(item)
indexPathes.append(indexPath)
}
self.items.append(contentsOf: newItems)
self.tableView.beginUpdates()
self.tableView?.insertRows(at: indexPathes, with: UITableViewRowAnimation.fade)
self.tableView.endUpdates()

Expandable cells in Swift table - cell reuse/dequeue?

I'm building a custom interface for the user to enter preference settings in my app. I'm using expandable rows following an example I found at AppCoda. I've reworked that example to use Swift 3/4 and to use cell information from code rather than read from a plist.
I'm having a problem with the way some cell content appears on the screen. The rows that expand and collapse contain textfields to allow user entry. There are four such rows in the example code below.
When an entry is made in one of those cells, it may or may not cause the last-entered value to appear in all four cells when they are expanded. The 'extra' text will even overwrite the information that belongs there.
I've tried everything I can think of to get rid of this offending text but I'm banging my head against the wall. What am I missing?
FWIW, I am now looking at similar solutions elsewhere. Here's one I like quite a bit:
https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section
This one looks interesting but is not in Swift:
https://github.com/singhson/Expandable-Collapsable-TableView
Same comment:
https://github.com/OliverLetterer/SLExpandableTableView
This looks very interesting - well supported - but I haven't had time to investigate:
https://github.com/Augustyniak/RATreeView
A similar request here:
Expand cell when tapped in Swift
A similar problem described here, but I think I'm already doing what is suggested?
http://www.thomashanning.com/the-most-common-mistake-in-using-uitableview/
Here is my table view controller code. I believe the problem is in the...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
... function, but for the life of me I can't see it.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
test = defineCellProps() // This loads my hard-coded cell properties into array "test"
configureTableView()
}
func configureTableView() {
loadCellDescriptors()
tblExpandable.delegate = self
tblExpandable.dataSource = self
tblExpandable.tableFooterView = UIView(frame: CGRect.zero)
tblExpandable.register(UINib(nibName: "NormalCell", bundle: nil), forCellReuseIdentifier: "idCellNormal")
tblExpandable.register(UINib(nibName: "TextfieldCell", bundle: nil), forCellReuseIdentifier: "idCellTextfield") // There are additional cell types that are not shown and not related to the problem
}
func loadCellDescriptors() { // Puts the data from the "test" array into the format used in the original example
for section in 0..<ACsections.count {
var sectionProps = findDict(matchSection: ACsections[section], dictArray: test)
cellDescriptors.append(sectionProps)
}
cellDescriptors.remove(at: 0) // Removes the empty row
getIndicesOfVisibleRows()
tblExpandable.reloadData() // The table
}
func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors { // cellDescriptors is an array of sections, each containing an array of cell dictionaries
var visibleRows = [Int]()
let rowCount = (currentSectionCells as AnyObject).count as! Int
for row in 0..<rowCount { // Each row is a table section, and array of cell dictionaries
var testDict = currentSectionCells[row]
if testDict["isVisible"] as! Bool == true {
visibleRows.append(row)
} // Close the IF
} // Close row loop
visibleRowsPerSection.append(visibleRows)
} // Close section loop
} // end the func
func getCellDescriptorForIndexPath(_ indexPath: IndexPath) -> [String: AnyObject] {
let indexOfVisibleRow = visibleRowsPerSection[indexPath.section][indexPath.row]
let cellDescriptor = (cellDescriptors[indexPath.section])[indexOfVisibleRow]
return cellDescriptor as [String : AnyObject]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
cell.textLabel?.text = nil
cell.detailTextLabel?.text = nil
cell.textField?.placeholder = nil
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
cell.textLabel?.text = primaryTitle as? String
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
if primaryTitle as! String == "" {
cell.textField.placeholder = currentCellDescriptor["secondaryTitle"] as? String
cell.textLabel?.text = nil
} else {
cell.textField.placeholder = nil
cell.textLabel?.text = primaryTitle as? String
}
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = "some text"
}
cell.detailTextLabel?.text = "some text"
// This next line, when enabled, always puts the correct row number into each cell.
// cell.textLabel?.text = "cell number \(indexPath.row)."
}
cell.delegate = self
return cell
}
Here is the CustomCell code with almost no changes by me:
import UIKit
protocol CustomCellDelegate {
func textfieldTextWasChanged(_ newText: String, parentCell: CustomCell)
}
class CustomCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
let bigFont = UIFont(name: "Avenir-Book", size: 17.0)
let smallFont = UIFont(name: "Avenir-Light", size: 17.0)
let primaryColor = UIColor.black
let secondaryColor = UIColor.lightGray
var delegate: CustomCellDelegate!
override func awakeFromNib() {
super.awakeFromNib() // Initialization code
if textLabel != nil {
textLabel?.font = bigFont
textLabel?.textColor = primaryColor
}
if detailTextLabel != nil {
detailTextLabel?.font = smallFont
detailTextLabel?.textColor = secondaryColor
}
if textField != nil {
textField.font = bigFont
textField.delegate = self
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func prepareForReuse() { // I added this and it did not help
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if delegate != nil {
delegate.textfieldTextWasChanged(textField.text!, parentCell: self)
}
return true
}
}
OMG, I'm slapping my palm to my forehead. There is one very important line missing from this code from above:
override func prepareForReuse() {
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
Can you see what's missing?
textField?.text = nil
That's all it took! I was mucking about with the label but not the textfield text itself.

How to implement UISearchController in iOS8?

I have tried to implement the UISearchController in IOS8 but failed.
The problem is when I have changed the text and the scope button, noting is presented to me.
And it seems that the updateSearchResultsForSearchController function is not even called when I update the search Bar or the scope button.
Here is my code:
class SearchTestController: UITableViewController, UISearchResultsUpdating {
struct Candy {
let category : String
let name : String
}
var searchcontroller = UISearchController(searchResultsController: nil)
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredcandy = candies.filter() { (candy:Candy) -> Bool in
let scopetest = ( self.category[self.searchcontroller.searchBar.selectedScopeButtonIndex] == "All" ) || ( candy.category == self.category[self.searchcontroller.searchBar.selectedScopeButtonIndex] )
//let texttest = candy.name.rangeOfString(self.searchcontroller.searchBar.text)
//let result = scopetest && (texttest != nil)
return scopetest
}
println(filteredcandy.count)
self.tableView.reloadData()
}
var candies = [Candy]()
var filteredcandy = [Candy]()
var category = ["Chocolate","Hard","Other","All"]
override func viewDidLoad() {
super.viewDidLoad()
// Sample Data for candyArray
self.candies = [Candy(category:"Chocolate", name:"chocolate Bar"),
Candy(category:"Chocolate", name:"chocolate Chip"),
Candy(category:"Chocolate", name:"dark chocolate"),
Candy(category:"Hard", name:"lollipop"),
Candy(category:"Hard", name:"candy cane"),
Candy(category:"Hard", name:"jaw breaker"),
Candy(category:"Other", name:"caramel"),
Candy(category:"Other", name:"sour chew"),
Candy(category:"Other", name:"gummi bear")]
// Reload the table
self.tableView.reloadData()
self.tableView.tableHeaderView = searchcontroller.searchBar
searchcontroller.searchBar.sizeToFit()
searchcontroller.searchBar.showsSearchResultsButton = true
self.definesPresentationContext = true
searchcontroller.searchBar.scopeButtonTitles = category
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchcontroller.active {
return self.candies.count
} else {
return self.candies.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//ask for a reusable cell from the tableview, the tableview will create a new one if it doesn't have any
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
var candy : Candy
// Check to see whether the normal table or search results table is being displayed and set the Candy object from the appropriate array
if searchcontroller.active {
candy = filteredcandy[indexPath.row]
} else {
candy = candies[indexPath.row]
}
// Configure the cell
cell.textLabel!.text = candy.name
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
Add the following lines to viewDidLoad()
searchcontroller.searchResultsUpdater = self
searchcontroller.delegate = self
Update:
Add the following line in viewDidLoad()
searchcontroller.searchBar.delegate = self
Then update the search results in searchBar(_:selectedScopeButtonIndexDidChange:)