У каждого опытного разработчика есть набор инструментов, к которым он привык и с которыми ему удобно работать. Это может быть простейшая настройка окружения, утилиты для промежуточных операций (к примеру, помощник по тестированию API Postman), проверенные временем и лично разработчиком библиотеки и сниппеты.

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

Где находится сердце шаблона


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

Такой инструмент существует, и все мы им постоянно пользуемся, когда создаем ‘Single View App’ в Xcode.

Мои поиски инструкции от Apple по созданию собственных шаблонов так и не дали результатов. Однако в Xcode можно добавить генерацию собственных файлов с уже заготовленным кодом, и это несложно.

Для легкого старта возьмем всем известный Single View App. Зайдите в Finder, нажмите комбинацию клавиш Cmd+Shift+G (переход к папке) и укажите путь:

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS/Application

Находим там «Single View App.xctemplate» и копируем, к примеру, на рабочий стол.
Как видите, шаблон состоит всего из 4 файлов:

  • Main.storyboard. Сториборд, содержащий главный вью-контроллер.
  • TemplateIcon.png. Иконка с разрешением 48х48.
  • TemplateIcon@2x.png. Иконка с разрешением 96x96.
  • TemplateInfo.plist. Конфигурационный файл для шаблона.

Значительную роль здесь играет только последний файл – TemplateInfo.plist, потому что именно в нем содержатся все настройки. Main.storyboard – это лишь один из возможных файлов, которые будут добавлены в каждое созданное по этому шаблону приложение. При отсутствии второго и третьего файлов из этого списка ваш шаблон будет иметь иконку по умолчанию. Поэтому сердцем шаблона является TemplateInfo.plist.

Основные свойства файла конфигураций


Условно свойства TemplateInfo можно разделить на несколько категорий:

1. Разметка шаблона
Присвоение шаблону уникального идентификатора и дополнительных полей для отображения в среде Xcode.

Обязательные поля:

Identifier
Уникальный идентификатор. Два шаблона с одинаковым ID одновременно существовать не могут. Также используется для наследования в других шаблонах.

Kind
Тип шаблона. Для проектов всегда используется Xcode.Xcode3.ProjectTemplateUnitKind.

Concrete
Поле, которое указывает, будет ли шаблон отображаться в списке при создании нового приложения. Есть смысл ставить NO, когда он вызывается из другого шаблона и не является независимым сам по себе. В остальных случаях всегда ставим YES.

Опциональные поля:

Platforms
Определяет, для каких платформ используется шаблон. Полезен только для предотвращения наследования в шаблонах для других платформ (к примеру, при попытке импортировать шаблон для iOS в шаблон для WatchOS).

Name
Отображаемое имя для шаблона. По умолчанию используется имя директории *.xctemplate.

SortOrder
Порядковый номер. Если такого поля нет, считается как последний.

Description
Описание шаблона. В Xcode последних версий не отображается.

2. Наследование
Шаблоны в Xcode имеют очень полезное свойство: они могут включать в себя свойства и файлы из других шаблонов. Очень полезно, если есть необходимость расширить шаблон дополнительными возможностями из другого, при этом не копируя заново файлы и не редактируя файл конфигурации. Прямая аналогия – наследование в ООП.

3. Ancestors
Список из Identifier-шаблонов, свойства и файлы которых будут включены в проект.

4. Генерация содержимого
Наполнить новое приложение можно как уже готовыми файлами, так и прописав их генерацию в настройках.

P.S. Так как проекты могут быть созданы с использованием как Objective-C, так и Swift, то нужно учитывать, что генерация файлов должна быть прописана для разных языков отдельно.
Для этого создается поле Options, являющееся массивом. В качестве первого элемента создаем Dictionary и создаем два ключа. Первый – Identifier типа String со значением «languageChoice». Второй – Units, являющийся Dictionary. В Units создаем еще два Dictionary с названиями «Objective-C» и «Swift» соответственно. Все созданные в дальнейшем Definitions и Nodes помещаем внутрь этих директорий. Если нет привязки к языку (к примеру, мы хотим добавить xib, storyboard или прописать контент для какого-нибудь файла), то Definitions и Nodes могут быть объявлены на одном уровне с остальными параметрами.



5. Definitions
Здесь объявляется генерация кода и прописываются пути к файлам, которые будут добавлены в приложение.

Definitions является ассоциативным массивом и содержит в себе список файлов или переменных. Каждый файл, в свою очередь, тоже является ассоциативным массивом, который содержит в себе путь к файлу. Он может состоять из двух свойств – Path и Group.

Path – непосредственный путь к файлу, который находится в директории .xctemplate.
Group – массив, каждым элементом которого является директория, входящая в указанный путь. Сам файл не указывается.

К примеру, файл имеет путь:

Presentation/Common/ViewController/Base/ViewController.swift

Тогда Definition для него будет выглядеть следующим образом:



Соответственно, если файл не имеет вложенности и лежит непосредственно в .xctemplate/, то массив Group не создается.

6. Nodes
После того, как мы указали пути расположения файлов, необходимо создать ссылки, которыми мы укажем либо на созданный файл, либо на содержимое. Nodes – обычный массив, элементами которого являются ключи из Definitions. Для указанного выше примера Nodes будет выглядеть так:



Генерация кода внутри TemplateInfo.plist


В файл, созданный в TemplateInfo.plist, можно добавить код, написав его в Definitions и указав на него ссылку в Nodes. Это делается при помощи оператора «:» после указания файла, после чего пишется код. К примеру, в стандартном шаблоне «Page-Based App» вы можете увидеть, что в Definitions описано очень много генерации кода:



Порядок расположения кода в файле зависит от порядка расположения ссылок в массиве Nodes. Если указанного файла не существует, то он будет создан.

В заранее созданных файлах и внутри TemplateInfo.plist работают следующие константы:

___COPYRIGHT___ Строка об авторских правах
___DATE___ Дата создания проекта (файла)
___DIRECTORY___ Полный путь к файлу
___FILEBASENAME___ Имя файла без расширения
___FILEEXTENSION___ Расширение файла
___FILENAME___ Полное имя файла
___FULLUSERNAME___ Имя пользователя, авторизованного в системе
___ORGANIZATIONNAME___ Название организации, указанное при создании проекта
___PACKAGENAME___ / ___PROJECTNAME___ Название продукта, указанное при создании проекта
___TIME___ Время, когда был создан проект (файл)
___USERNAME___ Имя учетной записи авторизованного пользователя

Редактирование окна настройки приложения


При создании шаблона можно также отредактировать окно настройки приложения, которое в дальнейшем будет появляться при вызове данного шаблона.

К примеру, для «Single View App» Apple предлагает нам несколько текстовых полей для ввода имени разработчика и названия организации, несколько чекбоксов для включения в проект юнит-тестов или CoreData, а также выпадающий список для выбора языка программирования. Все это регулируется полем Options – массивом из Dictionary. Для каждой опции доступен следующий перечень возможностей:

Identifier
Идентификатор, по которому можно менять или использовать значение, хранящееся в поле опции.

Default
Значение, которое для данного поля будет использоваться по умолчанию.

SortOrder
Порядковый номер, по которому данная опция будет тображаться в окне.

Nae
Заголовок для опции.

Type
Тип создаваемого поля. Бывают следующие типы полей:

  • Static. Статичный, нередактируемый текст. По смыслу – не что иное, как Label.
  • Checkbox. Обычный чекбокс. Так как он по своей сути является Bool-полем, для него также необходимо определить Units, в котором будет содержаться соответствующий набор действий для true и false значений. Пример использования можно посмотреть в базовом шаблоне «Core Data Cocoa Touch App.xctemplate», о котором будет упомянуто в разделе создания шаблона.
  • Text. Поле для ввода текста.
  • Popup. Предоставляет выбор из выпадающего списка, который должен быть определен в массиве Values.

Создаем шаблон приложения


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

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

$ mkdir -p ~/Library/Developer/Xcode/Templates/Project\ Templates/Private

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

Скопированный ранее «Single View App.xctemplate» переименуем в «MVVM Application.xctemplate» и скопируем в папку Private. Если вы сейчас запустите Xcode и перейдете в меню создания нового приложения, то уже сможете увидеть в самом низу новый раздел Private, где будет один единственный шаблон «MVVM Application». Так как мы еще ничего не меняли, то при его использовании получим все тот же Single View App (который, к слову, пропал из списка базовых, т.к. имеет такой же Identifier).

Следующим шагом перенесем папку Presentation и два файла иконки – TemplateIcon.png и TemplateIcon@2x.png. Теперь необходимо поменять Identifier, чтобы Xcode видел наш шаблон как совершенно новый. К примеру, зададим Identifier как «MVVMTemplate». Теперь при создании нового приложения в Xcode мы увидим, что «Single View App» вернулся на положенное ему место, а в разделе Private красуется «MVVM Application» с нашей иконкой.

Далее посмотрим, от каких шаблонов наследуется «Single View App». Открываем TemplateInfo.plist и заходим в Ancestors, где видим:

com.apple.dt.unit.storyboardApplication
com.apple.dt.unit.coreDataCocoaTouchApplication


Находятся шаблоны с этими идентификаторами там же, где и «Single View App». Давайте разбираться по порядку.

В первом шаблоне «Storyboard App.xctemplate» лежит только файл конфигураций, в котором можно увидеть поля Definitions и Nodes. В них прописано, что в качестве главного сториборда будет использоваться Main.storyboard, и указан путь к нему. Нам это не нужно, т.к. у нас уже есть файл MainScreen.storyboard, который мы хотим использовать в качестве главного. Поэтому мы удаляем из Ancestors шаблон «Storyboard App.xctemplate».

Далее идет шаблон «Core Data Cocoa Touch App.xctemplate». В его опциях добавляется поле checkbox для возможности использования CoreData, а в поле Units прописана вся необходимая кодогенерация и импорт файла ___PACKAGENAMEASIDENTIFIER___.xcdatamodeld. Допустим, что CoreData нам тоже не нужен, и также удалим этого родителя.

Но вот проблема: если мы сейчас попытаемся создать приложение по нашему шаблону, то его не будет видно в списке. Дело в том, что удаленные нами родители в свою очередь тоже имели список Ancestors, в котором находился необходимый базовый шаблон «Cocoa Touch App Base», имеющий идентификатор com.apple.dt.unit.cocoaTouchApplicationBase. Добавив его в список Ancestors нашего шаблона, мы снова вернем его в список доступных. Внимательно изучив этот базовый шаблон, вы поймете, почему он так необходим, а мы идем дальше.

Теперь давайте пропишем все пути и ссылки для скопированных файлов. Так как код написан для Swift, делать это я буду только для него.

Переходим в Options -> languageChoice (Item 0) -> Units -> Swift и создаем здесь Dictionary под названием Definitions. Теперь выпишем все пути к файлам, которые у нас есть:

Presentation/Common/View Controller/Base/ViewController.swift
Presentation/Common/View Model/Base/ViewModel.swift
Presentation/Common/View Model/ViewModelHolder.swift
Presentation/Main Screen/MainScreen.storyboard
Presentation/Main Screen/View Controller/MainViewController.swift
Presentation/Main Screen/View Model/MainViewModel.swift


Получаем такой список:



Теперь заполняем ссылки Nodes, выглядеть это будет так:



Осталось добавить последний штрих – поставить MainScreen.storyboard в качестве главного. Создаем Definitions и Nodes для всего файла и добавляем в них следующие поля:

Definitions:
Ключ
Info.plist:UIMainStoryboardFile

Значение

<key>UIMainStoryboardFile</key>
<string>MainScreen</string>

Nodes:
Info.plist:UIMainStoryboardFile

Получаем следующее:



В приложенном мною примере имеется готовый вариант файла TemplateInfo.plist.

В данной статье описаны не все функции для создания шаблонов и файлов. Вместить сюда описание каждого параметра попросту не представляется возможным. Поэтому в репозиторий, в котором лежит пример, я вложил документацию (неофициальную), в которой описаны все возможности и расписаны все существующие параметры (на момент существования Xcode 4). Большинство функций можно понять, покопавшись в базовых шаблонах от Apple, заодно увидев их реализацию. Я лишь описал необходимый минимум для простого создания незамысловатых шаблонов.

Если у вас возникнут какие-либо замечания и дополнения, буду рад их выслушать и дополнить статью с целью повышения ее полезности.

Комментарии (2)


  1. GDXRepo
    09.04.2018 22:31

    Интересный способ, спорить не буду. Немного не понял момент про «Xcode 4», когда сейчас актуальная 9-я версия… На самом деле, шаблонную механику не люблю за счет того, что каждая новая версия Хкода (даже не мажорная) может спокойно переломать все эти шаблоны, сменив их структуру, например, да и вести учет таких шаблонов нужно руками (опять-таки, из-за апдейтов Хкода). Я решаю проблему шаблонизации проще: имею проект, который лежит в облаке архивом, и представляет собой как раз «отправную точку» для большинства новых проектов. Надо начать новый проект? Ок, я копирую свой шаблонный проект, распаковываю его, переименовываю — и готово. И никаких проблем с потенциальной поломкой при апдейте версии Хкода. А если подрубить это все к репозиторию — то еще и обновлять можно удобно (мне пока не пригодилось, но будет больше шаблонов — применю этот вариант). Так выходит значительно меньше трудозатрат при том же результате, пусть и похоже на колхоз. За статью спасибо.


    1. DmitriyTu Автор
      09.04.2018 23:05

      Спасибо за комментарий!
      Думаю, что в этом и прелесть шаблонов — они актуальны еще с 4-й версии Xcode по сей день. Я так же храню все отдельным репозиторием в облаке, на всякий случай. Но если что-то с обновлением Xcode и слетит, всегда можно скопировать все заново. Я делаю подобные заготовки не только для архитектуры. Аналогичным способом можно создавать и шаблоны одиночных файлов, классов, оберток и т.п. Рамблер написали для таких дел генерамбу, для которой так же можно писать шаблоны, но, по сути, это то же самое, что шаблоны в Xcode. Только здесь инструмент помощнее, поскольку позволяет еще и настроить проект, а не только сгенерировать файлы.