I'm just starting out with UI Automation for my iOS app and am already having trouble. I'm unable to attach screen shots so I'll do my best to describe my scenario.
I'm building for iOS 6.0 and am using a storyboard. The app launches to a screen with a navigation controller. The root view controller contains a main view that has 1 UIView subview that takes up the bottom 60% of the screen and a segmented control that sits above that subview. I was able to configure accessibility for the main view (label "mainview"). I am then able to locate this element in my test no problem. However, I am now having trouble finding the segmented controller. So I decided to log out the length of "elements()" and "segementedControls()" from my "mainview" element and the length of each array is 0. So somehow when the test is running my app it's saying there are no sub-elements on my main view.
Another thing to note is that I could not find any accessibility section in the identity inspector of the storyboard editor for the segmented control. However I temporarily added a button to my main view and configured that with an accessibility label, just to test if the elements() or buttons() calls would subsequently show an element for the main view when running my test, but these arrays were still returning as empty, even with the button.
Here's my script:
var target = UIATarget.localTarget();
var app = target.frontMostApp();
function selectListView() {
var testName = "selectListView";
UIALogger.logStart(testName);
var view = app.mainWindow().elements()["mainview"];
if (!view.isValid()) {
UIALogger.logFail("Could not locate main view");
}
UIALogger.logMessage("Number of elements for sub element: " + view.elements().length);
var segmentedControl = view.segmentedControls()[0];
if (!segmentedControl.isValid()) {
UIALogger.logFail("Could not locate segmented control on physician collection parent view");
}
var listButton = segmentedControl.buttons()[1];
if (!listButton.isValid()) {
UIALogger.logFail("Could not locate list button on segemented controller on physician collection parent view");
}
UIALogger.logMessage("Tapping List button on physician collection view's segmented control");
listButton.tap();
UIALogger.logPass(testName);
}
selectListView();
Any help greatly appreciated.
EDIT: I added this to my script to search the entire view hierarchy from the main window, set an accessibility label value for my segmented control in initWithCoder (since I don't seem able to set one in the storyboard editor for a segmented control, as I stated earlier) and still could not find the element - it's as though it's just not in the view hierarchy, though it's on the screen and functions just fine:
function traverse(root, name) {
if (root.name() == name) {
return root;
}
var elements = root.elements();
for (var i = 0; i < elements.length; i++) {
var e = elements[i];
var result = traverse(e, name);
if(result != null) {
return result;
}
}
return null;
}
function selectListView() {
var testName = "selectListView";
var segmentedControl = traverse(UIATarget.localTarget().frontMostApp().mainWindow(), "mysegementedcontrol");
if (segmentedControl == null) {
UIALogger.logMessage("Still could not find it");
}
....
}
EDIT: Added call to app.logElementTree() and still no segmented control in sight ("PhysicianCollectionParentView" is my "mainview" - you can see, no sub-elements there):
EDIT: Here are some screen shots. The first shows my "master" view controller. The next shows that in addition to the segmented control there is also a UIView subview. The 3rd shows the basic entry point for the app in my storyboard.
Here is the class extension for my "master" view controller here, showing the outlets for the segmented control and the other UIView subview:
#interface PhysicianCollectionMasterViewController ()
#property (strong, nonatomic) IBOutlet UISegmentedControl *viewSelectionControl;
#property (strong, nonatomic) IBOutlet UIView *physicianCollectionView;
#end
EDIT: Here's something very interesting - I decided to go with a brand new script created within instruments and take advantage of the record feature. When I clicked on my segmented control, here's the JavaScript it created to show me how it had accessed one of the buttons on my segmented control:
var target = UIATarget.localTarget();
target.frontMostApp().mainWindow().elements()["PhysicianCollectionParentView"].tapWithOptions({tapOffset:{x:0.45, y:0.04}});
So, I guess worst-case I could go with something like this, but it just makes no sense to me that UI Automation just does not think that the control exists. So strange. There must be something I'm missing but my setup is so basic I can't imagine what it could be.
When you set accessibilityLabel for an element and flag it isAccessibilityElement = YES; the subviews of that element are hidden. For automation, you should use accessibilityIdentifier instead of accessibilityLabel and set isAccessibilityElement = NO;
In your objective C code after physicianCollectionView is rendered, remove the label and accessibility flag and do this instead:
physicianCollectionView.accessibilityIdentifier = #"PhysicianCollectionParentView";
physicianCollectionView.isAccessibilityElement = NO;
For last elements in element tree, which do not have sub views, set isAccessibilityElement = YES;
If you haven't tried it already, you might try adding a delay at the beginning of your script:
UIATarget.localTarget().delay(3);
I think that it is possible that your app isn't done rendering/animating before the logElementTree() that you have posted above. We add delays at the beginning and between each application transition in our automated testing scripts to ensure that the screen has finished rendering.
EDIT: After messing around with your setup in a test app, I believe that your issue is that you are enabling Accessibility on the UIView that contains your segmented control. With accessibility disabled on the UIView, I am able to get the UISegmentedControl to show in the element tree, but once I enable it, the UIView then begins displaying as a UIAElement with no children. My suggestion is to disable accessibility for the containing UIView, and only use accessibility for base controls (like buttons, table view cells, or your segmented button control).
Related
I have added a UI Button inside of a stack view which is inside of a table view in my storyboard. When I click on my button the correct output is printed in my debugger console but there is no indication in the app that the button has been clicked (no default animation). I have tried looking at my view hierarchy and changing all of the parent views to clip to bounds. Any idea why the button is functioning but not being animated to the user?
The quick fix to your problem is to set delaysContentTouches = false for your table view.
According to the Apple Docs,
If the value of this property is true, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is false, the scroll view immediately calls touchesShouldBegin(_:with:in:). The default value is true.
See the class description for a fuller discussion.
Alternatively, if you have subclassed the UIScrollView, you can get the same thing done by overriding the following function,
class MyScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return type(of: view) == UIButton.self
}
}
I have two ASTableNode
1. Notifications tabelNode
2.Comments tabelNode
now I want to make a segment controller as a sticky header on the table, when I click on Notifications segment the notificationTable should appear and when I click on comments segment the commentsTable should appear as in this image WatchList and Following Segment,How can I achieve this any help appreciated.
A bit late to the party, but hope this helps. It's not difficult at all to use native views for displaying nodes.
Here is how it's done for the segment control:
///Keep a reference to the segment control
private var segmentedView: UISegmentedControl?
///This node will contain the segment control
private lazy var segmentedNode: ASDisplayNode = {
///The node is initialized with a view block that initializes the segment
return ASDisplayNode(viewBlock: { () -> UIView in
self.segmentedView = UISegmentedControl(items: ["Watchlist", "Following"])
///Select Watchlist maybe? Your call.
self.segmentedView.selectedSegmentIndex = 1
///Configure additional appearance of the segment control
return self.segmentedView
})
}()
After that, do any node operations you want (i.e. include in stack view, set style) on the node, and any segmented control operations you want (i.e. value changed selector) on the native control.
I have a progress bar (with its own controller). This bar is supposed to be shown in different views depending on which view is visible. As the progress will be same, If possible I don't want to create many progress bar in many views rather I want to use same instance in all these views. Also in that way when I need to change any property of the progress bar it will be reflected commonly, which is required.
Please suggest me how can I use this common view. And also if my strategy is wrong, what would be the better design for such scenarios.
1) Well you have 2 options. You can declare a new Class ViewBox (or whatever name) and then use that inside your code
First View Controller
var box:ViewBox = ViewBox()
When you segue or transition to your next screen, you can have a predefined variable var box:ViewBox!. Then say when you press a button, the button has a function called transition.
//Now setup the transition inside the Storyboard and name the identifier "toThirdViewController"
override func prepareForSegue(segue:UIStoryboardSegue, sender:AnyObject?) {
if(segue.identifier == "toThirdViewController") {
var vc = segue.destinationViewController as! `nextViewController` //The class of your next viewcontroller goes here
vc.box = self.box
}
//Since The SecondViewController doesn't need ViewBox, we don't need it there.
}
where
nextViewController:UIViewController {
var box:ViewBox!
}
Or you could do a much simpler way and that is to look up a UIPageViewController :)
Here is a draft of what I would like to do :
I would like to have a main container, which will be used to trigger Menu from everywhere.
I try to do this thanks to view.addSubview and addChildViewController.
But the second view disables the first one. It simply goes over.
How could I do to keep both functionalities ?
UPDATE:
I found a way that works, but it seems dirty :
I just move down the secondView frame according to the menu height, here is the code :
var test = mainStoryboard.instantiateViewControllerWithIdentifier("TestViewController") as TestViewController
var test2 = mainStoryboard.instantiateViewControllerWithIdentifier("Test2ViewController") as Test2ViewController
//proposeOrChooseViewController.delegate = self
view.addSubview(test.view)
addChildViewController(test)
test2.view.frame = CGRectMake(0, 60, test2.view.frame.size.width, test2.view.frame.size.height)
view.addSubview(test2.view)
addChildViewController(test2)
Is the frame of second view controller's view set correctly? Visually you can test it by setting a background color of view.
Also you might want to explore the Container View in storyboard. Container View lets you add a View Controller as child of another view controller.
I am very new to the iOS UIAutomation, here is the problem I am facing
I have a view hierarchy as below and want to access CustomView2 elements in the automation sctipt
UIWindow > UIScrollView > CustomView1 (Multiple) > CustomView2 (Multiple)
The scrollview has subviews of type CustomView1 and CustomView1 in turn has subviews of type CustomView2.
I have assigned the accessibility information to all of the views in hierarchy, but I am not able to access the CustomView2 elements in my automation script.
When I do a logElementTree() on UIScrollView, all I get is the instances of CustomView2, CustomView2 is not even in the tree structure of UIWindow.
Please suggest if is there anything missing or anything going wrong.
Here is the code I am using
var mainWindow = application.mainWindow();
var scrollView = mainWindow.scrollViews()[0];
var custom1 = scrollView.elements().withName("CustomView1");
for(var index=0; index<custom1.length; index++){
currentIndustry.tap();
custom1[index].logElementTree();
var custom2 = custom1[index].elements().withName("CustomView2");
UIALogger.logPass("Custom2 Length : " + custom2.length);
}
The tree printed by
custom1[index].logElementTree();
does not contain instances of CustomView2
P.S. I need to access both CustomView1 and CustomView2 elements
This may help you if you haven't found your answer already:
UIAutomation Nested Accessibilty Elements Disappear from Hierarchy
In your CustomView1 class implement the following:
- (BOOL)isAccessibilityElement
{
return NO;
}
This will make your CustomView2 elements visible when you logElementTree().
If CustomView2 contains accessible elements and is basically a container view as well, then implement the above in that class as well, and it's child views will become accessible.