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?
Related
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.
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.
Recently I had a bad headache (and I'm still struggling) to find out how to retrieve all values inside an UIPickerWheel. For me should be enough to move at particular row of the wheel, but I can't! So frustrating! I tried to scroll row by row to retrieve all values (https://stackoverflow.com/a/39300344/821407) but it's so slow! Any clue?
NB: I can't use adjustToPickerWheelValue because my root problem is that I don't know the value since they are dynamic and I would like to avoid launchArguments/launchEnvironment.
This is probably not the answer you were hoping for but it is not possible to get the title of all rows in a UIPickerView in a UITest.
As you know when running a UITest you can only access your app's UI elements via the XCUIElement class. That class has a value property that gives you some information about the UI element you access. When accessing a UIPickerView the value gives you the title of the currently selected row. But only of the selected row. You can access the picker's row elements, but unfortunately the value property for the row elements is always empty. So, no luck here. All you the info you can get is the number of rows of your picker.
This is not really surprising though. Even if you had access to the UIPickerView, you could not access the titles of all rows directly. UIPickerView does not know about the titles that it displays. It is the job of the UIPickerViewDataSource to provide the titles.
So, unfortunately, if you need to know all the row titles of your UIPickerView in a UITest, you really have to select each value one by one via your app's user interface.
But it does not have to be as complicated as in the answer you linked. Instead of simulating a scroll you can simply tap on the next row to select it (should be slightly faster):
let pickerView = app.pickerWheels.element
let numRows = pickerView.children(matching: .any).count
var values: [String] = [pickerView.value as! String]
for _ in 0..<numRows {
pickerView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.55)).tap()
values.append(pickerView.value as! String)
}
In your question you do not really describe what you are trying to test. But if you want to test if the picker has the correct row titles maybe a UnitTest would be a more practical approach?
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.
My data is sourced in a plist and displayed in a tableview with navigation bar.
I want to load the subview with the cell that was clicked.
I know this would set it correct, but I'm not sure how to implement indexPath in the viewDidLoad method?
self.navigationItem.title = [[self.exerciseArray objectAtIndex:indexPath.row]objectForKey: #"name"];
You have an array of Dictionary With you. Now IndexPath is seen called in TableView delegate methods. I doesn't mean that you can't create one. Create an NSIndexpath obect and assign the indexes parameters and send it to the function which contains the above code( separate the code which sets the title from the Cell for row at index path).
But the problem is as I see, You want to show only a specific set of data in dictionary which is stored in an Array. The real problem is You should know which the index of the Array you want to load. to the title and the tableview. If you can know that then its solved.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSIndexPath_Class/Reference/Reference.html
Regards ,
Jackson Sunny Rodrigues