Alura > Cursos de Mobile > Cursos de iOS > Conteúdos de iOS > Primeiras aulas do curso iOS com SwiftUI: trabalhando com animações e DragGesture

iOS com SwiftUI: trabalhando com animações e DragGesture

Dando os primeiros passos com animações - Apresentação

Olá! Meu nome é Giovanna Moeller, eu sou instrutora na Alura, e quero te dar boas-vindas ao curso de iOS com SwiftUI voltado para animações!

Giovanna é uma mulher branca de cabelo loiro, liso e comprido, e olhos castanho-escuro. Ela veste uma blusa azul-escuro e está nos estúdios da Alura, sentada em uma cadeira preta em frente a uma parede cinza iluminada em roxo, com uma estante preta com plantas e enfeites à direita.

O que vamos fazer?

Nosso projeto será o aplicativo Chef Delivery e vamos implementar a ele novas funcionalidades, como uma tela de boas-vindas focada em animar os componentes, que são:

Além disso, vamos implementar um carrossel na página inicial de forma totalmente automática.

Nesse curso, entenderemos como o SwiftUI consegue tornar determinadas coisas muito mais simples e fáceis para nós, em relação à questão das animações. Uma tela com esse tipo de animação torna a experiência da pessoa usuária muito mais agradável, além de ser mais agradável também visualmente.

Pré-requisitos

Como pré-requisito desse curso, é importante que você já tenha certo conhecimento sobre a construção de layouts simples com Swift UI, e também sobre gerenciamento de estados com Swift UI.

Mas não se preocupe! Temos todos esses conteúdos disponíveis na plataforma, basta acessar a página da nossa escola de Mobile.

Em caso de dúvida, lembre-se de nos chamar no fórum e também no Discord, onde você vai encontrar outras pessoas que estão fazendo esse curso ou já finalizaram!

Espero que você tenha se animado para começar a implementar de fato as animações do projeto e deixar seu aplicativo muito mais bonito.

Te espero para a primeira aula!

Dando os primeiros passos com animações - Animando um texto

Nossa tarefa para esse curso será criar a tela de boas-vindas exibida abaixo, que consiste em bastante animação dos componentes!

Simulador de dispositivo móvel exibindo uma tela composta por: um fundo branco com dois círculos vermelhos desfocados, nos cantos superior esquerdo e inferior direito; o título "Chef Delivery" em vermelho centralizado no topo; o texto "Peça as suas comidas favoritas no conforto da sua casa." em preto centralizado abaixo; a ilustração de batatas fritas em um pote vermelho, um hambúrguer, e um copo vermelho com um canudo branco no centro da tela; e um botão retangular vermelho-claro de bordas arredondadas intitulado "Descubra mais" centralizado na base da tela, com um círculo vermelho na lateral esquerda do botão, contendo duas setas brancas para a direita.

O que são animações?

Animações são técnicas visuais que adicionam movimento à interface da pessoa usuária. Elas trazem dinamismo e interatividade, então são elementos incríveis para a experiência da pessoa usuária.

Eu, particularmente, acho incríveis os aplicativos que contêm animações, por isso reservamos um curso específico para animações com o SwiftUI.

Animando um texto

Criando a tela de boas-vindas

Retornando ao projeto, nosso primeiro passo será criar a tela de boas-vindas. Para isso, criaremos uma nova pasta dentro de "ChefDelivery", chamada "HomeView".

Dentro dela, vamos criar um novo arquivo do tipo "SwiftUI View" chamado HomeView.swift.

import SwiftUI

struct HomeView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

Como essa será a primeira tela do nosso aplicativo, precisamos fazer uma modificação no arquivo ChefDeliveryApp.swift, que por enquanto tem como primeira tela a ContentView(). Vamos substituí-la por HomeView().

import SwiftUI

@main
struct ChefDeliveryApp: App {
    var body: some Scene {
        WindowGroup {
            HomeView()
        }
    }
}

Construindo o layout

Feito isso, podemos retornar ao arquivo HomeView.swift para começar a construir o layout. Primeiramente, vamos remover o elemento Text() e adicionar uma VStack.

import SwiftUI

struct HomeView: View {
    var body: some View {
        VStack {
        
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

Entre as chaves dessa VStack, vamos usar o Text() para adicionar um texto, que será "Chef Delivery". Em seguida, colocaremos alguns modificadores de propriedade para esse componente.

Adicionando modificadores

O primeiro deles será relacionado à fonte (.font()). Queremos que ela seja bem grande, então vamos usar um tamanho fixo nesse caso. Para isso, passamos .system() para o modificador e adicionamos a ele a propriedade size com o valor 40.

Também vamos usar o modificador .fontWeight(), passando o .heavy como parâmetro.

Por fim, vamos utilizar um modificador de cor, o .foregroundColor(). Passaremos para ele a cor "ColorRed", que está dentro de "Assets".

Para finalizar, vamos adicionar o componente Spacer(), para que seja ocupado todo o espaço disponível e o texto seja posicionado na extremidade superior da tela.

import SwiftUI

struct HomeView: View {
    var body: some View {
        VStack {
            Text("Chef Delivery")
                .font(.system(size: 48))
                .fontWeight(.heavy)
                .foregroundColor(Color("ColorRed"))
                
            Spacer()
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

Simulador de dispositivo móvel exibindo um fundo branco com o texto "Chef Delivery" escrito em vermelho e em negrito em tamanho grande, centralizado no topo da tela.

Criando o efeito de opacidade

Agora nosso objetivo é criar um efeito de opacidade, de modo que, quando a tela aparecer, o texto vá de totalmente transparente até o opaco. Para fazer isso, começamos criando uma propriedade de estado (@State) logo abaixo da view HomeView. Ela será do tipo private var e a chamaremos de isAnimating.

Essa propriedade de estado será um booleano, ou seja, só possuirá valores de true (verdadeiro) ou false (falso). Iremos iniciar como false, pois o momento em que a tela aparece é quando mudamos para verdadeiro.

import SwiftUI

struct HomeView: View {

    @State private var isAnimating = false

    var body: some View {
        VStack {
            Text("Chef Delivery")
                .font(.system(size: 48))
                .fontWeight(.heavy)
                .foregroundColor(Color("ColorRed"))
                
            Spacer()
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

Como saber quando a tela estiver aparecendo? Para isso, existe um modificador de propriedade chamado .onAppear(). Vamos aplicá-lo ao VStack, mas em vez de parênteses após o modificador, abriremos chaves.

Dentro do .onAppear {}, passaremos que a propriedade isAnimating é igual a true.

import SwiftUI

struct HomeView: View {

    @State private var isAnimating = false

    var body: some View {
        VStack {
            Text("Chef Delivery")
                .font(.system(size: 48))
                .fontWeight(.heavy)
                .foregroundColor(Color("ColorRed"))
                
            Spacer()
        }
        .onAppear {
            isAnimating = true
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

Agora podemos adicionar os modificadores de propriedade com base nessa propriedade de estado. Por exemplo: no Text(), vamos incluir o modificador .opacity() e, entre parênteses, adicionaremos um operador if ternário.

Relembrando: o if ternário é composto por ponto de interrogação (?) e dois-pontos (:).

Entre os parênteses do modificador, vamos digitar isAnimating seguido de ?. Com isso, queremos dizer que se isAnimating for igual a true, o texto ficará totalmente opaco, então adicionamos o valor 1 após a interrogação. Se a propriedade isAnimating for igual a false, ou seja, :, o valor será 0, sem opacidade.

import SwiftUI

struct HomeView: View {

    @State private var isAnimating = false

    var body: some View {
        VStack {
            Text("Chef Delivery")
                .font(.system(size: 48))
                .fontWeight(.heavy)
                .foregroundColor(Color("ColorRed"))
                .opacity(isAnimating ? 1 : 0)
                
            Spacer()
        }
        .onAppear {
            isAnimating = true
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

Ao executar esse código, o resultado no simulador estará igual ao anterior, praticamente sem acontecer nada, pois o texto já vem diretamente opaco. Isso acontece porque não existe nenhuma transição suave entre o estado do isAnimating.

Ou seja, quando o isAnimating vai de false para true assim que a tela aparece, não existe nenhuma animação suave. Precisamos implementar isso!

Criando o bloco withAnimation()

Existe um bloco de código em SwiftUI chamado withAnimation(), que nos permite adicionar animação às alterações de propriedade. Interessante, não é?

Dentro do modificador .onAppear {}, vamos começar escrevendo withAnimation().

Se digitarmos um ponto (.) entre os parênteses, serão sugeridas várias opções de animação. A minha animação favorita é a easeInOut(), pois ela começa devagar, se torna mais rápida, e depois volta a ser devagar.

Vamos selecionar a opção easeInOut(duration:), para adicionar também a duração da animação. Por padrão, ela dura 0.25 segundos. Nesse caso, vamos definir a duração como 3 segundos, para visualizar melhor a animação acontecer.

/* Código suprimido */ 

.onAppear {
    withAnimation(.easyInOut(duration: 3))
    isAnimating = true
}

/* Código suprimido */ 

Em seguida, vamos adicionar chaves ao final do withAnimation() e deslocar a propriedade isAnimating da linha de código 28 para dentro desse bloco de código.

.onAppear {
    withAnimation(.easeInOut(duration: 3)) {
        isAnimating = true
    }
}

Executando o código com essas modificações, teremos o efeito de opacidade acontecendo com uma animação, que no caso é uma transição suave. Com o bloco de código withAnimation(), podemos animar as variáveis quando elas mudam de estado.

Criando um efeito de deslocamento

Vamos adicionar também um modificador de offset (.offset()). Esse modificador basicamente causa um deslocamento no eixo vertical (y) e no eixo horizontal (x).

Começaremos trabalhando com o eixo vertical, então vamos digitar y e, após dois-pontos, adicionar a propriedade isAnimating seguida de uma interrogação.

Queremos dizer que se a propriedade for verdadeira (valor true), o deslocamento será nulo, então digitamos 0. Se for falsa (valor false), teremos o deslocamento de -40, para criar um efeito do texto se deslocando de cima para baixo.

.offset(y: isAnimating ? 0 : -40)

Executando o código com essa alteração, teremos o efeito desejado do texto descendo, além da transição suave de 3 segundos, conforme definido anteriormente.

Nesse caso, 3 segundos é um tempo longo, que adicionamos apenas para visualizar a animação acontecer. Podemos alterar para 1.5, um tempo interessante para rodar a animação.

.onAppear {
    withAnimation(.easeInOut(duration: 1.5)) {
        isAnimating = true
    }
}

Adicionando o subtítulo

Agora vamos adicionar o subtítulo da tela. Após o componente Text() referente ao título, colocaremos um novo Text(), contendo o texto "Peça as suas comidas favoritas no conforto da sua casa".

Incluiremos também alguns modificadores, como:

É importante mencionar que, às vezes, as animações não funcionam tão bem no simulador, então se acontecer algum bug, não se preocupe, pois o XCode é assim mesmo!

Abaixo, o resultado do código de HomeView.swift até o momento:

import SwiftUI

struct HomeView: View {

    @State private var isAnimating = false

    var body: some View {
        VStack {
            Text("Chef Delivery")
                .font(.system(size: 48))
                .fontWeight(.heavy)
                .foregroundColor(Color("ColorRed"))
                .opacity(isAnimating ? 1 : 0)
                .offset(y: isAnimating ? 0 : -40)

            Text("Peça as suas comidas favoritas no conforto da sua casa.")
                .font(.title2)
                .padding()
                .multilineTextAlignment(.center)
                .foregroundColor(.black.opacity(0.7))
                .opacity(isAnimating ? 1 : 0)
                .offset(y: isAnimating ? 0 : -40)

            Spacer()
        }
        .onAppear {
            withAnimation(.easeInOut(duration: 1.5)) {
                isAnimating = true
            }
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

Simulador igual ao descrito anteriormente, agora com o subtítulo escrito em preto e centralizado logo abaixo do título.

Feito isso, teremos dois efeitos de animação aplicados a ambos os textos adicionados: o primeiro de opacidade, e o segundo de offset.

Conclusão

Você já entendeu um pouco mais sobre animações, como podemos utilizar o bloco withAnimation() para animar quando uma variável tem seu valor alterado, e também como utilizamos o operador ternário para criar modificadores de propriedade com a visualização de animação.

Agora precisamos criar o efeito de desfoque no fundo da tela. Para fazer isso, espero você no próximo vídeo!

Dando os primeiros passos com animações - Criando um efeito de desfoque no fundo

Já temos os textos criados na nossa tela. Agora precisamos criar o fundo!

Criando o efeito de desfoque

O fundo é composto por dois círculos vermelhos que estão com o efeito de desfoque, nos cantos superior esquerdo e inferior direito da tela. Para criar esse fundo, precisamos aprender sobre um container chamado ZStack, pois os círculos estão atrás dos textos, da imagem e do botão.

O ZStack faz referência ao eixo z, que é o eixo de profundidade.

Retornando ao código do arquivo HomeView.swift, vamos pressionar a tecla "Command" e clicar sobre a view VStack. Feito isso, selecionaremos a opção "Embed in ZStack".

Teremos o seguinte resultado no código:

import SwiftUI

struct HomeView: View {

    @State private var isAnimating = false

    var body: some View {

        ZStack {

            VStack {
                Text("Chef Delivery")
                    .font(.system(size: 48))
                    .fontWeight(.heavy)
                    .foregroundColor(Color("ColorRed"))
                    .opacity(isAnimating ? 1 : 0)
                    .offset(y: isAnimating ? 0 : -40)

                Text("Peça as suas comidas favoritas no conforto da sua casa.")
                    .font(.title2)
                    .padding()
                    .multilineTextAlignment(.center)
                    .foregroundColor(.black.opacity(0.7))
                    .opacity(isAnimating ? 1 : 0)
                    .offset(y: isAnimating ? 0 : -40)

                Spacer()
            }
        }
        .onAppear {
            withAnimation(.easeInOut(duration: 1.5)) {
                isAnimating = true
            }
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

O primeiro componente do ZStack será um círculo, então vamos escrever Circle(). Definiremos uma cor de fundo para ele, utilizando o modificador .foregroundColor(). A cor será "ColorRed", salva em "Assets" e utilizada também no título.

Além desse modificador, vamos utilizar o .frame() para diminuir o tamanho desse círculo. Para isso, o parâmetro width será definido como 200.

Agora precisamos adicionar um modificador chamado .position(). Esse modificador irá definir onde o círculo fica na tela. Para isso, ele recebe dois parâmetros: o x e o y, definindo os eixos horizontal e vertical, respectivamente.

Para o x, vamos passar o valor de 50, para que o círculo fique posicionado um pouco mais à esquerda. Lembrando que os eixos x e y iguais a 0, seria correspondente ao canto superior esquerdo da tela. Em seguida, vamos definir o y com o valor de 100, para movê-lo um pouco para baixo em relação à extremidade do topo.

Para criar o efeito de desfoque, podemos utilizar o modificador .blur() contendo o parâmetro radius, que irá receber o valor 60.

Vamos finalizar reduzindo um pouco do vermelho do círculo. Para isso, adicionamos o modificador de propriedade .opacity() e passaremos o valor 0.5.

import SwiftUI

struct HomeView: View {

    @State private var isAnimating = false

    var body: some View {

        ZStack {

            Circle()
                .foregroundColor(Color("ColorRed"))
                .frame(width: 200)
                .position(x: 50, y: 100)
                .blur(radius: 60)
                .opacity(0.5)

/* Código suprimido */

No momento, temos o seguinte resultado no simulador:

Simulador de dispositivo móvel exibindo um fundo branco com o título "Chef Delivery" escrito em vermelho e em negrito em tamanho grande, centralizado no topo da tela; o subtítulo "Peça as suas comidas no conforto da sua casa." centralizado abaixo e escrito em preto; e um círculo vermelho desfocado no canto superior esquerdo da tela, abaixo dos textos.

Como o círculo é o primeiro componente do ZStack, então ele fica atrás dos outros elementos. Caso o VStack viesse antes do círculo, o VStack ficaria atrás do círculo, então a ordem no código importa bastante nesse caso.

Vamos copiar o bloco Circle() que acabamos de criar e colar logo abaixo, pois será adicionado um novo elemento de círculo.

Você deve estar pensando que poderia ser criado um componente para o círculo, já que serão modificadas poucas coisas nesse processo. É verdade, você poderia criar esse componente, mas como não é esse o escopo do nosso curso, vamos manter dessa forma.

Para o segundo círculo, vamos alterar a cor no modificador .foregroundColor de "ColorRed" para "ColorRedDark", para trazer uma diferença entre os componentes.

Até o momento, temos o seguinte:

import SwiftUI

struct HomeView: View {

    @State private var isAnimating = false

    var body: some View {

        ZStack {

            Circle()
                .foregroundColor(Color("ColorRed"))
                .frame(width: 200)
                .position(x: 50, y: 100)
                .blur(radius: 60)
                .opacity(0.5)

            Circle()
                .foregroundColor(Color("ColorRedDark"))
                .frame(width: 200)
                .position(x: 50, y: 100)
                .blur(radius: 60)
                .opacity(0.5)

/* Código suprimido */

Agora temos um problema: gostaríamos de posicionar o segundo círculo no canto inferior direito da tela. Qual será a .position() do x e do y nesse caso? Será que precisamos testar valores até encontrar o resultado desejado? O que aconteceria se estivéssemos utilizando um iPad, por exemplo, cuja dimensão é bem maior?

Existe uma maneira de saber quais são os valores das extremidades de uma tela. Para fazer isso, podemos utilizar um container oferecido pelo SwiftUI chamado GeometryReader, que consegue pegar os valores das dimensões do nosso dispositivo.

Vamos pressionar a tecla "Command" e clicar sobre o ZStack na linha de código 16, para então selecionar a opção "Embed…". Será criado acima do ZStack um elemento Container {}, que vamos renomear para GeometryReader.

Para utilizar o GeometryReader, precisamos ter acesso a uma variável para dar os valores das dimensões, então logo após a abertura de chaves do GeometryReader, vamos adicionar geometry in.

import SwiftUI

struct HomeView: View {

    @State private var isAnimating = false

    var body: some View {

        GeometryReader { geometry in
            ZStack {

                Circle()
                    .foregroundColor(Color("ColorRed"))
                    .frame(width: 200)
                    .position(x: 50, y: 100)
                    .blur(radius: 60)
                    .opacity(0.5)

                Circle()
                    .foregroundColor(Color("ColorRedDark"))
                    .frame(width: 200)
                    .position(x: 50, y: 100)
                    .blur(radius: 60)
                    .opacity(0.5)

                VStack {
                    Text("Chef Delivery")
                        .font(.system(size: 48))
                        .fontWeight(.heavy)
                        .foregroundColor(Color("ColorRed"))
                        .opacity(isAnimating ? 1 : 0)
                        .offset(y: isAnimating ? 0 : -40)

                    Text("Peça as suas comidas favoritas no conforto da sua casa.")
                        .font(.title2)
                        .padding()
                        .multilineTextAlignment(.center)
                        .foregroundColor(.black.opacity(0.7))
                        .opacity(isAnimating ? 1 : 0)
                        .offset(y: isAnimating ? 0 : -40)

                    Spacer()
                }
            }
        }
        .onAppear {
            withAnimation(.easeInOut(duration: 1.5)) {
                isAnimating = true
            }
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

Feito isso, a variável geometry dará todas as informações referentes às extremidades e às posições do nosso dispositivo.

Vamos retornar ao bloco do segundo círculo, e em vez de passar o valor 200 para o parâmetro x, vamos digitar geometry.size.width. Dessa forma, será retornada a largura do dispositivo.

Em seguida, faremos uma conta básica de menos 50, adicionando - 50 após o geometry.size.width, para que o círculo fique posicionado um pouco mais à esquerda na tela.

No caso do y, vamos digitar geometry.size.height, correspondente à altura do dispositivo. Da mesma forma, vamos adicionar - 50 para mover o círculo um pouco para cima.

Circle()
    .foregroundColor(Color("ColorRedDark"))
    .frame(width: 200)
    .position(x: geometry.size.width - 50, y: geometry.size.height - 50)
    .blur(radius: 60)
    .opacity(0.5)

Teremos o seguinte resultado no simulador:

Simulador igual ao descrito anteriormente, agora com um segundo círculo semelhante ao primeiro posicionado no canto inferior direito da tela.

Conclusão

Já temos nosso fundo de desfoque criado, mas ainda precisamos adicionar alguma animação a eles, pois até o momento, quando executamos o código, os textos são animados e os círculos são estáticos.

Vamos trazer um pouco de vida para eles? Te espero para o próximo vídeo!

Sobre o curso iOS com SwiftUI: trabalhando com animações e DragGesture

O curso iOS com SwiftUI: trabalhando com animações e DragGesture possui 74 minutos de vídeos, em um total de 39 atividades. Gostou? Conheça nossos outros cursos de iOS em Mobile, ou leia nossos artigos de Mobile.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda iOS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas