UIPickerView and UI Unit Test: How to get values of UIPickerWheel? - swift

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?

Related

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.

Access to Checkboxes (NSButton) in a Controller View

I have a View with 50 checkboxes (NSButton) in a OS X app. I need to access to each one in code, but the only way I find is to create 50 IBOutlet bindings. The IBOutlet collection is not available in OS X. How can I do?
One way to refer to the 50 checkboxes in your ViewController class (I'm assuming that you use view controllers) is by first retrieving the superview that contains each of the 50 checkboxes. This will be an NSView object. To distinguish between the checkboxes I recommend setting different tag values right in interface builder, but it really depends on what you want to do with the checkboxes. Then, you can use a loop to iterate through each of the subviews in this view, like this:
for i in view.subviews.filter({$0 as? NSButton != nil}).map({$0 as! NSButton}) {
if i.bezelStyle == .regularSquare && !(i.cell as! NSButtonCell).imageDimsWhenDisabled {
print(i.title)
}
}
The if condition basically utilizes two properties of checkboxes to distinguish them from any other type of NSButton.
In this example, I simply printed the title of each checkbox. You can use a switch statement if you want to perform different tasks depending on which checkbox it is. The good thing is that with this method you can have infinitely many checkboxes. The iteration order is left to right, top to bottom.

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?

Collapsable Table Cells [Swift 3] [duplicate]

This question already has answers here:
Accordion table cell - How to dynamically expand/contract uitableviewcell?
(5 answers)
Closed 6 years ago.
I am trying to recreate the collapsable date picker that the calendar app uses when creating a new event. I've put an example of what I'm trying to do on github
In short, I've created a static table, added three cells. The first cell is for the date, and contains a button to toggle the second cell. The second cell is the date picker. The third cell is arbitrary. In the code I'm trying to set the height of the table cell (and the date picker if needed) to zero, and then toggle the size whenever the user clicks the button. No matter what I've tried, I can't a) get the cell to collapse without some sort of gap, and 2) get the animation to smoothly transition from expanded to collapsed and back again.
Edit: This question is not the same as the duplicate answer, in that I wanted to expand a separate table cell and not the same cell as being selected. But, I personally can live with using a same-cell expansion. I also updated my github project so future people can see a working example.
It's very simple; you are probably over-thinking things here. This functionality is built in; Apple wants you to be able to expand and contract a cell. You just aren't using the API Apple has provided. Use it! Here's how.
The date picker cells are always present. But their height is zero (and their clipsToBounds is true) so you don't see them. So implement heightForRowAtIndexPath to return zero for those cells.
To show a date picker cell, change what heightForRowAtIndexPath returns (this is easiest if you have a property that holds this value, so you can just change the property value and have heightForRowAtIndexPath read it from there) and say:
self.tableView.beginUpdates()
self.tableView.endUpdates()
That's all there is to it!
Here's a quick demo I made. The red and orange things are cells. The table has three cells but the second one, containing the date picker, starts out with zero height:

UITableView: moving a row into an empty section

I have a UITableView with some empty sections. I'd like the user to be able to move a row into them using the standard edit mode controls. The only way I can do it so far is to have a dummy row in my "empty" sections and try to hide it by using tableView:heightForRowAtIndexPath: to give the dummy row a height of zero. This seems to leave it as a 1-pixel row. I can probably hide this by making a special type of cell that's just filled with [UIColor groupTableViewBackgroundColor], but is there a better way?
This is all in the grouped mode of UITableView.
UPDATE: Looks like moving rows into empty sections is possible without any tricks, but the "sensitivity" is bad enough that you DO need tricks in order to make it usable for general users (who won't be patient enough to slowly hover the row around the empty section until things click).
I found that in iOS 4.3, the dummy row needs to have a height of at least 1 pixel in order to give the desired effect of allowing a row to be moved into that section.
I also found that the dummy row is only needed in the first and last section; any sections in between don't have this problem.
And it looks like in iOS 5.0, no dummy rows or special tricks are needed at all.
While managing the edit, you can monitor if the table view is in Edit Mode. Use that flag inside of cellForRowAtIndexPath to decide weather or not to display the 'blank' row. While in 'regular' mode, the row will not display, but when the user taps 'edit' cellForRowAtIndexPath should get called again and this time decide to display the row. The details of how to do that depend on your data source and how you are gluing it to the display. If you aren't getting the call again, you can manually inject rows with insertRowsAtIndexPaths / deleteRowsAtIndexPaths and/or call reloadData to force a refresh.
I found that if you return -1.0 from the heightForRowAtIndexPath method it will remove the 1 pixel line.