I am using the KIF Framework for functional UI testing. Let's say I am on a current iPad screen where many views (labels, buttons, textfields etc) have unique accessibility labels assigned. If I have the accessibilityLabel string handy, can I get a reference to the associated UIView from current screen using it?
For example, [[UIView alloc] viewWithTag:5] returns UIVIew of provided tag. I am looking for something like [[UIView alloc] viewWithAccessiblityLabel:#"my label"].
P.S: I know the brute-force method would be to iterate all views in self.subviews recursively, and compare accessibility label to find what am I searching for. I am looking for a better approach.
There is actually an incredibly simple way to achieve what you describe when using KIF for UI automation, though KIF doesn't make it obvious that this is possible. waitForViewWithAccessibilityLabel returns a reference to the view when it is found:
Swift
let view = tester().waitForView(WithAccessibilityLabel: "My label")
Objective-C
UIView *view = [tester waitForViewWithAccessibilityLabel:#"My label"];
Hugely useful, once discovered.
I am using KIF for UI automation! Here are the steps to get view from given accessibilityLabel. Method viewContainingAccessibilityElement:element is extension method to UIAccessibilityElement class.
UIAccessibilityElement *element = [[[UIApplication sharedApplication] keyWindow] accessibilityElementWithLabel:label];
UIView *view = (UIView*)[UIAccessibilityElement viewContainingAccessibilityElement:element];
It sounds to me (from your comment: "I need this functionality in automating UI tests") like you are looking for the accessibilityIdentifier. From the documentation:
The UIAccessibilityIdentification protocol is used to associate a unique identifier with elements in your user interface. You can use the identifiers you define in UI Automation scripts because the value of accessibilityIdentifier corresponds to the return value of the name method of UIAElement.
Related
I've got a subclass of UIView, let's say it's class DemoView: UIView { } which contains UILabel and UIButton. I needed to group it and add UIAccessibilityCustomAction so I've overriden the var accessibilityElements: [Any]? and used union to connect both elements. I've also assigned "Users" string to accessibilityLabel.
From user perspective this works as it should, VoiceOver reads Users and then user can select custom action which is named Edit.
Problem is that I don't know how can I fire this custom action from UITests. I know that XCUIElement contains array of UICustomActions and I can get its selector but then what?
I talked to Apple Engineers during WWDC19 Accessibility and Testing Labs and they said it is not possible right now. The reason why accessibility is not available during testing are security concerns. What they also said is that they don't use UITests for testing UIAccessibility and when creating accessibility elements support two cases - when there are UITests running and not.
I recommend making a suggestion using Feedback Assistant.
The purpose you're tring to reach isn't possible currently with iOS13 and Xcode 11.
The UITesting framework doesn't access the application code as unit tests do: you can't get an instance to perform selector + the array of custom actions is nil when in UITest ⇒ every custom action isn't reachable and then can't be fired at all.
XCUITEST works thanks to the accessibility properties like accessibilityIdentifier but isn't definitely designed to simply work for VoiceOver (inconceivable and incomprehensible in my view).
I hope that something new with UI testing will be introduced during the next WWDC for REAL accessibility testing.
For anyone else stuck on this, if you have a direct reference to the UIView in question, I've (well, a coworker of mine) found the following 'hack' to work quite well.
let view: UIView = YourViewWithCustomAction()
let customActionName: String = "<Your custom action name here>"
let action: UIAccessibilityCustomAction = view.accessibilityCustomActions!.first(where: { $0.name == customActionName })!
_ = action.target!.perform(action.selector, with: action)
As a disclaimer, this might only work when you have a custom UIView subclass that implements its own override var accessibilityElements.
I have 4 ivars:
UIView *view1;
UIView *view2;
UIView *view3;
UIView *view4;
I would like to be able to alloc and init them in a dynamic way, instead of doing:
view1 = [[MyView1 alloc] initWithFrame:....
view3 = [[MyView2 alloc] initWithFrame:....
view4 = [[MyView3 alloc] initWithFrame:....
view4 = [[MyView4 alloc] initWithFrame:....
So, I tried to use a array and store the names of these ivars in it:
[array addObject:#"view1"];
[array addObject:#"view2"];
[array addObject:#"view3"];
[array addObject:#"view4"];
And so that within a loop I would do:
[self valueForKey:[array objectAtIndex:x]] = [[[[self valueForKey:[array objectAtIndex:x]] class] alloc] initWithFrame:(CGRect){.....
The above generates the error:
Expression is not assignable.
Hope someone can tell me why the above cannot be done.
My question is:
I have a feeling that this is not a clever way of doing things.
Right now I have only 4 views, but who knows if in the future I might have more.
So, my idea is that instead of hard-coding things, I would like to find a more dynamic way of accomplishing this.
My idea is that at compile-time, all this views are just of UIViews.
And only until run-time would I resolve these views to the individual class types
(i.e. MyView1, MyView2 etc etc) and alloc and init them and assign them
accordingly to the ivars (i.e. view1, view2, view3, etc) within my class.
The reason why I use an array is because, if in the future I added another view called view5 of class type MyView5, I could loop the alloc and init process using [array count]. If this way of doing it is still not optimal, please correct me.
To sum up, I would like to set up my controller in a way that it only knows during compile-time that these objects are just of class type UIView. Only until run-time would I resolve them individually to MyView1, MyView2(subclass of UIView) etc and assign them to the ivars within my controller (again, they are named view1, view2 etc).
And if I added another view in the future, I wouldn't have to look all over the place within this controller and hard-code: view5 = [[MyView5 alloc] init....
Can someone show me how to accomplish this optimally (future-wise) and dynamically?
EDIT:
It just occurred to me: it would be even better if I could create these ivars only during runtime, so that in the future everything could be created dynamically.
If I understand what you're asking, let me provide a different approach which you might like:
// Set up a mutable array of objects
NSMutableArray *views = [[NSMutableArray alloc] init];
// Set up an array of strings representing the classes - you can add more
// later, or use -stringWithFormat: to make the class names variable
NSArray *classes = #[#"MyView1", #"MyView2", #"MyView3", #"MyView4"];
// Now loop through it and instantiate one of each kind
for (NSString *className in classes)
[views addObject:[[NSClassFromString(className) alloc] initWithFrame:CGRectZero]];
Remember to be careful with NSClassFromString, as you might accidentally send the -initWithFrame: message to a type that doesn't implement that.
Hope this helps!
Edit: I see that I have given you an overly implementation-based answer, while you seem to be looking for the program design aspect.
When you're designing your controller class, it's good that you're considering how you will use the class in the future. That said, you need to have a specific idea of how abstract you want the class to be. In other words, don't go trying to make the controller class completely decoupled, because at some point your class will be a bulk of useless management code.
So how do you go about writing a class that is both decoupled and functional at the same time? I suggest you look for examples in Apple's classes. Here are a few:
UIViewController, probably the most important and versatile class on iOS. They designed it to be easily subclassable, yet there are also many premade subclasses like the navigation controller and table view controller varieties.
UIDocument, a template for all document model objects you will ever need. The system implementation handles all the nitty-gritty of iCloud sync, file management, etc., while not knowing anything about the document contents itself. The user subclass, however, provides the necessary information in an NSData object.
UIGestureRecognizer, the foundation of the touch-based UI. Most people only use the system-provided tap/swipe/pinch subclasses, but the abstract superclass itself (whether subclassed or not) detects any gesture you want and sends the necessary messages. Again, the gesture recognizer doesn't know what views you attach it to, yet it still performs its job.
Do you see what I'm getting at here? Apple's classes illustrate that there are ways to provide the necessary functionality while staying abstract, but without going into runtime acrobatics. As one of the commenters suggested, all you really need is an array of view objects. Instead of having your controller class instantiate the views, maybe you should have your client objects do that. It's all about finding a balance between abstraction and functionality.
The problem is, that you're thinking about this, as if your calls to the array elements would replace your code before compiling (like macros would do). It just doesn't work that way. For example:
[self valueForKey:[array objectAtIndex:x]] = ...
The compiler sees [self valueForKey:[array objectAtIndex:x]] as a value and "thinks" hey you can't assing sth to a value.
Try the following (i've splitted it into multiple statements for better readability and also to make the code more self-explanatory):
string className = [array objectAtIndex:x];
Class classToInit = NSClassFromString(className);
UIView *viewToInit = [[classToInit alloc] initWithFrame:...];
[self setValue:viewToInit forKey:className];
Keep in mind that with this approach the property names (and btw. they also need to be properties not ivars for KVC to work) must match your class names, i.e. if your class is named MyView1 your property must also be called MyView1. You might not want this (in fact according to you description in the text, you don't). So to make it work you could create a dictionary mapping your property names to your class names and enumerate over it's keys:
NSMutableDictionary classNameMapping = [NSMutableDictionary new];
[classNameMapping setObject:#"MyView1" forKey:#"view1"];
[classNameMapping setObject:#"MyView2" forKey:#"view2"];
//...
foreach (string propertyName in [classNameMapping allKeys])
{
string className = [classNameMapping objectForKey:propertyName];
Class classToInit = NSClassFromString(className);
UIView *viewToInit = [[classToInit alloc] initWithFrame:...];
[self setValue:viewToInit forKey:propertyName];
}
I have read multiple times that we should not subclass a component (a UIButton for example) :
Why shouldn't I subclass a UIButton?
Subclassing a UIButton
The problem is when I use Interface Builder.
For example, I have a button with a precise appearance in a lot of my views. I can set them each time with IB (it's painful), or I can use a custom class to factorize the custom behavior and appearance.
It seems a bit contradictory to me that the only way to simplify the process with IB is to do it the way that everybody recommends against.
Is there a better solution ? Can I use a category with IB ?
Thanks.
You might be able to use the UIView appearance proxy. I don't know what all you're doing to your buttons but this might help:
Put this is your AppDelegate file in the application:didFinishLaunchingWithOptions: method
if([UIButton conformsToProtocol:#protocol(UIAppearanceContainer)]){
[[UIButton appearance] setBackgroundImage:[UIImage imageNamed:#"YourImage"] forState:UIControlStateNormal];
//modify any other UIButton properties you want to set globally
}
The second link you provided was pretty clear, and this is pretty much what apple itself states, subclass, but never mess with the internal structure.
Best example is iOS 7, now things are completely different and, for example, an application I'm maintaining had a subclassed UIControl, and now it has trouble running on the new iOS, simply because, it was built with assumptions on how the internal structure works (iterating the internal subviews replacing some things). You might not get your app rejected, but it will be a pain in the a** to maintain.
As a rule of thumb, anything you can do to an UIButton from the outside, something like this:
[myButton setBackgroundImage:... forState:...];
[myButton setTextColor:... forState:...];
myButton.titleLabel.font = ...
You can move it to the inside of a custom subclass method:
+ (UIButton*)fancyPantsButton
{
UIButton *button = [UIButton butonWithType:UIButtonTypeCustom];
[myButton setBackgroundImage:... forState:...];
[myButton setTextColor:... forState:...];
myButton.titleLabel.font = ...
return button;
}
You can also do this on init or awakeFromNib without problems (and I usually prefer the later).
UIAppearence is also an option, as was suggested by user hw731. Whatever floats your boat, really.
As for the second question, nib files pretty much create instance a class and then fill-in the things it stores using setValue:forKey: when loading (that's why you get an error like "class is not key-value compliant for something" when you screw up a nib), so if something is categorised when the nib is being loaded, then yes, nibs respect categories, as its simply using initWithCoder.. and then filling in the gaps.
And, by the same token, the nib file won't be able to fill-in custom properties, since it doesn't know about them, unless you explicitly add them on the "User Defined Runtime Attributes" in IB (iOS 5 onwards).
Another technique for nibs, is using
#property (strong) IBOutletCollection(UIButton) NSArray *buttons;
And then iterating and customising buttons accordingly (be it via a subclass, category, local method, ...). This method is really helpful if you want just a handful of custom buttons, but not enough to warrant using a subclass.
I don't see any reason that you shouldn't subclass UIButton, especially for your purpose of making configuration with IB easier. Neither of the links you provided explain why you shouldn't subclass, so their assertions don't seem reliable. On the other hand, the presence of UIButtonTypeCustom in UIButton.h gives the impression that the framework authors planned for UIButton subclasses.
I am trying to write a unit test case for calculator in Xcode, currently I am writing test case for the add function.
This is my digit pressed function:
- (IBAction)digitPressed:(UIButton *)sender.
I have seen an example where if the method name is
- (IBAction)digitPressed:(id)sender,
you can invoke the function with help of view tag like
[calc_view_controller digitPressed:[calc_view viewWithTag:6];
The instance defined above in implementation section as
app_delegate = [[UIApplication sharedApplication] delegate];
calc_view_controller = app_delegate.viewController;
calc_view = calc_view_controller.view;
now since my button type is (UIButton *) I cant use view tag, is there any other alternative for UIButton type?
If so, Can you give me an example?
A button is a view, so -viewWithTag: will find it just fine. The only issue is your sender type not agreeing with the type returned by -viewWithTag:, but you can solve that with a cast if you're sure the view you'll get back is a button, or you can check first:
[calc_view digitPressed:(UIButton*)[calc_view viewWithTag:6]];
or:
UIButton *button = (UIButton*)[calc_view viewWithTag:6]];
if ([button isKindOfClass:[UIButton class]]) {
[calc_view digitPressed:button];
}
Either works in practice; the latter is safer and makes it easy to add an additional test: you could fail the test if the button isn't a UIButton.
You do it in the bad way, you shouldn't compare sender.titleLabel.text. Think about what happens when you'll change the label text, because e.g you must ship your app in another language.
But if you still want to handle button recognizing in that way, you have to create UIButton in your unit test and set it the proper label text value.
Rather than invoking the IBAction method, I would invoke the specific method which peforms the addition.
There are a number of things that you could test here, for example:
The UIButton object sends the correct value to the digitPressed method
The digitPressed method correctly extracts the value from the UIButton object and passes this value to the add method
The result of the add method is what you expect
How much you test is up to you, personally I wouldn't get too hung up on 1 and 2. If the logic is wrong in either of these it will become obvious, and they are unlikely to attract regressions.
To me it is more important to fully test the add method. Send in plenty of common cases, edge cases and boundary values, rather than worrying about the flow from the UIButton press.
I have the following items in logElementTree output:
UIAButton: rect:{{20, 427}, {41, 41}}
UIAButton: rect:{{140, 427}, {41, 41}}
These buttons have no identifier, no name, and are not drawn in XIB. On my automation test script I only use index (something like target.frontMostApp().mainWindow().buttons()[7].tap())
But then, this line will not always work because index is changing. I just want to ask, if there's a way to tap this button other than using index? Please note that the button has no name, so I cannot use buttons()["name'"].tap()
Technically this would be the best way to go about doing what you would like, so I'll leave it here for other developers who see this question. In your case since you have limited technical experience, I would recommend asking your developer to assign ids or names to buttons. Providing good names for buttons and other UI elements means that your app is also accessible for those users with impaired eyesight, since voiceover will read them the names you've given to your buttons. The following block of code will programmatically assign a label and a accessibility hint to a dynamically created object that conforms to UIAccessibility.
From Apple's documentation (in this case they're doing it on a view, but you could do it on any object like a button):
- (id)init
{
_view = [[[MyCustomView alloc] initWithFrame:CGRectZero] autorelease];
[_view setIsAccessibilityElement:YES];
[_view setAccessibilityTraits:UIAccessibilityTraitButton];
[_view setAccessibilityLabel:NSLocalizedString(#"view.label", nil)];
[_view setAccessibilityHint:NSLocalizedString(#"view.hint", nil)];
}
http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html#//apple_ref/doc/uid/TP40008785-CH102-SW5
Javascript "hack" (not very clean, but it works...):
var window = UIATarget.localTarget();
window.tap({x:yourXCoordinateHere , y:yourYCoordinateHere});