diff --git a/Cineaste/Base.lproj/Localizable.strings b/Cineaste/Base.lproj/Localizable.strings index f10015ab..6d99ed8f 100644 Binary files a/Cineaste/Base.lproj/Localizable.strings and b/Cineaste/Base.lproj/Localizable.strings differ diff --git a/Cineaste/Custom UI/Button.swift b/Cineaste/Custom UI/Button.swift index b3cfbdab..a381aa5a 100644 --- a/Cineaste/Custom UI/Button.swift +++ b/Cineaste/Custom UI/Button.swift @@ -29,6 +29,8 @@ class LinkButton: Button { guard let title = title else { return } super.setTitle("▶︎ " + title, for: state) + + accessibilityLabel = title.replacingOccurrences(of: "▶︎ ", with: "") } override func setup() { diff --git a/Cineaste/Extension/String+Cineaste.swift b/Cineaste/Extension/String+Cineaste.swift index fc7f31fc..5db01242 100644 --- a/Cineaste/Extension/String+Cineaste.swift +++ b/Cineaste/Extension/String+Cineaste.swift @@ -52,6 +52,10 @@ extension String { static let searchInCategoryPlaceholder = NSLocalizedString("searchIn", comment: "Placeholder for search textField in MoviesList") static let noContentTitle = NSLocalizedString("noContent", comment: "Title for no content") static let onDate = NSLocalizedString("onDate", comment: "on a date") + static let genreAccessibilityLabel = NSLocalizedString("genre", comment: "Genre for a movie") + static let releasedOnDateAccessibilityLabel = NSLocalizedString("releasedOnDateVoiceOver", comment: "Movie released on dates") + static let releasedInYearAccessibilityLabel = NSLocalizedString("releasedInYearVoiceOver", comment: "Movie released in year") + static let releaseOnDateAccessibilityLabel = NSLocalizedString("releaseOnDateVoiceOver", comment: "Release date accessibility label for a movie") // MARK: SEARCH VIEWCONTROLLER static let discoverMovieTitle = NSLocalizedString("discoverMovieTitle", comment: "Title for search") @@ -69,7 +73,7 @@ extension String { } } - static func voting(for vote: String) -> String { + static func votingAccessibilityLabel(for vote: String) -> String { String.localizedStringWithFormat(NSLocalizedString("%@ of 10", comment: "Voting description"), vote) } diff --git a/Cineaste/Models/Movie+Formatted.swift b/Cineaste/Models/Movie+Formatted.swift index 937a5bdd..49a8f3a1 100644 --- a/Cineaste/Models/Movie+Formatted.swift +++ b/Cineaste/Models/Movie+Formatted.swift @@ -23,6 +23,14 @@ extension Movie { return release.formatted } + var accessibilityFormattedReleaseDate: String { + guard let release = releaseDate else { + return String.unknownReleaseDate + } + + return String.releasedOnDateAccessibilityLabel + " " + release.formatted + } + var formattedRelativeReleaseInformation: String { guard let release = releaseDate else { return String.unknownReleaseDate @@ -36,12 +44,39 @@ extension Movie { } } + // swiftlint:disable:next identifier_name + var accessibilityFormattedRelativeReleaseInformation: String { + guard let release = releaseDate else { + return String.unknownReleaseDate + } + + let isCurrentYear = release.formattedOnlyYear == Current.date().formattedOnlyYear + if isCurrentYear { + if soonAvailable { + // Release on May 4, 2030 + return String.releaseOnDateAccessibilityLabel + " " + release.formatted + } else { + // Released on May 4, 2023 + return String.releasedOnDateAccessibilityLabel + " " + release.formatted + } + } else { + // Released in 2024 + return String.releasedInYearAccessibilityLabel + " " + release.formattedOnlyYear + } + } + var formattedRuntime: String { - guard runtime != 0 else { + guard let runtime, runtime != 0 else { return "\(String.unknownRuntime) min" } - return "\(runtime?.formatted ?? String.unknownRuntime) min" + if #available(iOS 16.0, *) { + let duration = Duration.seconds(runtime * 60) + let format = duration.formatted(.units(allowed: [.minutes], width: .abbreviated)) + return format + } else { + return "\(runtime.formatted ?? String.unknownRuntime) min" + } } var formattedWatchedDate: String? { diff --git a/Cineaste/ViewController/MovieDetail/MovieDetailViewController.swift b/Cineaste/ViewController/MovieDetail/MovieDetailViewController.swift index 3564376b..7bb898ec 100644 --- a/Cineaste/ViewController/MovieDetail/MovieDetailViewController.swift +++ b/Cineaste/ViewController/MovieDetail/MovieDetailViewController.swift @@ -222,18 +222,35 @@ class MovieDetailViewController: UIViewController { else { return } titleLabel.text = movie.title + titleLabel.accessibilityTraits.insert(.header) + releaseDateAndRuntimeLabel.text = movie.formattedReleaseDate + " ∙ " + movie.formattedRuntime + releaseDateAndRuntimeLabel.accessibilityLabel = movie.accessibilityFormattedReleaseDate + + ", " + + movie.formattedRuntime if !movie.formattedGenres.isEmpty { genreLabel.isHidden = false genreLabel.text = movie.formattedGenres + genreLabel.accessibilityLabel = String.genreAccessibilityLabel + genreLabel.accessibilityValue = movie.formattedGenres } else { genreLabel.isHidden = true } + moreInformationStackView.isAccessibilityElement = true + moreInformationStackView.accessibilityTraits.insert(.link) + moreInformationStackView.accessibilityLabel = [ + moreInformationButton.accessibilityLabel, + buttonInfoLabel.text + ] + .compactMap { $0 } + .joined(separator: " ") + votingLabel.text = movie.formattedVoteAverage + votingLabel.accessibilityLabel = String.votingAccessibilityLabel(for: movie.formattedVoteAverage) descriptionTextView.text = movie.overview posterImageView.loadingImage(from: movie.posterPath, in: .original) } diff --git a/Cineaste/ViewController/Movies/SeenMovieCell.swift b/Cineaste/ViewController/Movies/SeenMovieCell.swift index 64dc2a92..a76fc130 100644 --- a/Cineaste/ViewController/Movies/SeenMovieCell.swift +++ b/Cineaste/ViewController/Movies/SeenMovieCell.swift @@ -43,11 +43,12 @@ class SeenMovieCell: UITableViewCell { private func applyAccessibility(for movie: Movie) { isAccessibilityElement = true + accessibilityTraits.insert(.button) accessibilityLabel = movie.title if let watchedDate = movie.formattedWatchedDate { - accessibilityLabel?.append(", \(watchedDate)") + accessibilityValue = watchedDate } } diff --git a/Cineaste/ViewController/Movies/WatchlistMovieCell.swift b/Cineaste/ViewController/Movies/WatchlistMovieCell.swift index 87077b3e..d1e5ea1a 100644 --- a/Cineaste/ViewController/Movies/WatchlistMovieCell.swift +++ b/Cineaste/ViewController/Movies/WatchlistMovieCell.swift @@ -52,13 +52,19 @@ class WatchlistMovieCell: UITableViewCell { private func applyAccessibility(for movie: Movie) { isAccessibilityElement = true + accessibilityTraits.insert(.button) accessibilityLabel = movie.title - let voting = String.voting(for: movie.formattedVoteAverage) - accessibilityLabel?.append(", \(voting)") - accessibilityLabel?.append(", \(movie.formattedRelativeReleaseInformation)") - accessibilityLabel?.append(", \(movie.formattedRuntime)") + let value = [ + String.votingAccessibilityLabel(for: movie.formattedVoteAverage), + movie.accessibilityFormattedRelativeReleaseInformation, + movie.formattedRuntime + ] + .compactMap { $0 } + .filter { !$0.isEmpty } + .joined(separator: ", ") + accessibilityValue = value } private func updatePosterWidthIfNeeded() { diff --git a/Cineaste/ViewController/SearchMovies/SearchMoviesCell.swift b/Cineaste/ViewController/SearchMovies/SearchMoviesCell.swift index 990e3fff..4a1618af 100644 --- a/Cineaste/ViewController/SearchMovies/SearchMoviesCell.swift +++ b/Cineaste/ViewController/SearchMovies/SearchMoviesCell.swift @@ -68,23 +68,24 @@ class SearchMoviesCell: UITableViewCell { private func applyAccessibility(for movie: Movie) { isAccessibilityElement = true + accessibilityTraits.insert(.button) accessibilityLabel = movie.title - if let state = String.state(for: movie.currentWatchState) { - accessibilityLabel?.append(", \(state)") - } - - let voting = String.voting(for: movie.formattedVoteAverage) - accessibilityLabel?.append(", \(voting)") - let isSoonAvailable = !soonHint.isHidden - accessibilityLabel?.append( + let value = [ + String.state(for: movie.currentWatchState), + String.votingAccessibilityLabel(for: movie.formattedVoteAverage), isSoonAvailable - ? ", \(String.soonReleaseInformationLong)" - : "" - ) - accessibilityLabel?.append(", \(movie.formattedRelativeReleaseInformation)") + ? String.soonReleaseInformationLong + : "", + movie.accessibilityFormattedRelativeReleaseInformation + ] + .compactMap { $0 } + .filter { !$0.isEmpty } + .joined(separator: ", ") + + accessibilityValue = value } private func updatePosterWidthIfNeeded() { diff --git a/Cineaste/ViewController/Settings/SettingsCell.swift b/Cineaste/ViewController/Settings/SettingsCell.swift index a1a5c1bb..f0230bd8 100644 --- a/Cineaste/ViewController/Settings/SettingsCell.swift +++ b/Cineaste/ViewController/Settings/SettingsCell.swift @@ -28,6 +28,7 @@ class SettingsCell: UITableViewCell { } else { descriptionLabel.isHidden = true } + accessibilityTraits.insert(.button) } } diff --git a/Cineaste/de.lproj/Localizable.strings b/Cineaste/de.lproj/Localizable.strings index f10015ab..54b71c88 100644 Binary files a/Cineaste/de.lproj/Localizable.strings and b/Cineaste/de.lproj/Localizable.strings differ diff --git a/Cineaste/en.lproj/Localizable.strings b/Cineaste/en.lproj/Localizable.strings index 6325e376..f7fb1e0d 100644 Binary files a/Cineaste/en.lproj/Localizable.strings and b/Cineaste/en.lproj/Localizable.strings differ