From 29d8d609b590eda57e27dfa4ec768e80207b88a9 Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Tue, 2 Feb 2021 14:33:54 -0800 Subject: [PATCH] VoiceOver wip --- Localizations/Localizable.strings | 1 + Views/UIKit/AccountFieldView.swift | 55 ++++++++++++++++++++++ Views/UIKit/TouchFallthroughTextView.swift | 4 +- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index 8dafb87..6cd4c22 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -11,6 +11,7 @@ "account.domain-block.confirm-%@" = "Block domain %@?"; "account.domain-unblock-%@" = "Unblock domain %@"; "account.domain-unblock.confirm-%@" = "Unblock domain %@?"; +"account.field.activate-link-accessibility-action-%@" = "Activate link: %@"; "account.field.verified" = "Verified %@"; "account.follow" = "Follow"; "account.following" = "Following"; diff --git a/Views/UIKit/AccountFieldView.swift b/Views/UIKit/AccountFieldView.swift index 991ec74..0b578fa 100644 --- a/Views/UIKit/AccountFieldView.swift +++ b/Views/UIKit/AccountFieldView.swift @@ -1,16 +1,22 @@ // Copyright © 2020 Metabolist. All rights reserved. +import Combine import Mastodon import UIKit final class AccountFieldView: UIView { let nameLabel = UILabel() let valueTextView = TouchFallthroughTextView() + private var cancellables = Set() // swiftlint:disable:next function_body_length init(name: String, value: NSAttributedString, verifiedAt: Date?, emojis: [Emoji]) { super.init(frame: .zero) + NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification) + .sink { [weak self] _ in self?.configureUserInteractionEnabledForAccessibility() } + .store(in: &cancellables) + backgroundColor = .systemBackground let nameBackgroundView = UIView() @@ -134,6 +140,51 @@ final class AccountFieldView: UIView { valueBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor), valueBackgroundView.bottomAnchor.constraint(equalTo: bottomAnchor) ]) + +// isAccessibilityElement = true + + let accessibilityAttributedLabel = NSMutableAttributedString(attributedString: mutableName) + + accessibilityAttributedLabel.appendWithSeparator(mutableValue) + + if let verifiedAt = verifiedAt { + accessibilityAttributedLabel.appendWithSeparator( + String.localizedStringWithFormat( + NSLocalizedString("account.field.verified", comment: ""), + Self.dateFormatter.string(from: verifiedAt))) + } + + isAccessibilityElement = true + + var accessibilityCustomActions = [UIAccessibilityCustomAction]() + + mutableValue.enumerateAttribute( + .link, + in: NSRange(location: 0, length: mutableValue.length), + options: []) { attribute, range, _ in + guard let url = attribute as? URL else { return } + + accessibilityCustomActions.append( + UIAccessibilityCustomAction( + name: String.localizedStringWithFormat( + NSLocalizedString("account.field.activate-link-accessibility-action-%@", comment: ""), + mutableValue.attributedSubstring(from: range).string)) { [weak self] _ in + guard let valueTextView = self?.valueTextView else { return false } + + _ = valueTextView.delegate?.textView?( + valueTextView, + shouldInteractWith: url, + in: range, + interaction: .invokeDefaultAction) + + return true + }) + } + + self.accessibilityAttributedLabel = accessibilityAttributedLabel + self.accessibilityCustomActions = accessibilityCustomActions + + configureUserInteractionEnabledForAccessibility() } @available(*, unavailable) @@ -150,4 +201,8 @@ private extension AccountFieldView { return formatter }() + + func configureUserInteractionEnabledForAccessibility() { + valueTextView.isUserInteractionEnabled = !UIAccessibility.isVoiceOverRunning + } } diff --git a/Views/UIKit/TouchFallthroughTextView.swift b/Views/UIKit/TouchFallthroughTextView.swift index c5fb04c..07b2f63 100644 --- a/Views/UIKit/TouchFallthroughTextView.swift +++ b/Views/UIKit/TouchFallthroughTextView.swift @@ -26,7 +26,9 @@ final class TouchFallthroughTextView: UITextView { } override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - shouldFallthrough ? urlAndRect(at: point) != nil : super.point(inside: point, with: event) + guard !UIAccessibility.isVoiceOverRunning else { return super.point(inside: point, with: event) } + + return shouldFallthrough ? urlAndRect(at: point) != nil : super.point(inside: point, with: event) } override func touchesBegan(_ touches: Set, with event: UIEvent?) {