Neomorfismo usando SwiftUI. Parte 1

Saudação, Khabrovites! Antecipando o lançamento do curso avançado “IOS Developer”, preparamos outra tradução interessante.




O design não mórfico é talvez a tendência mais interessante dos últimos meses, embora, na verdade, a Apple o tenha usado como motivo de design na WWDC18. Neste artigo, veremos como você pode implementar um design não mórfico usando o SwiftUI, por que você pode fazer isso e, o mais importante, como refinar esse design para aumentar sua acessibilidade.
Importante : O neomorfismo - também chamado de neuromorfismo - tem sérias conseqüências para a acessibilidade. Portanto, apesar da tentação de ler a primeira parte deste artigo e pular o resto, peço que você leia o artigo até o final e estude as vantagens e desvantagens para que você possa ver toda a imagem. .


Noções básicas de neomorfismo


Antes de avançarmos para o código, quero descrever brevemente dois princípios básicos dessa direção no design, pois eles serão relevantes à medida que avançamos:

  1. O neomorfismo usa brilho e sombra para determinar as formas dos objetos na tela.
  2. O contraste tende a diminuir; não são usados ​​branco ou preto completamente, o que permite destacar realces e sombras.

O resultado final é uma aparência que lembra “plástico extrudado” - um design de interface do usuário que certamente parece novo e interessante sem esbarrar nos olhos. Não posso deixar de repetir mais uma vez que reduzir o contraste e usar sombras para destacar formas afeta seriamente a acessibilidade, e voltaremos a isso mais tarde.

No entanto, ainda acho que o tempo gasto aprendendo sobre neomorfismo no SwiftUI vale a pena - mesmo que você não o use em seus próprios aplicativos, é como um kata para escrever código para ajudar a aprimorar suas habilidades.

Tudo bem, chega de conversa fiada - vamos para o código.

Construindo um mapa não mórfico


O ponto de partida mais simples é criar um mapa não mórfico: um retângulo arredondado que conterá algumas informações. A seguir, veremos como podemos transferir esses princípios para outras partes do SwiftUI.

Vamos começar criando um novo projeto iOS usando o modelo de aplicativo Single View. Certifique-se de usar o SwiftUI para a interface do usuário e depois nomeie o projeto Neumorfismo.

Dica: se você tiver acesso à pré-visualização do SwiftUI no Xcode, recomendo que você o ative imediatamente - será muito mais fácil experimentar.

Começaremos definindo uma cor que represente um tom cremoso. Isso não é cinza puro, mas sim uma sombra muito sutil que adiciona um pouco de calor ou frescor à interface. Você pode adicioná-lo ao diretório de ativos, se desejar, mas agora é mais fácil fazer isso no código.

Adicione isso Colorfora da estrutura ContentView:

extension Color {
    static let offWhite = Color(red: 225 / 255, green: 225 / 255, blue: 235 / 255)
}

Sim, é quase branco, mas é escuro o suficiente para fazer o branco parecer um brilho quando precisamos dele.

Agora podemos preencher o corpo ContentViewfornecendo-o ZStack, que ocupa a tela inteira, usando nossa nova cor quase branca para preencher todo o espaço:

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.offWhite
        }
        .edgesIgnoringSafeArea(.all)
    }
}

Para representar nosso mapa, usaremos um retângulo arredondado na resolução de 300x300 para torná-lo bonito e claro na tela. Então adicione isso ZStack, sob a cor:

RoundedRectangle(cornerRadius: 25)
    .frame(width: 300, height: 300)

Por padrão, será preto, mas, para a implementação do neomorfismo, queremos reduzir drasticamente o contraste, substituindo-o pela mesma cor que usamos no fundo, tornando a forma invisível.

Então mude assim:

RoundedRectangle(cornerRadius: 25)
    .fill(Color.offWhite)
    .frame(width: 300, height: 300)

Um ponto importante: determinamos a forma usando sombras, uma escura e uma clara, como se a luz emitisse raios do canto superior esquerdo da tela.

O SwiftUI nos permite aplicar modificadores várias vezes, o que facilita a implementação do neomorfismo. Adicione os dois modificadores a seguir ao seu retângulo arredondado:

.shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
.shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)

Eles representam o deslocamento da sombra escura no canto inferior direito e o deslocamento da sombra clara no canto superior esquerdo. A sombra clara é visível porque usamos um fundo quase branco e agora o mapa se torna visível.

Escrevemos apenas algumas linhas de código, mas já temos um mapa não mórfico - espero que você concorde que o SwiftUI surpreendentemente facilite o processo!



Criando um botão não mórfico simples


De todos os elementos de uma interface do usuário, o neomorfismo representa um risco bastante baixo para os cartões - se a interface do usuário dentro de seus cartões for clara, o cartão poderá não ter uma borda clara e isso não afetará a acessibilidade. Os botões são outra questão, porque são projetados para interagir, portanto, reduzir o contraste pode fazer mais mal do que bem.

Vamos lidar com isso criando nosso próprio estilo de botão, pois é dessa maneira que o SwiftUI permite distribuir as configurações de botões em muitos lugares. Isso é muito mais conveniente do que adicionar muitos modificadores a cada botão que você criar - podemos simplesmente definir o estilo uma vez e usá-lo em muitos lugares.

Vamos definir um estilo de botão que ficará vazio: o SwiftUI nos fornecerá o rótulo do botão, que pode ser texto, imagem ou outra coisa, e o enviaremos de volta sem alterações.

Adicione esta estrutura em algum lugar externo ContentView:

struct SimpleButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
    }
}

Este configuration.labelé o conteúdo do botão, e em breve adicionaremos outra coisa. Primeiro, vamos definir um botão que o utilize para que você possa ver como o design está evoluindo:

Button(action: {
    print("Button tapped")
}) {
    Image(systemName: "heart.fill")
        .foregroundColor(.gray)
}
.buttonStyle(SimpleButtonStyle())

Você não verá nada de especial na tela, mas podemos corrigi-lo adicionando nosso efeito não mórfico ao estilo do botão. Desta vez, não usaremos um retângulo arredondado, porque para ícones simples, o círculo é melhor, mas precisamos adicionar um pouco de recuo para que a área de clique do botão seja grande e bonita.
Altere seu método makeBody()adicionando um pouco de recuo e, em seguida, colocando nosso efeito não mórfico como pano de fundo para o botão:

configuration.label
    .padding(30)
    .background(
        Circle()
            .fill(Color.offWhite)
            .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
            .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
    )



Isso nos aproxima o suficiente do efeito desejado, mas se você executar o aplicativo, verá que, na prática, o comportamento ainda não é perfeito - o botão não reage visualmente quando pressionado, o que parece estranho.

Para corrigir isso, precisamos ler a propriedade configuration.isPresseddentro do nosso estilo de botão personalizado, que informa se o botão está pressionado ou não. Podemos usar isso para melhorar nosso estilo e fornecer alguma indicação visual de se o botão está pressionado.

Vamos começar com um simples: usaremos os Groupbotões como plano de fundo e, em seguida, verificaremos configuration.isPressede retornaremos um círculo plano se o botão for pressionado, ou nosso atual círculo escuro, caso contrário:

configuration.label
    .padding(30)
    .background(
        Group {
            if configuration.isPressed {
                Circle()
                    .fill(Color.offWhite)
            } else {
                Circle()
                    .fill(Color.offWhite)
                    .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
                    .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
            }
        }
    )

Como um isPressedcírculo com uma cor quase branca é usado no estado , ele torna nosso efeito invisível quando o botão é pressionado.

Aviso: devido ao modo como o SwiftUI calcula as áreas tocáveis, involuntariamente tornamos a área de clique do nosso botão muito pequena - agora você precisa tocar na própria imagem e não no design não morfológico ao seu redor. Para corrigir isso, adicione um modificador .contentShape(Circle())imediatamente depois .padding(30), forçando o SwiftUI a usar todo o espaço disponível.

Agora, podemos criar o efeito da concavidade artificial invertendo a sombra - copiando dois modificadores shadowdo efeito base, trocando os valores X e Y por branco e preto, como mostrado aqui:

if configuration.isPressed {
    Circle()
        .fill(Color.offWhite)
        .shadow(color: Color.black.opacity(0.2), radius: 10, x: -5, y: -5)
        .shadow(color: Color.white.opacity(0.7), radius: 10, x: 10, y: 10)
} else {
    Circle()
        .fill(Color.offWhite)
        .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
        .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
}

Estime o resultado.



Crie sombras internas para um clique no botão


Nosso código atual, em princípio, já funciona, mas as pessoas interpretam o efeito de maneira diferente - alguns o veem como um botão côncavo, outros veem que o botão ainda não foi pressionado, apenas a luz vem de um ângulo diferente.

A idéia da melhoria é criar uma sombra interna que simule o efeito de pressionar o botão para dentro. Isso não faz parte do kit SwiftUI padrão, mas podemos implementá-lo facilmente.

Criar uma sombra interna requer dois gradientes lineares, e eles serão apenas o primeiro de muitos gradientes internos que usaremos neste artigo; portanto, adicionaremos imediatamente uma pequena extensão auxiliar para LinearGradientsimplificar a criação de gradientes padrão:

extension LinearGradient {
    init(_ colors: Color...) {
        self.init(gradient: Gradient(colors: colors), startPoint: .topLeading, endPoint: .bottomTrailing)
    }
}

Com isso, podemos simplesmente fornecer uma lista variável de cores para recuperar seu gradiente linear na direção diagonal.

Agora, sobre o ponto importante: em vez de adicionar duas sombras invertidas ao círculo pressionado, vamos sobrepor um novo círculo com um borrão (traço) e depois aplicar outro círculo com um gradiente como máscara. Isso é um pouco mais complicado, mas deixe-me explicar aos poucos:

  • Nosso círculo base é nosso círculo atual com efeito neomórfico, preenchido com uma cor quase branca.
  • Colocamos um círculo em cima dele, emoldurado por uma moldura cinza, e desfocamos um pouco para suavizar as bordas.
  • Em seguida, aplicamos uma máscara com outro círculo a esse círculo sobreposto no topo, desta vez preenchido com um gradiente linear.

Quando você aplica uma vista como máscara a outra, o SwiftUI usa o canal alfa da máscara para determinar o que deve ser exibido na vista base.

Portanto, se desenharmos um traçado cinza embaçado e o mascararmos com um gradiente linear de preto para transparente, o traçado embaçado ficará invisível de um lado e aumentará gradualmente do outro - obteremos um gradiente interno suave. Para tornar o efeito mais pronunciado, podemos mudar ligeiramente os círculos sombreados nas duas direções. Depois de experimentar um pouco, descobri que desenhar uma sombra clara com uma linha mais grossa que uma escura ajuda a maximizar o efeito.

Lembre-se de que duas sombras são usadas para criar uma sensação de profundidade no neomorfismo: uma clara e outra escura; portanto, adicionaremos esse efeito da sombra interna duas vezes com cores diferentes.

Mude o círculo da configuration.isPressseguinte maneira:

Circle()
    .fill(Color.offWhite)
    .overlay(
        Circle()
            .stroke(Color.gray, lineWidth: 4)
            .blur(radius: 4)
            .offset(x: 2, y: 2)
            .mask(Circle().fill(LinearGradient(Color.black, Color.clear)))
    )
    .overlay(
        Circle()
            .stroke(Color.white, lineWidth: 8)
            .blur(radius: 4)
            .offset(x: -2, y: -2)
            .mask(Circle().fill(LinearGradient(Color.clear, Color.black)))
    )

Se você executar o aplicativo novamente, verá que o efeito de pressionar um botão é muito mais pronunciado e parece melhor.



Com isso, a primeira parte da tradução chegou ao fim. Nos próximos dias, publicaremos a continuação e agora convidamos você a aprender mais sobre o próximo curso .

All Articles