From 03a568cfdc918b13e58dd51bedf7365c7bd30961 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Tue, 29 Dec 2020 16:52:26 -0500 Subject: [PATCH 1/8] Add a11y labels/hints to post list and settings --- Shared/PostList/PostListView.swift | 69 +++++++++++-------- .../xcschemes/xcschememanagement.plist | 4 +- iOS/PostEditor/RemoteChangePromptView.swift | 8 +++ iOS/Settings/SettingsHeaderView.swift | 2 + 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index e20651a..0f9e371 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -20,33 +20,44 @@ struct PostListView: View { ) .toolbar { ToolbarItem(placement: .primaryAction) { - Button(action: { - let managedPost = WFAPost(context: self.managedObjectContext) - managedPost.createdDate = Date() - managedPost.title = "" - managedPost.body = "" - managedPost.status = PostStatus.local.rawValue - managedPost.collectionAlias = nil - switch model.preferences.font { - case 1: - managedPost.appearance = "sans" - case 2: - managedPost.appearance = "wrap" - default: - managedPost.appearance = "serif" - } - if let languageCode = Locale.current.languageCode { - managedPost.language = languageCode - managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft - } - withAnimation { - self.selectedCollection = nil - self.showAllPosts = false - self.model.selectedPost = managedPost - } - }, label: { - Image(systemName: "square.and.pencil") - }) + // We have to add a Spacer as a sibling view to the Button in some kind of Stack, so that any a11y + // modifiers are applied as expected: bug report filed as FB8956392. + ZStack { + Spacer() + Button(action: { + let managedPost = WFAPost(context: self.managedObjectContext) + managedPost.createdDate = Date() + managedPost.title = "" + managedPost.body = "" + managedPost.status = PostStatus.local.rawValue + managedPost.collectionAlias = nil + switch model.preferences.font { + case 1: + managedPost.appearance = "sans" + case 2: + managedPost.appearance = "wrap" + default: + managedPost.appearance = "serif" + } + if let languageCode = Locale.current.languageCode { + managedPost.language = languageCode + managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft + } + withAnimation { + self.selectedCollection = nil + self.showAllPosts = false + self.model.selectedPost = managedPost + } + }, label: { + Image(systemName: "square.and.pencil") + .scaleEffect(1.25) // These modifiers compensate for the resizing + .padding(.vertical, 12) // done to the Image (and the button tap target) + .padding(.leading, 12) // by the SwiftUI layout system from adding a + .padding(.trailing, 8) // Spacer in this ZStack (FB8956392). + }) + .accessibilityLabel(Text("Compose")) + .accessibilityHint(Text("Compose a new local draft")) + } } ToolbarItem(placement: .bottomBar) { HStack { @@ -55,6 +66,8 @@ struct PostListView: View { }, label: { Image(systemName: "gear") }) + .accessibilityLabel(Text("Settings")) + .accessibilityHint(Text("Open the Settings sheet")) Spacer() Text(postCount == 1 ? "\(postCount) post" : "\(postCount) posts") .foregroundColor(.secondary) @@ -70,6 +83,8 @@ struct PostListView: View { }, label: { Image(systemName: "arrow.clockwise") }) + .accessibilityLabel(Text("Refresh Posts")) + .accessibilityHint(Text("Fetch changes from the server")) .disabled(!model.account.isLoggedIn) } } diff --git a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist index 2723ebe..6cd8075 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_ orderHint - 0 + 1 WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_ orderHint - 1 + 0 diff --git a/iOS/PostEditor/RemoteChangePromptView.swift b/iOS/PostEditor/RemoteChangePromptView.swift index 0807155..184d6b3 100644 --- a/iOS/PostEditor/RemoteChangePromptView.swift +++ b/iOS/PostEditor/RemoteChangePromptView.swift @@ -9,6 +9,8 @@ struct RemoteChangePromptView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass @State private var promptText: String = "This is placeholder prompt text. Replace it?" @State private var promptIcon: Image = Image(systemName: "questionmark.square.dashed") + @State private var accessibilityLabel: String = "Replace" + @State private var accessibilityHint: String = "Replace this text with an accessibility hint" @State var remoteChangeType: RemotePostChangeType @State var buttonHandler: () -> Void @@ -18,6 +20,8 @@ struct RemoteChangePromptView: View { .font(horizontalSizeClass == .compact ? .caption : .body) .foregroundColor(.secondary) Button(action: buttonHandler, label: { promptIcon }) + .accessibilityLabel(Text(accessibilityLabel)) + .accessibilityHint(Text(accessibilityHint)) } .padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)) .background(Color(UIColor.secondarySystemBackground)) @@ -28,9 +32,13 @@ struct RemoteChangePromptView: View { case .remoteCopyUpdated: promptText = "Newer copy on server. Replace local copy?" promptIcon = Image(systemName: "square.and.arrow.down") + accessibilityLabel = "Update post" + accessibilityHint = "Replace this post with the server version" case .remoteCopyDeleted: promptText = "Post deleted from server. Delete local copy?" promptIcon = Image(systemName: "trash") + accessibilityLabel = "Delete" + accessibilityHint = "Delete this post from your device" } }) } diff --git a/iOS/Settings/SettingsHeaderView.swift b/iOS/Settings/SettingsHeaderView.swift index ca65578..090040b 100644 --- a/iOS/Settings/SettingsHeaderView.swift +++ b/iOS/Settings/SettingsHeaderView.swift @@ -15,6 +15,8 @@ struct SettingsHeaderView: View { }, label: { Image(systemName: "xmark.circle") }) + .accessibilityLabel(Text("Close")) + .accessibilityHint(Text("Dismiss the Settings sheet")) } Text("WriteFreely v\(Bundle.main.appMarketingVersion) (build \(Bundle.main.appBuildVersion))") .font(.caption) From 555b98828290f4ac2d691584d59226b486b50c45 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Tue, 29 Dec 2020 16:53:48 -0500 Subject: [PATCH 2/8] Add a11y label/hint to remote-change prompt on macOS --- Shared/PostEditor/PostEditorStatusToolbarView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Shared/PostEditor/PostEditorStatusToolbarView.swift b/Shared/PostEditor/PostEditorStatusToolbarView.swift index 7d8cac4..cc02858 100644 --- a/Shared/PostEditor/PostEditorStatusToolbarView.swift +++ b/Shared/PostEditor/PostEditorStatusToolbarView.swift @@ -20,6 +20,8 @@ struct PostEditorStatusToolbarView: View { }, label: { Image(systemName: "square.and.arrow.down") }) + .accessibilityLabel(Text("Update post")) + .accessibilityHint(Text("Replace this post with the server version")) } .padding(.horizontal) .background(Color.primary.opacity(0.1)) @@ -45,6 +47,8 @@ struct PostEditorStatusToolbarView: View { }, label: { Image(systemName: "trash") }) + .accessibilityLabel(Text("Delete")) + .accessibilityHint(Text("Delete this post from your Mac")) } .padding(.horizontal) .background(Color.primary.opacity(0.1)) From f9cce6c9016fc040ed28757d5c56d1e432c89654 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 30 Dec 2020 09:24:04 -0500 Subject: [PATCH 3/8] Fix button-image sizing and tap target/a11y frame --- Shared/PostList/PostListView.swift | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 0f9e371..6f7d4fa 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -49,11 +49,17 @@ struct PostListView: View { self.model.selectedPost = managedPost } }, label: { - Image(systemName: "square.and.pencil") - .scaleEffect(1.25) // These modifiers compensate for the resizing - .padding(.vertical, 12) // done to the Image (and the button tap target) - .padding(.leading, 12) // by the SwiftUI layout system from adding a - .padding(.trailing, 8) // Spacer in this ZStack (FB8956392). + ZStack { + Image("does.not.exist") + .accessibilityHidden(true) + Image(systemName: "square.and.pencil") + .accessibilityHidden(true) + .imageScale(.large) // These modifiers compensate for the resizing + .padding(.vertical, 12) // done to the Image (and the button tap target) + .padding(.leading, 12) // by the SwiftUI layout system from adding a + .padding(.trailing, 8) // Spacer in this ZStack (FB8956392). + } + .frame(maxWidth: .infinity, maxHeight: .infinity) }) .accessibilityLabel(Text("Compose")) .accessibilityHint(Text("Compose a new local draft")) @@ -65,6 +71,10 @@ struct PostListView: View { model.isPresentingSettingsView = true }, label: { Image(systemName: "gear") + .imageScale(.large) + .padding(.vertical, 12) + .padding(.leading, 8) + .padding(.trailing, 12) }) .accessibilityLabel(Text("Settings")) .accessibilityHint(Text("Open the Settings sheet")) @@ -82,6 +92,10 @@ struct PostListView: View { } }, label: { Image(systemName: "arrow.clockwise") + .imageScale(.large) + .padding(.vertical, 12) + .padding(.leading, 12) + .padding(.trailing, 8) }) .accessibilityLabel(Text("Refresh Posts")) .accessibilityHint(Text("Fetch changes from the server")) From 7d99c8afc46ab211903342fc6b0d570772000cd2 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 30 Dec 2020 10:03:53 -0500 Subject: [PATCH 4/8] =?UTF-8?q?Add=20=E2=80=9Cdoes.not.exist=E2=80=9D=201x?= =?UTF-8?q?1=20transparent=20image?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is to silence runtime warnings about missing images for the workaround used to add a11y labels/hints to toolbar buttons. --- .../does.not.exist.imageset/Contents.json | 23 ++++++++++++++++++ .../does.not.exist.png | Bin 0 -> 501 bytes .../does.not.exist@2x.png | Bin 0 -> 501 bytes .../does.not.exist@3x.png | Bin 0 -> 502 bytes 4 files changed, 23 insertions(+) create mode 100644 Shared/Assets.xcassets/does.not.exist.imageset/Contents.json create mode 100644 Shared/Assets.xcassets/does.not.exist.imageset/does.not.exist.png create mode 100644 Shared/Assets.xcassets/does.not.exist.imageset/does.not.exist@2x.png create mode 100644 Shared/Assets.xcassets/does.not.exist.imageset/does.not.exist@3x.png diff --git a/Shared/Assets.xcassets/does.not.exist.imageset/Contents.json b/Shared/Assets.xcassets/does.not.exist.imageset/Contents.json new file mode 100644 index 0000000..f89ec02 --- /dev/null +++ b/Shared/Assets.xcassets/does.not.exist.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "does.not.exist.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "does.not.exist@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "does.not.exist@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/does.not.exist.imageset/does.not.exist.png b/Shared/Assets.xcassets/does.not.exist.imageset/does.not.exist.png new file mode 100644 index 0000000000000000000000000000000000000000..6238655077803c0c0c18c3579a05886d62c448c8 GIT binary patch literal 501 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|%|szQ_Zq#7t-BfNW=X=lq=fqTqtWt|u+~aJ75A%WQ+Iiy78Ux$-DQ;V*kf z@MP{TF9`{W;D(&1pC&O0>}Eb8rpaW*bz&ZK(sJh;(hhG_IDT_P_0qtBFy8Q z2g|I3KbTKk^K=sOcTaMD%gUHH{mi4LIY+!&8IqaSsWMMl5;mnF-QnR;FB4M{Q*COU(Ktg>x3}@(O0fp7k>jo-Xb-^GtJkR zK?}&{U|?*|WMF~Di4>4#U|?Fn1Q%gkz>HvnByYXie*{Qzd%8G=aL6VnFfcJNure_A S?wOO|GKyppK57poIDp2l!g{@FekYb)G!R z+Q-Of=b*}CASGI}``7`FMf$Is&RD6MZuQ(6ci?)`vJY3g*SpL%xVo5O-IObjQWXBO zcLY!7?(&k5kO*$bdHQJ*lfZ7~6JnZ7R$M3MF()l|z9H@KMup=yM})7(1mg*{%qPM; z&UvuRI{1V6#5GSRA%FKI=eMkkdDG84YMOJztCb;{X`L$blqF$P8Zw^$X*#v&%$FV^ zR`&#rrCJrwRA0Z;kdTy^Wo*!JNM_NmVg(Bw$)o2Kj?8**Y;fb?O%wui|m2PtC zY;4!6|NpNK0|wOmGY3ADANv1)zxdz(|H};nKFhMTF;_B(DyF_OJ$~^wFyt*#BRtc5 zeHpZXYz_v-_DlvAAd3-*rGOY1GYgpDB1{XI5p0m;xeN0q04Z)y7sn6|+2n)+K#nj2 UqsY;!jX)NIr>mdKI;Vst0Cxkh)&Kwi literal 0 HcmV?d00001 diff --git a/Shared/Assets.xcassets/does.not.exist.imageset/does.not.exist@3x.png b/Shared/Assets.xcassets/does.not.exist.imageset/does.not.exist@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..364bf5d1d1ad835f04800398d1c9d122a02fc8c2 GIT binary patch literal 502 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc#=yX+sNbpyq!KfooddF+-JSDu@{58C5|dM1 zGK&jx5-S6Y85lGsCMP5WdFp7KNK8yfIB`~6ldGsasH3AND53tu0lt?S4*p<1am~|7$lpE5`7JAB-t;q%n&uqwYGp`fTBpi9Wl7kShK%Qbnocb`^QA|K z)jdIDsaC}^)z>dIBqSwf85=Yll3DbtSiwR^^5{8*BeUKc8{ByJ9|-bVW?ZjhYwLBG z8)Ge)Bqhef({zW2N4-o;MNGA+bw=Y1P2JwchnI`k*zS3-u|4E=u)TOR^O##grJI~O z8{4(&|Nra5fB`lC%z+Q(hyMTHFaG!c|8m2C&$4W7%#{qHimC5Rk6-)^40(&x2+uTM zUj{88n}dO|J(Gb2$YKOyDIjKGU|PTg7hzt&j9`N#OT=oDffSFYi(?3fY;uBv7?7vM U!1&>a{0or0r>mdKI;Vst0KP@96#xJL literal 0 HcmV?d00001 From 608c66e54301d7282800ffe0b47cae8c56af076a Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 30 Dec 2020 10:19:25 -0500 Subject: [PATCH 5/8] Add a11y labels/hints to post editor textviews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s impossible to differentiate these textviews because the placeholder text is not read by VoiceOver, so we need to add labels. --- .../angelo.xcuserdatad/xcschemes/xcschememanagement.plist | 4 ++-- iOS/PostEditor/PostTextEditingView.swift | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist index 6cd8075..2723ebe 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_ orderHint - 1 + 0 WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_ orderHint - 0 + 1 diff --git a/iOS/PostEditor/PostTextEditingView.swift b/iOS/PostEditor/PostTextEditingView.swift index a58fbae..18b70fb 100644 --- a/iOS/PostEditor/PostTextEditingView.swift +++ b/iOS/PostEditor/PostTextEditingView.swift @@ -49,6 +49,8 @@ struct PostTextEditingView: View { isFirstResponder: $titleIsFirstResponder, lineSpacing: horizontalSizeClass == .compact ? lineSpacingMultiplier / 2 : lineSpacingMultiplier ) + .accessibilityLabel(Text("Title (optional)")) + .accessibilityHint(Text("Add or edit the title for your post; use the Return key to skip to the body")) .frame(height: titleFieldHeight) .onChange(of: post.title) { _ in if post.status == PostStatus.published.rawValue && !updatingTitleFromServer { @@ -73,6 +75,8 @@ struct PostTextEditingView: View { isFirstResponder: $bodyIsFirstResponder, lineSpacing: horizontalSizeClass == .compact ? lineSpacingMultiplier / 2 : lineSpacingMultiplier ) + .accessibilityLabel(Text("Body")) + .accessibilityHint(Text("Add or edit the body of your post")) .onChange(of: post.body) { _ in if post.status == PostStatus.published.rawValue && !updatingBodyFromServer { post.status = PostStatus.edited.rawValue From 69cd86c1da81f168672ead3b85a29a3f079f40a1 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 30 Dec 2020 10:34:54 -0500 Subject: [PATCH 6/8] Prevent VO from reading the placeholder-text views --- iOS/PostEditor/PostTextEditingView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOS/PostEditor/PostTextEditingView.swift b/iOS/PostEditor/PostTextEditingView.swift index 18b70fb..51f5ed2 100644 --- a/iOS/PostEditor/PostTextEditingView.swift +++ b/iOS/PostEditor/PostTextEditingView.swift @@ -41,6 +41,7 @@ struct PostTextEditingView: View { .foregroundColor(Color(UIColor.placeholderText)) .padding(.horizontal, 4) .padding(.vertical, 8) + .accessibilityHidden(true) } PostTitleTextView( text: $post.title, @@ -68,6 +69,7 @@ struct PostTextEditingView: View { .foregroundColor(Color(UIColor.placeholderText)) .padding(.horizontal, 4) .padding(.vertical, 8) + .accessibilityHidden(true) } PostBodyTextView( text: $post.body, From 34c2cd181e7af597dc465a1f3c4cd1ed5b67e750 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 30 Dec 2020 10:39:48 -0500 Subject: [PATCH 7/8] Add a11y features to post editor context menu Adding an .onTapGesture modifier here to dismiss the keyboard removes focus from whatever textview is active in the post editor, which prevents VoiceOver from getting confused about which accessibility frame should be read. --- iOS/PostEditor/PostEditorView.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/iOS/PostEditor/PostEditorView.swift b/iOS/PostEditor/PostEditorView.swift index e5dfbd7..3421fc4 100644 --- a/iOS/PostEditor/PostEditorView.swift +++ b/iOS/PostEditor/PostEditorView.swift @@ -117,8 +117,19 @@ struct PostEditorView: View { } } }, label: { - Image(systemName: "ellipsis.circle") + ZStack { + Image("does.not.exist") + .accessibilityHidden(true) + Image(systemName: "ellipsis.circle") + .imageScale(.large) + .accessibilityHidden(true) + } }) + .accessibilityLabel(Text("Menu")) + .accessibilityHint(Text("Opens a context menu to publish, share, or move the post")) + .onTapGesture { + hideKeyboard() + } } } } From e68030ec717d19daf770c552fb2b403506f5005a Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 30 Dec 2020 14:49:07 -0500 Subject: [PATCH 8/8] Improve a11y labels on post editor menu --- iOS/PostEditor/PostEditorView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iOS/PostEditor/PostEditorView.swift b/iOS/PostEditor/PostEditorView.swift index 3421fc4..d866cc7 100644 --- a/iOS/PostEditor/PostEditorView.swift +++ b/iOS/PostEditor/PostEditorView.swift @@ -80,6 +80,8 @@ struct PostEditorView: View { }, label: { Label("Publish…", systemImage: "paperplane") }) + .accessibilityHint(Text("Choose the blog you want to publish this post to")) + .disabled(post.body.count == 0) } else { Button(action: { if model.account.isLoggedIn { @@ -97,6 +99,7 @@ struct PostEditorView: View { }, label: { Label("Share", systemImage: "square.and.arrow.up") }) + .accessibilityHint(Text("Open the system share sheet to share a link to this post")) .disabled(post.postId == nil) // Button(action: { // print("Tapped 'Delete...' button") @@ -130,6 +133,7 @@ struct PostEditorView: View { .onTapGesture { hideKeyboard() } + .disabled(post.body.count == 0) } } }