6

Overview:

I would like to set the accessibility focus to the navigation bar's title item.

By default the focus is set from top left, meaning the back button would be on focus.

I would like the title item to be in focus.

Attempts made so far:

    UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
                                    navigationController?.navigationBar.items?.last)

Problem:

  • The above code makes no difference, the back button is still in focus.

Possible Cause:

  • Not able to get the item corresponding to the title to be able to set the focus.
user1046037
  • 16,755
  • 12
  • 92
  • 138
  • 1
    Same problem, and similar attempt which did not work: UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.navigationController?.navigationBar.topItem) . Also tried adding delays in viewDidAppear - does not make a difference. – FranticRock Jul 30 '18 at 14:38
  • 1
    Noticed that even Apple’s apps such as Contacts also start with the back button first then followed by the title – user1046037 Jul 30 '18 at 14:40
  • This is appalling that Apple has not addressed this yet. – Charlie S Jun 20 '21 at 12:00

4 Answers4

4

Solution 1 I don't like it, but it was the minimum amount of hacking that does not rely on digging through hidden subviews (internal implementation of UINavigationBar view hierarchy).

First in viewWillAppear, I store a backup reference of the back button item, and then remove the back button item (leftBarButtonItem):

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    backButtonBackup = self.navigationItem.leftBarButtonItem
    self.navigationItem.leftBarButtonItem = nil  
}

Then I restore the back item, but only after I dispatch the screen changed event in viewDidAppear() :

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil)
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
        self?.navigationItem.leftBarButtonItem = self?.backButtonBackup
    }
}

Solution 2: Disable all accessibility on the nav bar and view controller up until viewDidAppear() is finished:

    self.navigationController.navigationBar.accessibilityElementsHidden = true
    self.view.accessibilityElementsHidden = true

, and then in viewDidAppear manually dispatching the layout element accessibility focused event to the label subview of UINavigationBar:

UIAccessibilityPostNotification( UIAccessibilityLayoutChangedNotification, self.navigationController.navigationBar.subviews[2].subviews[1])   
//  The label buried inside the nav bar.   Not tested on all iOS versions.  
// Alternately you can go digging for the label by checking class types. 
// Then use DispatchAsync, to re-enable accessibility on the view and nav bar again... 

I'm not a fan of this method either.

DispatchAsync delay in viewDidAppear seems to be needed in any case - and I think both solutions are still horrible.

FranticRock
  • 3,233
  • 1
  • 31
  • 56
  • Actually I liked the 2nd option would it be ok for you to edit your answer to add 2 headings (markdown) as “Option 1” and “Option 2”. It might help to understand that there are 2 options in your answer – user1046037 Jul 31 '18 at 00:38
1

I invoked UIAccessibilityScreenChangedNotification on navigation title from viewDidLoad and it worked

 UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
                                       self.navigationItem.title);
Durai Amuthan.H
  • 31,670
  • 10
  • 160
  • 241
  • this had zero effect :( uhhh – Boris Gafurov Feb 22 '23 at 18:15
  • what is more strange [[self.navigationItem titleView] setAccessibilityLabel:@"Nav Bar Title"]; or [[self.navigationItem title] setAccessibilityLabel:@"Nav Bar Title"]; have NO EFFECT either!!! when I back to the title, voiceover says its text and adds "heading" not title. – Boris Gafurov Feb 22 '23 at 18:30
0

First, we'll need to create an useful extension:

extension UIViewController {
    func setAccessibilityFocus(in view: UIView) {
        UIAccessibility.post(notification: .screenChanged, argument: view)
    }
}

Then, we'll be able to set our focus in the navigation bar title like this:

setAccessibilityFocus(in: self.navigationController!.navigationBar.subviews[2].subviews[1])
Mauro García
  • 107
  • 1
  • 2
0

Not ideal, but works: I had to completely forego automatic titles b/c using UIAccessibilityPostNotification in viewDIdLoad or viewWillAppear did not work at all, using it in veiwDidAppear brought focus to Back button not the title as all Apple apps do!!!

More so [[self.navigationItem titleView] setAccessibilityLabel:@"Nav Bar Title"]; or [[self.navigationItem title] setAccessibilityLabel:@"Nav Bar Title"]; had NO EFFECT!

I created label and placed it in the titleView in viewDidLoad and used UIAccessibilityPostNotification in viewDidAppear (neither viewDidLoad nor viewWillAppear worked):

- (void)viewDidLoad {
[super viewDidLoad];
...
UILabel *titl=[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)];
[titl setBackgroundColor:[UIColor clearColor]];
[titl setTextAlignment:NSTextAlignmentCenter];
[titl setTextColor:[UIColor blackColor]];
[titl setFont:[UIFont boldSystemFontOfSize:16]];
[titl setText:@"My Title"];
titl.isAccessibilityElement = YES;
[titl setAccessibilityLabel:@"my title alt text"];
[self.navigationItem setTitleView:titl];
}

-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
UIAccessibilityPostNotification( UIAccessibilityScreenChangedNotification, self.navigationItem.titleView);
}

So now, VoiceOver starts from the highest element in my self.view, but then quickly switches to the title. Also on the 1st try it say the accessibility label i provided, after that it adds word "heading" to it. It is not ideal (or horrible as FranticRock said, lol) and will fail WCAG 2.2 test for sure, but that was the only way I could make it got to the title.

Community
  • 1
  • 1
Boris Gafurov
  • 1,427
  • 16
  • 28