Olá a todos! Em antecipação ao lançamento do curso avançado “IOS Developer”, publicamos uma tradução da segunda parte do artigo sobre neomorfismo usando o SwiftUI ( leia a primeira parte ).
Tema escuro
Antes de começarmos a entender como podemos melhorar a disponibilidade do neomorfismo, vamos ver como podemos trabalhar com o efeito para criar outros estilos interessantes.Primeiro adicione mais duas cores à extensão, Color
para que tenhamos vários valores constantes à mão: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)
Podemos usá-los como pano de fundo ContentView
, substituindo o existente Color.white
, conforme mostrado aqui:var body: some View {
ZStack {
LinearGradient(Color.darkStart, Color.darkEnd)
O nosso SimpleButtonStyle
agora parece fora de lugar, porque impõe uma estilização brilhante em um fundo escuro. Então, criaremos um novo estilo escuro que funcione melhor aqui, mas desta vez vamos dividi-lo em duas partes: uma visualização em segundo plano que podemos usar em qualquer lugar, e um estilo de botão que o envolva com os modificadores de preenchimento e contentShape. Isso nos dará mais flexibilidade, como você verá mais adiante.A nova visualização de plano de fundo que adicionaremos nos permitirá especificar qualquer forma para nosso efeito visual, para que não fiquemos mais apegados a círculos. Ele também acompanhará se côncava nosso efeito convexo (interno ou externo), dependendo da propriedade isHighlighted
que podemos mudar do lado de fora.Começaremos com o mais simples, usando uma abordagem de inversão de sombra modificada para obter um efeito côncavo. Adicione esta estrutura: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 é que, quando o botão é pressionado, o tamanho da sombra diminui - usamos uma distância de 5 pontos em vez de 10.Em seguida, podemos envolvê-lo com a ajuda de DarkButtonStyle
padding e contentShape, como mostrado aqui: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 aplicar isso ao nosso botão ContentView
alterando-o ButtonStyle()
:.buttonStyle(DarkButtonStyle())
Vamos ver o que aconteceu - embora não tenhamos muito código, acho que o resultado parece bom o suficiente.
Algumas experiências
Agora é a hora de experimentar o efeito, porque ele ajudará você a entender melhor do que o SwiftUI é especificamente capaz.Por exemplo, podemos criar um botão convexo suave adicionando um gradiente linear a ele e girá-lo 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ê executar isso, verá que o botão anima suavemente para cima e para baixo quando pressionado e solto. Eu acho que a animação é um pouco perturbadora, por isso recomendo desativá-la adicionando esse modificador ao método makeBody()
from DarkButtonStyle
, após o modificador presente lá background()
:.animation(nil)
Esse efeito do botão de almofada é encantador, mas se você planeja usá-lo, aconselho a tentar as três alterações a seguir para destacar o botão um pouco mais.Em primeiro lugar, apesar do fato de que isso contradiz o princípio de baixo contraste do design neomórfico, eu substituiria o ícone cinza por um ícone branco para destacá-lo. Então, ContentView
você precisaria do seguinte:Image(systemName: "heart.fill")
.foregroundColor(.white)
Em segundo lugar, se você adicionar sobreposição para o botão no estado pressionado, isso não apenas fará com que pareça mais com um botão físico real pressionado uniformemente, mas também ajudará a distinguir seu estado pressionado do não pressionado.Para implementar isso, você precisa inserir o modificador overlay()
depois fill()
quando isHighlighted
é verdade, como aqui: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 obter uma aparência ainda mais nítida, você pode remover dois modificadores shadow()
para o estado pressionado que se concentram na sobreposição.Em terceiro lugar, você também pode adicionar sobreposição ao estado não compactado, apenas para marcar que é um botão. Coloque-o imediatamente depois fill()
, assim:} 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 switch
Uma das vantagens de separar um estilo de botão de um estilo de fundo não-mórfico é que agora podemos adicionar um estilo de opção usando o mesmo efeito. Isso significa criar uma nova estrutura compatível com protocolo ToggleStyle
semelhante a ButtonStyle
, exceto que:- Precisamos ler
configuration.isOn
para determinar se o interruptor está ativado. - Precisamos fornecer um botão para lidar com o ato de mudar, ou pelo menos algo assim
onTapGesture()
ou algo assim .
Adicione esta estrutura ao seu projeto:struct DarkToggleStyle: ToggleStyle {
func makeBody(configuration: Self.Configuration) -> some View {
Button(action: {
configuration.isOn.toggle()
}) {
configuration.label
.padding(30)
.contentShape(Circle())
}
.background(
DarkBackground(isHighlighted: configuration.isOn, shape: Circle())
)
}
}
Queremos colocar um deles ContentView
para que você possa testá-lo, então comece adicionando esta propriedade:@State private var isToggled = false
Em seguida, enrole o botão existente VStack
com espaçamento igual a 40 e coloque-o abaixo:Toggle(isOn: $isToggled) {
Image(systemName: "heart.fill")
.foregroundColor(.white)
}
.toggleStyle(DarkToggleStyle())
Sua estrutura ContentView
deve ficar assim:struct ContentView: View {
@State private var isToggled = false
var body: some View {
ZStack {
LinearGradient(Color.darkStart, Color.darkEnd)
VStack(spacing: 40) {
Button(action: {
print("Button tapped")
}) {
Image(systemName: "heart.fill")
.foregroundColor(.white)
}
.buttonStyle(DarkButtonStyle())
Toggle(isOn: $isToggled) {
Image(systemName: "heart.fill")
.foregroundColor(.white)
}
.toggleStyle(DarkToggleStyle())
}
}
.edgesIgnoringSafeArea(.all)
}
}
E é tudo - aplicamos nosso design neomórfico comum em dois lugares!Melhoria da acessibilidade
Tivemos tempo suficiente para brincar com vários estilos neomórficos, mas agora quero me concentrar nos problemas desse design: uma extrema falta de contraste significa que botões e outros controles importantes não são suficientes para se destacar do ambiente, o que dificulta o uso de nossos aplicativos para pessoas com visão imperfeita.Este é o momento em que observo alguns mal-entendidos, por isso quero dizer algumas coisas com antecedência:- Sim, entendo que os botões padrão da Apple se parecem com texto azul e, portanto, não se parecem com botões familiares, pelo menos à primeira vista, mas eles têm uma alta taxa de contraste.
- « , , » — — « », , , - .
- - SwiftUI, , VoiceOver Apple.
Você já viu como alteramos o ícone cinza para branco para obter aprimoramento instantâneo do contraste, mas os botões e interruptores ainda precisam de muito mais contraste se você quiser torná-los mais acessíveis.Então, vamos considerar quais mudanças poderíamos fazer para que os elementos realmente se destacassem.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)
Em segundo lugar, duplique o existente DarkBackground
e nomeie a cópia ColorfulBackground
. Nós vamos lidar com isso em um momento, mas, novamente, primeiro precisamos fazer alguma preparação.Terceiro, duplique o estilo escuro do botão e alterne, renomeie-o para ColorfulButtonStyle
e ColorfulToggleStyle
, em seguida, faça-os usar o novo ColorfulBackground
como plano de fundo.Então eles 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 alterne ContentView
para usar o novo estilo:Button(action: {
print("Button tapped")
}) {
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 isso não faz sentido - ele não mudou.Para trazer nossa versão colorida da vida, mudaremos os modificadores fill()
e overlay()
os estados pressionado e não pressionado. Assim, quando isHighlighted
verdadeiro, mudar darkStart
tanto 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 ele já melhorou significativamente: o estado pressionado agora possui uma cor azul brilhante, tornando-se claro quando os botões são pressionados e os comutadores estão ativos. Mas podemos fazer outra coisa - podemos adicionar a mesma cor ao redor do botão quando ele não é pressionado, ajudando a chamar a atenção para ele.
Para fazer isso, altere o overlay()
estado não estressado existente para isso:.overlay(shape.stroke(LinearGradient(Color.lightStart, Color.lightEnd), lineWidth: 4))
Portanto, o estilo do botão final deve ficar assim: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 um anel azul apareceu ao redor dos botões e interruptores e, quando clicado, é preenchido com azul - é muito mais acessível.
Conclusão
Na prática, você não terá vários estilos de botão diferentes em um projeto, pelo menos se não gostar de criar uma dor de cabeça para seus usuários. Mas este é um espaço interessante para experimentação, e espero que você entenda essa ideia do meu artigo, porque você pode criar efeitos realmente bonitos sem rasgar suas costas.Eu disse repetidamente que você deve sempre monitorar a disponibilidade do seu aplicativo, e isso significa mais do que apenas garantir que o VoiceOver funcione com a interface do usuário. Certifique-se de que seus botões pareçam interativos, verifique se os rótulos e os ícones de texto têm uma taxa de contraste suficiente em relação ao plano de fundo (pelo menos 4,5: 1, mas tendem a 7: 1) e verifique se as áreas clicáveis são confortáveis e grande (pelo menos 44x44 pixels).E, pelo amor de Deus, use o design neomórfico para experimentar e expandir seu conhecimento do SwiftUI, mas nunca se esqueça de que, se você sacrifica a usabilidade por uma nova tendência de design de moda, não ganha nada.Você pode obter o código-fonte completo para este projeto no GitHub .Leia a primeira parte.
Saiba mais sobre o curso.