Allan Garcia
Allan Garcia

Reputation: 568

How to make iOS VoiceOver read a Text element (not current focus) when it changes?

I'm trying to make an App that simulates the behavior of Calculator app from Apple. On the Calculator, when you tap = (Equals), the App reads "Result, Number.", from the main display view. How can I reproduce this behavior in SwiftUI ?

My Test Scenario:

struct ContentView: View {
    
    @State var result = 0
    
    var body: some View {
        VStack {
            Text("\(result)")
                .font(.largeTitle)
                .frame(height: 100)
                .padding()
            Spacer()
            Button(action: {
                self.result += 1
            }, label: {
                Text("Add one")
                    .font(.title)
                    .padding()
            })
            Button(action: {
                self.result -= 1
            }, label: {
                Text("Minus one")
                    .font(.title)
                    .padding()
            })
        }
    }
}

I want every time result changes, from the interaction of either Add or Minus Buttons, the VoiceOver system detects the Result changes and read the Result.

Upvotes: 1

Views: 3912

Answers (3)

Allan Garcia
Allan Garcia

Reputation: 568

Here is my final solution:

struct ContentView: View {

@State var result = 0 {
    didSet {
        announce(this: "\(result)")
    }
}

var body: some View {
    VStack {
        Text("\(result)")
            .font(.largeTitle)
            .frame(height: 100)
            .padding()
        Spacer()
        Button(action: {
            self.result += 1
        }, label: {
            Text("Add one")
                .font(.title)
                .padding()
        })
        Button(action: {
            self.result -= 1
        }, label: {
            Text("Minus one")
                .font(.title)
                .padding()
        })
    }
    //.accessibility(value: Text("\(result)"))
}

private func announce(this: String) {
    if UIAccessibility.isVoiceOverRunning {
        UIAccessibility.post(notification: .screenChanged, argument: "The new result is \(this).")
    }
}

}

The notification i'm looking for was the .screenChanged, but I'm also wanting to use the .announcement as mention by @Scriptable.

Sometimes there's a race condition you have to solve with DispatchQueue.main.asyncAfter, as mention at: Why is UIAccessibility.post(notification: .announcement, argument: "arg") not announced in voice over?

Here where I found the others notifications: Default UIAccessibilityElement after screen change

Upvotes: 0

David Pasztor
David Pasztor

Reputation: 54755

You can simply use the accessibility(value:) modifier on the body of your view.

var body: some View {
    VStack {
        Text("\(result)")
            .font(.largeTitle)
            .frame(height: 100)
            .padding()
        Spacer()
        Button(action: {
            self.result += 1
        }, label: {
            Text("Add one")
                .font(.title)
                .padding()
        })
        Button(action: {
            self.result -= 1
        }, label: {
            Text("Minus one")
                .font(.title)
                .padding()
        })
    }
    .accessibility(value: Text("\(result)"))
}

Upvotes: 1

Scriptable
Scriptable

Reputation: 19758

You can post a notification for Voice over to announce something. But I am not familiar with SwiftUI, so not sure where would be best to place this code in your example

The code to post the notificaiton is:

if UIAccessibility.isVoiceOverRunning {
    UIAccessibility.post(notification: .announcement, argument: "message to announce")
}

Maybe you can do it this way:

function announceResult() {
    if UIAccessibility.isVoiceOverRunning {
        UIAccessibility.post(notification: .announcement, argument: self.result)
    }
}

Button(action: {
     self.result += 1
     announceResult()

}

Upvotes: 2

Related Questions