Комбинированные ViewController’ы и VIPER
Здравствуйте, дорогие друзья! С вами на связи компания IT-Machine.
В одном из проектов, в котором мы работали над iOS-приложением, перед нами стояла задача сделать экран, содержащий несколько вкладок, причём сделать это так, чтобы при добавлении и удалении новых вкладок на экран нам не приходилось перелопачивать весь код. Этот опыт оказался довольно интересным для нас, так как мы отказались от традиционного подхода с использованием шаблона проектирования MVC в пользу VIPER. И сегодня нам бы хотелось поделиться этим опытом с вами.
Мы не будем вас здесь в мельчайших подробностях грузить тем, что такое VIPER и с чем его едят (об этом вы можете прочитать здесь), а так же рассказывать о том, как реализовать функционал того или иного экрана – этот творческий процесс мы оставим полностью на откуп вам, но мы постараемся доступным языком рассказать о том, каким образом структурировать ваше приложение, сделав каждый его экран (модуль) максимально независимым.
Ну что ж, приступим!
Пусть наш экран имеет подобную структуру:

Header View содержит названия вкладок, которые будут лежать в Body. При нажатии на название вкладки в Header’e соответственно будут меняться экраны в Body.
Итак, само по себе слово VIPER является бэкронимом для View, Interactor, Presenter, Entity и Routing. Соответственно, наш модуль будет состоять из следующих классов: View – отображает то, что говорит Presenter; Interactor – бизнес-логика приложения; Presenter – запрашивает данные от Interactor’а и подготавливает к показу во View; Entity – сущности, которыми управляет Interactor; Router – навигация между модулями.
Выглядит это все следующим образом:

Для того, чтобы настроить общение между элементами нашего модуля, необходимо реализовать input- и output-протоколы для View и Interactor’а, которые для удобства лучше будет вынести в отдельные заголовочные файлы.
View-протоколы (общение View и Presenter’а):
@protocol ViewInterfaceOutputView
@end
@protocol ViewInterfaceInputPresenter
@end
Interector-протоколы (общение Interactor’a и Presenter’a):
@protocol InteractorInterfaceInput
@end
@protocol InteractorInterfaceOutput
@end
Чтобы было удобнее работать и сделать наши вкладки максимально независимыми, разобьем наш модуль на несколько сабмодулей, которые будут отображаться в Body. Подобный подход позволит в дальнейшим, при необходимости, добавлять новые или же убирать ненужные экраны с минимальными затратами сил и времени. Header так же необходимо сделать отдельным сабмодулем. По своей сути и структуре сабмодули ничем не будут отличаться от нашего главного модуля, поэтому смело можете реализовывать в них те же самые классы и протоколы!
Немаловажным компонентом здесь так же является протокол DelegateInterface, благодаря которому мы сможем управлять нашими сабмодулями в главном модуле.
@protocol DelegateInterface
@end
Теперь, когда мы разобрались, как нам сконфигурировать наш модуль, поговорим о том, каким же образом происходит навигация внутри этого самого модуля. Ответ - очень просто! При нажатии на какую-либо из вкладок в Header’e, View тут же посылает сигнал в Presenter, где вызывается необходимый метод из DelegateInterface (этот метод мы реализуем в Presenter’e нашего модуля, если угодно - в главном Presenter’e) - там мы говорим Router’у, что в Body необходимо показать другой сабмодуль. Далее процедура стандартная для каждого модуля/сабмодуля: View спрашивает Presenter, что и как ей показывать, Presenter запрашивает данные у Interactor’a, подготавливает их к показу и дает ответ View. Вот, в общем-то, и все хитрости.
Упрощенно на схеме это можно представить следующим образом:

Если ваше приложение будет состоять из нескольких модулей, то таким же образом вы можете осуществить навигацию между модулями, ведь сабмодули по своей сути, как вы уже могли догадаться, являются ни чем иным, как модулями внутри модуля. На этом наш рассказ подходит к концу.
Ссылка на демо-проект, в котором вы можете посмотреть, как описанное выше выглядит на практике: https://github.com/it-machine/Modeles-Lesson
Благодарим каждого из вас за внимание и надеемся, что для кого-то этот рассказ был полезен и стал отправной точкой для покорения новых высот в разработке под iOS.