How to correctly add a row to NSTableView - swift

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)

Related

Swift - Save all documents of firestore collection in a list of objects

I want to retrieve all the documents of a firestore collection, and then write it in an object then append it my list of objects.
after that i display it in an UITableView.
Here is what I have, it works without errors but when I run it, nothing is displayed.
The list structure:
struct RewardsStruct {
//var rewardKey: String
var Reward: String
var noPoints: String
var QRimageURL: ImageURL = ImageURL(url: nil, didLoad: false)
var Desc: String
var isvalid: Bool
}
Here is my retrieving code:
private func getRewards() {
var rewardsList = [RewardsStruct]()
let db = Firestore.firestore()
db.collection("Rewards").getDocuments { (snapshot, error) in
if error != nil {
print(error)
} else {
for document in (snapshot?.documents)! {
let code = RewardsStruct( Reward: document.data()["Reward"] as! String , noPoints: document.data()["noPoints"] as! String , QRimageURL: document.data()["QRimageURL"] as! ImageURL, Desc:document.data()["Desc"] as! String, isvalid: (document.data()["isvalid"] != nil) )
self.rewardsList.append(code)
DispatchQueue.main.async {
self.CodeTable.reloadData()
}
}
}
}
}
The rest of code in ViewController as some requests
class RewardsVC: UIViewController {
var rewardsList = [RewardsStruct]()
var reward:RewardsStruct!
#IBOutlet weak var infoView: UIView!
#IBOutlet weak var ViewLabel: UILabel!
#IBOutlet weak var CodeTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
CodeTable.delegate = self
CodeTable.dataSource = self
ViewLabel.isHidden = true
infoView.makeCornerRounded(cornerRadius: 30, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner])
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
rewardsList.removeAll()
getRewards()
}
private func getRewards() {
....
}
extension RewardsVC: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
CodeTable.backgroundColor = UIColor(named: "#F5F5F5")
return 60
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 125
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("List number of rows")
print(rewardsList.count)
return rewardsList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RewardsCell") as! RewardsCell
let object = rewardsList[indexPath.row]
cell.Reward.text = object.Reward
cell.Desc.text = object.Desc
cell.noPoints.text = "-" + object.noPoints + " Points"
return cell
}
func addShadow(backgroundColor: UIColor = .white, cornerRadius: CGFloat = 12, shadowRadius: CGFloat = 5, shadowOpacity: Float = 0.1, shadowPathInset: (dx: CGFloat, dy: CGFloat), shadowPathOffset: (dx: CGFloat, dy: CGFloat)) {
} }
Here is my FireStore:
Try this example code, to ...save all documents of firestore collection in a list of objects....
getRewards(...) is called an asynchronous function, and needs a way to "wait" for the results to be available before you can use them.
There are many ways to do this, here I present an example code that uses a completion handler to pass the results (or errors) of getRewards(...) back to the calling function. Note, the code is untested since I do not have your database (or even Firestore).
// -- here some error type for testing
enum FireError: Error {
case decodingError
case badError
// ...
}
// -- here completion handler
private func getRewards(completion: #escaping ([RewardsStruct], FireError?) -> ()) {
var rewardsList = [RewardsStruct]()
let db = Firestore.firestore()
db.collection("Rewards").getDocuments { (snapshot, error) in
if error != nil {
print(error)
completion([], FireError.badError) // <-- here
} else {
for document in (snapshot?.documents)! {
let code = RewardsStruct(Reward: document.data()["Reward"] as! String , noPoints: document.data()["noPoints"] as! String , QRimageURL: document.data()["QRimageURL"] as! ImageURL, Desc:document.data()["Desc"] as! String, isvalid: (document.data()["isvalid"] != nil) )
self.rewardsList.append(code)
}
completion(rewardsList, nil) // <-- here
}
}
}
Use the function like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getRewards() { results, error in // <-- here
if error == nil {
rewardsList.removeAll()
rewardsList = results
DispatchQueue.main.async {
self.CodeTable.reloadData()
}
} else {
// todo deal with errors
}
}
}
Alternatively, you can also use this code, without using a completion handler, since getRewards(...) is inside your UIViewController.
private func getRewards() {
let db = Firestore.firestore()
db.collection("Rewards").getDocuments { (snapshot, error) in
if error != nil {
print(error)
} else {
self.rewardsList.removeAll() // <-- here
for document in (snapshot?.documents)! {
let code = RewardsStruct(Reward: document.data()["Reward"] as! String , noPoints: document.data()["noPoints"] as! String , QRimageURL: document.data()["QRimageURL"] as! ImageURL, Desc:document.data()["Desc"] as! String, isvalid: (document.data()["isvalid"] != nil) )
self.rewardsList.append(code) // <-- here
}
DispatchQueue.main.async { // <-- here
self.CodeTable.reloadData()
}
}
}
}
and use it like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getRewards()
}

how to add a searchbar in a tableview with a parsed JSON file

I have successfully parsed a JSON file with the following data model into my project and my tableview.
import Foundation
struct ActionResult: Codable {
let data: [Datum]
}
struct Datum: Codable {
let goalTitle, goalDescription, goalImage: String
let action: [Action]
}
struct Action: Codable {
let actionID: Int
let actionTit: String
}
Now I am trying to create a searchbar to search on the "actionTitle". My tableview has section headers and rows.
Relevant code:
var filteredData: [Action]?
let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
searchController.searchBar.delegate = self
filteredData = ????
navigationItem.searchController = searchController
parseJSON()
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = []
if searchText == ""{
filteredData = ????
}
else {
for actions in ???? {
if actions.lowercased().contains(searchText.lowercased()) {
filteredData.append(actions)
}
self.tableView.reloadData()
}
I do not know what code to use where I have ????.
Thanks.
You want to keep an instance of all of the available data (allActions), and always show your filter data (filteredData) in the tableView. So when there is nothing to filter, filteredData is equal to allActions (unless you intend to hide all data when the search is empty).
When searchBar(_:,textDidChange:) is called, you can use filter(_:) to evaluate if the item should be included. Apple's description on the filter closure:
A closure that takes an element of the sequence as its argument and
returns a Boolean value indicating whether the element should be
included in the returned array.
I don't know if there is a specific reason for declaring filteredData: [Action]?, is it because the data is not populated until parseJSON() is called? If so--I suggest initializing an empty arrays and populating them when the data is available.
Also, does parseData() produce an instance of Datum? I believe this piece of your code is not included, so I am adding datum: Datum?.
If I am wrong, please provide more info what parseJSON() populates and I will update my answer.
var result: ActionResult? {
didSet {
guard let result = result else { return }
allSectionDataActionMap = Dictionary(uniqueKeysWithValues: result.data.enumerated().map { ($0.0, ($0.1, $0.1.actions)) })
updateFilteredData()
}
}
var allSectionDataActionMap = [Int: (datum: Datum, actions: [Action])]()
// Maps the section index to the Datum & filtered [Action]
var filteredSectionDataActions = [Int: (datum: Datum, actions: [Action])]()
let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
searchController.searchBar.delegate = self
navigationItem.searchController = searchController
// ...
parseJSON()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return filteredSectionDataActions.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredSectionDataActions[section]?.actions.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .default, reuseIdentifier: "cell")
if let action = filteredSectionDataActions[indexPath.section]?.actions[indexPath.row] {
// setup cell for action
cell.textLabel?.text = action.actionTitle
}
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
updateFilteredData(for: searchText.lowercased())
tableView.reloadData()
}
func updateFilteredData(for searchText: String = String()) {
if searchText.isEmpty {
filteredSectionDataActions = allSectionDataActionMap
} else {
for (index, (datum, actions)) in allSectionDataActionMap {
let filteredActions = actions.filter { $0.actionTitle.lowercased().contains(searchText) }
if filteredActions.isEmpty {
filteredSectionDataActions[index] = (datum, actions)
} else {
filteredSectionDataActions[index] = (datum, filteredActions)
}
}
}
}

Modify controller to write some data to a log file every second

I'm a Rails developer but I need to modify the controller of an application to save some sensors data to an external file.
I have this controller that take some data and populate a TableView, and data are updated every time I push a button in my view.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var currentNames: [Any] = [], voltageNames: [Any] = [], thermalNames: [Any] = []
var currentValues: [Any] = [], voltageValues: [Any] = [], thermalValues: [Any] = []
override func viewDidLoad() {
super.viewDidLoad()
currentNames = currentArray()
voltageNames = voltageArray()
thermalNames = thermalArray()
currentValues = returnCurrentValues()
voltageValues = returnVoltageValues()
thermalValues = returnThermalValues()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch (section) {
case 0:
return currentNames.count
case 1:
return voltageNames.count
case 2:
return thermalNames.count
default:
return 0;
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cell")!
let string: NSMutableString = ""
switch (indexPath.section) {
case 0:
// cell.textLabel.text =
let name = currentNames[indexPath.row] as! NSString
let number = currentValues[indexPath.row] as! NSNumber
string.appendFormat("%#: %.2lf", name, number.doubleValue)
case 1:
let name = voltageNames[indexPath.row] as! NSString
let number = voltageValues[indexPath.row] as! NSNumber
string.appendFormat("%#: %.2lf", name, number.doubleValue)
case 2:
let name = thermalNames[indexPath.row] as! NSString
let number = thermalValues[indexPath.row] as! NSNumber
string.appendFormat("%#: %.2lf", name, number.doubleValue)
default:
break;
}
cell.textLabel?.text = string as String?
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3;
}
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
switch (section) {
case 0:
return "Current (A), \(currentNames.count) items"
case 1:
return "Voltage (V), \(voltageNames.count) items"
case 2:
return "Temperature (°C), \(thermalNames.count) items"
default:
return "";
}
}
#IBAction func reloadData(_ sender : AnyObject) {
currentValues = returnCurrentValues()
voltageValues = returnVoltageValues()
thermalValues = returnThermalValues()
tableView.reloadData()
}
}
I need to refresh data every second without pushing the button and I want to save this data in a log file.
I create a log.swift file
import Foundation
struct Log: TextOutputStream {
func write(_ string: String) {
let fm = FileManager.default
let log = fm.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("log.txt")
if let handle = try? FileHandle(forWritingTo: log) {
handle.seekToEndOfFile()
handle.write(string.data(using: .utf8)!)
handle.closeFile()
} else {
try? string.data(using: .utf8)?.write(to: log)
}
}
}
var logger = Log()
I know that I can do something every second with
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
}
But I don't understand where to put this.
I just need to save a log with thermalNames and thermalValues
Basically the cell I have in my tableView
let name = thermalNames[indexPath.row] as! NSString
let number = thermalValues[indexPath.row] as! NSNumber
string.appendFormat("%#: %.2lf", name, number.doubleValue)
Then I will parse the file with ruby to convert to a csv...
In order to do that you have to create two functions. One for the action you want to perform and the other one for timer itself
here are the functions:
#objc func save() {
//Your Saving action has to be here
}
You have to call this one in ViewDidLoad
func startTimerForShowScrollIndicator() {
self.timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.save), userInfo: nil, repeats: true)
}

Dictionary search the key and get the value

I got a plist object which contains all the words key=english and value=malay and I assigned in to 2 different arrays which is english and malay. Now I want a textfield where I want to search the english word and print the malay word in the label.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
#IBOutlet weak var selectedLabel: UILabel!
#IBOutlet weak var searchText: UITextField!
#IBOutlet weak var wordTable: UITableView!
var english = [String]()
var malay = [String]()
var words: [String: String] = [:]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
wordTable.dataSource = self
wordTable.delegate = self
searchText.delegate = self
if let path = Bundle.main.path(forResource: "words", ofType: "plist"){
if let plistData = FileManager.default.contents(atPath: path){
do {
let plistObject = try PropertyListSerialization.propertyList(from: plistData, options: PropertyListSerialization.ReadOptions(), format: nil)
words = (plistObject as? [String: String])!
english = [String] (words.keys)
malay = [String] (words.values)
} catch {
print("Error Serialize")
}
} else {
print("Error reading data")
}
} else {
print("Property list")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return english.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell!
cell = tableView.dequeueReusableCell(withIdentifier: "tabelCell")
if cell == nil {
cell = UITableViewCell(
style: UITableViewCellStyle.value2,
reuseIdentifier: "tableCell")
print("creating a table cell")
}
cell!.textLabel!.text = english[indexPath.row]
cell!.detailTextLabel?.text = malay[indexPath.row]
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedLabel.text = malay[indexPath.row]
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// Hide the keyboard
textField.resignFirstResponder()
return true
}
#IBAction func searchBtn(_ sender: UIButton) {
let result = words.filter {$0.key == searchText.text}
if result.count > 0 {
print(result)
selectedLabel.text! = result.values //error
} else {
print("Not found")
}
}
}
the output I expecting is textfield(Bus) which is english word then in the label show me the malay word(Bas)
You have a plist file as a Dictionary. So you can get the dictionary object from the plist file and already answer here.
Make a structure for better data binding.
struct Word {
var english: String
var malay: String
}
Then declare an array of words globally in your ViewController.
var words: [Word] = [] // An empty array
In viewDidLoad: fetch data from plist file.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
wordTable.dataSource = self
wordTable.delegate = self
searchText.delegate = self
if let path = Bundle.main.path(forResource: "words", ofType: "plist") {
if let plistData = FileManager.default.contents(atPath: path){
do {
guard let plistObject = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: String] else {
// Plist is not [String: String]
return
}
// Here you need to change the code. Converting the dictionary into struct array
var words: [Word] = plistObject.map {Word(english: $0.key, malay: $0.value)}
/// Then sort by english word if needed
words.sorted {$0.english < $1.english}
} catch {
print("Error Serialize")
}
} else {
print("Error reading data")
}
} else {
print("Property list")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return words.count
}
Update your cell data as well.
cell!.textLabel!.text = words[indexPath.row].english
cell!.detailTextLabel?.text = words[indexPath.row].malay
And your button action with minor modification:
#IBAction func searchBtn(_ sender: UIButton) {
let result = words.filter {$0.english == searchedText}
if let word = result.first {
selectedLabel.text = word.malay
} else {
selectedLabel.text = "" // No data found
}
}
You can replace $0.english == searchedText with {$0.english.contains(searchedText)} if you want to filter with contains, But in that case you might get the multiple result. I assume that in your case you need it as a translator so use ==.
Why don't you search in your plist object? I think it is simpler
#IBAction func searchBtn(_ sender: UIButton) {
guard let words = plistObject as? [String: String], let key = searchText.text else { return }
selectedLabel.text = words[key] ?? ""
}
Something like this.

NStableView does not show my data

I have a tableView with an image and a textField but they are not showing the data. What could be missing?
I have tableColumn identifier set as MainCell...
I converted obj-c from here to swift.
Any suggestions?
import Cocoa
class viewTime: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
var tableContents:NSMutableArray = []
#IBOutlet var tableView: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
let path: NSString = "/Library/Application Support/Apple/iChat Icons/Flags"
let fileManager:FileManager = FileManager.default
let directoryEnum:FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: path as String)!
while let file = directoryEnum.nextObject() as? NSString{
let filePath:NSString = path.appendingFormat("/%#", file)
let obj:NSDictionary = ["image": NSImage(byReferencingFile:filePath as String), "name": file.deletingPathExtension]
print("obj:\(obj)")
tableContents.add(obj)
}
self.tableView.reloadData()
}
func numberOfRows(in tableView: NSTableView) -> Int {
return tableContents.count
}
private func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView! {
let cellView = tableView.make(withIdentifier: "MainCell", owner: self) as! NSTableCellView
let flag:NSDictionary = tableContents[row] as! NSDictionary
let identifier:NSString = tableColumn!.identifier as NSString
if (identifier == "MainCell") {
cellView.textField?.stringValue = flag["name"] as! String
cellView.imageView!.image = flag["image"] as? NSImage
return cellView
}
return nil
}
}
Translating Objective-C to Swift literally is always a bad idea.
First of all make sure that both datasource and delegate of the table view are connected to the view controller in Interface Builder.
Make sure also that the Identifier of the NSTableCellView is MainCell
Most important change: Create a custom struct, it makes things so much easier.
struct Asset {
var name : String
var image : NSImage
}
Declare the data source array as Swift Array of the struct type.
var tableContents = [Asset]()
This is viewDidLoad in real Swift code. Don't annotate types unless the compiler tells you to do.
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(fileURLWithPath:"/Library/Application Support/Apple/iChat Icons/Flags")
let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [], options: .skipsHiddenFiles, errorHandler: nil)!
for case let fileURL as URL in enumerator {
let asset = Asset(name: fileURL.deletingPathExtension().lastPathComponent, image: NSImage(contentsOf:fileURL)!)
tableContents.append(asset)
}
self.tableView.reloadData()
}
numberOfRows is OK.
tableView:viewForColumn:Row can be simplified as there is only one identifier and one column.
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cellView = tableView.make(withIdentifier: "MainCell", owner: self) as! NSTableCellView
let asset = tableContents[row]
cellView.textField!.stringValue = asset.name
cellView.imageView!.image = asset.image
return cellView
}