Skip to content

Commit

Permalink
Automatically go to the next resource after VoiceOver reaches the end…
Browse files Browse the repository at this point in the history
… of the current one
  • Loading branch information
mickael-menu committed Oct 28, 2023
1 parent 687a8a2 commit 1389e4e
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 74 deletions.
25 changes: 25 additions & 0 deletions Sources/Navigator/EPUB/EPUBNavigatorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ open class EPUBNavigatorViewController: UIViewController,
override open func viewDidLoad() {
super.viewDidLoad()

// Will call `accessibilityScroll()` when VoiceOver reaches the end of
// the current resource. We can use this to go to the next resource.
view.accessibilityTraits.insert(.causesPageTurn)

paginationView.frame = view.bounds
paginationView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
view.addSubview(paginationView)
Expand Down Expand Up @@ -893,6 +897,27 @@ open class EPUBNavigatorViewController: UIViewController,
}
spreadView.evaluateScript(script, completion: completion)
}

// MARK: - UIAccessibilityAction

override open func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool {
guard !super.accessibilityScroll(direction) else {
return true
}

switch direction {
case .right:
return goLeft(animated: false)
case .left:
return goRight(animated: false)
case .next, .down:
return goForward(animated: false)
case .previous, .up:
return goBackward(animated: false)
@unknown default:
return false
}
}
}

extension EPUBNavigatorViewController: EPUBNavigatorViewModelDelegate {
Expand Down
83 changes: 9 additions & 74 deletions TestApp/Sources/Reader/Common/VisualReaderViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import UIKit

/// Base class for the reader view controller of a `VisualNavigator`.
class VisualReaderViewController<N: UIViewController & Navigator>: ReaderViewController<N>, VisualNavigatorDelegate {
private(set) var stackView: UIStackView!
private lazy var positionLabel = UILabel()

private let ttsViewModel: TTSViewModel?
Expand Down Expand Up @@ -43,50 +42,33 @@ class VisualReaderViewController<N: UIViewController & Navigator>: ReaderViewCon
addHighlightDecorationsObserverOnce()
updateHighlightDecorations()
updatePageListDecorations()

NotificationCenter.default.addObserver(self, selector: #selector(voiceOverStatusDidChange), name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)
}

@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

deinit {
NotificationCenter.default.removeObserver(self)
}

override func viewDidLoad() {
super.viewDidLoad()

view.backgroundColor = .white

updateNavigationBar(animated: false)

stackView = UIStackView(frame: view.bounds)
stackView.distribution = .fill
stackView.axis = .vertical
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
let topConstraint = stackView.topAnchor.constraint(equalTo: view.topAnchor)
// `accessibilityTopMargin` takes precedence when VoiceOver is enabled.
topConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
topConstraint,
stackView.rightAnchor.constraint(equalTo: view.rightAnchor),
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
stackView.leftAnchor.constraint(equalTo: view.leftAnchor),
])

addChild(navigator)
stackView.addArrangedSubview(navigator.view)
navigator.view.frame = view.bounds
navigator.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(navigator.view)
navigator.didMove(toParent: self)

stackView.addArrangedSubview(accessibilityToolbar)

positionLabel.translatesAutoresizingMaskIntoConstraints = false
positionLabel.font = .systemFont(ofSize: 12)
positionLabel.textColor = .darkGray
// Prevents VoiceOver from selecting the position label while reading
// the page.
positionLabel.isAccessibilityElement = false

view.addSubview(positionLabel)
NSLayoutConstraint.activate([
positionLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
Expand Down Expand Up @@ -143,8 +125,7 @@ class VisualReaderViewController<N: UIViewController & Navigator>: ReaderViewCon
}

func updateNavigationBar(animated: Bool = true) {
let hidden = navigationBarHidden && !UIAccessibility.isVoiceOverRunning
navigationController?.setNavigationBarHidden(hidden, animated: animated)
navigationController?.setNavigationBarHidden(navigationBarHidden, animated: animated)
setNeedsStatusBarAppearanceUpdate()
}

Expand All @@ -153,7 +134,7 @@ class VisualReaderViewController<N: UIViewController & Navigator>: ReaderViewCon
}

override var prefersStatusBarHidden: Bool {
navigationBarHidden && !UIAccessibility.isVoiceOverRunning
navigationBarHidden
}

// MARK: - VisualNavigatorDelegate
Expand Down Expand Up @@ -297,52 +278,6 @@ class VisualReaderViewController<N: UIViewController & Navigator>: ReaderViewCon
present(highlightContextMenu!, animated: true, completion: nil)
}
}

// MARK: - Accessibility

/// Constraint used to shift the content under the navigation bar, since it is always visible when VoiceOver is running.
private lazy var accessibilityTopMargin: NSLayoutConstraint = self.stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)

private lazy var accessibilityToolbar: UIToolbar = {
func makeItem(_ item: UIBarButtonItem.SystemItem, label: String? = nil, action: UIKit.Selector? = nil) -> UIBarButtonItem {
let button = UIBarButtonItem(barButtonSystemItem: item, target: (action != nil) ? self : nil, action: action)
button.accessibilityLabel = label
return button
}

let toolbar = UIToolbar(frame: .zero)
toolbar.items = [
makeItem(.flexibleSpace),
makeItem(.rewind, label: NSLocalizedString("reader_backward_a11y_label", comment: "Accessibility label to go backward in the publication"), action: #selector(goBackward)),
makeItem(.flexibleSpace),
makeItem(.fastForward, label: NSLocalizedString("reader_forward_a11y_label", comment: "Accessibility label to go forward in the publication"), action: #selector(goForward)),
makeItem(.flexibleSpace),
]
toolbar.isHidden = !UIAccessibility.isVoiceOverRunning
return toolbar
}()

private var isVoiceOverRunning = UIAccessibility.isVoiceOverRunning

@objc private func voiceOverStatusDidChange() {
let isRunning = UIAccessibility.isVoiceOverRunning
// Avoids excessive settings refresh when the status didn't change.
guard isVoiceOverRunning != isRunning else {
return
}
isVoiceOverRunning = isRunning
accessibilityTopMargin.isActive = isRunning
accessibilityToolbar.isHidden = !isRunning
updateNavigationBar()
}

@objc private func goBackward() {
navigator.goBackward()
}

@objc private func goForward() {
navigator.goForward()
}
}

// MARK: - Page list decorations
Expand Down

0 comments on commit 1389e4e

Please sign in to comment.