9. Nov 2021iOS

Xcode project templates Vol. 4: How to deal with third-party library licenses

Does it still take you a lot of time to create a new project? I've prepared another section dedicated to Xcode project templates that will save you time and a few hairs 😅. Read the how-to guide and maybe you'll change the way you create your projects. Today I'll show you how to integrate a screen with all third-party libraries into your project using the LicensePlist library.

Andrej JaššoiOS Developer

Before we jump into the last part of the Xcode project templates series, don't forget to read the previous articles. In those, you'll find step-by-step tutorials 
on how to start and where to find templates, how to automate project configuration, or how to configure swiftlink and swiftgen.

LicensePlist is a tool that generates a plist of all third party books including SPM, Carthage and Cocoapod libraries.

At the end of this article, after creating the project and successfully running 
it on the simulator, we should see a license button in the application settings where we will see all used third-party libraries without any setup. Once you have successfully created a template, all future integrations are alredy done.

But let's give you little context first. Why mention libraries in the application 
at all? The reason is simple. If you've ever browsed the github of a library, you've probably noticed the LICENSE file.

Here are the terms of use of the library and even though the library is open source it is still quite possible that it requires a declaration of use in apps that are publicly available. Not every library requires it, but there is a good chance that you will sooner or later come across a library that will. It's actually a way not to cause legal problems.

Now let's go to integration. The first thing to do is add your LicensePlist 
to the poddependencies in our sub template.

platform :ios, '___VARIABLE_VERSION___'

target '___PACKAGENAME___' do
  use_frameworks!

  pod 'SwiftGen'
  pod 'SwiftLint'
  pod 'LicensePlist'

end

The template for the team also makes sense because we make it easier 
to add more name search libraries, etc ...

As a next step, we will create a new sub-template where the only file will 
be settings.bundle and for the target we will determine, similarly to the previous article, the target where there will be 2 build scripts. These must refer 
to the Licene-plist script and the types will copy and paste to the settings.bundle file and then sequentially edit and delete the output.

<?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>
 	<false/>
	<key>Identifier</key>  <!--Template ID in the "template space"-->
	<string>com.goodrequest.License</string>
	<key>Definitions</key>  <!--Template variables-->
	<dict>
	<key>Application/Settings.bundle</key>
	<dict>
		<key>Path</key>
		<string>Settings.bundle</string>
		<key>Group</key>
		<array>
			<string>Application</string>
		</array>
	</dict>
</dict>
<key>Nodes</key>
<array>
	<string>Application/Settings.bundle</string>
</array>
<key>Targets</key>
<array>
	<dict>
		<key>TargetIdentifier</key>
		<string>com.apple.dt.cocoaTouchApplicationTarget</string>
		<key>BuildPhases</key>
		<array>
			<dict>
				<key>Name</key>
				<string>📁 Run licence generator</string>
				<key>SortOrder</key>
				<string>1</string>
				<key>Class</key>
				<string>ShellScript</string>
				<key>ShellPath</key>
				<string>/bin/sh</string>
				<key>ShellScript</key>
				<string>PLIST_PATH="${PODS_ROOT}/LicensePlist/license-plist"

if test -f "${PLIST_PATH}"; then
    "${PLIST_PATH}" --package-path "${PROJECT_NAME}.xcworkspace/xcshareddata/swiftpm/Package.swift" --suppress-opening-directory
else
    echo "warning: LicencePlist not installed, download from https://github.com/mono0926/LicensePlist 😭😩🤦‍♂️"
fi</string>
			</dict>
			<dict>
				<key>Name</key>
				<string>📁 Copy licence data into settings bundle</string>
				<key>SortOrder</key>
				<string>1</string>
				<key>Class</key>
				<string>ShellScript</string>
				<key>ShellPath</key>
				<string>/bin/sh</string>
				<key>ShellScript</key>
				<string>cp -R "${SRCROOT}/com.mono0926.LicensePlist.Output/" "${SRCROOT}/${PROJECT_NAME}/Application/Settings.bundle/"
					rm -rf "${SRCROOT}/com.mono0926.LicensePlist.Output/"</string>
			</dict>
		</array>
	</dict>
</array>
</dict>
</plist>

You can create Settings.bundle simply via xcode and select the settings template.

You will create something like this where the content will be taken care 
of by a script that will copy all the information it retrieves from the libraries 
in the project. Copy the file created in this way and put it in the folder 
of our sub-template.

We will then add this template to our main project template.

<?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.goodrequest.singleViewApplication</string>
 <key>Ancestors</key>
 <array>
	 <string>com.apple.dt.unit.applicationBase</string>
	  <string>com.apple.dt.unit.iosBase</string>
    <string>com.goodrequest.CocoaPods</string> <!--ID of the Pod template-->
    <string>com.goodrequest.License</string>
	</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>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>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>
  <key>Resources/SwiftGen/Colors.swift</key>
  <dict>
    <key>Path</key>
    <string>Resources/SwiftGen/Colors.swift</string>
    <key>Group</key>
    <array>
      <string>Resources</string>
      <string>SwiftGen</string>
    </array>
  </dict>
  <key>Resources/SwiftGen/Assets.swift</key>
  <dict>
    <key>Path</key>
    <string>Resources/SwiftGen/Assets.swift</string>
    <key>Group</key>
    <array>
      <string>Resources</string>
      <string>SwiftGen</string>
    </array>
  </dict>
  <key>Resources/SwiftGen/Localizable.swift</key>
  <dict>
    <key>Path</key>
    <string>Resources/SwiftGen/Localizable.swift</string>
    <key>Group</key>
    <array>
      <string>Resources</string>
      <string>SwiftGen</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>Resources/.swiftlint.yml</key>
  <dict>
    <key>Path</key>
    <string>Resources/.swiftlint.yml</string>
    <key>Group</key>
    <string>Resources</string>
  </dict>
 </dict>
 <key>Nodes</key>
 <array>
  <string>Helpers/TypeAliases.swift</string>
  <string>Application/AppDelegate.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>Views/SampleView/SampleView.swift</string>
  <string>Resources/Colors.xcassets</string>
  <string>Resources/Assets.xcassets</string>
  <string>Resources/SwiftGen/swiftgen.yml</string>
  <string>Resources/Colors.xcassets</string>
  <string>Resources/Assets.xcassets</string>
  <string>Resources/Localizable.strings</string>
  <string>Resources/SwiftGen/Assets.swift</string>
  <string>Resources/SwiftGen/Colors.swift</string>
  <string>Resources/SwiftGen/Localizable.swift</string>
  <string>Resources/.swiftlint.yml</string>
 </array>
 <key>Targets</key>
	<array>
		<dict>
			<key>TargetIdentifier</key>
			<string>com.apple.dt.cocoaTouchApplicationTarget</string>
			<key>BuildPhases</key>
			<array>
				<dict>
					<key>Name</key>
					<string>🛠 Generate enums with SwiftGen</string>
					<key>SortOrder</key>
					<string>1</string>
					<key>Class</key>
					<string>ShellScript</string>
					<key>ShellPath</key>
					<string>/bin/sh</string>
					<key>ShellScript</key>
					<string>SWIFT_GEN="${PODS_ROOT}/SwiftGen/bin/swiftgen"
SWIFT_GEN_CONFIG="${PROJECT_DIR}/${PROJECT_NAME}/Resources/SwiftGen/swiftgen.yml"

if test -f "${SWIFT_GEN}"; then
    if test -f "${SWIFT_GEN_CONFIG}"; then
        "${SWIFT_GEN}" config run --verbose --config "${SWIFT_GEN_CONFIG}"
    else
        echo "warning: Swifgen configuration file missing make sure the path "$SWIFT_GEN_CONFIG" contains the file 😭😩🤦‍♂️"
    fi
else
    echo "warning: SwiftGen not installed, download from https://github.com/SwiftGen/SwiftGen 😭😩🤦‍♂️"
fi</string>
				</dict>
				<dict>
					<key>Name</key>
					<string>🧹 Validate codestyle with SwiftLint</string>
					<key>SortOrder</key>
					<string>1</string>
					<key>Class</key>
					<string>ShellScript</string>
					<key>ShellPath</key>
					<string>/bin/sh</string>
					<key>ShellScript</key>
					<string>SWIFT_LINT="${PODS_ROOT}/SwiftLint/swiftlint"
 SWIFT_LINT_CONFIG="${SRCROOT}/${PROJECT_NAME}/Resources/.swiftlint.yml"

if test -f "${SWIFT_LINT}"; then
    if test -f "${SWIFT_LINT_CONFIG}"; then
        "${SWIFT_LINT}" --config "${SWIFT_LINT_CONFIG}"
    else
        echo "warning: Swiflint configuration file missing make your the submodule is fetched 😭😩🤦‍♂️"
    fi
else
    echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint 😭😩🤦‍♂️"
fi</string>
				</dict>
			</array>
		</dict>
	</array>
  <key>Project</key>
	<dict>
			<key>SharedSettings</key>
			<dict>
					<key>SWIFT_VERSION</key>
					<string>5.0</string>
			</dict>
	</dict>
 </dict>
</plist>

Now just generate a project and see how the settings bundle fills your library information.

And at the same time, when installing the application on the simulator you can see that the licenses are here.

That's all for today's tutorial. At the same time, it should be added that you will find all the materials on the appropriate branch of our template repository.

Do you want to be part of an innovative team? Join us in iOS 🍎

Andrej JaššoiOS Developer