Skip to content

Commit 1ceac73

Browse files
authored
Improve voice over labels and actions (#726)
* Improve voice over labels and navigation * Numerous label fixes, image removals, and actions * Fix UI-tests caused by adding accessibility actions which changes elements to buttons
1 parent 9557751 commit 1ceac73

23 files changed

+127
-4
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
55

66
### 🐞 Fixed
77
- Fix thread reply action shown when inside a Thread [#717](https://github.com/GetStream/stream-chat-swiftui/pull/717)
8+
- Improve voice over by adding missing labels, removing decorative images, and setting accessibility actions [#726](https://github.com/GetStream/stream-chat-swiftui/pull/726)
89
### 🔄 Changed
910
- Deprecate unused `ChatMessage.userDisplayInfo(from:)` which only accessed cached data [#718](https://github.com/GetStream/stream-chat-swiftui/pull/718)
11+
### 🎭 New Localizations
12+
Add localizable keys for supporting accessibility labels:
13+
- `channel.list.scroll-to-bottom.title`
14+
- `channel.header.info.title`
15+
- `message.attachment.accessibility-label`
16+
- `message.read-status.seen-by*`
17+
- `message.cell.sent-at`
18+
- `composer.picker.show-all`
19+
- `composer.audio-recording.*`
1020

1121
# [4.70.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.70.0)
1222
_January 15, 2025_

DemoAppSwiftUI/ChannelHeader/CustomChannelHeader.swift

+3
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@ public struct CustomChannelHeader: ToolbarContent {
3636
.background(colors.tintColor)
3737
.clipShape(Circle())
3838
}
39+
.accessibilityLabel(Text("New Channel"))
3940
}
4041
ToolbarItem(placement: .navigationBarLeading) {
4142
Button {
4243
actionsPopupShown = true
4344
} label: {
4445
StreamLazyImage(url: currentUserController.currentUser?.imageURL)
46+
.accessibilityLabel("Account Actions")
47+
.accessibilityAddTraits(.isButton)
4548
}
4649
}
4750
}

Sources/StreamChatSwiftUI/ChatChannel/ChannelHeader/ChatChannelHeaderViewModifier.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,23 @@ public struct DefaultChatChannelHeader: ToolbarContent {
7171
.clipShape(Circle())
7272
.offset(x: 8)
7373
}
74+
.accessibilityLabel(Text(L10n.Channel.Header.Info.title))
7475

7576
NavigationLink(isActive: $isActive) {
7677
LazyView(ChatChannelInfoView(channel: channel, shownFromMessageList: true))
7778
} label: {
7879
EmptyView()
7980
}
80-
81+
.accessibilityHidden(true)
82+
8183
ChannelAvatarView(
8284
avatar: headerImage,
8385
showOnlineIndicator: onlineIndicatorShown,
8486
size: CGSize(width: 36, height: 36)
8587
)
8688
.offset(x: 8)
8789
.allowsHitTesting(false)
90+
.accessibilityHidden(true)
8891
}
8992
.accessibilityIdentifier("ChannelAvatarView")
9093
}

Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoView.swift

+3
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ public struct ChatChannelInfoView: View, KeyboardReadable {
112112
.onTapGesture {
113113
viewModel.addUsersShown = false
114114
}
115+
.accessibilityAction {
116+
viewModel.addUsersShown = false
117+
}
115118
AddUsersView(
116119
loadedUserIds: viewModel.participants.map(\.id),
117120
onUserTap: viewModel.addUserTapped(_:)

Sources/StreamChatSwiftUI/ChatChannel/Composer/AttachmentPickerTypeView.swift

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public struct AttachmentPickerTypeView: View {
5656
pickerType: .media,
5757
selected: attachmentPickerType
5858
)
59+
.accessibilityLabel(Text(L10n.Composer.Picker.showAll))
5960
.accessibilityIdentifier("PickerTypeButtonMedia")
6061
}
6162

@@ -65,6 +66,7 @@ public struct AttachmentPickerTypeView: View {
6566
pickerType: .instantCommands,
6667
selected: attachmentPickerType
6768
)
69+
.accessibilityLabel(Text(L10n.Composer.Suggestions.Commands.header))
6870
.accessibilityIdentifier("PickerTypeButtonCommands")
6971
}
7072
case .collapsed:

Sources/StreamChatSwiftUI/ChatChannel/Composer/AttachmentPickerView.swift

+2
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ public struct AttachmentSourcePickerView: View {
159159
isSelected: selected == .files,
160160
onTap: onTap
161161
)
162+
.accessibilityLabel(L10n.Composer.Picker.file)
162163
.accessibilityIdentifier("attachmentPickerFiles")
163164

164165
AttachmentPickerButton(
@@ -176,6 +177,7 @@ public struct AttachmentSourcePickerView: View {
176177
isSelected: selected == .polls,
177178
onTap: onTap
178179
)
180+
.accessibilityLabel(L10n.Composer.Polls.createPoll)
179181
.accessibilityIdentifier("attachmentPickerPolls")
180182
}
181183

Sources/StreamChatSwiftUI/ChatChannel/Composer/SendMessageButton.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public struct SendMessageButton: View {
3131
)
3232
}
3333
.disabled(!enabled)
34-
.accessibilityAddTraits(.isButton)
34+
.accessibilityLabel(Text(L10n.Composer.Placeholder.message))
3535
.accessibilityIdentifier("SendMessageButton")
3636
}
3737

Sources/StreamChatSwiftUI/ChatChannel/Composer/TrailingComposerView.swift

+7
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,12 @@ public struct VoiceRecordingButton: View {
8585
}
8686
}
8787
)
88+
.accessibilityRemoveTraits(.isImage)
89+
.accessibilityAddTraits(.isButton)
90+
.accessibilityLabel(Text(L10n.Composer.AudioRecording.start))
91+
.accessibilityAction {
92+
viewModel.recordingState = .recording(.zero)
93+
viewModel.startRecording()
94+
}
8895
}
8996
}

Sources/StreamChatSwiftUI/ChatChannel/Composer/VoiceRecording/RecordingView.swift

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct RecordingView: View {
2020
HStack {
2121
Image(systemName: "mic")
2222
.foregroundColor(.red)
23+
.accessibilityHidden(true)
2324
RecordingDurationView(duration: audioRecordingInfo.duration)
2425

2526
Spacer()
@@ -30,6 +31,7 @@ struct RecordingView: View {
3031
}
3132
.foregroundColor(Color(colors.textLowEmphasis))
3233
.opacity(opacityForSlideToCancel)
34+
.accessibilityHidden(true)
3335

3436
Spacer()
3537

@@ -38,6 +40,7 @@ struct RecordingView: View {
3840
} label: {
3941
Image(systemName: "mic")
4042
}
43+
.accessibilityLabel(Text(L10n.Composer.AudioRecording.stop))
4144
}
4245
.padding(.all, 12)
4346
.overlay(

Sources/StreamChatSwiftUI/ChatChannel/MessageList/AsyncVoiceMessages/VoiceRecordingContainerView.swift

+1
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ struct VoiceRecordingView: View {
215215
.resizable()
216216
.aspectRatio(contentMode: .fit)
217217
.frame(height: 40)
218+
.accessibilityHidden(true)
218219
}
219220
}
220221
.onReceive(handler.$context, perform: { value in

Sources/StreamChatSwiftUI/ChatChannel/MessageList/BottomReactionsView.swift

+3
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ struct BottomReactionsView: View {
9595
.onLongPressGesture {
9696
onLongPress()
9797
}
98+
.accessibilityAction {
99+
viewModel.reactionTapped(reaction)
100+
}
98101
}
99102
}
100103

Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentView.swift

+5
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ public struct FileAttachmentView: View {
9797
.onTapGesture {
9898
fullScreenShown = true
9999
}
100+
.accessibilityAction {
101+
fullScreenShown = true
102+
}
100103

101104
Spacer()
102105
}
@@ -134,6 +137,7 @@ public struct FileAttachmentDisplayView: View {
134137
.resizable()
135138
.aspectRatio(contentMode: .fit)
136139
.frame(width: 34, height: 40)
140+
.accessibilityHidden(true)
137141
VStack(alignment: .leading, spacing: 8) {
138142
Text(title)
139143
.font(fonts.bodyBold)
@@ -146,6 +150,7 @@ public struct FileAttachmentDisplayView: View {
146150
}
147151
Spacer()
148152
}
153+
.accessibilityElement(children: .combine)
149154
}
150155

151156
private var previewImage: UIImage {

Sources/StreamChatSwiftUI/ChatChannel/MessageList/GiphyBadgeView.swift

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public struct GiphyBadgeView: View {
1414
BottomLeftView {
1515
HStack(spacing: 4) {
1616
Image(uiImage: images.commandGiphy)
17+
.accessibilityHidden(true)
1718
Text(L10n.Message.GiphyAttachment.title)
1819
.font(fonts.bodyBold)
1920
.foregroundColor(Color(colors.staticColorText))

Sources/StreamChatSwiftUI/ChatChannel/MessageList/ImageAttachmentView.swift

+7
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,11 @@ struct LazyLoadingImage: View {
371371
imageTapped(index ?? 0)
372372
}
373373
)
374+
.accessibilityLabel(L10n.Message.Attachment.accessibilityLabel((index ?? 0) + 1))
375+
.accessibilityAddTraits(source.type == .video ? .startsMediaSession : .isImage)
376+
.accessibilityAction {
377+
imageTapped(index ?? 0)
378+
}
374379
}
375380
} else if error != nil {
376381
Color(.secondarySystemBackground)
@@ -383,6 +388,7 @@ struct LazyLoadingImage: View {
383388

384389
if source.type == .video && width > 64 && source.uploadingState == nil {
385390
VideoPlayIcon()
391+
.accessibilityHidden(true)
386392
}
387393
}
388394
.onAppear {
@@ -414,6 +420,7 @@ struct LazyLoadingImage: View {
414420
.allowsHitTesting(false)
415421
.scaleEffect(1.0001) // Needed because of SwiftUI sometimes incorrectly displaying landscape images.
416422
.clipped()
423+
.accessibilityHidden(true)
417424
}
418425
}
419426

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift

+12-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public struct MessageAuthorAndDateView: View {
2626
}
2727
Spacer()
2828
}
29-
.accessibilityElement(children: .contain)
29+
.accessibilityElement(children: .combine)
3030
.accessibilityIdentifier("MessageAuthorAndDateView")
3131
}
3232
}
@@ -80,11 +80,16 @@ struct MessageDateView: View {
8080
return text
8181
}
8282

83+
var accessibilityLabel: String {
84+
L10n.Message.Cell.sentAt(text)
85+
}
86+
8387
var body: some View {
8488
Text(text)
8589
.font(fonts.footnote)
8690
.foregroundColor(Color(colors.textLowEmphasis))
8791
.animation(nil)
92+
.accessibilityLabel(Text(accessibilityLabel))
8893
.accessibilityIdentifier("MessageDateView")
8994
}
9095
}
@@ -119,6 +124,11 @@ public struct MessageReadIndicatorView: View {
119124
.customizable()
120125
.foregroundColor(!readUsers.isEmpty ? colors.tintColor : Color(colors.textLowEmphasis))
121126
.frame(height: 16)
127+
.accessibilityLabel(
128+
Text(
129+
readUsers.isEmpty ? L10n.Message.ReadStatus.seenByNoOne : L10n.Message.ReadStatus.seenByOthers
130+
)
131+
)
122132
.accessibilityIdentifier("readIndicatorCheckmark")
123133
}
124134
.accessibilityElement(children: .contain)
@@ -161,6 +171,7 @@ public struct MessagePinDetailsView: View {
161171
Image(uiImage: images.pin)
162172
.customizable()
163173
.frame(maxHeight: 12)
174+
.accessibilityHidden(true)
164175
Text("\(L10n.Message.Cell.pinnedBy) \(message.pinDetails?.pinnedBy.name ?? L10n.Message.Cell.unknownPin)")
165176
.font(fonts.footnote)
166177
}

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListView.swift

+2
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ public struct ScrollToBottomButton: View {
467467
.frame(width: buttonSize, height: buttonSize)
468468
.modifier(ShadowViewModifier(cornerRadius: buttonSize / 2))
469469
}
470+
.accessibilityLabel(Text(L10n.Channel.List.ScrollToBottom.title))
470471
.padding()
471472
.overlay(
472473
unreadCount > 0 ?
@@ -526,6 +527,7 @@ public struct DateIndicatorView: View {
526527
.padding(.all, 8)
527528
Spacer()
528529
}
530+
.accessibilityAddTraits(.isHeader)
529531
}
530532
}
531533

Sources/StreamChatSwiftUI/ChatChannel/MessageList/QuotedMessageView.swift

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ struct QuotedMessageViewContainer<Factory: ViewFactory>: View {
4747
.onTapGesture(perform: {
4848
scrolledId = quotedMessage.messageId
4949
})
50+
.accessibilityAction {
51+
scrolledId = quotedMessage.messageId
52+
}
5053
.accessibilityIdentifier("QuotedMessageViewContainer")
5154
}
5255
}

Sources/StreamChatSwiftUI/ChatChannel/MessageList/VideoAttachmentView.swift

+4
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ struct VideoAttachmentContentView: View {
155155
.scaledToFill()
156156
.clipped()
157157
.allowsHitTesting(false)
158+
.accessibilityHidden(true)
158159

159160
if width > 64 && attachment.uploadingState == nil {
160161
VStack {
@@ -166,6 +167,9 @@ struct VideoAttachmentContentView: View {
166167
.onTapGesture {
167168
fullScreenShown = true
168169
}
170+
.accessibilityAction {
171+
fullScreenShown = true
172+
}
169173
}
170174
} else if error != nil {
171175
Color(.secondarySystemBackground)

Sources/StreamChatSwiftUI/ChatChannel/Reactions/ReactionsView.swift

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ struct ReactionsContainer: View {
2828
.onLongPressGesture {
2929
onLongPressGesture()
3030
}
31+
.accessibilityAction {
32+
onTapGesture()
33+
}
3134
}
3235

3336
Spacer()

Sources/StreamChatSwiftUI/ChatChannelList/MoreChannelActionsView.swift

+2
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,14 @@ public struct ChannelMemberView: View {
164164
showOnlineIndicator: onlineIndicatorShown,
165165
size: memberSize
166166
)
167+
.accessibilityHidden(true)
167168

168169
Text(name)
169170
.font(fonts.footnoteBold)
170171
.multilineTextAlignment(.center)
171172
.lineLimit(2)
172173
.frame(maxWidth: memberSize.width, maxHeight: 34, alignment: .top)
174+
.accessibilityLabel(Text(name) + Text(onlineIndicatorShown ? ", \(L10n.Message.Title.online)" : ""))
173175
}
174176
}
175177
}

0 commit comments

Comments
 (0)