The accessibility identifier is a developer generated ID for GUI objects, which can be used for automation tests.
A UIBarButtonItem does not implement UIAccessibilityIdentification. However is there a possibility that I can assign an accessibility identifier?
You could subclass UIBarButtonItem and implement the UIAccessibilityIdentification protocol in that subclass, lets's say BarButtonWithAccesibility.
In BarButtonWithAccesibility.h:
#interface BarButtonWithAccesibility : UIBarButtonItem<UIAccessibilityIdentification>
#property(nonatomic, copy) NSString *accessibilityIdentifier NS_AVAILABLE_IOS(5_0);
The only (strict) requirement for adhering to this protocol is defining the accessibilityIdentifier property.
Now in your view controller, let's say in viewDidLoad, you could set up a UIToolbar and add your subclassed UIBarButtonItem:
#import "BarButtonWithAccesibility.h"
- (void)viewDidLoad{
[super viewDidLoad];
UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
BarButtonWithAccesibility *myBarButton = [[BarButtonWithAccesibility alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(buttonPressed:)];
myBarButton.accessibilityIdentifier = #"I am a test button!";
toolbar.items = [[NSArray alloc] initWithObjects:myBarButton, nil];
[self.view addSubview:toolbar];
}
And within the buttonPressed: you could verify that you have access to the accessibilityIdentifier:
- (void)buttonPressed:(id)sender{
if ([sender isKindOfClass:[BarButtonWithAccesibility class]]) {
BarButtonWithAccesibility *theButton = (BarButtonWithAccesibility *)sender;
NSLog(#"My accesibility identifier is: %#", theButton.accessibilityIdentifier);
}
}
Hope this helps.
As of iOS 5 you can do it like this:
UIBarButtonItem *btn = [[UIBarButtonItem alloc] init...;
btn.accessibilityLabel = #"Label";
If you have UIToolbar created inside that if you want to create multiple UIBarButtonItem programatically then it can be accessed like that and set accessibilityLabel as well like that below:-
-(void)viewDidAppear:(BOOL)animated
{
UIBarButtonItem *infoButtonItem=[[UIBarButtonItem alloc]initWithTitle:#"info" style:UIBarButtonItemStyleBordered target:self action:#selector(infoButtonClicked)];
[self.customToolBar setItems:[NSArray arrayWithObject:infoButtonItem]];
//Here if you have muliple you can loop through it
UIView *view = (UIView*)[self.customToolBar.items objectAtIndex:0];
[view setAccessibilityLabel:NSLocalizedString(#"Test", #"")];
}
The subclassing UIBarButtonItem is a good solution. Depending on your needs, though, it may make more sense to simply assign the accessibilityIdentifier to the custom image of your UIBarButtonItem, assuming your UIBarButtonItem uses a custom image.
Related
I am trying to replicate Facebook Messenger App, where there is a UITextView attached to the top of the keyboard.
Due to the nature of this app I need my view to be attached, instead of manually scrolling up and down a ScrollView when the keyboard appears.
This can be achieved by using a inputAccessoryView.
I read the docs on it here.
The documentation is very brief and says:
"This property is typically used to attach an accessory view to the system-supplied keyboard that is presented for UITextField and UITextView objects.
The value of this read-only property is nil. If you want to attach custom controls to a system-supplied input view (such as the system keyboard) or to a custom input view (one you provide in the inputView property), redeclare this property as read-write in a UIResponder subclass.
You can then use this property to manage a custom accessory view. When the receiver becomes the first responder, the responder infrastructure attaches the accessory view to the appropriate input view before displaying it."
I have tried declaring a property
#interface CommentViewController ()
#property (nonatomic, readwrite, retain) UIView *inputAccessoryView;
#end
And then setting it:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0, 20, 320, 100)];
[view setBackgroundColor:[UIColor greenColor]];
self.inputAccessoryView = view;
}
Then I have tried calling both of these:
[self.tableView becomeFirstResponder];
[view becomeFirstResponder];
Nothing happens. What am I doing wrong?
*Note - Extra information: I am using a UITableViewController that I want to have a UIView attached as an inputAccessoryView. Once I get the view working then I will add in a UITextView and more, but this is mainly an example.
Any help is greatly appreciated!
Add input accessory to your textField or textView rather than to pure UIView.
self.mytextField.inputAccessoryView = view;
The inputAccessoryView is a property of the UIResponder class. It allows you to define a custom input accessory view to display when the receiver becomes the first responder. Usually an instance of UIToolBar should be set as the accessory view.
A toolbar sample:
MYInputAccessoryToolbar.h
typedef void (^MYInputAccessoryToolbarDidDoneTap)(id activeItem);
#interface MYInputAccessoryToolbar : UIToolbar
#property (nonatomic, copy) MYInputAccessoryToolbarDidDoneTap didDoneTapBlock;
+ (instancetype)toolbarWithInputItems:(NSArray *)items;
- (instancetype)initWithInputItems:(NSArray *)items;
- (void)addInputItem:(id)item;
- (void)goToNextItem;
- (void)goToPrevItem;
#end
MYInputAccessoryToolbar.m
#interface MYInputAccessoryToolbar ()
#property (strong, nonatomic) UIBarButtonItem *nextButton;
#property (strong, nonatomic) UIBarButtonItem *prevButton;
#property (strong, nonatomic) UIBarButtonItem *doneButton;
#property (nonatomic, copy) NSMutableArray *inputItems;
#property (nonatomic) NSInteger activeItemIndex;
#property (nonatomic) id activeItem;
#end
#implementation MYInputAccessoryToolbar
+ (instancetype)toolbarWithInputItems:(NSArray *)items {
return [[self alloc] initWithInputItems:items];
}
#pragma mark - Initializations
- (instancetype)init {
self = [super init];
if (self) {
_inputItems = [NSMutableArray new];
_prevButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:101 target:self action:#selector(prevButtonTaped)];
_nextButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:102 target:self action:#selector(nextButtonTaped)];
_doneButton = [[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleBordered target:self action:#selector(doneButtonTaped)];
[_doneButton setTitleTextAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:17]} forState:UIControlStateNormal];
UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
fixedSpace.width = 20.0f;
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
NSArray<UIBarButtonItem *> *barButtons = #[_prevButton, fixedSpace, _nextButton, flexSpace, _doneButton];
[self sizeToFit];
self.items = barButtons;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(itemDidBeginEditing:)
name:UITextFieldTextDidBeginEditingNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(itemDidBeginEditing:)
name:UITextViewTextDidBeginEditingNotification
object:nil];
}
return self;
}
- (instancetype) initWithInputItems:(NSArray *)items {
self = [self init];
for (id item in items) {
[self addInputItem:item];
}
return self;
}
#pragma mark - Accessors
- (void)addInputItem:(id)item {
if ([item respondsToSelector:#selector(setInputAccessoryView:)]) {
[item setInputAccessoryView:self];
}
[_inputItems addObject:item];
}
#pragma mark - Actions
- (void)itemDidBeginEditing:(NSNotification *)noticifation {
NSInteger itemIndex = [_inputItems indexOfObject:noticifation.object];
if (itemIndex != NSNotFound && _activeItem != noticifation.object) {
_activeItemIndex = itemIndex;
_activeItem = noticifation.object;
[self activeItemChanged];
}
}
- (void)activeItemChanged {
_prevButton.enabled = _activeItemIndex != 0;
_nextButton.enabled = _activeItemIndex != _inputItems.count - 1;
}
- (void)prevButtonTaped {
[self goToPrevItem];
}
- (void)nextButtonTaped {
[self goToNextItem];
}
- (void)goToNextItem {
[_inputItems[_activeItemIndex + 1] becomeFirstResponder];
}
- (void)goToPrevItem {
[_inputItems[_activeItemIndex - 1] becomeFirstResponder];
}
- (void)doneButtonTaped {
if (_didDoneTapBlock) {
_didDoneTapBlock(_activeItem);
}
[_activeItem resignFirstResponder];
}
#pragma mark - Dealloc
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidBeginEditingNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil];
}
#end
Now assuming that we have a set of text field fields and text views we could use them to initialize an instance of our toolbar.
MYInputAccessoryToolbar *accessoryToolbar = [MYInputAccessoryToolbar toolbarWithInputItems:#[_passwordCurrentField, _passwordNewField, _passwordVerifyField]];
And then each of these fields will have a custom accessory view like this.
Remove self.inputAccessoryView = view; and then add the code below anywhere after -(void)viewDidLoad { ... } where view is your UIView:
-(void) viewDidLoad {
....
}
- (UIView *)inputAccessoryView
{
return self.view;
}
I am posting this answer to show other people my exact code and how easy it actually was, however all the credit goes to MadNik.
In your view controller class where you want a keyboard, in the implementation add the following:
#implementation CommentViewController {
UIView *toolbar;
UITextView *commentTextView;
UIButton *postComment;
}
The toolbar is the actual view that gets docked to your keyboard, and the rest of the objects go on top of the view.
Next it is as simple as initiating the toolbar and setting its frame:
toolbar = [[UIView alloc]initWithFrame:CGRectMake(0, self.view.frame.size.height-50, self.view.frame.size.width, 50)];
[toolbar setBackgroundColor:[UIColor whiteColor]];
I make the frame of the toolbar initially sit right at the bottom of the view controller.
Next I just initiate the rest of the objects I want on my toolbar, e.g the UITextField and a UIButton. Just lay them out how you want:
commentTextView = [[UITextView alloc]initWithFrame:CGRectMake(8, 8, self.view.frame.size.width - 16 - 75, 34)];
[commentTextView setBackgroundColor:[UIColor colorWithWhite:0.97 alpha:1]];
commentTextView.layer.cornerRadius = 5;
[commentTextView setFont:[UIFont fontWithName:#"Avenir Next" size:20]];
[commentTextView setTextColor:[UIColor colorWithWhite:0.35 alpha:1]];
postComment = [[UIButton alloc]initWithFrame:CGRectMake(self.view.frame.size.width-75, 0, 75, 50)];
[postComment setTitle:#"Post" forState:UIControlStateNormal];
[postComment.titleLabel setFont:[UIFont fontWithName:#"Avenir Next" size:20]];
[postComment setTitleColor:[UIColor colorWithRed:(255/255.0) green:(40/255.0) blue:(80/255.0) alpha:1.0] forState:UIControlStateNormal];
Next add your objects to your tool bar:
[toolbar addSubview:commentTextView];
[toolbar addSubview:postComment];
Now this is where the magic happens: You simply set your UITextView's inputAccessoryView to whatever view you want to be docked to the keyboard.
In this case it is toolbar, because the toolbar is acting as a dock that holds everything else.
Now all you need to do is add your toolbar to your view controller, and when you tap the UITextView, since its inputAccessoryView it the toolbar, the toolbar will be docked to the keyboard!
Since I am using a UITableViewController, I had to add my toolbar to the window:
[[[UIApplication sharedApplication]delegate].window addSubview:toolbar];
So simple! No extra classes or anything needs to be made!
What would be the best way to enforce a rule that all NavigationItem titles are truncated to a certain number of characters?
What I've looked at doing so far are the following:
subclassing UINavigationController and having all UINavigationControllers in the app inherit from and use this method
#interface MyNavController : UINavigationController {
#end
#implementation MyNavController
- (void)setTitle:(NSString *)title {
// do the truncation here, but it's not working
[super setTitle:myTruncatedTitle];
}
#end
It of course steps into this method anytime a viewcontroller inside this navcontroller calls self.title = ....
But it's not setting this new truncated title to the title that appears in the NavBar.
or I found this solution which involves a category on UINavigationItem and swizzling:
Make all instances of UINavigationBar titles lowercase without subclassing?
are there any other solutions that you can suggest?
self.navigationItem.titleView=[Utility setTitleOfbar:NSLocalizedString(#"title", nil)];
+(UIView *)setTitleOfbar:(NSString *)title{
title = [title substringToIndex: MIN(15, [title length])];
CGSize size=[[UIScreen mainScreen] bounds ].size;
UIView *view=[[UIView alloc]initWithFrame:CGRectMake(size.width/2-120, 5, 200, 44)];
[view setBackgroundColor:[UIColor clearColor]];
UILabel *label=[[UILabel alloc] initWithFrame:CGRectMake(0,3,200,44)];
label.text=title;
label.textAlignment=NSTextAlignmentCenter;
label.numberOfLines = 0;
[view addSubview:label];
return view;
}
In the app delegate's didFinishWithLaunching method, you can write below code:
[[UINavigationBar appearance] setTitleTextAttributes:AttributeDictionary];
I wish to add a UIToolbar programmatically to a view when a user clicks on a button (in my case when they zoom in on a photo).
It seems to work fine when I create the toolbar in the click method and add to the subview but if I create the toolbar in the viewDidLoad method, assign it to an instance variable,and add that instance variable later to the subview on click, nothing appears. The debugger shows that the instance variable is a UIToolbar and is not null. I didn't want to create and destroy the same toolbar on every click so I thought it was better just to keep it as an instance variable that I add and remove from the view as needed. Is this the right approach?
Why is it visible in the one case and not the other.
Setup
#synthesize toolBar;
- (UIToolbar*)createToolbar
{
UIToolbar* toolbar = [[UIToolbar alloc] init];
toolbar.frame = CGRectMake(0, self.view.frame.size.height - 44, self.view.frame.size.width, 44);
UIBarButtonItem *shareButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:#selector(sharePhoto:)];
NSArray *buttonItems = [NSArray arrayWithObjects:shareButton,nil];
[toolbar setItems:buttonItems];
return toolbar;
}
This works
- (void) clickMyButton {
toolBar = [self createToolbar];
[self.view addSubview:toolBar];
}
This doesn't show anything
- (void)viewDidLoad
{
[super viewDidLoad];
toolBar = [self createToolbar];
}
- (void) clickMyButton {
[self.view addSubview:toolBar];
}
Why doesn't it work in the latter case
The problem is that when viewDidLoad gets called, it is not guaranteed that the frames for your view and subviews are set. Try calling [self createToolbar] from viewWillAppear or viewDidAppear instead.
I am creating some custom UIBarButtonItems for my bottom toolbar with the following method:
- (void)initialisePageNoProperties
{
pageNoTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 50, 30)];
[pageNoTextField setDelegate:self];
pageNoTextField.text = #"0";
pageNoTextField.textColor = [UIColor blackColor];
pageNoTextField.backgroundColor = [UIColor whiteColor];
pageNoTextField.textAlignment = UITextAlignmentRight;
pageNoTextField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
pageNoTextField.borderStyle = UITextBorderStyleRoundedRect;
pageNoTextField.autocorrectionType = UITextAutocorrectionTypeNo; // no auto correction support
pageNoTextField.keyboardType = UIKeyboardTypeNumberPad;
pageNoTextField.returnKeyType = UIReturnKeyGo;
[pageNoTextField setClearsOnBeginEditing:YES];
pageNoBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:pageNoTextField];
pageNoBarButtonItem.style = UIBarButtonItemStyleBordered;
noOfPagesTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 50, 30)];
noOfPagesTextField.text = #"0";
noOfPagesTextField.textColor = [UIColor blackColor];
noOfPagesTextField.backgroundColor = [UIColor clearColor];
noOfPagesTextField.textAlignment = UITextAlignmentLeft;
noOfPagesTextField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
noOfPagesTextField.enabled = NO;
noOfPagesBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:noOfPagesTextField];
}
These buttons then get added to the bottom toolbar with the following method:
- (void)configurePageNoDisplay
{
if(![self.navigationController isToolbarHidden])
{
NSMutableArray *items = [[self.navigationController.toolbar items] mutableCopy];
bool insertIntoArray = ([items count] == 10); // without the page number display the items array will contain 10 items
if (insertIntoArray)
{
[items insertObject:pageNoBarButtonItem atIndex:3];
}
else
{
[items replaceObjectAtIndex:3 withObject:pageNoBarButtonItem];
}
if (insertIntoArray)
{
[items insertObject:noOfPagesBarButtonItem atIndex:4];
}
else
{
[items replaceObjectAtIndex:4 withObject:noOfPagesBarButtonItem];
}
[self.navigationController.toolbar setItems:items];
[self SetPageNoDisplay:[pdfViewCtrl GetCurrentPage]];
}
}
and the values for these buttons get set as follows:
- (void)SetPageNoDisplay:(NSInteger) pageNumber
{
pageNoTextField.text = [NSString stringWithFormat:#"%d", pageNumber];
noOfPagesTextField.text = [NSString stringWithFormat:#"of %d", [[pdfViewCtrl GetDoc] GetPageCount]];
}
The buttons and the fields they contain are declared as follows:
#property (strong, nonatomic) IBOutlet UIBarButtonItem *pageNoBarButtonItem;
#property (strong, nonatomic) IBOutlet UIBarButtonItem *noOfPagesBarButtonItem;
#property (strong, nonatomic) IBOutlet UITextField *pageNoTextField;
#property (strong, nonatomic) IBOutlet UITextField *noOfPagesTextField;
Originally they were not declared as IBOutlets - but I read a suggestion that doing so would help my problem - unfortunately it did not help.
I hide/show the bottom toolbar in response to a double tap from the user:
- (void)handleDoubleTap:(UITapGestureRecognizer *)gestureRecognizer
{
[self.navigationController setNavigationBarHidden:![self.navigationController isNavigationBarHidden] animated:true];
[self.navigationController setToolbarHidden:![self.navigationController isToolbarHidden] animated:true];
[self configurePageNoDisplay];
//[sideBarTab setHidden:![sideBarTab isHidden]];
}
The problem I have is that once the toolbar has been hidden - the buttons do not appear again when it is re-shown. If I rotate the iPad after re-showing the toolbar then the buttons appear again.
I ended up resolving this issue by taking a different approach. I used a standard UIToolbar, rather than the navigation controller toolbar. Using Interface Builder I was able to add a plain UIView to the toolbar and then add the 2 UITextFields inside the UIView. This way I did not have to worry about all the code to add the text fields to the toolbar inside of bar button items - or calling the code to reinstate the text fields on orientation change, hiding and showing of the toolbar etc. It ended up being a much simpler and more robust approach.
Use the setToolbarItems method in your UIViewController to set the toolbar buttons and that should solve it.
I'm trying to customize my NavigationBar with the help of a toolbar.
I've implemented it programmatically as follows:
UIToolbar* tools = [[UIToolbar alloc] initWithFrame: CGRectMake(0, 0, 100, 44.01)];
and then I added it to my NavigationBar. The problem is that I have this ugly effect on the borders:
I've tried to change the y and the height values, with no results.
Do you have any ideas to avoid this?
Thanks in advance, yassa
I wouldn't do it this way.
You can achieve the same effect by adding a view with 2 buttons to the navigationItem.rightBarButtonItem. it is very simple:
// view that will hold the buttons
UIView* container = [[UIView alloc] init];
// create 1 button and add it to the container
UIButton* button = [[UIButton alloc] init........];
[container addSubview:button];
//create 2 button and add it to the container
button = [[UIButton alloc] init.........];
[container addSubview:button];
// now create a Bar button item
UIBarButtonItem* barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:container];
// set the nav bar's right button item
self.navigationItem.rightBarButtonItem = barButtonItem;
I partially agree with previous answers and comments.
The solution you suggested works fine for custom buttons. But what if I want to implement standard Edit button?
Access to the standard buttons/icons is through the UIBarButtonItem class, not UIButton. And you can't add UIBarButtonItem objects to a UIView.
After many research on the web, I've found the solution that completely cover my requirement. The toolbar must be created in the following way:
UIToolbar *tools = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 95.0f, 44.01f)];
tools.tintColor = self.navigationController.navigationBar.tintColor;
tools.barStyle = -1;
And this is the result:
Hope it helps!
yassa
Or you can do it in this way.
Just create new subclass of UIToolbar like this
#interface MyToolbar : UIToolbar
#end
#implementation MyToolbar
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];
self.clearsContextBeforeDrawing = YES;
}
return self;
}
- (void)drawRect:(CGRect)rect {
// do nothing
}
- (void)dealloc {
[super dealloc];
}
#end
And use it as normal UIToolbar. I don't know why but it just works.