I'm trying to make my own table view displaying hierarchical data based on Apple's "Table View Animations and Gestures" sample code.
Everything works fine in Apple's demo, until I add more root items than it is displayable on the screen.
Problem is that if I expand first root item and scroll down, the disclosure button of last root item displays expanded state.
Then if I scroll to the top (sometimes it needs to be repeated few times), the disclosure button of first item already shows collapsed state but the item is still expanded.
If I click to such item the app crashes with error:
'NSInternalInconsistencyException', reason: 'attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update'
I have copied the sample code from here:
https://developer.apple.com/library/ios/samplecode/tableviewupdates/TableViewUpdates.zip
Data source is "PlaysAndQuotations.plist" and it has been changed following:
Copy all 3 root dict items (also with their array of dicts) 4 times, so it contains 12 root items with all their subitems.
My adapted Plist data file can be downloaded here:
https://drive.google.com/file/d/0B__nLmZ1h_-eUHVlTV9FbzN2aGc/edit?usp=sharing
Please help me to find out, why is the disclosure button changing its state by itself and how to fix the crashes.
This has solved the issue
(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
Add the following :
if (section == self.openSectionIndex ){
sectionHeaderView.disclosureButton.selected = YES;
}
else
sectionHeaderView.disclosureButton.selected = NO;
APLSectionHeaderView
- (void)toggleOpenWithUserAction:(BOOL)userAction {
// Comment this line self.disclosureButton.selected = !self.disclosureButton.selected;
if (userAction) {
// toggle the disclosure button state
self.disclosureButton.selected = !self.disclosureButton.selected; //Add here
Related
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.
In RadListView, is it possible to scroll to a specific y-position.
The problem is if I navigate to another page from a RadListView page and then come back - it initializes to the top of the listview. I would prefer to the same y position the user was at before navigating to another page.
I think there's a scroll-to-item method - but that's not necessarily the same y position.
You can cache your index using observable ViewModel and pass it when needed (in this case on the loaded event for your list - that is when the users will return back to the list page and the list will load)
e.g. TypeScript
page.ts
export function onListLoaded(args: RadListwModule.ListViewEventData) {
list = <RadListwModule.RadListView>args.object;
if (list.items) {
list.scrollToIndex(roversViewModel.get("cachedIndex"));
}
list.refresh();
}
export function onItemTap(args: RadListwModule.ListViewEventData) {
var tappedItemIndex = args.itemIndex;
// "cache" the index of our tapped item
roversViewModel.set("cachedIndex", tappedItemIndex);
// navigate to details page or do what you want to do on itemTap
frame.topmost().navigate(navEntry);
}
page.xml
<lv:RadListView items="{{ dataItems }}" loaded="onListLoaded" itemTap="onItemTap">
Also give your cachedIndex initial value of 0 for the first time the list is loaded.
Example based on this POC app .
Note that is not exactly the same as scroll-to-the-exact-y-position but you can modify the logic and scroll further to the exact position with getting the cells relative position offset.
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.
I have come across a simple issue that has totally flummoxed me for some reason.
I have one view controller (which I will call the Info page) which has a text view contained within it. The text view holds a large amount of information in the following format;
Header A
Information about Header A
Header B
Information about Header B
(and so on).
Previous to this, there are viewcontrollers for each of the Headers; on each of which there is a button to segue to the Info Page.
My problem is pretty simple. When the user presses the segue button to transfer to the Info page, how do I code a solution where by the text view automatically scrolls to the specific Header information.
For example, say the user is in the view controller for Header D. He or she clicks the button to segue to the Info page. Upon arrival, how do I make the text view scroll automatically to show the information for Header D.
My apologies if I haven't been clear enough with my question. Obviously I have set up the text box, and the segues... just this final set is alluding me even after spending an age looking around for the answer! Even just directing me to some reading on the subject I may have missed would be great.
Thanks for your help.
This might not be the cleanest solution to this, but this should work:
This goes in the first ViewController's buttons. Just iterate through the buttons setting the integer from 1 to 4 depending on which button you press.
-(void)firstBtnTUI
{
[[NSUserDefaults standardUserDefaults] setInteger:1 forKey:#"HEADER"];
//...
}
This goes in ViewDidLoad (or similar) in the pushed view controller
int headerNum = [[NSUserDefaults standardUserDefaults] integerForKey:#"HEADER"];
CGFloat offset;
if(headerNum == 1)
{
offset = firstHeader.frame.origin.y;
}else if(headerNum == 2)
{
offset = secondHeader.frame.origin.y;
}else if(headerNum == 3)
{
offset = thirdHeader.frame.origin.y;
}else if(headerNum == 4)
{
offset = fourthHeader.frame.origin.y;
}
[scrollview setContentOffset:offset animated:NO];
My GUI application has two views, First view has a ListBox control - Derived from CCoeControl - and from this object I want to switch to the second view, How can I do this ?
In other words, When user click ListBox Item ...the second view appears.
Assuming that your 'views' are derived from CAknView.
Code From this link
const TUid KDemo1ViewId = { 1 }; // UID of the first view
AppUI()->ActivateLocalViewL(KDemo1ViewId); // activate view 1
Also find these links very useful.Getting a pointer to AppUI
Getting a Pointer to a View
Edit :
CYourApplicationAppUi* appui = (static_cast<CYourApplicationAppUi*>(iEikonEnv>AppUi()));
// Get the view you want CSomeView* view = (CSomeView*)appui->View(TUid::Uid(ESomeViewId))