Display contents of NSDictionary in NSOutlineView and update this data - swift

I need to display the content of a NSDictionary in a NSOutlineView.
I do this by using a struct that holds my data and which is used by my NSOutlineView as DataSource.
struct DataItem {
var caption : String
var text : String
}
dataInOutlineView: [DataItem]
I loop over all my items in my NSDictionary and add them to my data array
data.append(DataItem(caption: "KeyFromDict", text: "TextFromDict"))
And then I reload my NSOutlineView
outlineView.reloadData()
This concept works find but my problem is, that the data inside the NSDictionary changes regularly and I want to display the updated values in the NSOutlineView.
My first attempt was to remove all items from the array
data.removeAll
Then added the data again using the method above and then reload the NSOutlineView again but this results in a complete refresh of the NSOutlineView which means that all expanded items are collapsed again and the NSOutlineView is scrolled to top.
What would be a better idea to realize this? (if not is there a way to update the data using the method above but remain all expanded/collapsed and scroll location state?)
Thanks!

Related

Discard changes inside UITableView do not reflect on the cell with UITextView that is not visible but active

I have UITableView with UITableViewCells that contains UITextView. And I have logic for temporary data and saved data. So, when user edit that cells everything is working, including discarding changes that I do with swapping temporary data and saved data. Then I do reloadData on table view and everything is showed correctly.
My problem is when the user clicks on the textfield and scrolls the table view until the "active" text view cell is not visible and the changes are then discarded. What happens is that all is discarded, but the active cell has the latest data that is not discarded. If I put "cursor" on some other cell and then discard changes again, it'll be ok, all cells that aren't active will discard data.
So, discarding data is not working only on cell that is not visible but still has active uitextview.
I tried to get "latest" active cell and manually reload them but it's not working. I have tried to resignFirstResponder() from tableview and then discarding data and reloading tableview but it's not working.
I expect all text views inside the tableview to have old data that is not saved when I click discard changes.
Cell that is still active (with an active text view) and is not visible on the screen won't discard changes, it will still have old data (but it will not be active anymore)
EDIT:
Here is how everything is working.
Here is how my code is working (very simplified).
So, I have
ViewModel -> ViewController -> Cell
On viewModel i have:
var data = ["Some", "things", "are", "good"]
tempData = []
on init I put tempData = data
(we are here talking about structs so value types, and everything is working as I said regarding that)
on cellForRow I have
cell.data = viewModel.data[indexPath.row]
and If I click discard changes on viewController I just do this in viewModel:
data = tempData
and on ViewController
tableView.reloadData()
and everything is working.
Only thing that is not working is that active cell that is not on the screen. It's not affected by changes in "core" dataset.
So maybe there's a connection about UITableView can't refresh cells that have active UITextView and are not visible at the moment of reloadData()? I don't know about that.
EDIT2:
cellForRow
guard let cell: ExampleTableViewCell = tableView.dequeueReusableCell(),
let data = viewModel.data[safe: indexPath.row] else {
return UITableViewCell()
}
cell.model = (name: data.name, surname: data.surname)
cell.onTextViewDidChange = { [weak self] text in
self?.tableView.beginUpdates()
self?.tableView.endUpdates()
UIView.setAnimationsEnabled(true)
self?.viewModel.update(text: text, index: indexPath.row)
}
return cell
One of the key features of UITableView is that it reuses the same instances of the cells over and over again, only keeping as many unique instances as it takes to simulate there being infinite instances. Notice how in tableView(_: cellForRowAt: ) you do not initialize a cell, you dequeue it. This is because UIKit manages when the cells are initialized for the first time and when they are "recycled", hence RecyclerView on Android.
Without being able to look at your code, I would guess that the issue is that your temporary data is stored inside of an instance of the cell and when the cell scrolls offscreen, something is happening in your tableView(_: cellForRowAt) function which tells the cell to display a different cell's data and doesn't save the temporary data. My suggestion for a fix if this is the case would be to store the temporary data in your UIViewController rather than in the cell. It is also better MVC to store this kind of data in the controller rather than in the view.

After sorting an NSTableview table bound to NSArrayController selected row no longer matches array element

I have setup Double Action in IB on the table and when I double-click on a tableview row after the table was sorted, tableview.selectedRow is now different that the index of the array it was originally loaded from.
let trade = trades[tableView.selectedRow] lets me see trade.tradeNo and other model elements but is not the object from the row I clicked on in the table (due to the sorting if I've clicked on a column header... my sorting is setup in IB not in code using sortDescriptors).
Is there a way to use tableView.selectedRow as the index to do something like:
let trade = arrayController.selectedObjects as! [TradeMode]
But this does not work.... there is no tr.tradeNo
For the life of my I cannot find out how to get the row's data.
I have iterated through arrayController.arrangedObjects like this
for trade in arrayController.arrangedObjects as [TradeModel]{
.... and I can see all the correct data..
eg trade.tradeNo etc.
But I just want to use an index and go grab the row I've double-clicked on.
All the web searching I've done keeps coming up with how tos on bindings of tableview to array controller.... that is all setup and working nicely. Here is a pic of the issue.... not the highlighted row in the table beneath sorted by date descending. selectedRow 5 is not longer index 5 in the array 'trades' shown in the details popup screen
TableView w/Popup detail screen
[2018-10-06]: I have found a workable solution although I am not certain it is the best. After the double action (double click) is made I am making a copy of arrayController.arrangedObjects into a new array
let tr = arrayController.arrangedObjects as! [Trade]
This array (tr) has all the trades now in the sorted order. So when I use tr[tableView.selectedRow].tradeNo I get the correct trade.
Again, I am not certain I am doing the best thing for my app. I am trying to use Swift and KVO and the arrayController to do all the heavy lifting. So, I am still questioning how to do this correctly.
Are there settings in IB for the arrayController (eg. in Bindings) for selectionIndexes and sortDescriptors and so forth that will keep my original array (trades) in synch with changes in the arrayController? Perhaps I really should not want that to happen. I simply do not know yet.
I found this solution by temporarily creating a copy of the custom object as NSMutableArray, sorting it with the NSArrayControllers sortDescriptors and setting the original array to the sorted array as the custom object. When you're clicking the table header so that the table rows get sorted by e.g. tradeNo, the sortDescriptor is sent to the NSTableViewDataSource where we can fetch it using sortDescriptorsDidChange.
Bind your table view's data source to ViewController and create an extension for it. (The two lines regarding row selection are not directly related, but you might wonder how to do it once you've implemented the solution.)
class ViewController: NSViewController {
#objc dynamic var objects: [Object] = []
// ...
}
extension ViewController: NSTableViewDataSource {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
let rowToBeSelected = IndexSet(integer: (tableView.selectedRow))
guard let sortDescriptor = tableView.sortDescriptors.first else { return }
let objectsAsArray = NSMutableArray(array: objects)
objectsAsArray.sort(using: [sortDescriptor]) //: or skip the "guard let" and use "tableView.sortDescriptors". Perhaps someone has advice on good practice here.
objects = objectsAsArray as! [Object]
tableView.reloadData()
tableView.selectRowIndexes(rowToBeSelected, byExtendingSelection: false)
}
}
While this solution works, it created some problems in regard to text fields that I bound to the table view (and by trial also directly to the array) for continuous updating. So I dared to attempt an all-IB-solution and eventually figured it out. This approach requires some different configurations, e.g. not binding the individual columns Table Cell View's to the ArrayController via Controller Key |> arrangedObjects, Model Key Path |> tradeNo, but the Table View. This worked fine with continuous updating at first, however, once the sortDescriptors were applied via clicking the table view header in the running app, Continuously Updates Value also caused unexpected behavior. And by deactivating it, the behavior is equal to that of the solution above.
Maybe I'll add a little walk-through later on if there's interest, but the programmatic solution should work.
Also, fyi, I only got it to work with Swift4. From 4.2 on, the binding (IB) methods from all guides I could find are deprecated; as is apple's documentation without reference to the crucial changes.

Swift problems with loading data into UITableView using dictionary keys and values

Im a beginner to developing apps and I'm trying to create my first real app using Xcode/Swift.
The problem I am having is setting up a table view to show certain information. I've managed to create other table views successfully but this one is slightly more complicated. I'll start off by breaking it down.
I have a variable that receives data via a segue called var recievedSelection = (choice: "", choiceValue:0.0) 'choice' being the name of selection and 'choiceValue' being the cost of that selection.
I then store this data into a dictionary array called var dict:[String:Double] = ["":0.0] by using this line of code:
self.dict[self.recievedSelection.choice] = self.recievedSelection.choiceValue
I have a table view all set up with delegate methods etc working and dequeueReusableCellWithIdentifier("basic", forIndexPath: indexPath). The numberOfSectionInTableView return 1 and numberOfRowsInSection return self.dict.count
What I am having problems with is using the data of each 'key' of self.dict to set as the cell.textLabel?.text and the data of each 'value' of self.dict to set as the cell.detailTextLabel?.text so that when the tableView loads the name and price of that selection is visible in each cell. Once user has made all their selections/choices as there will be a few to make throughout the app, there should be a list of those selections/choices that can be viewed in this table view.
Then there is a "Total" amount at the bottom just under the table view that calculates the overall amount.
I have also set up the table view to delete individual cells that when and if a cell is deleted that amount/price set into that cell is deducted from the total at the bottom of screen.
I am using NSUserDefaults to save the data loaded into the table view as there isn't any personal data being stored and it is only a brief estimate.
But the problem is, I can't get the table view to load both the values and keys of 'self.dict' into the table view textLabel and the detailTextLabel?
I hope that makes sense! I'd really appreciate any help.
I'm going to assume that you want to get values for your UITableViewDataSource from a Dictionary. An Array would be better, and you can likely change your code to use one for the UIViewController's data, but if you must, first get a set of the Dictionary's keys into an Array:
var myDataSource = Array(self.dict.keys)
Reassign this value EVERY TIME you edit/delete from the Dictionary, before you call tableView.reloadData(), to avoid index out of bounds crashes. Now, in your dataSource methods, you can check
self.dict[myDataSource[indexPath.row]]
and
myDataSource[indexPath.row]
and
return self.dict.count
This will give you both the size of the array that you need for number of rows, and the value and key you need from your Dictionary. If you like, you can also iterate over a Dictionary's keys and values with:
for (key, value) in self.dict {
//do something
}
I am not completely sure from your question what is causing the problem, meaning i'm not sure which value you are not getting. However, you can get those values by using:
let keyArray = dict.allKeys
then in your cellForIndexPath:
cell.textLabel.text? = keyArray[indexPath.row] //the "key"'s Value
cell.detailtextLabel.text? = dict[keyArray[indexPath.row]] // the "value"s value
Hope this helps your issue?

Store and retrieve the array of dictionary values and load it into tableview

In Swift.
I want to store this two values the 1.Name and 2.Address these are two text field which are in single row of table view cell and i have “+” button where i can add multiple entires.
so here what ever the values i have enter in the textfield it has to store locally.
And then the next time when i comes to screen i have to retrieve the data and load into the tableview.
So tried with creating the Array of dictionary with having the key value pair of name and address.
Storage Class:
class StorageModel: NSObject
var dict = [String: String]
*) Main Class:
With having the table view delegate and other stuff.
*) TableView Cell:
Which contains the
Name text field
Address text field.
So how can i implement this functionality. I was stuck on this. Please help me friends. Thanks in Advance.

Xcode Obj-C: Make new NSArray from one key each out of an Array of Dictionaries

This actually could be a multipart question. But here's the first part ...
I have an array (actually in a plist) of dictionaries. Each dictionary has 3 keys in it: (title), (points), and (description).
I am trying to make a NEW array with the values of the key "title" from each dictionary in that first array.
Let me explain WHY I am doing this and maybe that will provide a better all around explanation.
I am trying to let people pick from a pre-determined list. Heck, if this was a web page it would be very simple since all I really care about are the "points" and the "Title". On a web site I could simply do a drop down combo-box with the "points" being the value and the title being the text for each row.
But this is not a web page.
So what I am trying to do here is pop out a modal picker when they click the text field. The modal picker shows the alphabetical ordered "titles" from our new array. And whichever one they select, it closes the modal view and assigns that "title" text to the UITextField which cannot be edited by the user.
I have some code to get my modal picker to pop out. But I need to feed it an array of just the "titles" of each dictionary in my real array.
[array valueForKeyPath:#"title"]
That should return a new array with the result of calling [obj valueForKey:#"title"] on each object in the array.
In this case, you should be able to just use
[array valueForKey:#"title"]
as well, but if you wanted to go further into the dictionary (like "person.name" or something), the key-path version is very nice.