Se você estiver desenvolvendo um produto para o mercado de massa, provavelmente as pessoas com baixa visão o usarão. Se você se esforça para criar interfaces amigáveis, precisa fazê-lo de maneira conveniente para todos os clientes, incluindo pessoas com baixa visão. Eu acho que muitas vezes esquecemos disso. E é hora de consertar isso.

Entrei na consulta "entrega de pizza" na pesquisa na App Store, baixei os 24 primeiros aplicativos e verifiquei qual deles fornece uma interface para pessoas com baixa visão.
2 de 24 . E um dos dois, ao que parece, o fez por acidente: à medida que o tamanho da fonte aumenta, toda a interface "flutua" e fica apenas mais difícil de usar. Isso é triste
550.000 pessoas usam o aplicativo iOS Dodo Pizza todos os meses. Mesmo se 1% de nossos usuários tiverem fontes ampliadas, 5500 pessoas ficarão desconfortáveis ao usar nosso aplicativo. Nós vamos corrigi-lo.
Adicionar suporte de tipo dinâmico
- Usamos estilos de texto dinâmicos do sistema em vez de estáticos.
- Opcionalmente, ative a marca de seleção Ajustar fonte automaticamente nos rótulos nos storyboards. Ou, se o rótulo no botão ou for criado por meio do código,
adjustsFontForContentSizeCategory
seu parâmetro adjustsFontForContentSizeCategory
. - Ensinamos a interface a se estender para diferentes tamanhos de fonte:
- Utilizamos o cálculo automático dos tamanhos das células sempre que possível.
- Onde não podemos - obtemos as configurações atuais do tamanho da fonte e respondemos às alterações no método traitCollectionDidChange
. - Temos uma interface que é impossível de usar.

Mudamos a interface para que possa ser usada
Revertemos e começamos a pensar em como fazer tudo bem.
Use corretamente o local no menu
Agora há muito espaço vazio embaixo da foto da pizza. Vamos tentar colocar uma imagem acima do nome: dessa forma, ela ficará maior e o espaço vazio desaparecerá. Para fazer isso, envolvemos a imagem e
UIStackView
contêiner com tudo o mais no
UIStackView
e, em seguida, alternamos a direção da exibição da pilha, se necessário.

Não temos separadores entre os itens do menu; é por isso que, quando o tamanho é grande, as células começam a "ficar juntas" e o preço da pizza fica muito próximo da imagem da próxima pizza. Vamos tentar adicionar um separador.

Não é isso. Em primeiro lugar, parece mais ou menos. Em segundo lugar, será difícil ver pessoas com baixa visão. Mesmo se repintado de cinza para preto.
Nós o removemos e tentamos aumentar a Internet entre as células.

Agora então.
Subtotal: use mais espaço, os olhos saltam menos de uma linha para outra, a leitura ficou mais fácil.
Melhoramos o alongamento e a remoção
Agora você pode aumentar a largura do botão adicionar ao carrinho, caso contrário, ele flutua para a esquerda e não fica abaixo do seu dedo, embora exista muito espaço vazio à direita. Você pode, é claro, simplesmente movê-lo para o lado direito, mas será inconveniente para os canhotos e, em geral, vamos gesticular melhor do que descomprimir. E tornaremos o traçado mais gordo, e agora não é consistente com a fonte.

Eu olho para tudo isso e entendo que a foto da pizza, é claro, é muito grande. Vamos tentar esconder, talvez sem fotos você possa viver.

Em geral, um menu sem fotos não perdeu muita informação, mas agora um item de menu quase sempre interfere na tela do iPhone 6S. Mas tornou-se menos atraente, a baba não flui ao rolar. Tal. Por enquanto, vamos deixar assim, pensar com cuidado e talvez mais tarde retornar a imagem.
Não se esqueça de verificar "ao vivo"
Agora categorias. Em geral, mesmo com a primeira abordagem, o resultado foi tolerável. Ligue um novo.

Tentei navegar pelo menu e alternar entre categorias. No entanto, acabou mal: tudo desmorona em ação. Ao rolar pelo menu, as categorias alternam automaticamente e, em pinos tão grandes, atrai muita atenção.
Vamos substituir o
UICollectionView
por um botão que chamará o
UIActionSheet
.

Voooot. Agora você pode acessar o painel superior, onde a cidade, os estoques, o endereço e o código promocional.
Não se esqueça das linhas muito longas
Primeiro, vamos escolher a cidade. Não há nada complicado com a fonte no botão, mas é interessante ensinar os "triângulos" a crescer com a fonte. No nosso caso, o triângulo foi transformado em um ícone no botão, que é movido para o lado direito através do
CGAffineTransform
. Outra opção é coletar
NSAttributedString
do texto e do ícone do triângulo e depois alimentar tudo com o botão. Para normalizar o ícone, você pode usar uma imagem vetorial, que deve necessariamente estar em ativos com a marca de seleção Preservar dados vetoriais.

O ícone do triângulo é preto e é pintado de branco através do código. E, por alguma razão, com o tamanho padrão do texto, artefatos na forma de bordas pretas aparecem nele. É engraçado Na verdade não. Ele foi curado colocando um ícone originalmente branco em ativos.
Agora, esticamos os dodô-rublos, tudo é simples:

E agora a pergunta é: o que acontecerá se o nome da cidade for longo e tivermos muitos rublos? Em teoria, você precisa encurtar o nome da cidade. Lembre-se do que eu disse sobre a segunda opção para adicionar um ícone a um botão, por meio de
NSAttributedString
? Eu tentei e agora há um problema que, quando reduzimos o título, o ícone do triângulo desaparece, porque agora faz parte do título. Stosh. Teremos que retornar a lógica de mover o ícone através de transformações.
Se você conhece uma maneira conveniente de mover o ícone no botão para o lado direito e escalá-lo junto com a fonte no cabeçalho - descarte-o nos comentários, por favor.
Cram em
Finalmente estoques. Aqui você precisa sentar e pensar. O título pode ser longo e, mesmo agora, às vezes não cabe em uma linha. Em um tamanho grande, ele não se encaixa bem. Se você tornar o painel laranja superior em borracha e permitir que o título da ação em tamanho grande ocupe várias linhas, o bloco superior ocupará metade da tela, mesmo em iPhones grandes, e você não precisará se lembrar do 4S. Este não é o caso. Você pode brincar com um layout dentro da caixa de ação: ajuste a imagem e tome o lugar vago como um título. Mas as fotos dos materiais são personalizadas para um formato específico e não serão exibidas corretamente em outro. Isso é impossível.
Complicado
Então, mas você pode novamente remover completamente as fotos e ocupar todo o lugar com um título.

Sim é verdade. As mãos coçam para colorir o plano de fundo sob o título da ação, mas isso afetará negativamente a legibilidade. E nós, como, estamos tentando melhorá-lo. Portanto, não pintamos nada e passamos aos dois botões restantes sobre o endereço e os códigos promocionais.
Trabalhamos com restrições estritas
Os títulos nesses botões são irredutíveis. Mas se não forem reduzidos, os botões se arrastarão um contra o outro. E sim, você não pode ocultar esses botões.
Quando refiz o material, não queria aumentar a altura do painel laranja superior. Parece ter que. É bom que eles não tenham aumentado naquele momento, caso contrário, agora haveria aduha. Em geral, vou selecionar uma linha para cada botão.

Ufff, é isso. Quanto às fotos desativadas no menu, ainda não tenho certeza. Como alternativa, você pode mostrar apenas metade da foto da pizza em vez de um círculo inteiro, mas temos meia pizza reta no menu, para que não funcione, pois podemos confundir os usuários.
Vamos comparar a primeira abordagem com o resultado final:

Agora compare o "antes" e o "depois" com uma simulação de visão deficiente:

Não tenha medo de alterar a interface e os controles. Não há nada de errado em alguém ver outro botão ou, por exemplo, um controle deslizante. E não é fatal se alguém não vê algo ou se o título é diferente.
E não tocamos no UITabBarController
, porque, com um tamanho de texto grande, ele pode ser retirado da caixa com um toque longo e pode mostrar o ícone e o título da guia da mesma maneira que o iOS mostra a alteração no volume.
Mostramos como tudo funciona por dentro
Cada componente lógico da interface do usuário no aplicativo iOS do Dodo Pizza é alocado para um
UIViewController
separado. Cada um desses controladores possui um
UIView
alocado para um arquivo separado. Você pode ler mais sobre isso em nossos artigos:
Controlador, vá com calma! Retiramos o código no UIViewControlador de cebola. Dividimos telas em partesA remoção de componentes lógicos da interface do usuário em um
UIViewController
separado simplificou bastante a tarefa de modificar interfaces para diferentes estados. Recomendamos que você tente essa abordagem, mesmo se não planeja adicionar suporte ao Tipo dinâmico - é mais fácil controlar o status das telas: responda a alterações na autorização, direitos, funções e assim por diante.
Então aqui. Adicionamos uma camada extra entre esse componente de interface do usuário e seu contêiner pai. Nós o chamamos
StateViewController
.

O controlador com o menu integra o controlador de estado e já incorpora a
collection
- ou o
button
- controlador.
Esse
StateViewController
mostra esse ou aquele componente da interface do usuário, dependendo da situação.
Para fazer isso, o
StateViewController
precisa conhecer seus estados e
StateViewController
los, se necessário.
Neste exemplo, o
StateViewController
alternará a seleção de categorias no menu da coleção para o botão e vice-versa. E no caso de uma exibição "normal" e no caso de pessoas com deficiência visual, o seletor deve ser capaz de fazer o mesmo:
- Mostrar lista de categorias.
- Realce a categoria selecionada.
- Atualize a lista de categorias.
- Relate que a categoria "saiu".
Sinta esse maravilhoso cheiro de toras frescas? E, não, essa é uma equipe de pizza móvel entregue por API. 5 minutos de intervalo.
2 fatias depois"... Bem, envolvemos nossos componentes assim para selecionar categorias nos protocolos, E ELES SÃO IMEDIATAMENTE!"
Dica: inicie o Inspetor de acessibilidade para verificar facilmente como a interface responde às alterações nas configurações dinâmicas do bloco. Para fazer isso, no Xcode aberto, clique em Xcode → Abrir ferramenta de desenvolvedor → Inspetor de acessibilidade, selecione o simulador no dispositivo e vá para a última guia
Outra dica: execute o controle de bloqueio de dínamo no iPhone (não no simulador) no Centro de Controle para alterar com facilidade e rapidez o tamanho do texto. Para fazer isso, em um iPhone, vá para Configurações → Centro de controle → Personalizar controles e adicione Tamanho do texto.
Chamamos o seletor de categoria usual de
CategoriesCollectionViewController
e, para os deficientes visuais -
CategoriesButtonViewController
. Seu protocolo comum é chamado
CategoriesPickerProtocol
. O controlador de estado geral é
CategoriesStateViewController
.
Descrevemos os possíveis estados em nosso
CategoriesStateViewController
:
private enum State { case collection, button }
Nós o ensinamos a mostrar o controlador desejado para cada estado:
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()
- um método de uma extensão auto-escrita para um controlador de exibição, cria uma instância de controlador a partir de storyboards se eles tiverem o mesmo nome. O código está no código fonte no final do artigo.
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 }
Descrevemos o protocolo
CategoriesPickerProtocol
, adicionando simultaneamente mais dois protocolos: para o delegado e para a dataures.
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) }
Não há sentido em mostrar a implementação, mas todo piker exibe as categorias e relata mudanças para cima.
Um exemplo detalhado do uso de controladores de estado para o taype dinâmico pode ser encontrado em meu
repositório no GitHub .
→
A propósito, estamos expandindo