如果您正在为大众市场开发产品,那么最有可能视力障碍的人会使用它。 如果您努力创建用户友好的界面,则需要为所有客户(包括视力不佳的人)方便地进行操作。 我想我们经常会忘记它。 现在该修复它了。

我在App Store的搜索中输入了“比萨饼配送”查询,下载了前24个应用程序,并检查了其中哪个为弱视人群提供了界面。
2之24 。 而且似乎是其中之一是偶然发生的:随着字体大小的增加,整个界面“浮动”,并且变得更加难以使用。 真伤心
每月有55万人使用Dodo Pizza iOS应用程序。 即使我们有1%的用户使用了放大字体,也有5500人对使用我们的应用程序感到不舒服。 我们将纠正它。
添加动态类型支持
- 我们使用动态系统文本样式,而不是静态样式。
- (可选)在情节提要中的标签上启用“自动调整字体”复选标记。 或者,如果按钮中的标签或通过代码创建,我们将敲击其参数
adjustsFontForContentSizeCategory
。 - 我们教界面扩展到不同的字体大小:
-我们会尽可能使用自动计算像元大小的方法。
-在无法执行的地方-获取当前的字体大小设置,并响应traitCollectionDidChange
方法中的更改。 - 我们得到一个无法使用的界面。

我们更改界面以便可以使用
我们回滚并开始考虑如何做好所有事情。
正确使用菜单中的位置
现在,比萨图片下面有很多空白空间。 让我们尝试在名称上方放置图片:这样,它将变大,并且空白将消失。 为此,我们将图像和
UIStackView
容器与其他所有东西包装在
UIStackView
,然后在
UIStackView
时切换堆栈视图的方向。

我们在菜单项之间没有分隔符,这就是为什么当尺寸较大时,单元格开始“粘在一起”并且比萨饼的价格标签太接近下一个比萨饼的图片的原因。 让我们尝试添加一个分隔符。

不是那样 首先,它看起来马马虎虎。 其次,对于视力低下的人来说很难看到。 即使从灰色重新涂成黑色。
我们将其删除,只是尝试增加单元之间的Internet。

那现在
小计:使用更多的空间,眼睛之间的跳动更少,阅读变得更容易。
我们改善拉伸和去除
现在,您可以增加“添加到购物车”按钮的宽度,否则它会浮动到左侧并且不在您的手指下,尽管右侧有很多空白空间。 当然,您可以将其简单地移到右侧,但是这样对于惯用左手的人来说将是不便的,总的来说,比起未压缩的手势,我们要好得多。 而且我们将使笔画更胖,现在它与字体不一致。

我看了所有这些,并了解到披萨照片当然非常大。 让我们尝试隐藏它,也许没有照片就可以住。

通常,没有照片的菜单并不会丢失很多信息,但是现在一个菜单项几乎总是会干扰iPhone 6S的屏幕。 但是它的吸引力降低了,滚动时Drool不会流动。 这样的 现在,让我们这样保留它,仔细考虑,然后稍后再返回图片。
不要忘记检查“直播”
现在分类。 总的来说,即使采用第一种方法,结果也是可以容忍的。 打开一个新的。

我试图浏览菜单并在类别之间切换。 然而,结果却很糟糕:一切都在行动中分崩离析。 在菜单上滚动时,类别会自动切换,在如此大的针脚上,它会引起太多关注。
让我们用一个将调用
UIActionSheet
的按钮替换
UICollectionView
。

Vooot。 现在,您可以在顶部面板显示城市,股票,地址和促销代码。
不要忘了很长的线
首先,让我们选择一个城市。 按钮中的字体没有什么复杂的,但是教“三角形”随字体一起增长很有趣。 在我们的例子中,三角形在按钮中成为一个图标,该图标通过
CGAffineTransform
移动到右侧。 另一个选择是从文本和三角形图标中收集
NSAttributedString
,然后将其全部输入按钮。 要使图标规范化,您可以使用矢量图像,该矢量图像必须一定位于带有“保留矢量数据”复选标记的资产中。

三角形的图标为黑色,并通过代码涂成白色。 由于某种原因,在使用标准文本大小的情况下,会以黑色边框的形式出现伪像。 好笑 不完全是 他通过在资产中放置最初是白色的图标来治愈了他。
现在我们拉伸渡渡鸟-卢布,一切都很简单:

现在的问题是:如果这个城市的名字很长,而且我们有很多渡渡鸟卢布,将会发生什么? 从理论上讲,您需要缩短城市名称。 还记得我说过关于通过
NSAttributedString
将这样的图标添加到按钮的第二种选择的
NSAttributedString
吗? 我尝试过,现在遇到一个问题,当我们缩小标题时,三角形图标消失了,因为现在它是标题的一部分。 斯托什。 我们必须返回通过转换移动图标的逻辑。
如果您知道将按钮中的图标移至右侧并将其与标题中的字体一起缩放的便捷方法,请在注释中将其丢弃。
塞入
最后库存。 在这里,您需要坐下来思考。 标题可能很长,即使现在有时也无法排成一行。 从大的角度来看,他根本不合适。 如果您将上部橙色面板橡胶制成,并允许大尺寸的动作标题占据几行,那么即使在大型iPhone上,上部块也将占用整个屏幕的一半,并且您不必记住4S。 事实并非如此。 您可以在操作框内使用布局进行播放:使图片方形,并以空白处为标题。 但是,股票图片是按特定格式自定义的,不会以其他格式正确显示。 这是不可能的。
复杂化
因此,但是您可以再次完全删除图片,并使用标题占据整个位置。

是的,是。 双手会痒,以使动作标题下的背景变色,但这会对可读性产生不利影响。 而且,我们正在努力改善它。 因此,我们不绘制任何内容,而是转到地址和促销代码的其余两个按钮。
我们有严格的限制
这些按钮的标题不可简化。 但是,如果不减少它们,则按钮将彼此爬行。 是的,您无法隐藏这些按钮。
重新分配库存时,我不想增加橙色上部面板的高度。 似乎必须如此。 很好,他们当时没有增加它,否则现在总会有aduha了。 通常,我将为每个按钮选择一行。

乌夫(Ufff),就是这样。 至于菜单中关闭的照片,我仍然不确定。 另外,您只可以显示一半的比萨饼图片,而不是整个圆圈,但是菜单上有一半的比萨饼是平直的,所以它不起作用,我们可能会使用户感到困惑。
让我们将第一种方法与最终结果进行比较:

现在,将“之前”和“之后”与视力较差的模拟进行比较:

不要害怕更改界面和控件。 看到其他按钮或例如滑块的人没有任何问题。 如果某人没有看到任何东西或标题不同,这不是致命的。
而且,我们没有触摸UITabBarController
,因为文本大小很大,长按一下它就可以立即显示图标和选项卡标题,就像iOS显示音量变化一样。
我们展示了它是如何工作的
Dodo Pizza iOS应用程序中的每个逻辑UI组件都分配给一个单独的
UIViewController
。 每个此类控制器都有一个分配给单独文件的
UIView
。 您可以在我们的文章中阅读有关此内容的更多信息:
控制器,放轻松! 我们取出UIView中的代码洋葱控制器。 我们将屏幕分成几部分将逻辑UI组件删除到单独的
UIViewController
极大地简化了将接口修改为不同状态的任务。 即使您不打算增加对动态类型的支持,我们也建议您尝试这种方法-控制屏幕状态会更容易:响应授权,权限,角色等的更改。
所以在这里。 我们在这样的UI组件及其父容器之间添加了一个额外的层。 我们将其称为
StateViewController
。

带有菜单的控制器集成了状态控制器,并且已经嵌入了
collection
(或
button
控制器。
该
StateViewController
根据情况显示此或该UI组件。
为此,
StateViewController
需要了解其状态,并在必要时进行切换。
在此示例中,
StateViewController
会将菜单中类别的选择从集合切换为按钮,反之亦然。 在“正常”显示的情况下,以及在为视障人士显示的情况下,选择器应该能够执行以下操作:
- 显示类别列表。
- 突出显示所选的类别。
- 更新类别列表。
- 报告该类别为“退出”。
感觉到新鲜的小原木的美妙气味? 而且,不,这是一个由移动API交付的披萨团队。 休息5分钟。
2片后“……好吧,我们像这样包装我们的组件,以选择协议中的类别,然后就可以了!”
提示:启动辅助功能检查器以轻松检查界面如何响应动态磁贴设置中的更改。 为此,在打开的Xcode中,单击Xcode→打开开发人员工具→Accessibility Inspector,在设备中选择模拟器,然后转到最后一个选项卡
另一个提示:将iPhone(而不是模拟器)上的dynamo taip控件移至控制中心,以轻松快速地更改文本大小。 为此,在iPhone上,转到“设置”→“控制中心”→“自定义控件”,然后添加“文字大小”。
我们将通常的类别选择器称为
CategoriesCollectionViewController
,将视障者称为
CategoriesButtonViewController
。 它们的通用协议称为
CategoriesPickerProtocol
。 通用状态控制器是
CategoriesStateViewController
。
我们在
CategoriesStateViewController
描述可能的状态:
private enum State { case collection, button }
我们教他显示每种状态所需的控制器:
private var state: State = .collection { didSet { if state != oldValue { updateViewController(for: state) } } } private func updateViewController(for state: State) { let viewController = self.viewController(for: state) self.updateController(with: viewController) } private func viewController(for state: State) { switch state { case .collection: return CategoriesCollectionViewController.instantiateFromStoryboard() case .button: return CategoriesButtonViewController.instantiateFromStoryboard() } }
instantiateFromStoryboard()
-从自写扩展名到视图控制器的方法,如果故事板具有相同的名称,则从情节提要中创建控制器实例。 该代码在文章末尾的源代码中。
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) self.updateStateToCurrentContentSize() } private func updateStateToCurrentContentSize() { let contentSize = self.traitCollection.preferredContentSizeCategory self.updateState(to: contentSize) } private func updateState(to contentSize: UIContentSizeCategory) { self.state = contentSize.isAccessibilityCategory ? .button : .collection }
我们描述了
CategoriesPickerProtocol
协议,同时添加了两个协议:用于委托和用于数据保证。
protocol CategoriesPickerProtocol where Self: UIViewController { var datasource: CategoriesDatasource? { get set } var delegate: CategoriesDelegate? { get set } func select(_ category: ProductCategoryModule.ProductCategoryViewModel) func updateCategories() var selectedCategory: ProductCategoryModule.ProductCategoryViewModel? { get } } protocol CategoriesDatasource: class { var categories: [ProductCategoryModule.ProductCategoryViewModel] { get } func index(of category: Product.ProductCategory) -> Int } protocol CategoriesDelegate: class { func productCategoriesView(_ categoriesPicker: CategoriesPickerProtocol, didSelect category: ProductCategoryModule.ProductCategoryViewModel) }
展示实现没有意义,只是每个筹码者都显示类别并报告向上的变化。
在GitHub上的回购中可以找到使用状态控制器进行动态taype的详细示例。
→
顺便说一句,我们正在扩大