O design neumórfico é facilmente a tendência de design mais interessante dos últimos meses, embora seja justo dizer que a Apple já o usou como seu motivo de design há muito tempo atrás no WWDC18. Neste artigo, vamos olhar para como você pode criar designs neumórficos usando SwiftUI, por que você poderia querer fazer isso e – mais importante ainda – como podemos modificar esses designs para serem mais acessíveis.
Importante: O Neumorfismo – às vezes chamado de neomorfismo – tem implicações sérias para a acessibilidade, então embora seja tentador ler apenas a primeira parte deste artigo e pular o resto, encorajo você a ficar por aqui e aprender os pontos negativos bem como positivos para que você tenha uma visão completa.
As bases do neumorfismo
Antes de entrarmos no código, eu só quero tocar brevemente nos dois princípios fundamentais desta tendência de design, porque isso será relevante à medida que avançamos:
- Ele usa destaque e sombras para definir as formas dos objetos na tela.
- A contraste é geralmente reduzido; branco ou preto total não são usados, o que permite que os destaques e sombras se destaquem.
O resultado final é uma aparência de “plástico extrudado” – um design de interface do usuário que certamente parece fresco e interessante sem ser agressivo para seus olhos. Não posso repetir o suficiente que reduzir contraste e confiar em sombras para formas tem impactos sérios na acessibilidade, e voltaremos a isso mais tarde.
No entanto, ainda acho que vale a pena dedicar um tempo para explorar o neumorfismo no SwiftUI – mesmo se você não usar em seus próprios aplicativos, é como uma codificação kata que ajudará a aperfeiçoar suas habilidades.
OK, basta de conversa fiada – vamos olhar para algum código.
Construindo um cartão neumórfico
O ponto de partida mais simples é construir um cartão neumórfico: um retângulo arredondado que conterá algumas informações. A partir daí, veremos como podemos mover isso para outras partes do SwiftUI, mas os princípios permanecem os mesmos.
Comece criando um novo projeto iOS usando o modelo de Aplicativo Único View. Certifique-se de usar SwiftUI para a interface do usuário e nomeie-o Neumorphism.
Dica: Se você tiver acesso ao pré-visualizador SwiftUI do Xcode, recomendo ativá-lo agora – você encontrará muito mais fácil para experimentação.
Vamos começar definindo uma cor para representar um tom off white. Isso não é cinza, mas um toque sutil que adiciona um pouco de calor ou frieza à interface do usuário. Você pode adicionar isso em um catálogo de ativos se quiser, mas aqui fica mais fácil no código.
Adicione esta Color extensão fora da estrutura ContentView:
extension Color {
static let offWhite = Color(red: 225 / 255, green: 225 / 255, blue: 235 / 255)
}
Sim, isso é quase branco, mas está escuro o suficiente para que o branco real se destaque como um glow quando precisarmos dele.
Agora podemos preencher o corpo do ContentView, dando a ele uma ZStack que ocupa toda a tela e usando nossa nova cor off white para preencher todo o espaço:
struct ContentView: View {
var body: some View {
ZStack {
Color.offWhite
}
.edgesIgnoringSafeArea(.all)
}
}
Vamos usar um retângulo arredondado para representar nosso cartão, e fixaremos em 300x300 para torná-lo claro na tela. Então, adicione isso à ZStack, abaixo da cor:
RoundedRectangle(cornerRadius: 25)
.frame(width: 300, height: 300)
Isso terá uma cor padrão de preto, mas no neumorfismo queremos reduzir dramaticamente o contraste, então substituímos isso pela mesma cor que usamos para nosso fundo – efetivamente tornando a forma invisível.
Então, mude-o para isso:
RoundedRectangle(cornerRadius: 25)
.fill(Color.offWhite)
.frame(width: 300, height: 300)
Agora para a parte importante: definimos a forma usando sombras, uma escura e uma clara, como se houvesse uma luz lançando raios do canto superior esquerdo da tela.
A SwiftUI permite que apliquemos modificadores múltiplas vezes, o que torna fácil alcançar o neumorfismo. Adicione esses dois modificadores ao seu retângulo arredondado para vê-lo em ação:
.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 é uma sombra escura deslocada no canto inferior direito e uma sombra clara deslocada no canto superior esquerdo. A sombra clara é visível porque usamos off-white para nosso fundo, e juntas a carta se torna visível.
Nós escrevemos apenas algumas linhas de código, mas já temos uma carta neumórfica – espero que você concorde que o SwiftUI faz isso surpreendentemente fácil!

Construindo um botão neumórfico simples
Como elementos de interface do usuário, as cartas são relativamente baixo risco para o neumorfismo – tanto quanto a interface dentro das suas cartas é clara, a borda da carta poderia facilmente não existir e você não afetaria a acessibilidade. Botões são uma questão diferente porque eles existem para serem tocados, então reduzir seu contraste pode fazer mais mal do que bem.
Vamos explorar isso criando um estilo de botão personalizado, que é como o SwiftUI nos permite compartilhar configurações de botão em muitos lugares. Isso é muito mais conveniente do que anexar muitos modificadores a cada botão que criamos – podemos apenas definir o estilo uma vez e compartilhá-lo em muitos lugares.
Vamos definir um estilo de botão que é essencialmente vazio: o SwiftUI nos dará o rótulo do botão, que pode ser algum texto, uma imagem ou algo mais, e enviaremos de volta sem modificações.
Adicione esta estrutura em algum lugar fora de ContentView:
struct SimpleButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
}
Aquele configuration.label é o que mantém o conteúdo do botão, e adicionaremos mais a ele em breve. Primeiro, no entanto, vamos definir um botão que usa isso, para você ver como a design evolui:
Button(action: {
print("Botão tocado")
}) {
Image(systemName: "heart.fill")
.foregroundColor(.gray)
}
.buttonStyle(SimpleButtonStyle())
Você não verá nada especial na tela, mas podemos corrigir isso adicionando nosso efeito neumórfico ao estilo do botão. Dessa vez não vamos usar um retângulo arredondado porque para ícones simples um círculo parece melhor, mas precisamos adicionar algum espaço em branco para que a área de toque do botão seja grande.
Modifique seu método makeBody() para que adicionemos algum espaço em branco, então coloquemos nosso efeito neumórfico como um fundo ao 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 leva muito longe do efeito que queremos, mas se você rodar o aplicativo de volta verá que não funciona bem na prática – o botão não responde visualmente quando pressionado, o que é apenas estranho.
Para corrigir isso, precisamos ler a propriedade configuration.isPressed dentro do nosso estilo de botão personalizado, que relata se o botão está atualmente sendo pressionado ou não. Podemos usar isso para ajustar nossa estilização para dar alguma indicação visual de se o botão foi pressionado.
Vamos começar simples: usaremos um Group para o fundo do botão, então verificaremos configuration.isPressed e retornaremos ou um círculo plano se o botão for pressionado, ou retornaremos nosso círculo atual com sombras de outra forma:
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)
}
}
)
Porque o estado isPressed usa um círculo com a cor off white, isso faz nosso efeito ficar invisível quando o botão está sendo pressionado.
Aviso: Devido à forma como SwiftUI calcula áreas clicáveis, acabamos de tornar a área clicável do nosso botão muito pequena – você precisa tocar na imagem em si, não no design neumórfico ao seu redor. Para corrigir isso, adicione o modificador .contentShape(Circle()) diretamente após .padding(30), forçando SwiftUI a usar todo o espaço disponível para o toque.
Agora, poderíamos criar um efeito falso de pressionamento invertendo as sombras – copiando os dois modificadores shadow do efeito regular, então trocando os valores X e Y brancos e pretos, assim:
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)
}
Tente isso e veja o que você acha!

Criando sombras internas para o pressionamento do botão
Nosso código atual funciona de certa forma, mas as pessoas interpretam o efeito de maneira diferente – algumas veem isso como um botão côncavo, outras veem o botão ainda sendo levantado, mas a luz vindo de um ângulo diferente.
Uma ideia melhor é criar uma sombra interna que parece muito mais como o botão está sendo pressionado para dentro. Isso não vem com SwiftUI por padrão, mas podemos fazer isso facilmente.
Criar uma sombra interna requer dois gradientes lineares, e esses serão os primeiros de muitos gradientes internos que usaremos neste artigo, então adicionaremos um pequeno helper extension para LinearGradient para tornar a criação de gradientes padrão mais fácil:
extension LinearGradient {
init(_ colors: Color...) {
self.init(gradient: Gradient(colors: colors), startPoint: .topLeading, endPoint: .bottomTrailing)
}
}
Com isso em vigor, podemos apenas fornecer uma lista variádica de cores para obter um gradiente linear delas na direção diagonal.
Agora a parte importante: ao invés de adicionar duas sombras invertidas ao nosso círculo pressionado, vamos sobrepor um novo círculo nele, dar uma borda com traço borrado e então mascará-lo com outro círculo que tem um gradiente. Isso é muito, mas deixe-me explicar:
- Nosso círculo base é o círculo de efeito neumórfico que temos agora, preenchido com nossa cor off white.
- Colocamos um círculo sobre isso, bordado com uma borda cinza e borrada um pouco para torná-lo uma borda suave.
- Em seguida, mascaramos esse círculo sobreposto com outro círculo, desta vez preenchido com um gradiente linear.
Quando você mascara uma visualização com outra, SwiftUI usa o canal alfa da máscara para determinar o que deve ser exibido na visualização subjacente. Portanto, se desenhamos uma borda cinza borrada e a mascaramos usando um gradiente linear de preto para transparente, a borda cinza borrada será invisível em um lado e irá esmaecer no outro – obtemos um gradiente interno suave. Para fazer o efeito mais pronunciado, podemos deslocar os círculos com traço borradinho um pouco em qualquer direção, e com um pouco de experimentação eu descobri que desenhar a sombra da luz em uma linha mais espessa do que a sombra escura realmente ajudou a maximizar o efeito.
Lembre-se, o neumorfismo usa duas sombras, uma clara e outra escura, para criar um sentido de profundidade, então adicionaremos este efeito de sombra interna duas vezes com cores diferentes.
Modifique o círculo configuration.isPressed para:
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ê rodar o aplicativo novamente, verá que o efeito de pressionamento do botão é muito mais pronunciado e também parece melhor.

Vindo para o escuro
Antes de começarmos a olhar como podemos melhorar os problemas de acessibilidade do neomorfismo, vamos dar uma olhada em como podemos brincar com o efeito para criar outros estilos interessantes.
Primeiro, adicione mais duas cores à extensão Color, para que tenhamos valores constantes para trabalhar:
static let darkStart = Color(red: 50 / 255, green: 60 / 255, blue: 65 / 255)
static let darkEnd = Color(red: 25 / 255, green: 25 / 255, blue: 30 / 255)
Agora podemos usar isso como o fundo para ContentView, substituindo seu Color.white existente, assim:
var body: some View {
ZStack {
LinearGradient(Color.darkStart, Color.darkEnd)
Nossa SimpleButtonStyle parece fora de lugar agora, porque aplica um estilo brilhante em um fundo escuro. Então, criaremos um novo estilo escuro que funciona melhor, mas desta vez vamos dividi-lo em duas partes: uma view background que podemos aplicar em qualquer lugar e um estilo de botão que envolve isso junto com os modificadores de padding e forma do conteúdo. Isso nos permitirá mais flexibilidade, como você verá.
A nova view background que estamos adicionando nos permitirá especificar qualquer forma para o nosso efeito visual, então não estamos limitados apenas a círculos. Também acompanhará se desenhar um efeito côncavo ou convexo (dentro ou fora) dependendo de uma propriedade isHighlighted que podemos enviar externamente.
Vamos começar simples, usando uma abordagem modificada para obter o efeito pressionado. Adicione essa estrutura agora:
struct DarkBackground<S: Shape>: View {
var isHighlighted: Bool
var shape: S
var body: some View {
ZStack {
if isHighlighted {
shape
.fill(Color.darkEnd)
.shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
.shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)
} else {
shape
.fill(Color.darkEnd)
.shadow(color: Color.darkStart, radius: 10, x: -10, y: -10)
.shadow(color: Color.darkEnd, radius: 10, x: 10, y: 10)
}
}
}
}
A modificação aí é que o tamanho da sombra é reduzido quando o botão é pressionado – usamos uma distância de 5 pontos em vez de 10.
Podemos então envolver isso dentro de um DarkButtonStyle, que aplica padding e forma do conteúdo, assim:
struct DarkButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding(30)
.contentShape(Circle())
.background(
DarkBackground(isHighlighted: configuration.isPressed, shape: Circle())
)
}
}
Finalmente, podemos ativar isso para o nosso botão em ContentView alterando seu modificador buttonStyle():
.buttonStyle(DarkButtonStyle())
Tente e veja o que você acha – mesmo com pouco código, acho que o resultado parece bem legal.

Um pouco de experimentação
Agora é um ótimo momento para experimentar com o efeito, pois isso ajudará você a entender exatamente o que o SwiftUI está fazendo por nós.
Por exemplo, poderíamos criar um botão suave parecido com uma bolha adicionando uma gradiente linear ao botão e invertendo-a quando pressionado:
if isHighlighted {
shape
.fill(LinearGradient(Color.darkEnd, Color.darkStart))
.shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
.shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)
} else {
shape
.fill(LinearGradient(Color.darkStart, Color.darkEnd))
.shadow(color: Color.darkStart, radius: 10, x: -10, y: -10)
.shadow(color: Color.darkEnd, radius: 10, x: 10, y: 10)
}
Se você tentar isso verá que o botão anima suavemente para cima e para baixo conforme você pressiona e solta. Eu acho essa animação um pouco distraente, então recomendo desabilitá-la adicionando esse modificador no método makeBody() de DarkButtonStyle, após o existente background() modificador:
.animation(nil)

Este efeito de botão “travesseiro” é fascinante, mas se você está considerando usar isso eu sugiro que tente três mudanças para ajudar os botões a se destacarem um pouco mais.
Primeiro, embora vá contra o princípio de baixa contraste do design neumórfico, eu desfaria com o ícone cinza e usaria branco em vez disso, então que realmente se destaque. Então, no ContentView, você usaria isso:
Image(systemName: "heart.fill")
.foregroundColor(.white)
Segundo, se você adicionar um overlay no estado pressionado do botão, não só parece mais como um botão físico sendo pressionado plano, mas também ajuda a distinguir seu estado pressionado de seu estado sem pressionar.
Para fazer isso acontecer, você precisa inserir um overlay() modificador após o fill() quando isHighlighted é verdadeiro, como este:
if isHighlighted {
shape
.fill(LinearGradient(Color.darkEnd, Color.darkStart))
.overlay(shape.stroke(LinearGradient(Color.darkStart, Color.darkEnd), lineWidth: 4))
.shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
.shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)

Para uma aparência ainda mais nítida, você poderia remover os dois shadow() modificador para o estado pressionado, que realmente foca no overlay em vez disso.
Terceiro, você também poderia adicionar um overlay ao estado não pressionado, apenas para ajudar a marcar que é um botão. Coloque-o diretamente após o fill() lá também, como este:
} else {
shape
.fill(LinearGradient(Color.darkStart, Color.darkEnd))
.overlay(shape.stroke(Color.darkEnd, lineWidth: 4))
.shadow(color: Color.darkStart, radius: 10, x: -10, y: -10)
.shadow(color: Color.darkEnd, radius: 10, x: 10, y: 10)
}

Adicionando um estilo de alternância
Um dos benefícios de dividir nosso estilo de botão do estilo de fundo neumórfico é que agora podemos adicionar um estilo de alternância usando o mesmo efeito. Isso significa criar uma nova estrutura que atende ao protocolo ToggleStyle, que é semelhante a ButtonStyle exceto:
- Nós devemos ler
configuration.isOnpara determinar se o alternador está habilitado ou não. - Nosso precisa fornecer um botão para lidar com a ação de alternância real, ou pelo menos algum tipo de
onTapGesture()ou similar.
struct EstiloAlternativo: ToggleStyle {
func makeBody(configuracao: Self.Configuration) -> some View {
Botao(acao: {
configuracao.ehLigado.toggle()
}) {
configuracao.rotulo
.padding(30)
.contentShape(Circle())
}
.background(
FundoAlternativo(ehDestacado: configuracao.ehLigado, forma: Circle())
)
}
}
Desejamos colocar um desses em ContentView para que você possa experimentá-lo por conta própria, então comece adicionando essa propriedade lá:
@State private var isToggled = false
A seguir, envolva o botão existente em um VStack com espaçamento de 40 e coloque isso abaixo:
Toggle(isOn: $isToggled) {
Imagem(systemName: "coracao.preenchido")
.foregroundColor(.white)
}
.toggleStyle(EstiloAlternativo())
Sua estrutura ContentView deve ficar assim:
struct ContentView: View {
@State private var isToggled = false
var body: some View {
ZStack {
Gradiente(Color.darkStart, Color.darkEnd)
VStack(spacing: 40) {
Botao(acao: {
print("Botão pressionado")
}) {
Imagem(systemName: "coracao.preenchido")
.foregroundColor(.white)
}
.buttonStyle(DarkButtonStyle())
Toggle(isOn: $isToggled) {
Imagem(systemName: "coracao.preenchido")
.foregroundColor(.white)
}
.toggleStyle(EstiloAlternativo())
}
}
.edgesIgnoringSafeArea(.all)
}
}
E é isso – compartilhamos nosso design neumórfico comum em dois lugares!
Corrigindo a acessibilidade
Tivemos tempo suficiente para brincar com os vários estilos neumórficos, mas agora quero abordar seus problemas de frente: a falta extrema de contraste significa que botões e outros controles importantes não se destacam o suficiente em relação às coisas ao seu redor, o que dificulta a utilização dos aplicativos para pessoas com deficiências visuais.
Este é um ponto onde já vi alguma confusão circulando, então quero dizer algumas coisas de frente:
- Sim, percebo que os botões padrão da Apple parecem apenas texto azul e, portanto, não “parecem” botões pelo menos no início, mas eles tem uma razão de contraste alta.
- Não é suficiente dizer “funciona para mim, posso adicionar uma opção especial para torná-lo mais acessível”, a acessibilidade não é um “bom recurso para ter”, ela é uma exigência, então nossos aplicativos devem ser acessíveis por padrão em vez de “acessíveis opt-in”.
- Os controles que criamos ainda são botões e interruptores SwiftUI, o que significa que todas as nossas alterações não afetaram sua visibilidade ou funcionalidade para VoiceOver ou outras tecnologias assistivas da Apple.
Você já viu como poderíamos trocar ícones cinzentos por brancos para obter um impulso imediato no contraste, mas botões e interruptores ainda precisam de muito mais contraste se quiserem ser acessíveis.
Portanto, vamos explorar as mudanças que podemos fazer para ajudar as coisas a realmente se destacarem.
Primeiro, gostaria que você adicionasse duas novas cores à nossa extensão:
static let lightStart = Color(red: 60 / 255, green: 160 / 255, blue: 240 / 255)
static let lightEnd = Color(red: 30 / 255, green: 80 / 255, blue: 120 / 255)
Segundo, duplique seu DarkBackground existente e chame a cópia de FundoAlternativo. Vamos editar isso em um momento, mas precisamos terminar o resto da configuração primeiro.
Terceiro, duplique o estilo de botão escuro e interruptor, renomeie-os para ColorfulButtonStyle e ColorfulToggleStyle, então faça com que eles usem o novo FundoAlternativo como fundo.
Então, devem ficar assim:
struct ColorfulButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding(30)
.contentShape(Circle())
.background(
ColorfulBackground(isHighlighted: configuration.isPressed, shape: Circle())
)
.animation(nil)
}
}
struct ColorfulToggleStyle: ToggleStyle {
func makeBody(configuration: Self.Configuration) -> some View {
Button(action: {
configuration.isOn.toggle()
}) {
configuration.label
.padding(30)
.contentShape(Circle())
}
.background(
ColorfulBackground(isHighlighted: configuration.isOn, shape: Circle())
)
}
}
E finalmente, edite o botão e o toggle em ContentView para usar o novo estilo de botão:
Button(action: {
print("Botão pressionado")
}) {
Image(systemName: "heart.fill")
.foregroundColor(.white)
}
.buttonStyle(ColorfulButtonStyle())
Toggle(isOn: $isToggled) {
Image(systemName: "heart.fill")
.foregroundColor(.white)
}
.toggleStyle(ColorfulToggleStyle())
Você pode executar o aplicativo se quiser, mas não há muito sentido - ele não mudou de fato.
Para trazer nossa versão colorida à vida, vamos alterar os modificadores fill() e overlay() para os estados pressionado e não pressionado. Assim, quando isHighlighted for verdadeiro, mude darkStart e darkEnd para lightStart e lightEnd, como este:
if isHighlighted {
shape
.fill(LinearGradient(Color.lightEnd, Color.lightStart))
.overlay(shape.stroke(LinearGradient(Color.lightStart, Color.lightEnd), lineWidth: 4))
.shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
.shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)
Se você executar o aplicativo novamente verá que já é uma grande melhoria: o estado pressionado agora tem um forte tom de azul, então fica claro quando os botões estão pressionados ou quando os toggles estão ativos. Mas ainda podemos fazer melhor - podemos adicionar a mesma cor ao redor do botão quando ele não está pressionado, ajudando a chamar atenção para ele.

Para fazer isso, altere o overlay() existente para o estado não pressionado para este:
.overlay(shape.stroke(LinearGradient(Color.lightStart, Color.lightEnd), lineWidth: 4))
Sendo assim, o estilo de botão finalizado deve parecer com isto:
ZStack {
if isHighlighted {
shape
.fill(LinearGradient(Color.lightEnd, Color.lightStart))
.overlay(shape.stroke(LinearGradient(Color.lightStart, Color.lightEnd), lineWidth: 4))
.shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
.shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)
} else {
shape
.fill(LinearGradient(Color.darkStart, Color.darkEnd))
.overlay(shape.stroke(LinearGradient(Color.lightStart, Color.lightEnd), lineWidth: 4))
.shadow(color: Color.darkStart, radius: 10, x: -10, y: -10)
.shadow(color: Color.darkEnd, radius: 10, x: 10, y: 10)
}
}
Agora execute o aplicativo novamente e você verá que há um anel azul ao redor dos botões e toggles, e ele preenche com azul quando pressionado - é muito mais claro.

Sempre disse que você deve manter o foco na acessibilidade, e isso significa mais do que apenas garantir que VoiceOver funcione com sua interface. Certifique-se de que seus botões pareçam interativos, certifique-se de que suas legendas de texto e ícones têm contraste suficiente em relação ao fundo (pelo menos 4,5:1, mas busque por 7:1) e garanta que as áreas de toque sejam grandes o suficiente (no mínimo 44x44 pontos).
Portanto, use a design neumórfico para experimentar e para ajudar a aumentar seu conhecimento sobre SwiftUI, mas nunca esqueça de que, se você sacrificar a usabilidade por uma nova tendência de design sofisticada, ninguém ganha.
Baixe o código-fonte completo deste projeto: Baixar do GitHub

