Зачем UIKit в 2022
В начале 2022 года может показаться, что использование UIKit на фоне SwiftUI не актуально или даже старомодно. Но так как еще очень много приложений которые созданы на основе фреймворка UIKit и у нас в России до сих пор на нем создается, то его актуальность еще на довольно солидном уровне.
А еще более важным будет знание, как это работает. Сейчас идет постепенный переход от Storyboard к чистому коду в SwiftUI. Переход медленный и это дает нам возможность, еще пока, учится с помощью картинок и кода создавать приложения и одновременно учится создавать приложения с помощью кода.
Задача
Давайте сразу перейдем к задаче:
Создайте новый проект с названием
Navigation
, используя шаблонSingle View App
.Удалите из проекта
Main.storyboard
, который создался по умолчанию. Не забудьте изменить конфигурацию вInfo.plist
.В
SceneDelegate.swift
добавьтеUITabBarController
. Добавьте в него дваUINavigationController
. Первый будет показывать ленту пользователя, а второй — профиль.Измените
Tab Bar Item
у добавленных контроллеров, добавьте заголовок и картинку. Картинки можно добавить вAssets.xcassets
или использовать SF Symbols.Создайте
FeedViewController
иProfileViewController
и добавьте их как root view controller у навигационных контроллеров.Добавьте
PostViewController
для показа выбранного поста. Поменяйте заголовок у контроллера и цвет главной view. Добавьте кнопку наFeedViewController
и сделайте переход на экран поста. Контроллер должен показаться в стекеUINavigationController
.Создайте структуру
Post
со свойствомtitle: String
. Создайте объект типаPost
вFeedViewController
и передайте его вPostViewController
. В классеPostViewController
выставьтеtitle
полученного поста в качестве заголовка контроллера.На
PostViewController
добавьтеBar Button Item
в навигейшн бар. При нажатии на него должен открываться новый контроллерInfoViewController
. Контроллер должен показаться модально.На
InfoViewController
создайте кнопку. При нажатии на неё должен показатьсяUIAlertController
с заданнымtitle
,message
и двумяUIAlertAction
. При нажатии наUIAlertAction
в консоль должно выводиться сообщение.
В рамках данной статьи мы не будем рассматривать пункты 1 и 2. Не в смысле их примитивности, а потому-что подобными ответами пестрит интернет. Замечу только, что после удаления не забудьте вернуть в SceneDelegate (если вы еще используете Xcode до 13 версии, то AppDelegate), строку, которая удаляется автоматически с Main.storyboard.
var window: UIWindow?
Реализация
В данной статье мы рассмотрим пункты 3, 4, 5 и 6. Остальны в последующих статьях. Приступим!
Для реализации пункта 2 существуют 2 способа:
Релизация
UITabBarController
внутриSceneDelegate.swift
Создание собственного
UITabBarController.swift
и в него инкапсулировать дваUINavigationController
Релизация UITabBarController внутри SceneDelegate.swift
Перейдем в SceneDelegate.swift
и первым делом заменим ViewController на TabBarController
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
window?.rootViewController = UITabBarController()
window?.makeKeyAndVisible()
Теперь можно удалить сам контроллер просмотра ViewController.swift
. А сейчас мы пойдем от обратного. Наш TabBarController будет удерживать навигационные контроллеры и для того, чтобы TabBarController что-то отобразил, нам придется сначала создать навигационные контроллеры. Приступим.
Создаем 2 ViewController
(cmd⌘ + N -> Cocoa Touch Class), где Class: у нас будут FeedViewController
и ProfileViewController
, а Subclass of: UIViewController
соответственно. С самого начала контроллеры прозрачные и для того, чтобы мы могли понять, что они у нас есть, мы зададим им фоновый цвет:
override func viewDidLoad() {
super.viewDidLoad()
// Задаем фоновый цвет
view.backgroundColor = .lightGray
}
Конечно для фона лучше использовать системные цвета (например: .systemBlue). Apple адаптировала системные цвета к темному и светлому режижам работы в отличии от простых цветов.
В качестве эксперимента давай зададим нашим ViewController
разные цвета.
Теперь давай создадим два навигационных контроллераr и добавимFeedViewController
и ProfileViewController
и как root view controller. В SceneDelegate.swift
перед window = UIWindow(frame: windowScene.coordinateSpace.bounds
создаем навигационные контроллеры
let feedViewController = UINavigationController(rootViewController: FeedViewController())
let profileViewController = UINavigationController(rootViewController: profileViewController())
Теперь помещаем навигационные контроллеры в панель вкладок пользовательского интерфейса. Создаем панель вкладок:
let tabBarController = UITabBarController()
Помещаем в нее навигационнные контроллеры:
tabBarController.viewControllers = [feedViewController, profileViewController]
Не забудь поменять UITabBarController()
на пользовательский tabBarController
в
window?.rootViewController = UITabBarController()
Теперь когда мы запустим наш проект, мы увидим что экран отображает заданный цвет и если пощелкать внизу справа и слева, то даже будут переключаться экраны. Хотя наш TabBar и не отображает ничего. Это говорит, что у нас все прекрасно работает.
Давай теперь настроим наш TabBar, чтобы не играть в игру: Куда я нажимаю?
Настройка TabBar
Давай сделаем наш код красивым и не будем все собирать в кучу, а разделим каждый блок в методы.
В первый метод соберем наш FeedViewController
, во второй ProfileViewController
, ну а в третий TabBarController
. Реализация методов в SceneDelegate.swift
станет выполнением пункда 6 нашего задания, мы добавим и иконки и заголовки.
Создаем фукцию контроллера поисковой навигации, который возвращает контроллер пользовательского интерфейса:
func createFeedViewController() -> UINavigationController {
Инициализируем поисковый контроллер, который заменим созданный выше
let feedViewController = FeedViewController()
Добавляем заголовок, который отобразится вверху нашего экрана
feedViewController.title = "Лента"
А теперь настроим и саму кнопку, добавив на нее иконку и название. Создаем элемент панели вкладок пользовательского интерфейса UITabBarIt
, где title:
это заголовок, а image:
в нашем случае системная иконка (системные иконки можно найти в программе "Символы SF" или в самом Xcode Edit -> Emoji & Symbols) и последнее tag:
это индекс положения, где 0 - положение слева, а 1 справа.
feedViewController.tabBarItem = UITabBarItem(title: "Лента", image: UIImage(systemName: "doc.richtext"), tag: 0)
Возвращаем навигатор пользовательского интерфейса
return UINavigationController(rootViewController: feedViewController)
}
Делаем тоже самое для навигационного контроллера ProfileViewController
func createProfileViewController() -> UINavigationController {
let profileViewController = ProfileViewController()
profileViewController.title = "Профиль"
profileViewController.tabBarItem = UITabBarItem(title: "Профиль", image: UIImage(systemName: "person.circle"), tag: 1)
return UINavigationController(rootViewController: profileViewController)
}
Теперь удалим, за ненадобностью вверху:
let feedViewController = UINavigationController(rootViewController: FeedViewController())
let profileViewController = UINavigationController(rootViewController: profileViewController())
Теперь давай создадим метод для панели вкладок и передадим эту функцию корневому представлению
func createTabBarController() -> UITabBarController {
Удали сверху и вставь в метод строки кода
let tabBarController = UITabBarController()
Настроим внешний вид панели вкладок и установим основной цвет синий
UITabBar.appearance().backgroundColor = .systemBlue
Поменяем наши контроллеры на методы созданные чуть выше
tabBarController.viewControllers = [createFeedViewController(), createProfileViewController()]
Возвращаем панель вкладок
return tabBarController
}
Не забудь поменять tabBarController()
созданный метод createTabBarController()
в
window?.rootViewController = tabBarController
Теперь можещь запустить созданный проект и посмотреть на ту красоту, которую мы создали. Благодаря методам мы сделали красивый и хорошо пахнущий код, который не засоряет собою SceneDelegate.swift
Давай перейдем ко второму методу.
Создание собственного UITabBarController.swift и в него инкапсулировать два UINavigationController
Кому-то может не понравится способ создания кода в SceneDelegate.swift
и это тоже хорошо и по-этому мы рассмотрим второй способ.
Во-первых, мы создаем свой TabBarController
final class TabBarController: UITabBarController {
В него мы инкапсулируем FeedViewController
, во второй ProfileViewControlle
. Мы можен инкапсулировать больше двух контроллеров, то есть сколько айтемов, столько и кейсов. Создаем перечисление TabBarItem
с описанием наших контроллеров
private enum TabBarItem: Int {
case feed
case profile
Добавим заголовки
var title: String {
switch self {
case .feed:
return "Лента"
case .profile:
return "Профиль"
}
}
Добавим иконки
var iconName: String {
switch self {
case .feed:
return "house"
case .profile:
return "person.crop.circle"
}
}
}
Теперь загрузим это все в TabBarController
override func viewDidLoad() {
super.viewDidLoad()
self.setupTabBar()
}
В методе мы создадим создадим контроллеры FeedViewController
и ProfileViewControlle
для TabBarController
. Не забудьте как и в первом способе создать сами swift файлы!
private func setupTabBar() {
let dataSource: [TabBarItem] = [.feed, .profile]
self.viewControllers = dataSource.map {
switch $0 {
case .feed:
let feedViewController = FeedViewController()
return self.wrappedInNavigationController(with: feedViewController, title: $0.title)
case .profile:
let profileViewController = ProfileViewController()
return self.wrappedInNavigationController(with: profileViewController, title: $0.title)
}
}
self.viewControllers?.enumerated().forEach {
$1.tabBarItem.title = dataSource[$0].title
$1.tabBarItem.image = UIImage(systemName: dataSource[$0].iconName)
$1.tabBarItem.imageInsets = UIEdgeInsets(top: 5, left: .zero, bottom: -5, right: .zero)
}
}
Ну и в самом конце методом wrappedInNavigationController(with: profileViewController, title: $0.title)
мы обернем переданный контроллер FeedViewController
или ProfileViewControlle
в NavigationController
private func wrappedInNavigationController(with: UIViewController, title: Any?) -> UINavigationController {
self.navigationController?.pushViewController(with, animated: true)
return UINavigationController(rootViewController: with)
}
Второй способ позволит нам оставить чистым SceneDelegate.swift
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
self.window?.makeKeyAndVisible()
self.window?.rootViewController = TabBarController()
В следующей статье мы рассмотрим оставшиеся пункты, а так же программный способ создания интерфейса.