NSTableView not updating on initial loading - swift

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

Related

How to Increase count of Page in Url for loading more data and show indicator at bottom?

I Creating a demo of webservices, In this I want to increase page count and load more data from api, and add in table view after activity indicator refreshing. I find many tutorials but Not found useful... They are all Advance and I'm beginner so i didn't get properly. Can Any one please tell how to do this.
Here's My Demo details...
This Is Page Count of URL
"info": {
"count": 826,
"pages": 42,
"next": "https://rickandmortyapi.com/api/character/?page=3",
"prev": "https://rickandmortyapi.com/api/character/?page=1"
},
My json Model
import UIKit
import Foundation
// MARK: - JsonModel
struct JSONModel:Decodable {
let info: Info
let results: [Result]
}
// MARK: - Info
struct Info : Decodable {
let count, pages: Int
let next: String
let prev: NSNull
}
// MARK: - Result
struct Result : Decodable {
let id: Int
let name: String
let status: Status
let species: Species
let type: String
let gender: Gender
let origin, location: Location
let image: String
let episode: [String]
let url: String
let created: String
}
enum Gender {
case female
case male
case unknown
}
// MARK: - Location
struct Location {
let name: String
let url: String
}
enum Species {
case alien
case human
}
enum Status {
case alive
case dead
case unknown
}
This is my View controller Class
import UIKit
import Kingfisher
class ViewController: UIViewController,UISearchBarDelegate{
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var results = [Results]()
var filteredData = [Results]()
var batchSize = 42
var fromIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
tableView.delegate = self
tableView.dataSource = self
apiCalling()
filteredData = results
}
override func viewWillAppear(_ animated: Bool) {
filteredData = results
self.tableView.reloadData()
}
func apiCalling(){
guard let url = URL(string: "https://rickandmortyapi.com/api/character/") else { return }
URLSession.shared.dataTask(with: url) {[weak self]data, response, error in
if error != nil{
print("error While Fetching Data")
}
guard let data = data else {
return
}
do {
let resultData = try JSONDecoder().decode(JsonModel.self, from: data)
self?.results = resultData.results!
self?.filteredData = self!.results
DispatchQueue.main.async {
self?.tableView.reloadData()
}
} catch {
print(error.localizedDescription)
}
}.resume()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let searchText = searchBar.text!
guard !searchText.isEmpty else {
filteredData = results
tableView.reloadData()
return
}
filteredData = results.filter({ $0.name!.lowercased().contains(searchText.lowercased() ) })
tableView.reloadData()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchBar.showsCancelButton = true
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false
searchBar.text = ""
searchBar.resignFirstResponder()
filteredData.removeAll()
self.tableView.reloadData()
}
}
This My Tableview Extension
extension ViewController : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UserTableViewCell
let row = filteredData[indexPath.row]
let imageUrl = URL(string: row.image!)
cell.userImage.kf.setImage(with: imageUrl)
cell.lblGender.text = "Gender:- \(row.gender ?? "no value")"
cell.lblID.text = "ID:- \(row.id ?? 0)"
cell.lblName.text = "Name: \(row.name!)"
cell.lblSpecies.text = "Species:- \(row.species ?? "No Speies")"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 250
}
}
u need save page info.
self?.info = resultData.info!
call "loadpage" when u loading more data
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
tableView.delegate = self
tableView.dataSource = self
filteredData = []
result = []
apiCalling(apiurl:"https://rickandmortyapi.com/api/character/")
}
func apiCalling(apiurl:String){
guard let url = URL(string: apiurl) else { return }
URLSession.shared.dataTask(with: url) {[weak self]data, response, error in
if error != nil{
print("error While Fetching Data")
}
guard let data = data else {
return
}
do {
let resultData = try JSONDecoder().decode(JsonModel.self, from: data)
self?.results.append(resultData.results!)
self?.info = resultData.info!
filterWord()
} catch {
print(error.localizedDescription)
}
}.resume()
}
func filterWord(){
let searchText = searchBar.text!
guard !searchText.isEmpty else {
filteredData = results
tableView.reloadData()
return
}
filteredData = results.filter({ $0.name!.lowercased().contains(searchText.lowercased() ) })
tableView.reloadData()
}
func loadPage(){
guard let page = self?.info.next,!page.isEmpty else{
return
}
apiCalling(apiurl:page)
}
under indicator simple example like this
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
guard let page = self?.info.next,!page.isEmpty else{
return nil
}
//press to call loadPage
let loading = UIButton.init()
let view = UIView.init()
view.addSubview(loading)
return view
}
I'm Giving My own Questions answer Here...
I Have Create 3 more variables
var curentIndex : Int = 0
// I'm Putting Default Limit Here...
var numberArray = Array(1...42)
var fetchingMore = false
Api Call
func apiCalling(){
guard !fetchingMore else {
print("Didn't call Get Data")
return
}
fetchingMore = true
guard let url = URL( string: "\(baseUrl)?page=\(numberArray[curentIndex])") ?? URL(string: "" ) else {
fetchingMore = false
return
}
curentIndex += 1
URLSession.shared.dataTask(with: url) {[weak self]data, response, error in
if error != nil{
print("error While Fetching Data")
}
guard let data = data else {
return
}
do {
let resultData = try JSONDecoder().decode(JsonModel.self, from: data)
self?.results += resultData.results!
self?.filteredData = self!.results
DispatchQueue.main.async {
self?.tableView.reloadData()
}
} catch {
print(error.localizedDescription)
}
self?.fetchingMore = false
}.resume()
}
**Here's My CellForRowMethod **
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UserTableViewCell
let row = filteredData[indexPath.row]
if indexPath.row == filteredData.count - 1 && curentIndex <= row.id ?? 0 {
apiCalling()
}
let imageUrl = URL(string: row.image!)
cell.userImage.kf.setImage(with: imageUrl)
cell.lblGender.text = "Gender:- \(row.gender ?? "no value")"
cell.lblID.text = "ID:- \(row.id ?? 0)"
cell.lblName.text = "Name: \(row.name!)"
cell.lblSpecies.text = "Species:- \(row.species ?? "No Speies")"
return cell
}

How to display the list of data initially in View Controller

This is my Pokedex. It works per say... A minor issue I have with this is when you initially open the app, it doesn't show the list of data, just blank. When you enter something in search bar, it appears even when you delete everything and no letters in the search bar.
I looked for how to display the list initially and compared with a functional code but can't figure out what caused this.
Any helps and ideas will be appreciated.
Here is my main ViewController:
import UIKit
class ViewController: UITableViewController, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
var pokemon: [Pokemon] = []
var filteredData: [Pokemon] = []
func capitalize (text: String) -> String {
function and followed by the remaining text without the first letter
return text.prefix(1).uppercased() + text.dropFirst()
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://pokeapi.co/api/v2/pokemon?limit=151")
guard let u = url else {
return
}
URLSession.shared.dataTask(with: u) { (data, reponse, error) in
guard let data = data else {
return
}
do {
let pokemonList = try JSONDecoder().decode(PokemonList.self, from: data)
self.pokemon = pokemonList.results
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch let error {
print("\(error)")
}
}.resume()
searchBar.delegate = self
filteredData = pokemon
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PokemonCell", for: indexPath)
cell.textLabel?.text = capitalize(text: filteredData[indexPath.row].name)
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PokemonSegue" {
if let destination = segue.destination as? PokemonViewController {
destination.pokemon = filteredData[tableView.indexPathForSelectedRow!.row]
}
}
}
func searchBar (_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = []
if searchText == "" {
filteredData = pokemon
}
else {
for pokemon in pokemon {
if pokemon.name.lowercased().contains(searchText.lowercased()){
filteredData.append(pokemon)
}
}
}
self.tableView.reloadData()
}
}
this:
let pokemonList = try JSONDecoder().decode(PokemonList.self, from: data)
self.pokemon = pokemonList.results
DispatchQueue.main.async {
self.tableView.reloadData()
}
Happens asynchroniously after you receive the data and way after this
searchBar.delegate = self
filteredData = pokemon
is executed, you can put breakpoints and check for yourself.
You should assign pokemon to filteredData in the "do" statement
right before tableView.reloadData()
URLSession works asynchronously. Move the line
filteredData = pokemon
into the completion handler of the data task
do {
let pokemonList = try JSONDecoder().decode(PokemonList.self, from: data)
self.pokemon = pokemonList.results
self.filteredData = pokemonList.results
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch let error {
print("\(error)")
}
}.resume()
searchBar.delegate = self
}
And your function capitalize is redundant, String has a property capitalized
And this is a more efficient version of textDidChange
func searchBar (_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
filteredData = pokemon
} else {
filteredData = pokemon.filter { $0.name.range(of: searchText, options: .caseInsensitive) != nil }
}
self.tableView.reloadData()
}

Is there a way I can identify .storyboard associated with viewController?

In a storyboard it is possible to find the id of the associated viewController. But from what I can tell, it's not possible the other way around: that is, in a viewController, can I see what storyboard it is connected to?
Thing is, I'm currently working with a viewController but have no clue what storyboard it is associated with.
Here is code with suggestion from Shreeram-Bhat:
import UIKit
protocol PickNumberVieDelegate: class {
func dismissPickNumberView(tempDef:String, tempAlter:String)
}
class PickNumberViewController: UIViewController,UITableViewDataSource, UITableViewDelegate{
weak var delegate: PickNumberVieDelegate?
var whatTypOfNumber = 0
#IBOutlet weak var tableview: UITableView!
#IBOutlet weak var indicator: UIActivityIndicatorView!
var arr = [PresentationNumberOption]()
var tempAlernatNr = ""
var tempDefaultNr = ""
override func viewDidLoad() {
super.viewDidLoad()
//HERE
var storyboard: UIStoryboard? { get }
print("🐥here", storyboard);
self.setUserInterfaceStyleLight()
if (whatTypOfNumber == 0) {
self.title = NSLocalizedString("TimeControlled AfterWork Time" , comment: "")
}else if (whatTypOfNumber == 1){
self.title = NSLocalizedString("TimeControlled Showing Number" , comment: "")
}else{
self.title = NSLocalizedString("Display Number", comment: "")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
var mobilNumber = ""
if let mobileNumberForThisDevice = CurrentPerson.shared().thisMobilePhone(){
mobilNumber = mobileNumberForThisDevice.address
};
Communication.sendGetCustomPresentationNumberOptionsforNumber(mobilNumber, withCallbackMethod: #selector(self.gotPresentationNumberOptions(_:)), callbackFailMethod: #selector(self.failedToGetNumber), on: self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let CellIdentifier = "CellId"
var cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier)
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: CellIdentifier)
}
var option: PresentationNumberOption?
option = arr[indexPath.row]
var str = option?.displayText()
str = str?.replacingOccurrences(of: "[", with: "")
str = str?.replacingOccurrences(of: "]", with: "")
cell?.textLabel?.text = str
if( whatTypOfNumber == 0){
if (ModelManager.shared()?.tcsm.defaultNr == option?.address) {
cell?.accessoryType = .checkmark
} else {
cell?.accessoryType = .none
}
}else{
if (ModelManager.shared()?.tcsm.alternateNr == option?.address) {
cell?.accessoryType = .checkmark
} else {
cell?.accessoryType = .none
}
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tempAlernatNr = (ModelManager.shared()?.tcsm.alternateNr)!
tempDefaultNr = (ModelManager.shared()?.tcsm.defaultNr)!
let presentationNumber: PresentationNumberOption?
presentationNumber = arr[indexPath.row]
if( whatTypOfNumber == 0){
if(presentationNumber?.address == ""){
if presentationNumber?.type == kPresentationNumberOptionTypeMobileNumber{
self.showVerificationScreen()
}else{
ModelManager.shared()?.tcsm.defaultNr = presentationNumber?.address ?? ""
delegate?.dismissPickNumberView(tempDef: tempDefaultNr, tempAlter: tempAlernatNr)
self.navigationController?.popViewController(animated: true)
}
}else{
ModelManager.shared()?.tcsm.defaultNr = presentationNumber?.address ?? ""
delegate?.dismissPickNumberView(tempDef: tempDefaultNr, tempAlter: tempAlernatNr)
self.navigationController?.popViewController(animated: true)
}
}else{
if(presentationNumber?.address == ""){
if presentationNumber?.type == kPresentationNumberOptionTypeMobileNumber{
self.showVerificationScreen()
}else{
ModelManager.shared()?.tcsm.alternateNr = presentationNumber?.address ?? ""
delegate?.dismissPickNumberView(tempDef: tempDefaultNr, tempAlter: tempAlernatNr)
self.navigationController?.popViewController(animated: true)
}
}else{
ModelManager.shared()?.tcsm.alternateNr = presentationNumber?.address ?? ""
delegate?.dismissPickNumberView(tempDef: tempDefaultNr, tempAlter: tempAlernatNr)
self.navigationController?.popViewController(animated: true)
}
}
}
#objc func gotPresentationNumberOptions(_ presentationNumbers: [Any]?) {
if let aNumbers = presentationNumbers {
self.arr = aNumbers as! [PresentationNumberOption]
}
indicator.stopAnimating()
tableview.reloadData()
}
#objc func failedToGetNumber() {
//ALog("Failed to get presentation number options:%#", error)
indicator.stopAnimating()
}
func numberOfSections(in tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
func showVerificationScreen() {
print("🐥Show Verification Screen");
let nextController = NumberVerificationViewController(nibName: "NumberVerificationViewController", bundle: Bundle.main)
navigationController?.pushViewController(nextController, animated: true)
}
}
You can get it from "storyboard" property. Below is the declaration of the property.
var storyboard: UIStoryboard? { get }
It will be nil if you have instantiated ViewController from code. You get it like,
if let storyboard = self.storyboard {
if let name = storyboard.value(forKey: "name") as? String {
print(name)
}
}

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

Columns added programmatically to NSTableView not recognised in Delegate

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) {
}
}