I have successfully created a cell-based NSTableView purely in code. I would like to make the cells a little more interesting and I have read that I need to create a view-based NSTableView.
I have been tutorials like this.
The rest of my UI is entirely in code. I have been trying to do the same for this tableview without much luck.
Here is how I am defining the TableView — I need to stop registering the Nib and I am not sure how:
let nib = NSNib(nibNamed: "TransactionCellView", bundle: NSBundle.mainBundle())
tableOfTransactions.registerNib(nib!, forIdentifier: "TransactionCellView")
tableOfTransactions.headerView = nil
tableOfTransactions.setDelegate(self)
tableOfTransactions.setDataSource(self)
tableOfTransactions.reloadData()
Here is my stub code for each cell:
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView?{
var testCell = NSView()
testCell.frame = NSRect(x: 0, y: 0, width: 300, height: 200)
return testCell
}
Any pointers or suggestions on how to achieve this would be much appreciated!
Your implementation of -tableView(_:viewForTableColumn:row:) should look something like this:
func tableView(tableView: NSTableView,
viewForTableColumn
tableColumn: NSTableColumn?,
row: Int) -> NSView? {
var retval: NSView?
if let spareView = tableView.makeViewWithIdentifier("CodeCreatedTableCellView",
owner: self) as? NSTableCellView {
// We can use an old cell - no need to do anything.
retval = spareView
} else {
// Create a text field for the cell
let textField = NSTextField()
textField.backgroundColor = NSColor.clearColor()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.bordered = false
textField.controlSize = NSControlSize.SmallControlSize
// Create a cell
let newCell = NSTableCellView()
newCell.identifier = "CodeCreatedTableCellView"
newCell.addSubview(textField)
newCell.textField = textField
// Constrain the text field within the cell
newCell.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat("H:|[textField]|",
options: [],
metrics: nil,
views: ["textField" : textField]))
newCell.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat("V:|[textField]|",
options: [],
metrics: nil,
views: ["textField" : textField]))
textField.bind(NSValueBinding,
toObject: newCell,
withKeyPath: "objectValue",
options: nil)
retval = newCell
}
return retval
}
In the case where your table contains hundreds of rows, Cocoa will attempt to reuse views that have already been created, but are no longer on screen. The first part of this snippet uses an NSTableView method to look for such a view. If none is found, you need to create one from scratch.
If you've got no reason not to, you should use an instance (or subclass) of NSTableCellView as your view. It doesn't add much to NSView, but one of its key features is that it retains a reference to the model that the view represents (set by -tableView(_:objectValueForTableColumnRow:row:)). In this example I've used this feature to set the string value of the text field using bindings.
The other thing to note is that you should give your view an identifier that matches the identifier that you gave to the NSTableColumn in which the view will sit. Doing so allows your table view to make use of the reusable-view feature discussed above.
This was quite useful in my attempt to program scrolling tables without IB & Nibs. Here's my toy version, updated to Swift 4.2.
The custom cell view subclass is only there to let me see that cells actually get re-used, which they do:
import Cocoa
class DIYTableViewDataSource: NSObject, NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return 25
}
}
class CustomTableCellView: NSTableCellView {
var count = 1
}
func createCell(_ id: NSUserInterfaceItemIdentifier) -> CustomTableCellView {
// Create a text field for the cell
let textField = NSTextField()
textField.backgroundColor = NSColor.clear
textField.translatesAutoresizingMaskIntoConstraints = false
textField.isBordered = false
// Create a cell
let cell = CustomTableCellView() // Doing this to see that cells get re-used
cell.identifier = id
cell.addSubview(textField)
cell.textField = textField
// Constrain the text field within the cell
textField.widthAnchor.constraint(equalTo: cell.widthAnchor).isActive = true
textField.heightAnchor.constraint(equalTo: cell.heightAnchor).isActive = true
textField.bind(NSBindingName.value, to: cell, withKeyPath: "objectValue", options: nil)
return cell
}
class DIYTableViewDelegate: NSObject, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let id = tableColumn!.identifier
var view = tableView.makeView(withIdentifier: id, owner: nil) as? CustomTableCellView
if view == nil {
view = createCell(id)
}
view!.textField!.stringValue = "\(id.rawValue) \(row) \(view!.count)"
view!.count += 1
return view
}
}
Swift 5
You need to implement delegate method of NSTableView. No need to register any nib files 😉
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let view = CustomView()
view.configure(with: row)
return view
}
Just create instance of you custom view (subclass of NSView).
It can looks like this:
class CustomView: NSView {
private let label = NSTextField()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init coder has not been implemented")
}
func setupView() {
self.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: self.topAnchor, constant: 8),
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 8),
label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 8),
label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 8)
])
}
func configure(with row: Int) {
label.stringValue = String(describing: row + 1)
}
}
Related
I need to display a SwiftUI view in an existing UITableViewController, as the tableHeaderView. However, it seems that the sizing of the SwiftUI view is broken when it is added to a UITableViewController.
If I simply convert my SwiftUI View to a UIView using a UIHostingController and set it to the tableHeaderView, the view is displayed off screen:
func addHeaderView() {
let view = VerticalTextStack()
let hostingController = UIHostingController(rootView: view)
tableView.tableHeaderView = hostingController.view
}
Incorrect layout
To counteract this, I've tried to fix the height of the view several different ways. Adding an NSLayoutConstraint didn't do anything. When setting tableHeaderView.frame.size manually, the results were better, since at least now the view is displayed on-screen, but the multiline Texts become single-line and truncated.
As you can see here, the 2nd Text gets truncated:
Here's a simplified example showcasing the problem:
/// `UITableViewController` displaying a `UIView` as its `tableHeaderView`
class TableViewController: UITableViewController {
let cellReuseIdentifier = "cell"
let data = ["A", "B", "C", "D", "E"]
let themeManager = AppThemeManager()
// MARK: - UIViewController lifecycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
addHeaderView()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
fixTableHeaderViewSize()
}
// MARK: - UITableViewDelegate
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
cell.textLabel?.text = data[indexPath.row]
return cell
}
// MARK: - SwiftUI view
func fixTableHeaderViewSize() {
guard let tableHeaderView = tableView?.tableHeaderView else { return }
let expectedHeight = tableHeaderView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize).height
let expectedSize = CGSize(width: tableHeaderView.frame.width, height: expectedHeight)
tableHeaderView.frame.size = expectedSize
}
func addHeaderView() {
let view = VerticalTextStack()
let hostingController = UIHostingController(rootView: view)
tableView.tableHeaderView = hostingController.view
}
}
private struct VerticalTextStack: View {
let data = ["First", "I am a very long text that only fits in multiple lines. I still continue.", "Third"]
let themeManager = AppThemeManager()
var body: some View {
VStack(spacing: 10) {
ForEach(data, id: \.self) { value in
Text(value)
}
}
}
}
I've also tried moving addHeaderView to other UIViewController functions, such as viewWillLayoutSubviews, but that didn't change anything.
Setting lineLimit to nil or any large number on the Text inside VerticalTextStack and adding .layoutPriority(.greatestFiniteMagnitude) to the Text did not make the Text multiline either.
Here is a possible solution.
Change your add header view funcation with this.
func addHeaderView() {
let view = VerticalTextStack()
let hostingController = UIHostingController(rootView: view)
let headerViewMain = UIView()
headerViewMain.backgroundColor = .red
headerViewMain.addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostingController.view.topAnchor.constraint(equalTo: headerViewMain.topAnchor),
hostingController.view.leftAnchor.constraint(equalTo: headerViewMain.leftAnchor),
headerViewMain.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor),
headerViewMain.rightAnchor.constraint(equalTo: hostingController.view.rightAnchor)
]
NSLayoutConstraint.activate(constraints)
headerViewMain.frame.size.height = headerViewMain.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
headerViewMain.frame.size.width = headerViewMain.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).width
self.tableHeaderView = headerViewMain
self.layoutIfNeeded()
self.setNeedsLayout()
self.reloadData()
}
I want to show data in NSTableView. The number of columns is unspecified (could be any from 1 to ?? depending on the data), so I can't use Interface Builder.
So I initialize (with IB) my table to 1 column, and thereafter add new columns as required (then remove the no-longer needed 0-th column). To each added column I provide a unique identifier. So far so good.
I implement the tableView(-:viewForTableColumn:row) function, as shown below, but the makeViewWithIdentifier returns nil. What's the matter ?
If I detect the nil return, I create an instance of NSTableCellView with the proper identifier. But the data do not show in the table. What could be wrong ?
The code is below (with unnecessary lines removed) :
import Cocoa
class MyViewController: NSViewController {
#IBOutlet weak var dataTable: NSTableView!
}
var donnees: DataFrame = DataFrame() // Some table-like data with unspecified number of columns
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
initData() // get the actual data
// Make the ad-hoc number of columns
if donnees.nbCol > 0 {
for k in 0..<donnees.nbCol {
let newColumn = NSTableColumn(identifier: idArray[k]) // idArray : [String] of unique identifiers
dataTable.addTableColumn(newColumn)
}
}
dataTable.removeTableColumn(dataTable.tableColumns[0]) // remove the original column, now unnecessary
}
}
extension MyViewController : NSTableViewDataSource {
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
return self.donnees.nbRow
}
}
extension MyViewController : NSTableViewDelegate {
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
let columnID = tableColumn!.identifier
var cellView: NSTableCellView
let cellViewTmp = tableView.makeViewWithIdentifier(columnID, owner: self)
if cellViewTmp == nil { // then create a new NSTableCellView instance
cellView = NSTableCellView(frame: NSRect(x: 0, y: 0, width: (tableColumn?.width)!, height: 20))
cellView.identifier = columnID
print("CellView créé pour id \(columnID) au rang \(row)")
} else {
cellView = cellViewTmp as! NSTableCellView
}
cellView.textField?.stringValue = "AAA"
return cellView
}
}
Bingo ! Thanks to Willeke I rewrote my code as follows :
var donnees: DataFrame = DataFrame() // Some table-like data with unspecified number of columns
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
initData() // get the actual data
self.setTableColumns() // and prepare the columns accordingly
}
func setTableColumns() {
// In Interface Builder I've prepared a cell view in the 0-th tableColumn and set the identifier of this NSTableColumn to "ModelCellView"
let myCellViewNib = dataTable.registeredNibsByIdentifier!["ModelCellView"] // I save the cellView's Nib
// Make the ad-hoc number of columns
if donnees.nbCol > 0 {
for k in 0..<donnees.nbCol {
let newColumn = NSTableColumn(identifier: idArray[k]) // idArray : [String] of unique identifiers
dataTable.addTableColumn(newColumn)
dataTable.registerNib(myCellViewNib, forIdentifier: newColumn.identifier) // I register the above Nib for the newly added tableColumn
}
dataTable.removeTableColumn(dataTable.tableColumns[0]) // remove the original column, now unnecessary
}
}
extension MyViewController : NSTableViewDataSource {
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
return self.donnees.nbRow
}
extension MyViewController : NSTableViewDelegate {
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
let columnID = tableColumn!.identifier
let cellView = tableView.makeViewWithIdentifier(columnID, owner: self) as! NSTableCellView
cellView.textField?.stringValue = "theActualCellData"
return cellView
}
And it works perfectly as intended. Again, thanks to Willeke.
I want to fill my NSTableView with content. Per table-cell-row are 3 items (2 NSTextFields and 1 NSImageView). For that I created a custom NSTableCellView where I want to set the #IBOutlets of the 3 Items, to set there the value for them. But when I try to set the referencing outlets, the only option is to create an action.
When I try to write #IBOutlet weak var personName: NSTextfield and then set the references, I can't because "xcode cannot locate the class in the current workspace"
When I create the NSTableViewinside a main.storyboard, I'm able to set the outlet references. So what is the different behavior between .storyboard and .xib?
When I try to connect the #IBOutlet with the Item "Person Name"
My NSViewController (owner of the .xib)
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var tableView: NSTableView! //ref to tableView in xib
var persons:[Person] =Â [] //content to fill tableview
override func viewDidLoad() {
super.viewDidLoad()
persons.append(Person(name: "John", age: 23, piRef: "/Users/xy/Desktop/profilePic.png"))
persons.append(Person(name: "Marie", age: 26, piRef: "/Users/xy/Desktop/profilePic.png"))
tableView.delegate = self
tableView.dataSource = self
}
func numberOfRows(in tableView: NSTableView) -> Int {
return persons.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let tableCellView:personTableCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "defaultRow"), owner: self) as! personTableCell
//NSTableColumn in xib has id "defaulRow"
if let person:Person = persons[row] {
tableCellView.setPerson(person: person) //call method inside NSTableCellView-subclass to set item values
}
return tableCellView
}
}
The custom NSTableCellView subclass ("personTableCell")
class personTableCell: NSTableCellView {
var person:Person! = nil
//here should be:
//#IBOutlet weak var personName: NSTextField!
//#IBOutlet weak var personAge: NSTextField!
//#IBOutlet weak var personImg: NSImageView!
func setPerson(person: Person) {
self.person = person
self.personName = person.name
self.personAge = person.age
self.personImg = NSImage(byReferencingFile: person.profileImgRef)
}
}
I want to be able to add the item outlet references to my NSTableCellView-subclass.
It appears to me you're making this harder than it needs to be. makeView is giving you a reference to the cell. Therefore you can access its members directly. No need for outlets (which is why Xcode won't make them for you.)
I can't read your screenshots well enough to tell how the textfields are defined (old eyes), so I can only give you a generic example from a working demo of a custom cell class:
class DIYTableViewDelegate: NSObject, NSTableViewDelegate {
var count = 0 // counts the number of views actually created
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let id = tableColumn!.identifier
var view = tableView.makeView(withIdentifier: id, owner: nil) as? CustomTableCellView
if view == nil {
view = createCell(id)
count += 1
}
view!.textField!.stringValue = "\(id.rawValue) \(row) \(view!.count) \(count)"
view!.count += 1
return view
}
}
Also, it's customary in Swift to capitalize the first letter of types (classes, structures, enums, protocols) and lowercase methods & properties. Doesn't affect how your code compiles, but it helps other Swifties read it.
Here's another example that may help:
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let vw = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? CustomTableCellView else { return nil }
vw.textField?.stringValue = String(pictures[row].dropLast(4))
vw.imageView?.image = NSImage(named: pictures[row])
return vw
}
Firstly I must note that this is my first GUI-app (XIB) in Swift, in other words I am working on and trying to learn Swift and MacOS software development. I have looked through several questions, here at Stack, as well as the Apple documentation on NSTableView, but I'm stuck.
Trying to make a simple app to read some attributes of selected files. I have a custom NSView where the user drags and drop in a file and it reads some attributes off it - which is ok.
>>> print("\(fileDataToShow)\n\(resultTable)")
Optional([["filename": "foo.jpeg", "state": "1"],["filename": "bar.jpeg", "state": "1"]])
Optional(<NSTableView: 0x101203070>)
The #IBOutlet weak var resultTable: NSTableView!, at top of the file containing the class/NSView, show that it is connected, MainMenu.XIB—ResultTable.
I have come up with following code, in an attempt to display the data in the NSTableView, from my custom class View: NSView {
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
<...>
func numberOfRowsInTableView(in tableView: NSTableView) -> Int {
return fileDataToShow?.count ?? 0
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?{
var result:NSTableCellView
result = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) as! NSTableCellView
result.textField?.stringValue = fileDataToShow?[row][(tableColumn?.identifier.rawValue)!]! as! String
return result
}
resultTable?.beginUpdates()
// print(type(of:fileDataToShow)) // Optional<Array<Dictionary<String, Any>>>
resultTable.insertRows(at: IndexSet(integer: fileDataToShow?.count ?? 0), withAnimation: .effectFade)
resultTable.reloadData()
resultTable?.endUpdates()
}
Content of fileDataToShow is ok, but the other lines of code, .beginUpdates() / .insertRows(.., etc. doesn't seem to have any action.
As mentioned, I can't figure this out and don't know where or how to figure this... Anyone got some tips and/or pointers for me ?
I have defined all of the keys in fileDataToShow to correspond with the Identifiers in my XIB.
Hope I have managed to explain my problem in an ok way.
EDIT:
The Debug area giving following output when I run my app:
*** Illegal NSTableView data source (<NSObject: 0x600000000b90>). Must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
EDIT2/Update:
Thank you #vadian, but I still haven't managed to fix this, here's a little update.
Here's my whole file, DropZone.swift:
```
class DropView: NSView/*, NSTableViewDataSource, NSTableViewDelegate*/ {
#IBOutlet weak var resultTable: NSTableView!
let dropZoneEnteredBackgroundColor = CGColor(red: 165/255, green: 165/255, blue: 165/255, alpha: 1.0)
let dropZoneExitedBackgroundColor = CGColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 1.0)//NSColor.gray.cgColor
required init?(coder: NSCoder) {
super.init(coder: coder)
self.wantsLayer = true
self.layer?.backgroundColor = dropZoneExitedBackgroundColor
registerForDraggedTypes([NSPasteboard.PasteboardType.URL,
NSPasteboard.PasteboardType.fileURL])
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
self.layer?.backgroundColor = dropZoneEnteredBackgroundColor
return .copy
}
override func draggingEnded(_ sender: NSDraggingInfo) {
self.layer?.backgroundColor = dropZoneExitedBackgroundColor
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
guard let pasteboard = sender.draggingPasteboard.propertyList(forType:
NSPasteboard.PasteboardType(rawValue: "NSFilenamesPboardType")) as?
NSArray else {return false}
var droppedItems: [String: String] = [:]
for path in pasteboard {
guard let fullPath = path as? String else { return false }
let fileManager = FileManager.default
var isDir: ObjCBool = false
if fileManager.fileExists(atPath: fullPath, isDirectory:&isDir) {
if isDir.boolValue {
// the dropped item exists and it's a directory
droppedItems[path as! String] = "folder"
}
else {
// file exists and it's not a directory, hence a normal file
droppedItems[path as! String] = "file"
}
}
}
do {
var fileDataToShow = [[String:Any]]()
for object in droppedItems {
if object.value == "file" {
do {
//let fullPath = object.key
let attributes = try object.key.extendedAttributes() // Array<String>
let filename = object.key.fileName() + "." + object.key.fileExtension()
fileDataToShow.append(["state": "1",
"filename": filename,
"metadata":removed_attributes
])
}
catch {
debugPrint("Error info: \(error)")
}
}
else if object.value == "folder" {
// TODO
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
return fileDataToShow.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?{
let cell = tableView.makeView(withIdentifier: tableColumn?.identifier ?? NSUserInterfaceItemIdentifier(rawValue: ""), owner: self) as! NSTableCellView
// This line could crash if there values which are not String
cell.textField?.stringValue = fileDataToShow[row][tableColumn?.identifier.rawValue ?? ""] as! String
return cell
}
let insertionIndex = fileDataToShow.count
//debugPrint(resultTable) // Optional(<NSTableView: 0x10100ba10>)
//debugPrint(fileDataToShow) // [["filename": "img1.jpeg", "metadata": ["com.apple.metadata..", "com.a..."], "state": "1"]]
resultTable.insertRows(at: IndexSet(integer: insertionIndex), withAnimation: .effectGap)
} // do
return true
}
}
This is now giving the following error:
*** Canceling drag because exception 'NSTableViewException' (reason 'NSTableView error inserting/removing/moving row 2 (numberOfRows: 0).') was raised during a dragging session
Sorry, but have had trouble with this since the last reply from #vadian, so have to ask again.
What am I doing wrong?
EDIT 3:
Appreciate your answers, #vadian, but I do not get this. I have places the numberOfRows and tableView function right underneath the init function. And implemented following code last in the do-block, in an attempt to update the table:
resultTable.beginUpdates()
var i = 0
for row in fileDataToShow {
print("state:",row["state"]!) // 1
print("filename:",row["filename"]!) // file.jpg
print("metadata:",row["metadata"]!) // ["com.apple.metadata..", "com.a..."]
resultTable.insertRows(at: IndexSet(integer: i), withAnimation: .effectFade)
i += 1
}
resultTable.endUpdates()
New lines is added to the table, but they are all empty. How do I - in any way - bind fileDataToShow against resultTable.insertRows.
If you understand my poor spelling and fussy questions :)
Swift is hard but fun to learn!
There are many issues in the code.
numberOfRowsInTableView is numberOfRows(in tableView: in Swift 3+.
The datasource / delegate methods must be on the top level in the class, not in performDragOperation.
You are using too many question marks.
Don not declare the data source array as optional, declare it as empty non-optional array.
var fileDataToShow = [[String:Any]]()
func numberOfRows(in tableView: NSTableView) -> Int {
return fileDataToShow.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?{
let cell = tableView.makeView(withIdentifier: tableColumn.identifier!, owner: self) as! NSTableCellView
// This line could crash if there values which are not String
cell.textField?.stringValue = fileDataToShow[row][tableColumn.identifier!.rawValue)] as! String
cell result
}
To insert a row with animation don't call reloadData(). Get the last index of the array, append the item to the array and insert the row.
Begin-/endUpdates is useless
let insertionIndex = fileDataToShow.count
fileDataToShow.append([:]) // append some suitable dictionary
resultTable.insertRows(at: IndexSet(integer: insertionIndex), withAnimation: .effectGap)
I'd need to show a View with some UIImageView, texts and links in my TableViewController if list controller is empty.
I know you can insert a label through this code:
func numberOfSections(in tableView: UITableView) -> Int
{
var numOfSections: Int = 0
if youHaveData
{
tableView.separatorStyle = .singleLine
numOfSections = 1
tableView.backgroundView = nil
}
else
{
let noDataLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height))
noDataLabel.text = "No data available"
noDataLabel.textColor = UIColor.black
noDataLabel.textAlignment = .center
tableView.backgroundView = noDataLabel
tableView.separatorStyle = .none
}
return numOfSections
}
But I'd need to show a complex View designed in the Storyboard in a different ViewController.
I tried with this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let emptyViewController = storyboard.instantiateViewController(withIdentifier :"empty_controller") as! EmptyViewController
tableView.backgroundView = emptyViewController.emptyView
Where emptyView is defined in this way
class EmptyViewController: UIViewController {
#IBOutlet weak var emptyView: UIView?
...
}
But it doesn't work, I can't see that view in the table if the list is empty.
How can I do it?
Thank you a lot
Best bet is to use two containers view one with tableview and one with emptyVC, then in parent of that container you can hide/unhide or remove/add view depending upon your logic
EmptyViewController has its own view, why you have to create another emptyView?
Remote emptyView property and use this code:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let emptyViewController = storyboard.instantiateViewController(withIdentifier :"empty_controller") as! EmptyViewController
tableView.backgroundView = emptyViewController.view
Set you view controller to inherit from UITableViewDelegate and set the tableView.delegate to point to your VC.
class VC : UITableViewController, UITableViewDelegate {
override func viewDidLoad() {
...
tableView.delegate = self
}
}
Then override the following two functions
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
}
This'll add a header to your tableview section. You can edit what this header is based on the contents of the table