Displaying different values for each row in NSTableView - xcode

I am trying to make a program that will have an NSTableView that I can add and remove values from, and for each value it will store a few different variables that will be displayed in text boxes when a user selects an item from the table. I've already wrote the code to add and remove values, but can't seem to find a way to get the rest of the functionality to work. How can I do this?

I suggest that you you represent each element in your tableview datasource (your array of objects) as a NSDictionary. This enables you to keep several variables for each tableview item, which can be displayed in textboxes when an item is clicked. Apple has a very good example that illustrates what I think you are trying to do. Take a look at the NSTableViewBinding example. In the example, when the user double-clicks an item in the tableview an inspect method is called. You can use this method to display your variables from the dictionary in textboxes:
- (void)inspect:(NSArray *)selectedObjects
{
// this is an example of inspecting each selected object in the selection
int index;
int numItems = [selectedObjects count];
for (index = 0; index < numItems; index++)
{
NSDictionary *objectDict = [selectedObjects objectAtIndex:index];
if (objectDict != nil)
{
NSLog(#"inspect item: {%# %#, %#}",
[objectDict valueForKey:#"firstname"],
[objectDict valueForKey:#"lastname"],
[objectDict valueForKey:#"phone"]);
[myTextBox1 setStringValue:[objectDict valueForKey:#"firstname"]];
[myTextBox2 setStringValue:[objectDict valueForKey:#"lastname"]];
}
}
}

Related

Xcode UITest scrolling to the bottom of an UITableView

I am writing an UI test case, in which I need to perform an action, and then on the current page, scroll the only UITableView to the bottom to check if specific text shows up inside the last cell in the UITableView.
Right now the only way I can think of is to scroll it using app.tables.cells.element(boundBy: 0).swipeUp(), but if there are too many cells, it doesn't scroll all the way to the bottom. And the number of cells in the UITableView is not always the same, I cannot swipe up more than once because there might be only one cell in the table.
One way you could go about this is by getting the last cell from the tableView. Then, run a while loop that scrolls and checks to see if the cell isHittable between each scroll. Once it's determined that isHittable == true, the element can then be asserted against.
https://developer.apple.com/documentation/xctest/xcuielement/1500561-ishittable
It would look something like this (Swift answer):
In your XCTestCase file, write a query to identify the table. Then, a subsequent query to identify the last cell.
let tableView = app.descendants(matching: .table).firstMatch
guard let lastCell = tableView.cells.allElementsBoundByIndex.last else { return }
Use a while loop to determine whether or not the cell isHittable/is on screen. Note: isHittable relies on the cell's userInteractionEnabled property being set to true
//Add in a count, so that the loop can escape if it's scrolled too many times
let MAX_SCROLLS = 10
var count = 0
while lastCell.isHittable == false && count < MAX_SCROLLS {
apps.swipeUp()
count += 1
}
Check the cell's text using the label property, and compare it against the expected text.
//If there is only one label within the cell
let textInLastCell = lastCell.descendants(matching: .staticText).firstMatch
XCTAssertTrue(textInLastCell.label == "Expected Text" && textInLastCell.isHittable)
Blaines answer lead me to dig a little bit more into this topic and I found a different solution that worked for me:
func testTheTest() {
let app = XCUIApplication()
app.launch()
// Opens a menu in my app which contains the table view
app.buttons["openMenu"].tap()
// Get a handle for the tableView
let listpagetableviewTable = app.tables["myTableView"]
// Get a handle for the not yet existing cell by its content text
let cell = listpagetableviewTable.staticTexts["This text is from the cell"]
// Swipe down until it is visible
while !cell.exists {
app.swipeUp()
}
// Interact with it when visible
cell.tap()
}
One thing I had to do for this in order to work is set isAccessibilityElement to true and also assign accessibilityLabel as a String to the table view so it can be queried by it within the test code.
This might not be best practice but for what I could see in my test it works very well. I don't know how it would work when the cell has no text, one might be able to reference the cell(which is not really directly referenced here) by an image view or something else. It's obviously missing the counter from Blaines answer but I left it out for simplicity reasons.

Xcode 7 ui automation - loop through a tableview/collectionview

I am using xCode 7.1. I would like to automate interaction with all cells from a table/collection view. I would expect it to be something like this:
for i in 0..<tableView.cells.count {
let cell = collectionView.cells.elementBoundByIndex(i)
cell.tap()
backBtn.tap()
}
However this snippet only queries current descendants of the table view, so it will loop through the first m (m < n) loaded cells out of total n cells from the data source.
What is the best way to loop through all cells available in data source? Obviously querying for .Cell descendants is not the right approach.
P.S.: I tried to perform swipe on table view after every tap on cell. However it swipes to far away (scrollByOffset is not available). And again, don't know how to extract total number of cells from data source.
Cheers,
Leonid
So problem here is that you cannot call tap() on a cell that is not visible. SoI wrote a extension on XCUIElement - XCUIElement+UITableViewCell
func makeCellVisibleInWindow(window: XCUIElement, inTableView tableView: XCUIElement) {
var windowMaxY: CGFloat = CGRectGetMaxY(window.frame)
while 1 {
if self.frame.origin.y < 0 {
tableView.swipeDown()
}
else {
if self.frame.origin.y > windowMaxY {
tableView.swipeUp()
}
else {
break
}
}
}
}
Now you can use this method to make you cell visible and than tap on it.
var window: XCUIElement = application.windows.elementBoundByIndex(0)
for i in 0..<tableView.cells.count {
let cell = collectionView.cells.elementBoundByIndex(i)
cell.makeCellVisibleInWindow(window, inTableView: tableView)
cell.tap()
backBtn.tap()
}
let cells = XCUIApplication().tables.cells
for cell in cells.allElementsBoundByIndex {
cell.tap()
cell.backButton.tap()
}
I face the same situation however from my trials, you can do tap() on a cell that is not visible.
However it is not reliable and it fails for an obscur reason.
It looks to me that this is because in some situation the next cell I wanted to scroll to while parsing my table was not loaded.
So here is the trick I used:
before parsing my tables I first tap in the last cell, in my case I type an editable UITextField as all other tap will cause triggering a segue.
This first tap() cause the scroll to the last cell and so the loads of data.
then I check my cells contents
let cells = app.tables.cells
/*
this is a trick,
enter in editing for last cell of the table view so that all the cells are loaded once
avoid the next trick to fail sometime because it can't find a textField
*/
app.tables.children(matching: .cell).element(boundBy: cells.count - 1).children(matching: .textField).element(boundBy: 0).tap()
app.typeText("\r") // exit editing
for cellIdx in 0..<cells.count {
/*
this is a trick
cell may be partially or not visible, so data not loaded in table view.
Taping in it is will make it visible and so do load the data (as well as doing a scroll to the cell)
Here taping in the editable text (the name) as taping elsewhere will cause a segue to the detail view
this is why we just tap return to canel name edidting
*/
app.tables.children(matching: .cell).element(boundBy: cellIdx).children(matching: .textField).element(boundBy: 0).tap()
app.typeText("\r")
// doing my checks
}
At least so far it worked for me, not sure this is 100% working, for instance on very long list.

NSTableView – select clicked row after editing was ended by clicking a different row

I have an NSTableView which gets populated without bindings.
In the datasource method tableView:objectValueForTableColumn:row:, I re-sort my data and tell the table view to reload after committing the edit to the model, so that the correct sort order is restored after editing.
If the user ends editing by clicking a different row, however, and the sort order has changed because of the edit that has just been ended, it might happen that the row the user intended to select has just moved after he clicked it. So instead of that row, a different row that is now in that spot gets selected.
I tried various combinations of the NSTableViewDelegate methods, but could not find a solution that adjusts the selection in a way that when a re-sort after editing has ended has occurred, the row that has just moved away gets selected. How can I achieve this?
I would use this:
I recommend to sort your data in NSTableView delegate setObjectValue: instead of objectValueForTableColumn:
And change the selection in NSTableView delegate selectionIndexesForProposedSelection:
This solution manages a single selection, If you want it manages single and multiple selection, I would change my answer.
If the user double-click, it doesn't work since it changes the selection to the second click.
You need these variables :
int originalRowIndex;
NSUInteger newRowIndex;
I initialize this variable: originalRowIndex = -1;
- (void)tableView:(NSTableView *)aTable setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aColumn row:(int)rowIndex {
id objectAtRow = [myMutableArray objectAtIndex:rowIndex];
NSString *columnKey = [aColumn identifier];
[objectAtRow setValue:anObject forKey:columnKey];
originalRowIndex = rowIndex; // get the original index
[myMutableArray sortUsingDescriptors:[aTable sortDescriptors]]; // re-sort the mutable array
newRowIndex = [myMutableArray indexOfObjectIdenticalTo:objectAtRow]; // get the new index
if (newRowIndex == originalRowIndex) originalRowIndex = -1; // same position
}
// not called on empty selection
- (NSIndexSet *)tableView:(NSTableView *)tableView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes {
int oIndex = originalRowIndex;
originalRowIndex = -1;
if (oIndex > -1 && [proposedSelectionIndexes count] > 1) { // if oIndex = -1 or multi selection , do nothing
int i = [proposedSelectionIndexes firstIndex];
if (oIndex < newRowIndex) {
if (i > oIndex && i <= newRowIndex) return [NSIndexSet indexSetWithIndex:(i - 1)];//shift the row index
} else {
if (i < oIndex && i >= newRowIndex) return [NSIndexSet indexSetWithIndex:(i + 1)];//shift the row index
} //else doesn't change the selection, this index is out of range (originalIndex...newIndex)
}
return proposedSelectionIndexes;// doesn't change the selection
}
I've always done this the hard way: before doing something that needs the selection to be restored, I remember the current selection -- not by row index, but by something I can find in my data. Often I have an array of dictionaries, so I just remember the dictionary pointer.
After doing whatever I need to do on the tableview, I just go through the data again, looking for my object to find the new index...

Restyling dynamically styled SlickGrid cells after Sort

Ok, let me explain my scenario more clearly:
When a cell is edited, it becomes 'dirty' and I style it a certain way by adding a CSS class to the cell via javascript.
Then, if the user Sorts the grid, the styling is lost (I believe because all the rows are recreated) and I need a way to restore the styling to the appropriate cell/row after a Sort.
What I attempted to do is add an entry into data[] called 'status' and onCellChange I loop through data[] and match the args.item.Id to appropriate entry in data[].
grid.onCellChange.subscribe(function (e, args) {
var done = false;
for (var i = 0; i < data.length && !done; i++) {
if (data[i].id == args.item.id) {
data[i].status = "dirty";
done = true;
}
}
}
However, onSort I'm not sure how to match the sorted rows to the data array. (Since I have no args.item) I've attempted to do selector statements:
$(".slick-row")
to restyle the correct cells, but I have no way to associate the rows with their entry in data[].
1) There is no need to search for the item in your onCellChange handler - it is available at "args.item".
2) Sorting the "data" array will not wipe out your change to the item in #1.
3) You mention dynamically styling cells. I see no code for that. If your custom formatter is the piece of code that looks at "item.status" and renders it differently if it is dirty, then you don't have to do anything extra. Sorting the data and telling the grid to re-render will preserve the "dirty" cell styles.

how to implemet database logic in tableview to controll rows information?

My meaning is i want to implement the table from where i will get data from the database.I store all the data into database now the problem is i am not able to show this data in tableView as describe below.Also you can see the example that i want to develop table like chipottle Chipottle app. There is a bag for created order list information.If you do not get my question then please see the chipottle app just create order it will store into bag in main view click on bag button you will see one table with all your created order information and in last subtitle.Please See the app it's totally free i really stuck into this problem so please try to help me.
I stored 10 fields in database
Ordername ={Order1,Order2}
category = {category1,category2}
submenu1 = {{sub00,sub01,sub02},{sub11,sub12}}
price1 = {{$2,$3.1,$4.2},{$7.85,$2.3}}
submenu2 = {{sub21,sub22,sub23,sub24},{sub31,sub32}}
price2 = {{$2.2,$4.5,$7.7,$3.2},{$4.4,$1.3}}
drinkmenu = {pepsi,pepsi1,soda,lemon,pinaplesoda}
dirnkprice = {$4.4,$8.5,$3.7,$2.8,$3.4}
I count the no. of section like = [Ordername count] .also able to display the section title as per the Ordername.
Now i want to display category ,submenu1,price1,submenu2,price2 all at Zero(0) index means (at indexpath.row = 0) all value as per the Ordername.
and for drinkmenu it is display on different different indexpath.row but after at zero index.
Thank You.
Please it is very urgent.
go through following link you will have your work done.
http://blog.webscale.co.in/?p=284
It is customization of UITableViewCell.
Hope it helps.
All you've got to do, is create a special order tableViewCell and put in your data, show it for indexPath.row == 0 only. Here is some example code:
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)anIndexPath
{
// sectioning logic here...
if(anIndexPath.row == 0) {
// Helper to create a fresh cell, implements dequeueing
OrderTableViewCell *cell = [TableViewCellFactory orderTableViewCell:aTableView];
// pass on the cell to do fill in data
[self configureOrderCellWithCell:cell andIndexPath:anIndexPath];
return cell;
} else {
// process other cells
}
}
- (void)configureVideoClipCellWithCell:(VideoClipTableViewCell *)aCell andIndexPath:(NSIndexPath *)anIndexPath
{
// get model at current indexPath
Order *order = [orders objectAtIndex:anIndexPath.row];
// publish data in cell
aCell.category.text = order.category;
aCell.submenu1.text = order.submenu1;
aCell.price1.text = order.seprice1rie;
aCell.submenu2.text = order.submenu2;
aCell.price2.text = order.price2;
}

Resources