Microinterações no iOS. Palestra Yandex

Algumas semanas atrás, um evento especial da comunidade CocoaHeads foi realizado no escritório da Yandex - maior do que as mitaps tradicionais. O desenvolvedor Anton Sergeyev falou nesta reunião e falou sobre o modelo de microinteração que os designers de UX costumam usar, bem como sobre como colocar as idéias nele em prática. Anton prestou mais atenção à animação.


- É muito importante para mim que foi uma honra conhecer os convidados. Vejo aqui aqueles com quem eu conheço há muito tempo, aqueles com quem eu conheci recentemente e aqueles com quem ainda não conheci. Bem-vindo ao CocoaHeads.

Vou falar sobre micro-interações. Isso é um pouco de isca de clique - somos engenheiros, desenvolvedores, falaremos mais sobre a parte do software, mas começaremos com um tópico muito humanitário, como microinterações. Como resultado, aplicaremos esse tema humanitário na parte técnica para aprender a projetar de forma mais eficiente e simples o design de componentes visuais muito pequenos, como botões, carregadeiras pequenas e barras. Eles estão saturados de animação, e o código de animação ramificada geralmente pode parecer muito complicado, é extremamente difícil de manter.

Mas primeiro, um pouco de distração. Pense nisso, você se lembra de quando decidiu se tornar um desenvolvedor? Eu lembro claramente disso. Tudo começou com uma mesa. Uma vez eu decidi aprender ObjC. Linguagem elegante, divertida, assim, sem planos de longo alcance. Encontrei um livro, ao que parece, Big Nerd Ranch, e comecei a ler capítulo por capítulo, fazendo cada exercício, conferindo, lendo, até chegar à mesa. Em seguida, me familiarizei com o padrão de delegado, mais precisamente com sua subespécie "Fonte de dados", fonte de dados. Esse paradigma me parece muito simples agora: existe uma fonte de dados, delegado, tudo é simples. Mas então me surpreendeu: como posso separar uma tabela de dados completamente diferentes? Você já viu uma tabela em um pedaço de papel na qual é possível colocar um número infinito de linhas, dados completamente abstratos. Isso me influenciou bastante. Percebi que a programação, o desenvolvimento tem tremendas oportunidades, e será muito interessante aplicá-las. Desde então, decidi me tornar um desenvolvedor.

Durante o desenvolvimento, vários padrões foram encontrados. Enorme, chamados de arquiteturas que descrevem todo o aplicativo. Pequenos que cabem em dezenas em um botão pequeno. É importante entender que todos esses padrões não vieram do ar, mas do setor humanitário. O mesmo padrão de delegação. A delegação apareceu muito antes da programação, e a programação assume todas essas coisas humanitárias para um trabalho mais eficiente.

Hoje vou falar sobre outra abordagem que assume outra coisa humanitária. Em particular, sobre micro-interações.

Tudo começou com um carregador. No trabalho anterior, antes do Yandex, eu tinha a tarefa de repetir o carregador de design de material do Google. Existem dois deles, um indefinido, o outro definido. Eu tinha a tarefa de combiná-los em um, ele tinha que ser capaz de tanto certo quanto por tempo indeterminado, mas havia requisitos rígidos - para que fosse extremamente suave. A qualquer momento, podemos passar de um estado para outro, e tudo deve ser animado de maneira suave e precisa.

Sou um desenvolvedor inteligente, fiz tudo. Eu tenho mais de 1000 linhas de código de macarrão incompreensível. Funcionou, mas recebi um comentário interessante sobre a revisão do código: "Realmente espero que ninguém nunca edite esse código". E para mim é praticamente inadequado. Eu escrevi código horrível. Funcionou bem, foi uma das minhas melhores animações, mas o código era horrível.

Hoje vou tentar descrever a abordagem que encontrei depois que deixei o emprego.



Vamos começar com o tópico mais humanitário - modelos de microinteração. Como eles são incorporados e geralmente onde estão ocultos em nossos aplicativos? Vamos continuar usando esse modelo em nosso mundo técnico. Considere como o UIView, que lida com exibição e animação, como isso funciona. Em particular, falaremos muito sobre o mecanismo CAAction, que está intimamente integrado e funciona com o UIView, CALayer. E então considere pequenos exemplos.

Definição primeiro. Aparentemente, o autor realmente gostou do prefixo "micro", mas não há macro ou nano-interações, o tamanho não importa. Por simplicidade, os chamaremos de simplesmente interações. Esse é um modelo tão conveniente que permite descrever qualquer interação com o aplicativo, do início ao fim. Ele consiste em quatro pontos: um gatilho, lógica de negócios que precisa ser implementada nessa interação, feedback para transmitir algo ao usuário e uma alteração no estado do aplicativo.

Vou contar uma história com três papéis diferentes. Vou começar com o usuário, como a coisa mais importante no desenvolvimento. Quando eu estava me preparando para o relatório, fiquei doente. Eu precisava encontrar uma farmácia e abri o Yandex.Map. Abri o aplicativo, olhe para ele, ele olha para mim, mas nada acontece. Percebi então que eu era o usuário, sou o principal, dou instruções sobre o que fazer para o aplicativo. Eu orientei, cliquei no botão "pesquisar", digitei "farmácia", clique em "OK", o aplicativo fez o trabalho interno, localizou as farmácias necessárias próximas a mim e a exibiu na tela.

Procurei o caminho certo e descobri que, além das farmácias, um botão especial aparecia na tela - para construir uma rota. Assim, o aplicativo mudou para um novo estado. Cliquei e fui à farmácia. Entrei neste aplicativo por algum motivo - para encontrar uma farmácia. Eu a alcancei. Eu sou um usuário feliz.

Antes que o aplicativo aparecesse e eu pudesse procurar algo nele, ele foi desenvolvido. O que o designer de UX achou quando criou esse processo? Tudo começou com o fato de que era necessário sair de uma cena muda quando o usuário e o aplicativo se entreolham, e nada acontece. Para isso, era necessário algum tipo de gatilho. Tudo tem um começo, e aqui também era necessário começar em algum lugar.

O gatilho foi selecionado - botão de pesquisa. Ao clicar nele, era necessário resolver o problema do ponto de vista técnico. Solicite dados no servidor, analise a resposta, de alguma forma atualize o modelo, analise. Solicite a posição atual dos usuários e muito mais. E assim obtivemos esses dados e sabemos exatamente onde estão todas as farmácias.

Parece que era possível acabar com isso. Afinal, resolvemos o problema, encontramos todas as farmácias. Há apenas um problema: o usuário ainda não sabe nada sobre essas farmácias. Ele precisa entender.

De alguma forma, empacote nossa solução para esse problema e leve-a para um pacote bonito, para que ele entenda. Acontece que os usuários são pessoas, eles interagem com o mundo exterior através dos sentidos. O estado atual da tecnologia é tal que nós, como desenvolvedores de aplicativos móveis, temos apenas três sentidos: visão - podemos mostrar algo na tela, audição - podemos reproduzir nos alto-falantes e uma sensação tátil, podemos empurrar o usuário na mão.

Mas o homem é muito mais funcional. Mas o estado atual da tecnologia é tal que, no momento, podemos confiar apenas nesses três. E, neste caso, selecionamos uma tela, mostramos no mapa as farmácias mais próximas e com uma lista com informações mais detalhadas sobre essas farmácias. E parece que isso é exatamente tudo, o usuário encontrou farmácias e está tudo bem.

Mas há um problema. Quando o usuário entrou no aplicativo, ele estava em um contexto em que não sabia onde as farmácias estavam localizadas. E suas tarefas eram encontrá-la. Mas agora o contexto mudou, ele sabe onde estão as farmácias e não precisa mais procurá-las. Ele teve a seguinte tarefa - obter instruções para a próxima farmácia. É por isso que precisamos exibir controles adicionais na tela, em particular, este é um botão para construir uma rota, ou seja, transferir o aplicativo para outro estado em que esteja pronto para aceitar novos gatilhos para as próximas interações novamente.

Imagine, o designer de UX descobriu tudo isso, vem ao desenvolvedor e começa a descrever em cores como o usuário clica no botão, como e o que acontece, como é pesquisado, como o usuário está satisfeito, como aumentamos a DAU e assim por diante. A pilha de perguntas não resolvidas do desenvolvedor transbordou para outro lugar na primeira frase, quando mencionamos o botão pela primeira vez.

Ele pacientemente ouve tudo e, no final, quando termina, ele diz que tudo bem, isso é legal, mas vamos discutir o botão. Este é um elemento importante.

Durante a discussão, verifica-se que o botão é inerentemente um gatilho, contém lógica em si mesma, segundo a qual pode receber mensagens do sistema, em particular, sobre cliques do usuário na tela. Com base nesse clique, ele pode iniciar uma cadeia de eventos, que começa com o fato de que o mesmo botão envia mensagens para diferentes objetos sobre a necessidade de iniciar vários processos, nesse caso, solicitar informações sobre o servidor, mais um pouco.

Quando pressionado, o botão muda de estado e fica pressionado. Quando o usuário libera, ele deixa de ser pressionado. Ou seja, fornece feedback ao usuário para que ele entenda o que esperar desse botão. E o botão pode ser pressionado, não pressionado, ativo ou inativo, em diferentes estados, e mover de acordo com a lógica diferente de um estado para outro.

Assim, vimos que o mesmo modelo de microinteração, que consiste em um gatilho, lógica de negócios, feedback e alterações de estado, pode descrever nosso aplicativo em várias escalas, como na escala de um caso de usuário inteiro, uma grande procura pela farmácia mais próxima, Então, em termos de um pequeno botão.

E esse é um modelo muito conveniente que permite simplificar a interação dentro da equipe e descrever programaticamente, separar quatro entidades: gatilho, lógica de negócios, feedback e mudança de estado. Vamos ver o que o UIKit nos fornece para usá-lo. E não apenas fornece, mas usa. Ao implementar várias animações, os pequenos componentes das subclasses do UIView, ele usa apenas esse mecanismo e não é diferente.

Vamos começar com o UIView, como ele se encaixa nesse modelo. Em seguida, consideraremos o CALayer que ele nos fornece para apoiar esses estados e o mecanismo de ações, o momento mais interessante.

Vamos começar com o UIView. Nós o usamos para exibir alguns retângulos na tela. Mas, de fato, o UIView não sabe desenhar, usa outro objeto CALayer para isso. De fato, o UIView está comprometido em receber mensagens sobre como tocar no sistema, bem como sobre outras chamadas, sobre a API que definimos em nossas subclasses do UIView. Assim, o próprio UIView implementa a lógica do acionador, ou seja, o lançamento de alguns processos, recebendo essas mensagens do sistema.

O UIView também pode notificar seus delegados sobre os eventos que ocorreram e também enviar mensagens aos assinantes, como, por exemplo, fazer com que o UIControl subclasse eventos diferentes. Dessa maneira, a lógica comercial deste UIView é implementada. Nem todos eles têm lógica de negócios, muitos deles são apenas elementos de exibição e não têm feedback no sentido de lógica de negócios.



Analisamos dois pontos, um gatilho e uma lógica de negócios. E onde o feedback e a mudança de estado estão ocultos no UIView? Para entender isso, devemos lembrar que o UIView não existe por si só. Quando criado, ele cria uma camada traseira, uma subclasse de CALayer.



E nomeia-se seu delegado. Para entender como o UIView usa o CALayer, ele pode existir em vários estados.

Como distinguir um estado de outro? Eles diferem no conjunto de dados que precisa ser armazenado em algum lugar. Vamos considerar quais recursos o CALayer nos fornece para o UIView, para que ele armazene o estado.



Nossa interface está se expandindo um pouco, a interação entre o UIView e o CALayer, o UIView tem uma tarefa adicional - atualizar o repositório dentro do CALayer.

Um fato pouco conhecido que poucas pessoas usam: CALayer pode se comportar como uma matriz associativa, o que significa que podemos gravar dados arbitrários nele em qualquer chave da seguinte maneira: setValue (_: forKey :).



Esse método está presente em todas as subclasses NSObject, mas, ao contrário de muitos outros, quando uma chave é recebida e não é substituída por ela, ela não trava. E ele escreve corretamente, e então podemos ler. É uma coisa muito conveniente, que permite, sem criar subclasses do CALayer, gravar quaisquer dados lá e depois lê-los, consultando-os. Mas este é um repositório simples muito primitivo, de fato, um dicionário. O CALayer é muito mais progressivo. Ele suporta estilos.

Isso é implementado pela propriedade Style que qualquer CALayer possui. Por padrão, é nulo, mas podemos redefinir e usá-lo.



Em geral, este é um dicionário comum e nada mais, mas tem uma peculiaridade sobre como o CALayer funciona com ele, se solicitarmos valor para Key, outro método que o NSObject possui. Ele age de maneira muito interessante, pois procura recursivamente os valores necessários no dicionário de estilos. Se empacotarmos um estilo existente em um novo estilo com a tecla style e escrevermos algumas teclas lá, mas a aparência será a seguinte.



Primeiro, olhe para a raiz, depois para o interior e assim por diante, até que faça sentido. Quando o estilo se torna nulo, não faz sentido procurar mais.

Dessa forma, o UIView pode, usando a infraestrutura fornecida pelo CALayer, organizar alterações de estado, atualizar o repositório interno do CALayer, usando o estilo, um repositório muito poderoso que pode simular uma pilha ou usando uma matriz associativa regular, que também é muito eficiente e muito útil .

Terminado o armazenamento, comece com CAAction. Vou lhe contar mais sobre ele.



Há uma nova tarefa para o UIView - solicitar ações do CALayer. O que são ações?



O CAAction é apenas um protocolo que possui apenas um método - executado. A Apple geralmente adora temas de cinema, ação aqui como "câmera, motor!". Esse "motor" é apenas uma ação, e não apenas esse nome foi usado. O método run significa iniciar uma ação que pode iniciar, executar e terminar, a mais importante. Este método é muito genérico, possui apenas uma sequência de eventos e todo o resto pode ser de qualquer tipo. No ObjC, isso é tudo id e um NSDictionary normal.



Existem classes dentro do UIKit que atendem ao protocolo CAAction. O primeiro é a animação. Primeiro, sabemos que a animação pode ser adicionada a uma camada, mas isso é algo de nível muito baixo. Uma abstração de alto nível acima é executar uma ação com os parâmetros necessários com uma camada.

A segunda exceção importante é o NSNull. Sabemos que ele não pode ser chamado por nenhum método, mas ele satisfaz o protocolo CAAction, e isso é feito para procurar convenientemente CAAction em camadas.



Como dissemos antes, o UIView é um delegado para o CALayer, e um dos métodos de delegado é a ação (para: forKey :). A camada possui um método, ação paraKey.



Podemos chamá-lo a qualquer momento na camada, e a qualquer momento dará a ação correta ou nula, uma vez que também pode dar. O algoritmo é uma pesquisa muito incomum. O pseudocódigo está escrito aqui, vamos dar uma olhada nas linhas. Após o recebimento dessa mensagem, ele primeiro consulta o delegado. Um delegado pode retornar nulo, o que significa que a pesquisa deve continuar em outro lugar ou pode retornar uma ação válida, um objeto válido que satisfaça o protocolo CAAction. Mas existe uma regra lógica: se retornar NSNull, que satisfaz esse protocolo, mais tarde será convertido em zero. Ou seja, se retornarmos Nulo, na verdade isso significará "pare de procurar". Não há ação e não é necessário.

Mas há o seguinte. Depois de consultar o delegado e o delegado retornar nulo, ele continuou a pesquisar. Primeiro, no dicionário Actions, que a camada possui, e depois pesquisará recursivamente no dicionário de estilo, onde também pode haver um dicionário com a chave actions, no qual muitas ações podem ser gravadas, e também será possível procurá-las recursivamente. Se não deu certo, ele solicitará à classe a ação padrão para o método Key, que é definida pelo CALayer e até recentemente retornou algo, mas ultimamente sempre retorna nulo nas versões recentes do iOS.

Entendeu a teoria. Vamos ver como tudo é aplicado na prática.

Existem eventos, eles têm chaves, algumas ações ocorrem nesses eventos. Fundamentalmente, dois tipos diferentes de eventos podem ser distinguidos. A primeira é a animação das propriedades armazenadas. Suponha que quando chamamos Viewcolor = red em View, é teoricamente possível animar.



Que relatório sobre padrões sem circuito? Eu desenhei um casal. O UIView possui algum tipo de interface que definimos para subclasses ou que é recebida do sistema com eventos. A tarefa do UIView é solicitar a ação desejada, atualizar o armazenamento interno e iniciar a ação que ocorreu. A ordem é muito importante em relação à solicitação: ação, somente então atualize a ação e somente então atualize a loja e a ação.



O que acontece se na UIView atualizarmos backgroundColor. Sabemos que no UIView tudo o que é exibido na tela ainda é um proxy para o CALayer. Ele armazena em cache tudo o que recebe, apenas por precaução, mas ao mesmo tempo tudo transmite o CALayer e lida com toda a lógica adicional do CALayer. O que acontece no CALayer quando ele recebe uma tarefa para alterar o plano de fundo? Tudo é um pouco mais complicado aqui.



Para começar, ele pedirá ação. E é importante entender que a ação será solicitada primeiro. Isso permitirá que você pergunte ao CALayer seus valores atuais, incluindo backgroundColor, no momento da criação da ação, somente a loja será atualizada e, quando a ação recebida receber o comando de execução, poderá consultar o CALayer e obter novos valores. Assim, ele possuirá o antigo e o novo, e isso permitirá que ele crie animação, se necessário.

Mas há um recurso no UIView, se alterarmos o backgroundColor no UIView, se fizermos isso no bloco de animação, ele será animado e, se estiver fora do bloco de animação, não será animado.



Tudo é muito simples, não há mágica. Mas lembre-se de que o UIView é um representante do CALayer, ele possui esse método. Tudo é muito simples.

Se esse método foi executado no bloco de animação, ele retornará algum tipo de ação. Se estiver fora do bloco de animação, esse método retornará NSNull, o que significa que nada precisa ser animado. , CALayer .

, UIView , . . ?



, . UIView , read only, , inheritedAnimationDuration. . , . .

? duration, . , run, , .



, CAAction, backgroundcolor opacity, UIView . , , , , . . setValue forKey , , , , , , .

, , , , .

— . , «» «» . .

.



. , , , . UIView CALayer, , , CAAction, , , .





, , , . , . .

. - .

CAAction, , . , , , .



, , , home, . , . , .



. - .



, - , - . , , . , .

, , .



, CAAction , . , UIControl, - , - , , , - .

, . , UIView -, , - , , , .

— . .

? . , . — . activating, inactive active. , , .



. , onOrderIn onOrderOut. , UIKit, .

, -, — , .



. UIView , : isActive progress. . CAAction, .

, . , , 30 CAACtion, . , 30 , NSNull. 15 15 . . — . , — .

, . .

. , , : , -, .

, , . . , UIKit , . . , , , . Obrigado pela atenção.

Source: https://habr.com/ru/post/pt429580/


All Articles