В данной статье возьмем курс на проектировку и реализацию переключателя страниц (view pager) — элемента UI, применяемого во многих приложениях. По итогам обучения получим вот такой результат:
Мы также сделаем ViewPager
переиспользуемым и компонуемым, что даст возможность задействовать его как с верхними вкладками, так и без них.
Исходный код проекта доступен в конце статьи.
Начало работы
Если разложить компонент ViewPager
на составляющие, то станет ясно, что он образован из двух частей: представления со вкладками (tabbed view) в верхней области и представления со страницами (paged view) в нижней. В идеале лучше одновременно использовать оба элемента или ограничиться только вторым. В связи с этим у нас будут 3 основных класса:
ViewPager
объединяетTabbedView
иPagedView
, обеспечивая их совместную работу;TabbedView
— верхний элемент, допускающий выбор одной вкладки за раз;PagedView
— нижний элемент, содержащий “страницы” с возможностью их прокрутки.
PagedView
Начнем с создания простого подкласса UIView
и его протокола делегата:
import UIKit
protocol PagedViewDelegate: AnyObject {
func didMoveToPage(index: Int)
}
class PagedView: UIView {
// MARK: - Инициализация
init() {
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Свойства
public weak var delegate: PagedViewDelegate?
}
Позднее воспользуемся PagedViewDelegate
для отправки уведомлений другим классам при прокрутке пользователем страниц.
За кадром PagedView
привлечет UICollectionView
для реализации разбивки на страницы. Поэтому создаем свойство collectionView
и приводим в соответствие с протоколами UICollectionViewDelegateFlowLayout
и UICollectionViewDataSource
:
import UIKit
protocol PagedViewDelegate: AnyObject {
func didMoveToPage(index: Int)
}
class PagedView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
// MARK: - Инициализация
init() {
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Свойства
public weak var delegate: PagedViewDelegate?
// MARK: - Источник данных
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// 2
}
// MARK: - Делегат макета
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// 3
return CGSize(width: self.collectionView.frame.width,
height: self.collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
// 4
return 0
}
}
Данный код подразумевает выполнение следующих действий:
- В методе
numbersOfItemsInSection
возвращаем общее количество страниц вPagedView
. - В методе
cellForItemAt
возвращаетсяUICollectionViewCell
, представляющий определенную страницу. - Метод
sizeForItemAt
позволяет установить размер для каждой страницы. Они занимают все свободное пространство. - Поскольку интервалы между страницами не требуются, то внутри метода делегата макета
minimumLineSpacingForSectionAt
возвращается 0.
Теперь реализуем фактический UICollectionView
и добавляем его в иерархию представления:
import UIKit
protocol PagedViewDelegate: AnyObject {
func didMoveToPage(index: Int)
}
class PagedView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
// MARK: - Инициализация
init() {
super.init(frame: .zero)
self.setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Свойства
public weak var delegate: PagedViewDelegate?
private lazy var collectionView: UICollectionView = {
// 1
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: layout
)
// 2
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = true
// 3
collectionView.delegate = self
collectionView.dataSource = self
// 4
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
// MARK: - Настройка UI UI Setup
func setupUI() {
// 5
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(collectionView)
collectionView.backgroundColor = .white
NSLayoutConstraint.activate([
collectionView.widthAnchor
.constraint(equalTo: self.widthAnchor),
collectionView.heightAnchor
.constraint(equalTo: self.heightAnchor),
collectionView.centerXAnchor
.constraint(equalTo: self.centerXAnchor),
collectionView.centerYAnchor
.constraint(equalTo: self.centerYAnchor)
])
}
// MARK: - Источник данных
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
}
// MARK: - Делегат макета
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.collectionView.frame.width,
height: self.collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
Последовательно добиваемся следующих результатов:
- Инициализируем
UICollectionViewFlowLayout
с горизонтальным направлением прокрутки. - Скрываем индикатор горизонтальной прокрутки представления коллекции и разрешаем разбивку на страницы.
- Связываем делегата и источник данных представления коллекции.
- Устанавливаем
translatesAutoresizingMaskIntoConstraints
значениеfalse
, так как UI создается программным способом. - Внутри метода
setupUI()
добавляемСоllectionView
на экран и добиваемся, чтобы он занял все свободное пространство. Кроме того, не забываем вызвать этот метод внутри инициализатора.
Теперь приступаем к реализации PageCollectionViewCell
, представляющего каждую страницу:
import UIKit
class PageCollectionViewCell: UICollectionViewCell {
// MARK: - Инициализация
override init(frame: CGRect) {
super.init(frame: frame)
self.setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// 1
public var view: UIView? {
didSet {
self.setupUI()
}
}
private func setupUI() {
guard let view = view else { return }
self.contentView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
// 2
NSLayoutConstraint.activate([
view.leftAnchor
.constraint(equalTo: self.contentView.leftAnchor),
view.topAnchor
.constraint(equalTo: self.contentView.topAnchor),
view.rightAnchor
.constraint(equalTo: self.contentView.rightAnchor),
view.bottomAnchor
.constraint(equalTo: self.contentView.bottomAnchor)
])
}
}
- Создаем свойство
view
, которое представляет содержимое страницы. - Добиваемся заполнения
view
всей ячейки.
Обновляем файл PagedView.swift
:
import UIKit
protocol PagedViewDelegate: AnyObject {
func didMoveToPage(index: Int)
}
class PagedView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
// MARK: - Инициалиация
// 1
init(pages: [UIView] = []) {
self.pages = pages
super.init(frame: .zero)
self.setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Свойства
public weak var delegate: PagedViewDelegate?
// 2
public var pages: [UIView] {
didSet {
self.collectionView.reloadData()
}
}
private lazy var collectionView: UICollectionView = {
...
// 3
collectionView.register(PageCollectionViewCell.self, forCellWithReuseIdentifier: "PageCollectionViewCell")
...
return collectionView
}()
// MARK: - Настройка UI
...
// MARK: - Источник данных
// 4
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PageCollectionViewCell", for: indexPath) as! PageCollectionViewCell
let page = self.pages[indexPath.item]
cell.view = page
return cell
}
// MARK: - Делегат макета
...
}
Вносим следующие корректировки:
- Добавляем параметр
pages
в инициализатор. - Создаем свойство
pages
, после чего перезагружаем представление коллекции. - Регистрируем ранее созданный
PageCollectionViewCell
в представлении коллекции. - Возвращаем количество
pages
в методе источника данныхnumberOfItemsInSection
. Помимо этого, инициализируемPageCollectionViewCell
, снабжаем его страницей и возвращаем внутри метода источника данныхcellForItemAt
.
С компонентом PagedView
работа почти закончена. Осталось лишь внести действие для перемещения страницы программным образом и способ уведомить о нем делегата. С этой целью добавляем методы moveToPage(at index:)
и scrollViewDidEndDecelerating
:
import UIKit
protocol PagedViewDelegate: AnyObject {
func didMoveToPage(index: Int)
}
class PagedView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
// MARK: - Инициализация
...
// MARK: - Свойства
...
// MARK: - Настройка UI
...
// MARK: - Источник данных
...
// MARK: - Действия
// 1
public func moveToPage(at index: Int) {
self.collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
}
// MARK: - Делегат
// 2
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let page = Int(self.collectionView.contentOffset.x / self.collectionView.frame.size.width)
self.delegate?.didMoveToPage(index: page)
}
// MARK: - Делегат макета
...
}
- При вызове
moveToPage(at index:)
представление коллекции прокручивается до заданной страницы. - Как только пользователь закончил прокрутку страницы, получаем ее индекс и уведомляем об этом делегата
PagedView
. Как упоминалось ранее, делегатом может быть другое представление или его контроллер. При намерении использовать толькоPagedView
делегатом обычно становится контроллер представления. В случае примененияViewPager
, содержащего иPagedView
, иTabbedView
поверх него, он послужит делегатомPagedView
.
Закончив с PagedView
переходим к созданию компонента TabbedView
со вкладками. За раз может быть выбрана только одна из них:
TabbedView
Начнем с создания подкласса UIView
, представленного ниже:
import UIKit
// 1
protocol TabbedViewDelegate: AnyObject {
func didMoveToTab(at index: Int)
}
class TabbedView: UIView {
// 2
enum SizeConfiguration {
case fillEqually(height: CGFloat, spacing: CGFloat = 0)
case fixed(width: CGFloat, height: CGFloat, spacing: CGFloat = 0)
var height: CGFloat {
switch self {
case let .fillEqually(height, _):
return height
case let .fixed(_, height, _):
return height
}
}
}
// MARK: - Жизненный цикл
// 3
init(sizeConfiguration: SizeConfiguration) {
self.sizeConfiguration = sizeConfiguration
super.init(frame: .zero)
self.setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Свойства
weak var delegate: TabbedViewDelegate?
public let sizeConfiguration: SizeConfiguration
// 4
private var currentlySelectedIndex: Int = 0
// 5
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = .zero
let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: layout
)
collectionView.backgroundColor = .white
collectionView.dataSource = self
collectionView.delegate = self
collectionView.showsHorizontalScrollIndicator = false
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
// MARK: Настройка UI
// 6
private func setupUI() {
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.leftAnchor
.constraint(equalTo: self.leftAnchor),
collectionView.topAnchor
.constraint(equalTo: self.topAnchor),
collectionView.rightAnchor
.constraint(equalTo: self.rightAnchor),
collectionView.bottomAnchor
.constraint(equalTo: self.bottomAnchor)
])
}
}
Здесь мы выполняем следующие действия:
- Добавляем
TabbedViewDelegate
для отправки уведомлений по факту выбора пользователем определенной вкладки. - Перечисление
SizeConfiguration
позволяет либо задать размер для вкладок, либо заполнить ими свободное пространство. Благодаря.fillEqually
SizeConfiguration
UI выглядит так:
3. В инициализаторе устанавливаем свойство sizeConfiguration
и запускаем метод setupUI
.
4. Свойство currentlySelectedIndex
помогает отслеживать выбранную вкладку.
5. Также как и в PagedView
создаем горизонтальный UICollectionView
.
6. Метод setupUI
размещает представление коллекции на экране и заполняет им свободное пространство.
Следующая задача — реализовать UICollectionViewCell
, представляющий вкладку. Создаем новый класс TabCollectionViewCell
:
import UIKit
// 1
protocol TabItemProtocol: UIView {
func onSelected()
func onNotSelected()
}
class TabCollectionViewCell: UICollectionViewCell {
// MARK: - Инициализация
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Свойства
// 2
public var view: TabItemProtocol? {
didSet {
self.setupUI()
}
}
// 3
var leftConstraint = NSLayoutConstraint()
var topConstraint = NSLayoutConstraint()
var rightConstraint = NSLayoutConstraint()
var bottomConstraint = NSLayoutConstraint()
public var contentInsets: UIEdgeInsets = UIEdgeInsets(
top: 0,
left: 0,
bottom: 0,
right: 0
) {
didSet {
leftConstraint.constant = contentInsets.left
topConstraint.constant = contentInsets.top
rightConstraint.constant = -contentInsets.right
bottomConstraint.constant = -contentInsets.bottom
self.contentView.layoutIfNeeded()
}
}
// MARK: - Настройка UI
// 4
private func setupUI() {
guard let view = view else { return }
self.contentView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
leftConstraint = view.leftAnchor
.constraint(equalTo: self.contentView.leftAnchor,
constant: contentInsets.left)
topConstraint = view.topAnchor
.constraint(equalTo: self.contentView.topAnchor,
constant: contentInsets.top)
rightConstraint = view.rightAnchor
.constraint(equalTo: self.contentView.rightAnchor,
constant: -contentInsets.right)
bottomConstraint = view.bottomAnchor
.constraint(equalTo: self.contentView.bottomAnchor,
constant: -contentInsets.bottom)
NSLayoutConstraint.activate([
leftConstraint,
topConstraint,
rightConstraint,
bottomConstraint
])
}
}
Последовательность действий:
- Создаем протокол
TabItemProtocol
, которому должна соответствовать каждая вкладка. При использованииTabbedView
необходимо обеспечить пользовательское представление для каждой вкладки. Оно требует реализации методовonSelected()
иonNotSelected()
протоколаTabItemProtocol
.
2. После установки свойства представления запускаем метод setupUI()
.
3. Добавляем способ установки необходимых отступов (insets) для каждой вкладки. После установки свойства contentInsets
все ограничения обновляются соответствующим образом.
4. Метод setupUI()
добавляет ранее определенное свойство view
на экран и активирует его ограничения.
Отлично! Теперь необходимо обновить файл TabbedView.swift
путем реализации протоколов UICollectionViewDataSource
, UICollectionViewDelegate
и UICollectionViewDelegateFlowLayout
:
import UIKit
protocol TabbedViewDelegate: AnyObject {
func didMoveToTab(at index: Int)
}
class TabbedView: UIView {
...
// MARK: - Жизненный цикл
// 1
init(sizeConfiguration: SizeConfiguration,
tabs: [TabItemProtocol] = []) {
self.sizeConfiguration = sizeConfiguration
self.tabs = tabs
super.init(frame: .zero)
self.setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Свойства
weak var delegate: TabbedViewDelegate?
public let sizeConfiguration: SizeConfiguration
// 2
public var tabs: [TabItemProtocol] {
didSet {
self.collectionView.reloadData()
self.tabs[currentlySelectedIndex].onSelected()
}
}
private var currentlySelectedIndex: Int = 0
private lazy var collectionView: UICollectionView = {
...
// 3
collectionView.register(TabCollectionViewCell.self, forCellWithReuseIdentifier: "TabCollectionViewCell")
...
return collectionView
}()
// MARK: - Действие
// 4
public func moveToTab(at index: Int) {
self.collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
self.tabs[currentlySelectedIndex].onNotSelected()
self.tabs[index].onSelected()
self.currentlySelectedIndex = index
}
// MARK: Настройка UI
private func setupUI() {
...
}
}
extension TabbedView: UICollectionViewDelegateFlowLayout {
// 5
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch sizeConfiguration {
case let .fillEqually(height, spacing):
let totalWidth = self.frame.width
let widthPerItem = (
totalWidth - (
spacing * CGFloat((self.tabs.count + 1))
)
) / CGFloat(self.tabs.count)
return CGSize(width: widthPerItem,
height: height)
case let .fixed(width, height, spacing):
return CGSize(width: width - (spacing * 2),
height: height)
}
}
// 6
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
switch sizeConfiguration {
case let .fillEqually(_, spacing),
let .fixed(_, _, spacing):
return spacing
}
}
}
extension TabbedView: UICollectionViewDataSource {
// 7
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tabs.count
}
// 8
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TabCollectionViewCell", for: indexPath) as! TabCollectionViewCell
cell.view = tabs[indexPath.row]
return cell
}
}
extension TabbedView: UICollectionViewDelegate {
// 9
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.moveToTab(at: indexPath.item)
self.delegate?.didMoveToTab(at: indexPath.item)
}
}
Перечень изменений:
- В инициализатор добавляем параметр
[TabItemProtocol]
для установки вкладок. - Как только вкладки установлены, перезагружаем данные представления коллекции и выбираем вкладку в соответствии со свойством
curentlySelectedIndex
. По умолчанию оно равно 0, поэтому выбирается первая вкладка. - Регистрируем
TabCollectionViewCell
в представлении коллекции. moveToTab(at index:)
позволяет программно выбрать нужную вкладку и соответствующим образом обновить состояние.- В методе
sizeForItemAt
делегата макета изучаем предоставленное свойствоsizeConfiguration
и устанавливаем размер для каждой вкладки. - Метод
minimumLineSpacingForSectionAt
задает интервалы между вкладками, если в этом есть необходимость. Как в предыдущем случае, изучаем свойствоsizeConfiguration
и возвращаем требуемое значение. По умолчаниюspacing
равняется 0. - В методе
numberOfItemsInSection
просто возвращаем количество вкладок. - Снабжаем каждую ячейку
TabCollectionViewCell
представлением, являющимся вкладкой. - Наконец, реагируем на нажатие вкладок, корректируя переменные состояния посредством метода
moveToTab(at index:)
. Кроме того, уведомляем делегата о переходе пользователя на определенную вкладку.
Реализация TabbedView
завершена, и осталось лишь наладить совместную работу PagedView
и TabbedView
. Для этого создадим новый класс ViewPager
.
ViewPager
Этот класс выглядит просто и отвечает за совместную работу PagedView
и TabbedView
:
import UIKit
class ViewPager: UIView {
// MARK: - Инициализация
// 1
init(tabSizeConfiguration: TabbedView.SizeConfiguration) {
self.sizeConfiguration = tabSizeConfiguration
super.init(frame: .zero)
self.setupUI()
tabbedView.delegate = self
pagedView.delegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public let sizeConfiguration: TabbedView.SizeConfiguration
// 2
public lazy var tabbedView: TabbedView = {
let tabbedView = TabbedView(
sizeConfiguration: sizeConfiguration
)
return tabbedView
}()
public let pagedView = PagedView()
// MARK: - Настройка UI
// 3
private func setupUI() {
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(tabbedView)
self.addSubview(pagedView)
NSLayoutConstraint.activate([
tabbedView.leftAnchor
.constraint(equalTo: self.leftAnchor),
tabbedView.topAnchor
.constraint(equalTo: self.topAnchor),
tabbedView.rightAnchor
.constraint(equalTo: self.rightAnchor),
tabbedView.heightAnchor
.constraint(equalToConstant: sizeConfiguration.height)
])
NSLayoutConstraint.activate([
pagedView.leftAnchor
.constraint(equalTo: self.leftAnchor),
pagedView.topAnchor
.constraint(equalTo: self.tabbedView.bottomAnchor),
pagedView.rightAnchor
.constraint(equalTo: self.rightAnchor),
pagedView.bottomAnchor
.constraint(equalTo: self.bottomAnchor)
])
}
}
// 4
extension ViewPager: TabbedViewDelegate {
func didMoveToTab(at index: Int) {
self.pagedView.moveToPage(at: index)
}
}
extension ViewPager: PagedViewDelegate {
func didMoveToPage(index: Int) {
self.tabbedView.moveToTab(at: index)
}
}
- Предоставляем параметр
TabbedView.SizeConfiguration
, чтобы внести конфигурацию размера для представления с вкладками. - В свойстве
tabbedView
передаем внесенную конфигурацию размера. - Метод
setupUI()
размещаетTabbedView
сверху, аPagedView
снизу. - Мы также следуем
TabbedViewDelegate
иPagedViewDelegate
для получения уведомления о прокрутке любого из компонентов. Когда пользователь выбирает вкладку,PagedView
перемещается на нужную страницу программным способом. Таким же образом при прокрутке пользователем страницы выбирается соответствующая вкладка.
Отлично! Наконец-то, переиспользуемый ViewPager
готов. Теперь применим его в контроллере представления.
Практический пример
Начнем с пустого UIViewController
, который отображает сверху заголовок “ViewPager”:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.overrideUserInterfaceStyle = .light
self.view.backgroundColor = .white
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.title = "ViewPager"
self.navigationController?.navigationBar.standardAppearance.backgroundColor = .systemBlue
self.navigationController?.navigationBar.standardAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
}
}
Затем создаем ViewPager
и добавляем его на экран следующим образом:
import UIKit
class ViewController: UIViewController {
lazy var viewPager: ViewPager = {
// 1
let viewPager = ViewPager(
tabSizeConfiguration: .fillEqually(height: 60, spacing: 0)
)
// 2
let view1 = UIView()
view1.backgroundColor = .red
let view2 = UIView()
view2.backgroundColor = .blue
let view3 = UIView()
view3.backgroundColor = .orange
// 3
viewPager.tabbedView.tabs = [
AppTabItemView(title: "First"),
AppTabItemView(title: "Second"),
AppTabItemView(title: "Third")
]
viewPager.pagedView.pages = [
view1,
view2,
view3
]
viewPager.translatesAutoresizingMaskIntoConstraints = false
return viewPager
}()
override func viewDidLoad() {
...
// 4
self.view.addSubview(viewPager)
NSLayoutConstraint.activate([
viewPager.widthAnchor.constraint(equalTo: self.view.widthAnchor),
viewPager.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.7),
viewPager.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
viewPager.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor)
])
}
override func viewWillAppear(_ animated: Bool) {
...
}
}
- Инициализируем
ViewPager
с конфигурацией размера.fillEqually
. - Далее создаем три представления разных цветов. Они воплощают страницы.
- Добавляем 3 вкладки с названиями “First,” “Second” и “Third”, а также снабжаем
ViewPager
ранее созданными страницами. - Размещаем
ViewPager
на экране так, чтобы он занимал 70% доступной высоты.
Закончив сборку и запустив приложение, получаем желаемый результат:
Дополнительные ресурсы
Исходный код доступен на GitHub.
Читайте также:
- Как с With() улучшить написание кода на Swift
- Поиск утечек памяти с помощью автоматизированных тестов
- Насколько вы знакомы с языком Swift
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Zafar Ivaev: How To Create a View Pager in Swift 5