16/08/2018 Marcos Tanaka

Criando onboarding com UICollectionView e Storyboard

Sabe aquelas telas de boas-vindas que vemos quando abrimos um app pela primeira vez? Este processo é feito para familiarizar o usuário com o app e suas funções. É uma oportunidade para apresentar as principais funcionalidades ou diferenciais do app, novidades de uma versão, ou tentar convencer usuários a se registrarem no serviço.

Neste tutorial irei ensinar como criar um onboarding no iOS usando UICollectionView e Storyboard. Os controllers das views serão escritos em Swift 4. Se quiser baixar o projeto completo, siga este link para o GitHub.

Storyboard

Acho legal usar o storyboard pra desenhar as views porque economiza muito tempo e linhas de código, fora a vantagem de poder ver todo o fluxo de telas da aplicação de uma vez!

Por isso, vamos criar um novo projeto, abrir o storyboard, remover a View Controller padrão e colocar em seu lugar uma Collection View Controller. Com a ajuda do Document Outline, selecione a Collection View que está dentro da Collection View Controller. Queremos alterar algumas propriedades para que ela se comporte como um onboarding com scroll horizontal:

Criando um projeto com storyboard

Além disso, vamos desmarcar as propriedades Show Horizontal Indicator e Show Vertical Indicator para que as barras de rolagem não apareçam. Desmarque também Bounce On Scroll e Bounce On Zoom, para que não ocorra o efeito de bounce ao fazer o scroll. E também marque a opção Paging Enabled, para que o scroll não pare entre uma página e outra.

Por fim, ainda no painel Utilities, na aba Size inspector, vamos alterar as propriedades Min Spacing For Cells e Min Spacing For Lines para 0. Isto fará qua não haja espaço entre as células, para que cada uma ocupe de fato a tela inteira (este tamanho vamos definir em código mais adiante).

A propriedade Items da Collection View controla quantas células ela possui. Neste onboarding faremos com que a célula ocupe a tela inteira. Assim, Items dirá quantas páginas vamos ter no onboarding.

Vamos agora desenhar o protótipo da célula da Collection View. Todas as células seguirão este protótipo, mudaremos apenas as informações de cada uma. O pequeno quadrado no canto superior esquerdo da Collection View (🔬) é o protótipo da célula. Dentro dele ficarão os componentes visuais que compõem o protótipo. Para conseguirmos posicioná-los, aumente o tamanho da célula (não se preocupe com o tamanho real, pois definiremos isso programaticamente para preencher a tela inteira).

O layout será uma Image View no topo, com dois Labels em baixo e um Button. Para diminuir o número de Constraints, empilharemos tudo em uma Stack View vertical:

Vamos configurar a Stack View para ter Distribution: Equal Centering e Spacing: Standard. Depois, centralizá-la e definir a largura de 260px usando constraints.

Para os Labels dentro da Stack View, defina a propriedade Lines: 0 para que textos maiores quebrem a linha automaticamente. O primeiro label será o título, então usaremos a fonte com Style: Bold e Size: 24px.

Para a Image View, vamos adicionar uma constraint de altura fixa 120px. Além disso, o Content Mode será Aspect Fit.

E o Button terá a constraint de altura e largura fixos. A largura será >= 260px, e a altura será 40px. Assim ele ocupa a largura inteira da Stack View.

Código

Vamos agora escrever as classes por trás das views que desenhamos no storyboard. Precisaremos das seguintes:

  • OnboardingCollectionViewController: responsável pela Collection View – data source e delegate.
  • OnboardingCollectionViewCell: responsável por preencher os dados de cada célula da Collection View.
  • OnboardingModel: struct que define a estrutura dos dados apresentados no onboarding.
  • OnboardingModelFactory: para facilitar a instanciação dos modelos que serão usados no onboarding.

Seguem abaixo:

import UIKit
class OnboardingCollectionViewController: UICollectionViewController {
}
extension OnboardingCollectionViewController: UICollectionViewDelegateFlowLayout {
}
import UIKit
class OnboardingCollectionViewCell: UICollectionViewCell {
static let reuseIdentifier = OnboardingCell
}

import UIKit
struct OnboardingModel {
let title: String
let content: String
let iconImage: String
let backgroundColor: UIColor
let hideButton: Bool
}

import UIKit
class OnboardingModelFactory {
static func getPages() -> [OnboardingModel] {
return [
OnboardingModel(title: Feature 1,
content: Esta feature faz com que o aplicativo faça coisas incríveis!,
iconImage: iconOnboarding1,
backgroundColor: Color.lightPurple,
hideButton: true),
OnboardingModel(title: Feature 2,
content: Com esta feature, o aplicativo se diferencia dos concorrentes, pois faz X, Y e Z.,
iconImage: iconOnboarding2,
backgroundColor: Color.lightGreen,
hideButton: true),
OnboardingModel(title: Feature 3,
content: Concluindo o porque este aplicativo é demais, e convidando o usuário a começar.,
iconImage: iconOnboarding3,
backgroundColor: Color.lightOrange,
hideButton: false)
]
}
}

UICollectionViewController

Depois de criar os arquivos, volte para o storyboard e defina as classes da Collection View Controller e da Collection View Cell. Além disso, defina o Reuse Identifier da célula como “OnboardingCell” (painel Utilities, na aba Attributes inspector).

Vamos agora implementar os métodos da classe OnboardingCollectionViewController. Nela definiremos a quantidade de páginas do onboarding, as informações de cada página, o tamanho (tela inteira), e também colocaremos um UIPageControl para indicar ao usuário qual página ele está atualmente.

import UIKit
class OnboardingCollectionViewController: UICollectionViewController {
private let pages = OnboardingModelFactory.getPages()
private lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.numberOfPages = pages.count
return pageControl
}()
override func viewDidLoad() {
super.viewDidLoad()
addPageControlToView()
}
private func addPageControlToView() {
view.addSubview(pageControl)
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8).isActive = true
pageControl.heightAnchor.constraint(equalToConstant: 40).isActive = true
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
}
extension OnboardingCollectionViewController {
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pages.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: OnboardingCollectionViewCell.reuseIdentifier, for: indexPath)
as? OnboardingCollectionViewCell else { fatalError(Could not cast dequeued cell) }
cell.fill(with: pages[indexPath.row])
return cell
}
}
extension OnboardingCollectionViewController: UICollectionViewDelegateFlowLayout {
// MARK: UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
}
override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageNumber = Int(targetContentOffset.pointee.x / view.frame.width)
pageControl.currentPage = pageNumber
}
}

Usando a factory criada anteriormente, instanciamos os conteúdos das três páginas de onboarding e colocamos na constante pages. Ela é a fonte de dados que alimenta a Collection View. Criamos também um UIPageControl e adicionamos programaticamente na view, definindo as constraints no método ddPageControlToView().

O tamanho de cada célula da Collection View é definido no método collectionView(_:layout:sizeForItemAt:). O tamanho será igual ao tamanho da Collection View, ou seja, a tela inteira.

O UIPageControl é atualizado no método scrollViewWillEndDragging(_:withVelocity:targetContentOffset:).

UICollectionViewCell

A OnboardingCollectionViewCell tem referência para os componentes de cada célula, e um método responsável por preencher essas referências com os dados:


Definimos também uma constante estática para o reuse identifier da célula (o mesmo definido no storyboard). Vamos usar essa constante no view controller, para referenciar a célula que será reaproveitada. A classe final ficará assim:

import UIKit
class OnboardingCollectionViewCell: UICollectionViewCell {
static let reuseIdentifier = OnboardingCell
@IBOutlet weak var pageImage: UIImageView!
@IBOutlet weak var pageTitle: UILabel!
@IBOutlet weak var pageDescription: UILabel!
@IBOutlet weak var startButton: UIButton!
func fill(with data: OnboardingModel) {
// Se quiser imagem no background, descomentar as linhas abaixo
// e criar atributo backgroundImage no OnboardingModel
// let backgroundImage = UIImageView(image: UIImage(named: data.backgroundImage))
// backgroundImage.contentMode = .scaleAspectFill
// backgroundView = backgroundImage
backgroundColor = data.backgroundColor
pageImage.image = UIImage(named: data.iconImage)
pageTitle.text = data.title
pageDescription.text = data.content
startButton.alpha = data.hideButton ? 0 : 1
}
}

Perceba também que as primeiras instruções do método fill definem a cor de background da célula com base no OnboardingModel recebido.

Resources

O último passo é definir os resources usados em cada página. Basta editar o arquivo Assets.xcassets, dar um nome para cada recurso e arrastar as imagens para os respectivos slots:


Obs.: em um projeto real você vai querer definir as imagens para as resoluções 1x, 2x e 3x, para que a correta seja utilizada conforme a densidade de pixels do aparelho. Para facilitar este exemplo, estou usando uma imagem única para as três densidades.

Alguns detalhes

Fora o que está descrito aqui, melhorei alguns outros detalhes como o espaçamento entre a imagem e os textos, alguns detalhes do botão, e a organização dos arquivos do projeto. A ideia é que ele leve à view inicial do app, ou para o login. Recomendo baixar o projeto do GitHub para ver como ficou o resultado final.

Comentários

comentários

Sobre o autor

Marcos Tanaka Marcos Tanaka é desenvolvedor de software com experiência em tecnologias como Java, Spring Framework, Hibernate, JavaScript, Node.js, Swift, iOS, Kotlin, Python e Machine Learning. Criador dos aplicativos Kiwi Queue e Really Simple Finance. Você pode encontrá-lo no LinkedIn e aqui: https://about.me/marcosatanaka