20. Sep 2021iOS

Xcode project templates 2: Ako na automatickú konfiguráciu projektu

Pri každom zakladaní nového projektu trvalo celý pracovný deň, kým som ho pripravil pre vývoj. Až kým som neobjavil šablóny. Xcode šablóny sú tu už od čias Xcode 4, avšak dozvedel som sa o nich až nedávno a úplne zmenili spôsob, ako sa pozerám an tvorbu projektu. Skús si ich somnou a nové projekty budú zrazu o to jednotnejšie a udržiavanejšie.

Andrej JaššoiOs Developer

V predchádzajúcom článku o Xcode projet templates som vás previedol spustením tohto nástroja krok za krokom a poukázal na jeho výhody. Tentokrát vám predstavím ďalšie schopnosti Xcode projekt template-ov, kde pri vygenerovaní projektu budeme mať pripravený podfile s predvyplnenou konfiguráciou. Čo v praxi znamená, že po vygenerovaní vstúpime do projektu cez konzolu, zavoláme

pod install

a projekt bude mať pody nakonfigurované. Toto značne uberie na konfigurácii projektu a štýl podfilu bude jednotný pokiaľ sa jedná o komplexnejšie podfiles.

Ako prvé je potrebné vytvoriť nový template podobne, ako minule v zložke projektových templatov s koncovkou .xctemplate

Tu si najskôr vytvoríme súbor Podfile a dovnútra vložíme pody, ktoré chceme importovať do každého nového projektu.

platform :ios, '___VARIABLE_VERSION___'

target '___PACKAGENAME___' do
  use_frameworks!

  pod 'SwiftGen'
  pod 'SwiftLint'
  
end

Všimnite si použitých premenných VARIABLE_VERSION, ktorá nastaví podom minimálnu verziu projektu a PACKAGENAME, ktorá nastaví názov projektu. Pri vytváraní projektu bude VARIABLE_VERSION vstupným parametrom a PACKAGENAME bude prevzaté od názvu projektu.

Následne je potrebné nakonfigurovať TemplateInfo.plist súbor, ktorý prestavuje konfiguráciu template-u

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Kind</key> <!--States the template type-->
	<string>Xcode.Xcode3.ProjectTemplateUnitKind</string>
	<!--States if the template is a standalone template or a part
	of template used in a diffrent template. In this case it will
	be hidden from the template wizzard-->
	<key>Concrete</key>
 	<true/>
	<key>Identifier</key>  <!--Template ID in the "template space"-->
	<string>com.goodrequest.CocoaPods</string>
	<key>Definitions</key>  <!--Template variables-->
	<dict>
		<key>../Podfile</key>
		<dict>
			<key>Path</key>
			<string>Podfile</string>
			<key>Group</key>
			<string>Resources</string>
		</dict>
	</dict>
	<key>Options</key>  <!--"USER INPUT" options-->
	<array>
		<dict>
			<key>Identifier</key> <!--Option ID-->
			<string>cocoapods</string>
			<key>Name</key> <!--Field title-->
			<string>CocoaPods</string>
			<key>Description</key> <!--Field tooltip-->
			<string>Integrate CocoaPods template</string>
			<key>SortOrder</key> <!--Sorting priority-->
			<integer>250</integer>
			<key>Type</key> <!--Field type-->
			<string>checkbox</string>
			<key>Default</key> <!--Default value-->
			<string>false</string>
			<key>Units</key> <!--Field outcome-->
			<dict>
				<key>true</key> <!--True branch outcome-->
				<array>
					<dict>
						<key>Nodes</key> <!--Files to include if True-->
						<array>
							<string>../Podfile</string>
						</array>
					</dict>
				</array>
			</dict>
		</dict>
		<dict>
			<key>Identifier</key>  <!--Option ID-->
			<string>VERSION</string>
			<key>Required</key> <!--Required to fill in the field-->
			<true/>
			<key>Name</key> <!--Field title-->
			<string>iOS Min. Version</string>
			<key>Description</key> <!--Field tooltip-->
			<string>Minimum supported iOS version</string>
			<key>Type</key> <!--Field type-->
			<string>text</string>
			<key>Default</key> <!--Default value-->
			<string>13.0</string>
			<key>NotPersisted</key> <!--Remember last filled in choice-->
			<true/>
		</dict>
	</array>
</dict>
</plist>

Tu je podstatné si všimnúť si identifier premenne:

  • Kind: Typ template-u je projektový
  • Concrete: Určuje, či je template viditeľný pri výbere templatu, alebo či je iba súčasťou iného templatu a teda pri výbere viditelný nieje. Tento template použijeme ako súčasť template-u z predošlého článku a teda nastavíme false.
  • Identifier: Určuje ID template-u medzi templatami. Toto slúži nato, aby sme vedeli referovať k templatu z iného template-u
  • Definitions: Slúži na zadefinovanie premennej, ktorou je v našom prípade súbor Podfile a miesto, kde bude vytvorený v projekte.
  • DefinitionsPath: Slúži, ako cesta ku súboru v zložke templatov
  • DefinitionsGroup: Slúži, ako cesta ku súboru po vytvorení nového projektu. Ak ide o viac vnorení treba ju zadefinovať ako array stringov kde každý string prestavuje jedno vnorenie cesty
  • Options: Tu máme zadefinované všetky vstupy používateľa templatu pri vytváraní. Je tu checkbox či chceme zahrnúť podfile a verzia iOS ako textový vstup.
  • OptionsIdentifier: ID optionu. K tomu sa da dostať zo súborov cez VARIABLE_VERSION__ kde namiesto VERSION vložite názov vášeho vstupu.
  • OptionsName: Názov poľa
  • OptionsDesciption: Tooltip poľa
  • OptionsSortOrder: Určuje pozíciu poľa vo formulári template-u
  • OptionsType: Určuje typ používateľského vstupu
  • OptionsDefault: Určuje defaultnú hodnotu
  • OptionsUnits: Určuje aké súbory vyberie template v rámci logického vetvenia, v tomto prípade v rámci checkboxu je to buď true alebo false pričom, ak je možnosť false nevyberie žiadne súbory a teda nie je v našom prípade nutné možnosť definovať.
  • OptionsRequired: Určuje, či je možne vytvoriť projekt aj nevyplnenou možnostou
  • OptionsNotPersisted: Určuje či si bude template pamätať poslednú vyplnenú možnosť

Tento template chceme teraz vložiť do nášho projektového templatu z minulého návodu.

Výsledok bude takýto:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Kind</key>
	<string>Xcode.Xcode3.ProjectTemplateUnitKind</string>
	<key>Identifier</key>
	<string>com.apple.dt.unit.singleViewApplication</string>
	<key>Ancestors</key>
	<array>
		<string>com.goodrequest.cocoaTouchApplicationBase</string>
		<string>com.goodrequest.CocoaPods</string> <!--ID of the Pod template-->
	</array>
	<key>Concrete</key>
	<true/>
	<key>Definitions</key>
	<dict>
		<key>Application/AppDelegate.swift</key>
		<dict>
			<key>Path</key>
			<string>Application/AppDelegate.swift</string>
			<key>Group</key>
			<array>
				<string>Application</string>
			</array>
		</dict>
		<key>Extensions/Foundation/String+Extensions.swift</key>
		<dict>
			<key>Path</key>
			<string>Extensions/Foundation/String+Extensions.swift</string>
			<key>Group</key>
			<array>
				<string>Extensions</string>
				<string>Foundation</string>
			</array>
		</dict>
		<key>Extensions/UIKit/UIView+Extensions.swift</key>
		<dict>
			<key>Path</key>
			<string>Extensions/UIKit/UIView+Extensions.swift</string>
			<key>Group</key>
			<array>
				<string>Extensions</string>
				<string>UIKit</string>
			</array>
		</dict>
		<key>Helpers/TypeAliases.swift</key>
		<dict>
			<key>Path</key>
			<string>Helpers/TypeAliases.swift</string>
			<key>Group</key>
			<string>Helpers</string>
		</dict>
		<key>Models/Error/AppError.swift</key>
		<dict>
			<key>Path</key>
			<string>Models/Error/AppError.swift</string>
			<key>Group</key>
			<array>
				<string>Models</string>
				<string>Error</string>
			</array>
		</dict>
		<key>Models/Response/SampleResponse.swift</key>
		<dict>
			<key>Path</key>
			<string>Models/Response/SampleResponse.swift</string>
			<key>Group</key>
			<array>
				<string>Models</string>
				<string>Response</string>
			</array>
		</dict>
		<key>Models/Request/SampleRequest.swift</key>
		<dict>
			<key>Path</key>
			<string>Models/Request/SampleRequest.swift</string>
			<key>Group</key>
			<array>
				<string>Models</string>
				<string>Request</string>
			</array>
		</dict>
		<key>Managers/Dependency/DependencyContainer.swift</key>
		<dict>
			<key>Path</key>
			<string>Managers/Dependency/DependencyContainer.swift</string>
			<key>Group</key>
			<array>
				<string>Managers</string>
				<string>Dependency</string>
			</array>
		</dict>
		<key>Managers/Request/RequestManager.swift</key>
		<dict>
			<key>Path</key>
			<string>Managers/Request/RequestManager.swift</string>
			<key>Group</key>
			<array>
				<string>Managers</string>
				<string>Request</string>
			</array>
		</dict>
		<key>Managers/Request/RequestManagerType.swift</key>
		<dict>
			<key>Path</key>
			<string>Managers/Request/RequestManagerType.swift</string>
			<key>Group</key>
			<array>
				<string>Managers</string>
				<string>Request</string>
			</array>
		</dict>
		<key>Managers/Request/Endpoint.swift</key>
		<dict>
			<key>Path</key>
			<string>Managers/Request/Endpoint.swift</string>
			<key>Group</key>
			<array>
				<string>Managers</string>
				<string>Request</string>
			</array>
		</dict>
		<key>Coordinators/Coordinator.swift</key>
		<dict>
			<key>Path</key>
			<string>Coordinators/Coordinator.swift</string>
			<key>Group</key>
			<string>Coordinators</string>
		</dict>
		<key>Coordinators/AppCoordinator.swift</key>
		<dict>
			<key>Path</key>
			<string>Coordinators/AppCoordinator.swift</string>
			<key>Group</key>
			<string>Coordinators</string>
		</dict>
		<key>Screens/SampleController/SampleViewController.swift</key>
		<dict>
			<key>Path</key>
			<string>Screens/SampleController/SampleViewController.swift</string>
			<key>Group</key>
			<array>
				<string>Screens</string>
				<string>SampleController</string>
			</array>
		</dict>
		<key>Screens/BaseViewController.swift</key>
		<dict>
			<key>Path</key>
			<string>Screens/BaseViewController.swift</string>
			<key>Group</key>
			<array>
				<string>Screens</string>
			</array>
		</dict>
		<key>Views/SampleView/SampleView.swift</key>
		<dict>
			<key>Path</key>
			<string>Views/SampleView/SampleView.swift</string>
			<key>Group</key>
			<array>
				<string>Views</string>
				<string>SampleView</string>
			</array>
		</dict>
		<key>Resources/Assets.xcassets</key>
		<dict>
			<key>Path</key>
			<string>Resources/Assets.xcassets</string>
			<key>Group</key>
			<array>
				<string>Resources</string>
			</array>
		</dict>
		<key>Resources/Colors.xcassets</key>
		<dict>
			<key>Path</key>
			<string>Resources/Colors.xcassets</string>
			<key>Group</key>
			<array>
				<string>Resources</string>
			</array>
		</dict>
		<key>Resources/SwiftGen/swiftgen.yml</key>
		<dict>
			<key>Path</key>
			<string>Resources/SwiftGen/swiftgen.yml</string>
			<key>Group</key>
			<array>
				<string>Resources</string>
				<string>SwiftGen</string>
			</array>
		</dict>
	</dict>
	<key>Nodes</key>
	<array>
		<string>Application/AppDelegate.swift</string>
		<string>Extensions/UIKit/UIView+Extensions.swift</string>
		<string>Extensions/Foundation/String+Extensions.swift</string>
		<string>Helpers/TypeAliases.swift</string>
		<string>Models/Error/AppError.swift</string>
		<string>Models/Response/SampleResponse.swift</string>
		<string>Models/Request/SampleRequest.swift</string>
		<string>Managers/Dependency/DependencyContainer.swift</string>
		<string>Managers/Request/RequestManager.swift</string>
		<string>Managers/Request/RequestManagerType.swift</string>
		<string>Managers/Request/Endpoint.swift</string>
		<string>Coordinators/Coordinator.swift</string>
		<string>Coordinators/AppCoordinator.swift</string>
		<string>Screens/SampleController/SampleViewController.swift</string>
		<string>Screens/BaseViewController.swift</string>
		<string>Views/SampleView/SampleView.swift</string>
		<string>Resources/SwiftGen/swiftgen.yml</string>
		<string>Resources/Colors.xcassets</string>
		<string>Resources/Assets.xcassets</string>
		<string>Info.plist:UIMainStoryboardFile</string>
		<string>Info.plist:UIApplicationSceneManifest:UISceneStoryboardFile</string>
	</array>
</dict>
</plist>

Jedine v čom sa tieto súbory líšia je kľuč ancestors kde pridáme Identifier z templatu, ktorý sme práve vytvorili.

<key>Ancestors</key>
	<array>
		<string>com.goodrequest.cocoaTouchApplicationBase</string>
		<string>com.goodrequest.CocoaPods</string> <!--ID of the Pod template-->
	</array>

Teraz už stačí iba vytvoriť projekt cez template a vykonať nad repozitárom pod install a je to 🙂

Ďakujem za pozornosť.

Andrej JaššoiOs Developer