Change UINavigationItem accessibility elements order - ios

I have a UINavigationBar which contains 3 parts:
titleView (which is UISearchBar)
leftBarButtonItems
rightBarButtonItems
when the voice-over is on, the search bar will be the first one to be focused.
which is not confirmed to the left to right order we are familiar with.
I tried to set UINavigationItem's accessibilityElements, but it still will highlight the searchBar first. assume because, inside the UINavigationItem, the titleView is the first subView.
any ideas on how to change the order, thanks~

Any ideas on how to change the order?
I assume your problem deals with the way you added the search bar in the navigation bar.
I created a blank project as follows:
The code snippet hereafter is an example of adding a search bar as the title view: 🤓
class SearchViewController: UIViewController, UISearchBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let search = UISearchBar()
search.showsCancelButton = true
search.delegate = self
self.navigationItem.titleView = search
}
}
The result with VoiceOver on gives rise to the following screenshots:
Following this rationale, you have the VoiceOver initial reading order from left to right in the navigation bar. 🥳🎊🎉
I suggest to take a look at this site if further information is needed about a11y in the navigation bar and especially if you want to make a specific reading order. 👍

Related

How can I disable some of the tabs of the moreNavigationController of my UITabBarController?

I'm using XCode 13.0, Swift 5.5 and Storyboard. This is a mobile app for iPhone with iOS 15.
I have altogether 7 tabs, all of them with icons. I have 4 tabs and a "More" tab in the tab bar of my app.
3 additional tabs show up after the user clicks on the "More" tab. I'd like the first 2 of these additional tabs to be disabled. They need to be grey coloured and when the user clicks on any of these 2, I'd like to app to do nothing (not to show any page, not to navigate anywhere). I'm using the original UITabBarController and moreNavigationController defined by Apple, I didn't subclass any of them.
On my storyboard I added 2 UIViewControllers to these 2 disabled tabs, but I set them disabled this way:
This didn't work at all. When the user clicks on the 2 disabled tabs, they show 2 empty ViewControllers. I'd expect them not to show anything.
I also tried accessing the moreNavigationController's tabs from the page my UITabBarController first opens. I tried setting these 2 tabs disabled programmatically. However I didn't manage to access these 2 tabs, I only manage to access the main tabs that show up in the tab bar (I don't need to access these). How can I disable and colour grey the tabs that show up after clicking on the More tab?
let moreControllerItems = tabBarController?.moreNavigationController.toolbarItems
if let tabArray = moreControllerItems {
let tabBarItem1 = tabArray[0]
let tabBarItem2 = tabArray[1]
tabBarItem1.isEnabled = false
tabBarItem2.isEnabled = false
}`
This code doesn't work, the moreControllerItems variable is nil. The tabBarController variable isn't nil, I can access my UITabBarController from here, but it doesn't help me much.
I googled this issue lots of different ways but I couldn't find the solution. Any help would be appreciated. I really need to solve this, please write a comment if you have any idea how to solve this. Thank you!
You can disable tabBar items from TabBarController.
class TabVC: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
}
func disableTabBar(itemNo: Int) {
if let items = tabBar.items, itemNo < items.count {
items[itemNo].isEnabled = false
}
}
And you can access this function from
any child Viewcontroller attached to TabBarController
class MoreVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let tabBarVC = tabBarController as? TabVC {
tabBarVC.disableTabBar(itemNo: 1)
}
}
In short: You cannot disable it. If you want to disable the item in the More Viewcontroller, you have to use a custom Viewcontroller in the fifth Tab
I am explaining here why we cannot do it and what limitations we have.
Apple will show the More tab if items are more than five in the UITabBarController. And more tab holds the UINavigationController which is attached to a kind of UIMoreListController. you can check by
print moreNavigationController.viewControllers[0]
UIMoreListController is not accessible. In your case, the remaining three items are listed in the UIMoreListController. Although we can still access the Viewcontroller's tabBarItem and we can disable it. You can check by clicking the edit option.
But still, users can click on it as long as it is in the UIMoreListController. Because we are not interacting with the tabBar instead we are interacting with the items on the list. Users can't click once you move the item to any of the first four positions using the edit option in the MoreViewcontroller.

iOS 11 tableView with Style Grouped makes large title become invalid

I gave my tableViewController a large title Style programmatically:
self.navigationController?.navigationBar.prefersLargeTitles = true
and in storyboard, I made tableView "Static Cell" content, and "Grouped" Style in Attributes inspector:
But largeTitle didn't work when I ran the app, if tableView style is "Plain", it works. How can I fix it?
I'm not sure if it's your case, but for me using tableView.bounces = false prevented the large-title from working. Removing this line did the trick.
Where did you put your self.navigationController?.navigationBar.prefersLargeTitles = true. I putted it in ViewDidload and it's works properly
It's not clear to me why this occurs. Quite possibly a bug. But here is a work around. In Document Outline. Select your Navigation Controller Scene -> Navigation Bar then select Prefer Large Titles (at the top) in Attribute Inspector like so:
After making this change, if you want other view controllers to have regular / plain titles, simply add this to viewDidLoad() on that particular V.C.
navigationItem.largeTitleDisplayMode = .never
Remove title from your storyboard
In your UIViewController over viewDidLoad function:
navigationItem.title = "Search title"
navigationController?.navigationBar.prefersLargeTitles = true

UISearchBar briefly displays gray background when returning to title from a show segue

I have a customized UISearchController with a custom UISearchBar as the title of a navigation controller, so that there's a persistent search bar control visible throughout the changes of child view controllers, which is shown in the first image of my gallery (not enough reputation to post images yet):
http://imgur.com/a/IikEw
However, when a user taps a search result displayed in a child table view controller, the navigation title is replaced with a text string, so that it looks like the second image in my gallery.
The problem occurs when I navigate back to the table view showing the search results, because for a brief moment, the search bar has a light gray background that looks like the third image in my gallery.
And when I slow down the animations in the simulator, I'm able to enter the Debug View Hierarchy in Xcode to see what the offending element is, which turns out to be a UIImageView named "UISearchBarBackground" that exists only until the transition animation ends, and the color snaps back to the intended result, as shown in the fourth image in my gallery.
In the initial view controller that initializes and sets the properties of my UISearchController, I've set the following properties of the search bar in viewDidLoad:
let controller = CustomSearchController(searchResultsController: self.searchResultsController)
controller.searchBar.backgroundColor = UIColor.clear
controller.searchBar.backgroundImage = nil
...and I have these custom classes, which I implemented so that I could get rid of the search controller's cancel button, which won't stay removed with delegate statements:
class CustomSearchBar: UISearchBar {
override func setShowsCancelButton(_ showsCancelButton: Bool, animated: Bool) {
super.setShowsCancelButton(false, animated: false)
}
}
class CustomSearchController: UISearchController {
lazy var _searchBar: CustomSearchBar = {
[unowned self] in
let customSearchBar = CustomSearchBar(frame: CGRect.zero)
return customSearchBar
}()
override var searchBar: UISearchBar {
get {
return _searchBar
}
}
}
As far as I can understand, at no point is my custom search controller deinitialized, and it is only initialized once, so I feel like the problem is appearing because of the drawing cycle, which temporarily places a visible view with a gray background before my settings take effect.
I'm pretty much at a loss as to what exactly is going on here, and how to solve this issue. I'm wondering if I'm just overlooking something simple, or if I instead have to create a custom transition object to solve the problem. I'm about an intermediate level with iOS architecture and Swift, but am always looking to learn more.
I've figured out that this issue is caused by not having a value set to the optional barTintColor property of UISearchBar. If the property is nil during a transition animation, then the view will appear light gray until the animation ends. To solve the problem, I set controller.searchBar.barTintColor = UIColor(red: 76/255, green: 203/255, blue: 124/255, alpha: 1), and now I'm getting the intended results.

UI testing a tab bar controller

I have built a simple tab bar with 3 tabs.
I want to run a UI test to make sure that if the user clicks a tab bar item, the correct view controller shows. How would I go about doing that? Below is the code I would start with, just don't know how to write my assertion.
func testTabBarMyProfileButton() {
let tabBarsQuery = XCUIApplication().tabBars
tabBarsQuery.buttons["My Profile"].tap()
}
func testTabBarGraphsButton() {
let tabBarsQuery = XCUIApplication().tabBars
tabBarsQuery.buttons["Graphs"].tap()
}
func testTabBarAboutButton() {
let tabBarsQuery = XCUIApplication().tabBars
tabBarsQuery.buttons["About"].tap()
}
You can access the tabbar button by its position:
app.tabBars.buttons.element(boundBy: 2).tap()
If you have different controls in each view controller shown on each tab bar, you can make assertions if they exist or not (what is expected).
For example if the first tab bar has UILabel named "First name" you can assert if it exists by writing
Let theLabel = app.staticTexts["myValue"]
XCTAssert(theLabel.exists).to(beTrue)
And on the other screens do the same thing for the different controls.
If anyone finds this looking to UI test the contents of another app, I just found a solution..
The tab bar item is a lazy variable and needs to be touched before you can reference a tab bar button by value. Add this line:
tabBarItem.accessibilityIdentifier = "my-snazzy-identifier"
to the viewDidLoad method and you should be able to do this in your UI tests:
app.tabBars.buttons["Button Title"].tap()
You can test the title of the navigation bar.
XCTAssert(app.navigationBars["Graphs"].exists)
See my GitHub repo for a more detailed UI Testing example.

Prevent UISearchBarController to show UINavigationBar

I'm using a UINavigationBar ALWAYS hidden (I'm using NavigationBar facilities to push ou pop views but I'm not showing it to final user), the problem is that in one of those views I have a tableView with UISearchBar. When I select the searchBar, make a search and click on it's "Cancel" button the NavigationBar appears, but I want to keep the Navigation hidden as it is.
I've tried to hidden the navigationBar one more time by willDismissSearchController or didDismissSearchController by
func willDismissSearchController(searchController: UISearchController) {
self.navigationController?.navigationBar.hidden = true
}
but it did not worked as I want.
Thank you in advance.
I've found a solution, so as it is a unusual question I'll reply for other people know the solution.
the following code did worked for me:
override func viewDidLayoutSubviews() {
self.navigationController?.navigationBar.hidden = true
}

Resources