Hey everyone. I’m a completely-blind developer picking-up Swift as a second language. I have a solid grasp on XCode at this point, and have made two simple SwiftUI projects. However, tracking-down errors seems overly-cumbersome/incomplete with Voiceover, so I must be missing something. Secondly, I have yet to figure-out how to actually navigate my project in the Preview group or the XCode Simulator, and Apple doesn’t seem to have any documentation on navigating those areas with Voiceover.
First, here’s what I’m running-into when tracking-down errors:
When a ttempting to build or run my project fails, I’ve noticed three separate areas to view information about the erros:
1: The Jump Bar group located inside of the Editor Group and group for whichever file is currently-open in your project. The jump Bar group is at the same level as the scroll area that allows you to access the editor. In this Jump Bar group, I can open a menu that lists the exceptions thrown by XCode, but not the location of the error.
2: Inside of the Editor Group and the Debug Bar Group. Here I can view the location of the error, but not the exception that was thrown along with the error.
Lastly, Inside of the Debug Area Group, I can interact with the console and read information printed there, which seems to be hit-or-miss in my experience.
As you all know, getting to these three different areas is rather cumbersome, as there are quite a few steps to navigating between each of these areas, returning to the editor, and trying the process all-over again after attempting to fix your code.
I’m using the latest version of MacOS Monterey and XCode. Am I missing something, or is XCode truly this cumbersome to use with Voiceover?
Secondly, there doesn’t seem to be any documentation on using the Simulator and Accessibility Inspector with Voiceover Inspector. My two iOS projects have a NavigationBar with an associated title, on Form, two TextFields with labels, a Picker that uses a ForEach view to populate the Picker with the desired options, and one Text filed to display a message and the value of a computed property. All of these views are nested within var body, inside of the ContentView Struct.
Note: I’ll paste the code from one of these projects, which is an exact copy of the “We Split” project from www.hackingwithswift.com.
When opening the simulator, I can access the main toolbar and buttons related to trying to direct the navigation of the pointer, but there’s no representation of the project’s Views to navigate over with Voiceover on my Mac. Similarly, when electing to have the Simulator speak the elements it finds, it doesn’t pick-up on any of the Views in my project.
When using the Accessibility Inspector, and directing it to the open Simulator, it seems to be reading information from the Simulator window itself, rather than my project. As I use the Next and Previous buttons to shift the inspector’s focus to successive elements, it seems to be picking-up on the close, maximize, and minimize buttons before getting stuck on the text field that displays the device the Simulator is simulating.
Example Code from Hacking With Swift Tutorial
//
// ContentView.swift
// HWS Project 1 (We Split)
//
// Created by Zach Tidwell on 7/5/22.
//
import SwiftUI
struct ContentView: View {
@State private var checkAmount = 0.0
@State private var numberOfPeople = 2
@State private var tipPercentage = 20
//We use this variable to let Swift know when the keyboard should disappear from the screen
@FocusState private var amountHasFocus: Bool
let tipPercentages = [10, 15, 20, 25, 0]
var totalPerPerson: Double {
//We have to add two to the numberOfPeople because, while the Picker has a range of 2 to 100, it starts counting at 0, similarly to how storage types index at 0
let peopleCount = Double(numberOfPeople + 2)
//We also convert our tipPercentage variable to a double so that we can use it alongside the peopleCount variable when calculating the tip amount per person
let tipSelection = Double(tipPercentage)
let tipValue = Double(checkAmount / 100 * tipSelection)
let grandTotal = tipValue + checkAmount
let amountPerPerson = grandTotal / peopleCount
return amountPerPerson
}//End totalPerPerson
var body: some View {
NavigationView {
Form{
Section {
//The parameter passed in the .currency() function gets the currency defined by the user's device. This is passed as an optional, and our code defaults to US dollars if the user doesn't have a preferred currency set.
TextField("Amount", value: $checkAmount, format: .currency(code: Locale.current.currencyCode ?? "USD"))
.focused($amountHasFocus)
//This code makes it so the keyboard presented to users is the numeric keyboard with a decimal point
//This adds a picker
.keyboardType(.decimalPad)
Picker("Number of people", selection: $numberOfPeople) {
ForEach(2 ..< 100) {
Text("\($0) people")
}//End ForEach
}//End Picker)`
}//End section
Section {
Picker("Tip percentage", selection: $tipPercentage) {
ForEach(tipPercentages, id: \.self) {
Text($0, format: .percent)
}//End ForEach
}//End Picker
.pickerStyle(.segmented)
//This line ends the Picker, then creates a new text view as a header to appear just-above the picker.le(.segmented)
} header: {
Text("HowHow much tip do you want to leave?")
}//End header
Section {
Text(totalPerPerson, format: .currency(code: Locale.current.currencyCode ?? "USD"))
}//End Section
}//End For
.navigationTitle("We Split")
//This line allows us to specify specific ToolbarItemGroups and where they'll appear
.toolbar {
//This line is establishing a ToolbarItemGroup and telling Swift that we want it attached to the keyboard. ToolbarItemGroups allow us to add buttons in a specific loaction
ToolbarItemGroup(placement: .keyboard) {
//This spacer view is a flexible space. By default, the Spacer view pushes all other views to one side automatically. Depending on the scenario, Swift may push those views up, down, left, or right. Placing it before the Button will push it to the right of the toolbar.
Spacer()
//This block of code establishes a button, sets its name to "Done", and then declares code for it to execute when pressed. In this case, it sets the amountHasFocus boolean to false so that Swift will know that the keyboard should disappear from the screen.
Button("Done") {
amountHasFocus = false
}//End done button
}//End toolbarItemGroup
}//End toolbar
}//End NavigationViewm
}//End var boyd
}//End ContentVie
Comments
Cumbersome, but There are Shortcuts
For your specific problem, a more hidden solution that doesn't work 100% reliably, but better than what you're currently dealing with is as follows.
This will, at least in theory, jump you to the file with the issue and to the effected part of the code. In my own experience, it may not land exactly on the line , but it should be close if not.
As to the simulator, this is something broken fairly recently. Back in March, I was successfully able to use Voice Over to focus and even activate elements of my apps within the simulator. That was on Big Sur and XCode 13.3 I believe. Unfortunately, I then got a new Mac with Monterey and upgraded XCode and found that the simulator no longer allowed Voice Over on the Mac to explore the app contents. I say unfortunately because it's difficult to be sure what upgrade broke the accessibility here. Someone else I know tried with an older version of XCode without success. I believe we tried on Big Sur again as well, so I can't point to one or the other as the cause. It could easily be some other change Apple made that is less visible on the user side. It may be worth writing to Accessibility at Apple and seeing if we can get some traction on the problem, though.
Swift UI is a great language, so here's hoping Apple can continue to improve XCode's accessibility and usability as well.
Good luck!