Accessibility
- 关于SwiftUI的Accessibility
- 如何将DynamicType与自定义Font一起使用
- 如何指定视图支持的动态类型的大小
- [如何检测"reduce motion"的关联功能设置](#如何检测“reduce motion”的关联功能设置 "#View4")
- 如何检测深色模式
- 如何使用装饰图像来减少屏幕阅读器的混乱
- 如何根据需求减少动画
- 如何让VoiceOver单独朗读字符
概述
文章主要分享SwiftUI Modifier的学习过程,将使用案例的方式进行说明。内容浅显易懂,Accessibility未调试,不过测试代码是齐全的。如果想要运行结果,可以移步Github下载code -> github案例链接
1、关于SwiftUI的Accessibility
默认情况下,SwiftUI应用程序具有非常高的可访问性,除非主动更改默认设置。这种行为的关键是SwiftUI布局是基于Stack的方法。将视图放在Hstack和VStack中,一切都有默认的顺序,因此系统可以大致理解布局应该如何设定。相比之下,UIKit和AutoLayout可以将视图放在任何地方,因此系统必须有效的对视图应该如何排序做出最佳猜测。
SwiftUI还建议我们为所有的交互添加标签,明确用途。但是,我们可以屏蔽标签,以及它们被隐藏,系统仍然会使用它们作为屏幕阅读器的音频提示。因此,SwiftUI提供了很多的可访问性。然而,还有额外的工具来更好的体验来解决下面的问题:
- 屏幕阅读器应该如何阅读内容?
- 是否所有内容都需要阅读?
- 如果用户不喜欢复杂的动画怎么办?
- ...
2、如何将DynamicType与自定义Font一起使用
如果在iOS14或更高的版本,就可以实现自动移Font的自动缩放。但是,如果需要字体相对于特定动态类型进行缩放,则使用relativeTo
修饰符。
Swift
struct FFFontDynamicType: View {
var body: some View {
//使字体从24pt开始,但它会相对于Headline DynamicType字体进行缩放。
Text("meta BBLv")
.font(.custom("Georgia", size: 24, relativeTo: .headline))
//如果想禁用字体的动态类型,使用fixedSize修饰符字体大小,无论动态类型如何设置都不会影响字体大小
Text("metabblv@163.com")
.font(.custom("Georgia", fixedSize: 24))
}
}
3、如何指定视图支持的动态类型的大小
SwiftUI对动态类型的自动支持意味着视图可以根据用户的偏好放大或缩小。但是,一些情况下会超出屏幕导致UI的不一致,可以使用dyanmicTypeSize()
来限制。
Swift
struct FFSpecifyDynamicType: View {
var body: some View {
VStack {
//与固定值一起使用,意味着视图将忽略所有动态类型的大小
Text("This will stay small")
.dynamicTypeSize(.xxLarge)
//可以指定范围
Text("This won't go above large")
.dynamicTypeSize(...DynamicTypeSize.large)
Text("This will scale within a range")
.dynamicTypeSize(DynamicTypeSize.large...DynamicTypeSize.xxxLarge)
Text("This will scale to any Size")
//许多用户喜欢以来较大的动态字体来使用App。但是,随着屏幕内信息量的增加,
//在某些特定的场景下要限制这种特性。
}
}
}
4、如何检测"reduce motion"的关联功能设置
随着iOS的发展,越来越多的用户对画面更加敏感、更挑剔,尤其是那些大型或复杂的动画。因此,iOS有一个名为"reduce motion"
的内置辅助功能设置,应用程序可以读取该设置并根据需要做出响应。现在由需求来决定"reduce motion"的具体含义:
- 删除动画?
- 还是将动画更改为不那么强烈?
- 是否应该保留一些重要的动画并删除那些视觉吸引力的动画?
例如,如果希望为大多数用户提供弹性动画,但对于想要reduce motion的用户根本不需要动画,如何设定。
Swift
struct FFAccessibilityReduceMotion: View {
//在SwiftUI中,此设置作为环境的Bool值公布,将其属性添加到视图中
@Environment(\.accessibilityReduceMotion) var reduceMotion
@State private var scale = 1.0
var body: some View {
VStack {
Spacer()
Circle()
.frame(width: 20, height: 20)
.scaleEffect(scale)
.animation(reduceMotion ? nil : .spring(response: 1, dampingFraction: 0.1), value: scale)
Spacer()
Button("Increase scale") {
scale *= 1.5
}
}
}
}
这会创建一个小圆圈,每次按下按钮时都会通过弹簧动画将其放大。但如果用户启用"reduce motion",动画将被完全删除。
5、如何检测深色模式
SwiftUI可以使用环境检测键(colorScheme)
检测当前是否启用了深色模式或浅色模式。如果使用了@environment声明了此属性,就可以在视图中引用它,并且当配色方案更改时将自动加载。
Swift
struct FFAccessibilityDarkMode: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
Text(colorScheme == .dark ? "META BBLV in dark mode" : "META BBLV in light mode")
}
}
6、如何使用装饰图像来减少屏幕阅读器的混乱
SwiftUI会自动使用图像名称作为屏幕阅读器标签,通常情况下会很有用。然而,有些图像并不适合阅读,因为它只是装饰性的。它们不会传达屏幕其他地方没有的信息,只是为了让用户界面看起来更好看。
Image("star")这种方式创建的图像,则会将她们作为标准UI的一部分读出。更好的方式是使用Image(decorative:)
初始化程序来创建它们,它告诉SwiftUI图像不应该暴露给屏幕阅读去器。
Swift
struct FFAccessibilityDecorative: View {
var body: some View {
Image(decorative: "star")
//通过这种方式构建的UI,使用VoiceOver检查时,屏幕阅读器是不会读出此标签的。
}
}
7、如何根据需求减少动画
SwiftUI的withAnimation()
修饰符可以轻松的在视图上执行自定义动画,但它不遵守"reduce mode"辅助功能的设置。因此对于有特定需求的人来讲,可能不符合需求了。
Swift
struct FFAccessibilityReduceModeRequest: View {
@State private var scale = 1.0
var body: some View {
Text("Meta BBLv")
.scaleEffect(scale)
.onTapGesture {
withOptionalAimation {
scale *= 1.5
}
}
}
//如果想要在withAnimation()同时遵守辅助功能的设置,添加一个全局函数可能是一个有效的解决方案
func withOptionalAimation<Result>(_ animation: Animation? = .default, _ body: () throws -> Result) rethrows -> Result {
if UIAccessibility.isReduceMotionEnabled {
return try body()
} else {
return try withAnimation(animation, body)
}
}
//在每次触发动画是,都会自动检查是否启用了"reduce mode",并为特别请求reducemode的用户来禁用它。
}
8、如何让VoiceOver单独朗读字符
大多数的文本都可以作为单词阅读,但某些特殊文本(例如密码,股票代码和某些特定的数字)必须通过VoiceOver诸葛字母的阅读才能发挥作用。在SwiftUI中,可以使用speechSpellsOutCharacters()
修饰符启用此功能。
Swift
struct FFAccessibilityVoiceOver: View {
var body: some View {
//当对整组元素启用辅助功能时,会有更好的结果
VStack {
Text("Your password is")
Text("abCayer-muQai")
.font(.title)
.speechSpellsOutCharacters()
}
.accessibilityElement(children: .combine)
//使用该代码,VoiceOver会自然的自动读出"你的密码是",然后根据要求拼代码部分。
}
}
调试意外
我目前的设备情况:
- macOS:Version 13.4.1 、 Version 14.0 beta 2
- Xcode:Version 15.0 beta 2 (15A5161b)
- Simulator iPhone 14 Pro:iOS 17.0 beta 2
- iPhone 14 Pro:iOS 17.0 beta 6
由于是Accessibility部分调试,涉及到调试中修改控制中心
设置问题,所以无法使用模拟器来调试,在真机调试中报出下面的问题:
terminal
dyld[5218]: Symbol not found: _$s21DeveloperToolsSupport7PreviewV7SwiftUIE_6traits4bodyACSSSg_AA0D5TraitVyAC10ViewTraitsOGdAD0J0_pyctcfC
Referenced from: <48CEF6E7-4F6C-3597-9EA3-182CF891DB38> /private/var/containers/Bundle/Application/CBD3CB7C-D7CE-472D-B4BC-46DC960A6B06/FFModifier.app/FFModifier
Expected in: <FE724F2F-E9CA-3711-BED5-5124FE98C7C0> /System/Library/Frameworks/SwiftUI.framework/SwiftUI
推测是由于我的iPhone版本与Xcode版本不一致导致的SwiftUI版本不一致问题,所以出现了"Symbol not found:"。没过于纠结,后面如果可以调试了,我会将图补充的。