Рано или поздно практически любой уважающий себя iOS-разработчик сталкивается с необходимостью выпуска двух практически идентичных по функционалу приложений, зачастую - обычной и lite-версии своего продукта. Первое, что приходит в голову в таком случае разработчику, не сталкивавшемуся с подобной задачей ранее, - скопировать проект и модифицировать его код. Однако такой подход приведет к необходимости поддерживать уже не один, а два раздельных проекта. Изменения, вносимые в один проект, зачастую необходимо дублировать и в другом проекте. Это неудобно и, более того, опасно: всегда существует вероятность забыть исправить код второго проекта, после изменения первого.

Правильным способом решения этой задачи является добавление дополнительного target в исходный проект. В результате, при сохранение общей кодовой базы исходного проекта разработчик может собирать и старый и новый продукт буквально несколькими кликами. Об этом способе мы и поговорим в данной заметке.

Сперва поговорим о том, что такое этот пресловутый target. Данный объект представляет собой набор инструкций по сборке бинарного файла приложения и включает в себя флаги компиляции, настройки provisioning profile-ов, имя бандла и прочую мета-информацию. Если мы хотим собрать еще одно приложение помимо существующего на основе имеющейся кодовой базы, нам нужно создать новый target.

Как добавить новый target?

Для добавления нового target в проект нужно аккуратно проделать ряд простых действий.

Прежде всего, выберите текущий target проекта (см. рис 1). В нашем примере он имеет имя ContractFinder.

Текущий target проекта

Рис. 1. Текущий target проекта


Далее нажмите на текущем (и пока еще единственном) target правой кнопкой мыши и выберите Duplicate (см. рис. 2). Это создаст копию текущего target-а. Копировать target в нашем случае проще всего, поскольку создаваемый таким образом новый target будет иметь найстройки, практически идентичные исходному. При создании target с нуля процесс будет отличаться.

Создаем копию target-а проекта

Рис. 2. Создаем копию target-а проекта


В нашем примере мы создаем новый target для iPhone-приложения, поэтому Xcode спрашивает нас, хотим ли мы продублировать существующий target или же нам надо сделать iPad-версию нашего приложения (см. рис. 3). Выбираем Duplicate Only.

Выбираем вариант создания копии

Рис. 3. Выбираем вариант создания копии


В результате этих действий в проекте появится второй target с именем вида Исходное-имя Copy, а также копия plist-а изначального target’а. Файл plist рекомендую сразу переименовать, чтобы не возникало путаницы при добавлении следующего target в этот же проект. Проверявшаяся мною версия Xcode 4.6.3 не умела отслеживать изменения имени plist-файла для нового target, поэтому если при выборе target вы наблюдаете кнопку Choose Info.plist File… как на рис. 4, смело нажимайте эту кнопку и выбирайте переименованный файл.

Указываем Plist-файл

Рис. 4. Указываем Plist-файл


Поскольку мы копировали существующий target, все настройки нового target будут идентичны исходному target за исключением параметра Product Name. Этот параметр используется для идентификации приложения в Bundle Identifier (см. рис. 5 - в нашем примере ContractFinder-copy). Я настоятельно рекомендую заменять Product Name на что-нибудь более осмысленное, например на Имя-проекта Lite.

Второй target создан

Рис. 5. Второй target создан


Для смены Product Name, выберите новый target, перейдите на вкладку Build Settings, найдите пункт Product Name и замените его на нужное значение. В нашем примере (см. рис. 6) мы заменили строку ContractFinder-copy на строку Z-monitor MSK.

Задаем Product Name

Рис. 6. Задаем Product Name


После этой нехитрой операции Bundle Identifier нового target примет корректный вид (см. рис. 7).

Новый вид Bundle Identifier-а после изменения Product Name

Рис. 7. Новый вид Bundle Identifier-а после изменения Product Name


Поскольку по-умолчанию в plist-файле target-а подпись под иконкой приложения берется именно из Product Name, а приложения различаются своими Bundle Identifier-ами, мы получаем не одно, а два разных приложения в одном проекте. На устройстве они будут выглядеть как на рис. 8.

Два приложения на устройстве

Рис. 8. Два приложения на устройстве


Как написать код, специфичный только для одного target?

Для того, чтобы различать в какой именно версии приложения должен работать тот или иной код, принято использовать условные дерективы компилятора. Для этого мы можем создать флаг компиляции (в нашем примере, он называется MSK_VERSION) в Build Settings нового target’а как в примере на рис. 9. Чтобы его создать найдите параметр Other C Flags и для каждой из схем сборки (в данном примере это Debug, Distribution Ad Hoc и Release) добавьте строку вида -DNAME_OF_YOUR_FLAG (в нашем примере, повторюсь, это -DMSK_VERSION). Обратите внимание на то, что в пункте Other C++ Flags добавленный флаг появится самостоятельно, руками его туда вписывать не надо.

Добавляем флаг компиляции в настройки нового target-а

Рис. 9. Добавляем флаг компиляции в настройки нового target-а


В нашем примере у схем Distribution Ad Hoc и Release видна строка -DNS_BLOCK_ASSERTI... исключительно потому, что она предшествует строке -DMSK_VERSION; строка -DMSK_VERSION указана для всех трех схем сборки.

В самом исходном коде для ветвления между собираемыми версиями target-ов при этом необходимо использовать код вида:

#ifdef NAME_OF_YOUR_FLAG
        // Здесь пишем код для версии приложения с флагом
#else
        // Здесь пишем код для версии приложения без флага
#endif

Как добавить ресурсы, специфичные только для одного target?

Если в проект необходимо добавить ресурсы только для одного target-а, необходимо при перетаскивании этих ресурсов в экран Project Navigator-а в окрывшемся диалоговом окне добавления файла в проект установить галочку напротив нужного target-а для которого данный ресурс добавляется.

Если из нового target-а необходимо удалить какие-либо ресурсы, которые были нужны для старого target-а, но не нужны для нового, это легко сделать перейдя ко вкладке Build Phases, развернув список Copy Bundle Resources и удалив ненужные для данного target-а ресурсы.

Под ресурсами в данном пункте подразумеваются не только изображения и файлы sqlite, но и, например, xib-файлы.

Как изменить название новой схемы сборки?

При создании нового target в проекте появится дополнительно еще одна схема сборки, повторяющая изначальную схему с добавлением слова copy в конце названия (см. рис. 10. На схему Kal можно не обращать внимания, она возникла из-за специфики нашего проекта).

Внешний вид меню схемы

Рис. 10. Внешний вид меню схемы


Для переименования схемы необходимо в меню из рис. 10 выбрать пункт Manage Schemes…. После этого в открывшемся окне (см. рис. 11) необходимо найти имя новой схемы с copy на конце, нажать на нем последовательно два раза (первый раз для выбора строки со схемой, второй - для перехода в режим редактирования имени схемы) и переименовать схему.

Перечень доступных схем

Рис. 11. Перечень доступных схем


Результат переименования схемы в нашем проекте можно увидеть на рис. 12.

Измененная новая схема

Рис. 12. Измененная новая схема


Как изменить иконку и загрузочный экран (splash screen) для нового target?

Созданный target использует иконку и загрузочные экраны исходного приложения. Едва ли нас это устроит с учетом того, что создаваемая lite-версия, скорее всего, должна отличаться в оформлении от основной версии.

Для изменения иконок и загрузочных экранов прежде всего необходимо подготовить файлы. Я настоятельно рекомендую назвать иконки и загрузочные экраны единообразно с их аналогами для исходного приложения с добавлением какого-либо префикса. В нашем проекте мы использовали префикс Red.

С изменением иконок все достаточно просто - выбираем новый target, идем на вкладку Summary, находим пункт App Icons и перетаскиваем новые иконки прямо поверх старых. Если имена старых и новых иконок отличаются, все проходит гладко (проверял на Xcode 6.4.3; если у вас возникли проблемы, читайте дальше про способ с загрузочными экранами и сделайте с иконками так же), в проекте сохраняются оба набора иконок, иконки правильно назначаются для каждого target-а.

С загрузочными экранами этот номер не проходит, поскольку Xcode имеет привычку переименовывать изображения с загрузочным экраном в нечто вида Default.png. В этом случае при попытке изменить загрузочные экраны тем же способом, что и иконки, приведет к тому, что в исходном target эти изображения изменятся на новые (что не всегда нужно).

Для корректного задания загрузочных экранов я рекомендую для каждого из target-ов добавить в соответствующий Info.plist файл по ключу UILaunchImageFile (см. рис. 13 и 14).

Внешний вид Raw Key нового target...

Рис. 13. Внешний вид Raw Key нового target...


... а это внешний вид того же ключа, но в человекопонятном виде для старого target-а

Рис. 14. ... а это внешний вид того же ключа, но в человекопонятном виде для старого target-а


В зависимости от имени заданного файла Xcode будет искать файлы для ретина-дисплеев по своим внутренним правилам именования загрузочных экранов. Если задаваемый файл имеет имя вида Default.png, то остальные файлы при именовании в стиле Default@2x.png и Default-568h@2x.png будут успешно обнаружены и использованы (см. рис. 15 и 16)

Иконки и загрузочные экраны старого target

Рис. 15. Иконки и загрузочные экраны старого target


Иконки и загрузочные экраны нового target

Рис. 16. Иконки и загрузочные экраны нового target


В результате проделанной процедуры новое приложение будет отличаться от старого и на экране мобильного устройства (см. рис. 17).

Новый вид второго приложения проекта

Рис. 17. Новый вид второго приложения проекта


Заключение

В данной заметке мы рассмотрели практически со всех сторон процесс добавления нового target-а в iOS-проект. Хочу обратить ваше внимание на то, что мы использовали Xcode 4.6.3 и работали с проектам, поддерживающим iOS 4.3 и выше. В более новых версиях Xcode и iOS могут быть незначительные отличия, которые, тем не менее, не будут сильно отличаться от приведенной процедуры.

При возникновении каких-либо ко мне вопросов/комментариев/пожеланий по рассмотренной теме, прошу присылать их мне на электронную почту max[at]maxmikheev.org.