DocC documentation in Xcode

What is DocC?

DocC is a built-in documentation rendering tool that allows developers to easily build documentation for their code. Traditionally, developers would create documentation for their code via comments and then separately create documentation files for reference for other developers. DocC combines the 2 steps into a single step, making it easy for developers to write documentation and for others to read that documentation.

Requirements

This functionality is built into Xcode, so no other tool is required. You may need a hosting server incase you wish to host a web based version of the documentation. In this article we will be using Github pages to host our documentation.

Markdown comments

A key component to generate the documentation are comments that you have written. The comments must be formatted in a particular way so that Xcode can read them and use them to build the documentation. I have already covered this in an earlier article though we will have a look at some of those in a bit.

Creating additional documentation resources

The documentation tools generate documentation based on several resources:

  • Markdown comments
  • Type definitions, property declaration, and function declarations
  • available attribute

All these sources combined together provide a lot of information. But we are not just limited to these sources. We can add 2 other kinds of resources to our projects.:

  • Articles
  • Tutorial

Articles allow us to provide a little more context to the documentation. This is where advanced concepts such as functionality, underlying behavior, and things to know are presented to the user. It is possible to add diagrams and pictures to explain the concepts too.

Tutorials on the other hand allow the creator of the code to offer help to anyone who uses the API so that they can learn how to use the different features with the help of step by step instructions.

Both articles and tutorials add to the resources to make the documentation richer and more helpful.


Creating documentation for apps and packages

The process of creating documentation for apps/packages/frameworks is largely similar.

We will be using an example to understand how this goes. I will show only a small snippets of the code/comments here. You can download the completed project at the bottom of the file.

We will take a structured approach towards the design of our documentation.

  • First we will add the availability attributes
  • Second we will put comments for our code
  • Third we will add articles to our code
  • Fourth we will provide tutorials for our code

Adding availability attributes

Adding @available attributes is an important and useful part of the documentation process. It helps other users of your code know thing like which version of the language is required. What’s the minimum OS version that is required, and so on. All this becomes part of the documentation too. Let us look at how we can do this.

The code below represents a type called author. It’s a complete code but it’s missing the availability attributes. In fact, you should also see Xcode report an error for the link where we use Date.now saying that it’s only available from macOS 12 or later and that we should put an availability attribute for the same.

public struct Author {
    public var name         : String    = ""
    public var email        : String    = ""
    public var dateOfBirth  : Date      = Date.now
    public var phone        : String    = ""
    public var photo        : Data?
    public var website      : URL?
}

extension Author : CustomStringConvertible {
    public var description: String {
        let df : DateFormatter  = DateFormatter()
        df.dateStyle            = .medium
        df.timeStyle            = .medium
        
        return """
        Author
        ----------
        Name:       \(self.name)
        Email:      \(self.email)
        Birthday:   \(df.string(from: self.dateOfBirth))
        Phone:      \(self.phone)
        Website:    \(self.website?.description ?? "")
        """
    }
}

extension Author : Equatable {
    public static func ==(lhs : Author, rhs : Author) -> Bool {
        lhs.name == rhs.name && lhs.dateOfBirth == rhs.dateOfBirth
    }
}

Let’s add the attributes and see how it looks. The code show now look like this:

@available(swift 5.0)
@available(iOS 14, macOS 12, *)
public struct Author {
    public var name         : String    = ""
    public var email        : String    = ""
    public var dateOfBirth  : Date      = Date.now
    public var phone        : String    = ""
    public var photo        : Data?
    public var website      : URL?
}

@available(swift 5.0)
@available(iOS 14, macOS 12, *)
extension Author : CustomStringConvertible {
    public var description: String {
        let df : DateFormatter  = DateFormatter()
        df.dateStyle            = .medium
        df.timeStyle            = .medium
        
        return """
        Author
        ----------
        Name:       \(self.name)
        Email:      \(self.email)
        Birthday:   \(df.string(from: self.dateOfBirth))
        Phone:      \(self.phone)
        Website:    \(self.website?.description ?? "")
        """
    }
}

@available(swift 5.0)
@available(iOS 14, macOS 12, *)
extension Author : Equatable {
    public static func ==(lhs : Author, rhs : Author) -> Bool {
        lhs.name == rhs.name && lhs.dateOfBirth == rhs.dateOfBirth
    }
}

Adding comments

Let us continue with our previous example. Next we will add comments to the code. We will go use the markup features we saw in the earlier article. The comments should provide more details about the type. Things like version, copyright, date created, author, tips, contact details. In the case of functions you can have information about arguments and return types too. Let us add that to our code.

//
//  File.swift
//  
//
//  Created by Arun Patwardhan on 04/07/23.
//

import Foundation

/**
 Represents the author of the book.
 
 **Protocols**
 
 Conforms to `CustomStringConvertible` and `Equatable`
 
 - version: 1.0
 - note: The `name` and `dateOfBirth` are deemed to be unique properties.
 - since: iOS 14, macOS 11
 - author: Arun Patwardhan
 - copyright: Amaranthine (c) 2023
 - date: 3rd July 2023
 - requires: Swift 5.x
 - Tip: See the article on creating markup comments [Adding formatted Text to Swift](https://arunpatwardhan.com/2017/11/09/adding-formatted-text-to-swift-in-xcode/)
 
 [arun@amaranthine.co.in](mailto:arun@amaranthine.co.in)
 */
@available(swift 5.0)
@available(iOS 14, macOS 12, *)
public struct Author {
    public var name         : String    = ""
    public var email        : String    = ""
    public var dateOfBirth  : Date      = Date.now
    public var phone        : String    = ""
    public var photo        : Data?
    public var website      : URL?
}

@available(swift 5.0)
@available(iOS 14, macOS 12, *)
extension Author : CustomStringConvertible {
    public var description: String {
        let df : DateFormatter  = DateFormatter()
        df.dateStyle            = .medium
        df.timeStyle            = .medium
        
        return """
        Author
        ----------
        Name:       \(self.name)
        Email:      \(self.email)
        Birthday:   \(df.string(from: self.dateOfBirth))
        Phone:      \(self.phone)
        Website:    \(self.website?.description ?? "")
        """
    }
}

@available(swift 5.0)
@available(iOS 14, macOS 12, *)
extension Author : Equatable {
    public static func ==(lhs : Author, rhs : Author) -> Bool {
        lhs.name == rhs.name && lhs.dateOfBirth == rhs.dateOfBirth
    }
}

Note that we did not add comments to the extensions of the type. Also, it isn’t always necessary to add comments. Sometimes the types are fairly simple and self explanatory.

Creating the Documentation catalog

Now that we have added comments letโ€™s add the DocC documentation.

In fact, you donโ€™t have to do much. Simply select build documentation and it should create it for you with the code, comments, and availability information that is already there.

To create our documentation just select Product > Build Documentation from the menu.

You can see that already a lot of information is available without having to add any documentation.

The documentation we add will just build on this.

Creating Documentation Catalogs

The resources that we need for documentation are added within a documentation catalog. Items like Articles, Tutorial, Sample code, images are all added to the documentation catalog. These items are used to build our documentation. Let us add a documentation catalog to our project.

  1. With our package open select File > New > File from the menu bar.
  2. Choose the Documentation catalog option from the template wizard.
  1. Click Next.
  2. You should see the catalog added to your project.
  1. Select the file called Documentation within the Documentation folder. This is the top level documentation file. We will place information about the package in here.
  2. Rename this file to match the name of our project.
  3. Next add the code shown below to our file. We will examine the different items in a moment.
# ``AmaranthineLibrary``

The types available in ths package are to be used in applications that work with books and collections of books.

## Overview

![Library Types](library)

In this documentation we will look at the different types available. The idea behind these types is to support the creation of apps that work in different libraries. This should allow all kinds of institutions to quickly develop their own solutions for in-house libraries.

### Types

| Type | Description |
| --------- | --------------------------------------- |
| `Genre` | This describes the `Genre` of the book. |
| `Book` | This represents a single book |
| `Author` | This describes the author of the book |
| `Library` | This describes the Library type. |

- <enum:Genre>

## Topics

- <doc:BookInformation>
- <doc:LibraryInformation>
- <doc:Tutorial-Table-of-Contents>

@Small {
MIT License
}
  1. Under the resources folder add any image of your choice. I have an image called “library” and have added that in there.

That’s it for now. Let us look at what we have written.

First up a lot of the formatting you see is similar to markdown style. This means that many thing like headings with a ‘#’, designing tables are already familiar. Let us look at the first line.

# ``AmaranthineLibrary``

Next we have text description giving us information about the framework. This is followed by the heading for Overview.

![Library Types](library)

This line of code adds an image to the documentation. The text in the square brackets is the description followed by the name of the image file in round brackets. This name is the same as the name used while uploading the image in step 8. It is also possible to provide variations of the image. You can provide images with different scales and support for dark mode as long as you follow the correct naming convention.

<image name>~dark<scale>.<file extension>

For example, I could have provided different version of the library image.

library~dark@2x.png
library@2x.png

The system picks the correct one based on the need.

| Type      | Description  |
| --------- | --------------------------------------- |
| `Genre` | This describes the `Genre` of the book. |
| `Book` | This represents a single book |
| `Author` | This describes the author of the book |
| `Library` | This describes the Library type. |

This creates a simple table. A single back tick is used to make the text appear in the code syntax.

- <enum:Genre>

This is another form of a link. This links directly to the Genre type documentation page. There are other ways of creating links to documentation pages. We see them in the code snippet below.

- <doc:BookInformation>
- <doc:LibraryInformation>
- <doc:Tutorial-Table-of-Contents>

Now we haven’t created these articles yet but this is how we would create links to them. The name matches the name of the article itself. It could also be a link to a tutorial page.

@Small {
MIT License
}

Finally this allows us to include any fine print text we wish to add to our page. Build the documentation and look at the output. Notice that we get errors for the links to the articles as we have created them yet. For now we will delete the links to the LibraryInformation and the Tutorial table of contents.

Next we will look at creating articles.

Creating Articles

Articles allow us to provide more information about the different types that we have declared in our code. As I mentioned earlier. It allows us to add more information to the existing documentation that has been built. Let us go ahead and create the article for the Book type.

  1. Click on File > New > File from the menu bar.
  2. Select the document type as Article from the template wizard.
  3. Name it BookInformation
  4. Create it.
  5. Add the following code to the article.
# BookInformation

The ``Book`` type represents a single book.

## Overview

![Book](book)

The type is built up using several different properties. ``Book/author``, ``Book/genre``, ``Book/pageCount``, ``Book/publishedOn``, ``Book/title``, and ``Book/isbn``. Is a ``Book`` is to be uniquely identified then the ``Book/isbn`` property can be used for the same.

### Protocols supported
- `CustomStringConvertible`
- `Equatable`

### Output format

```shell
"E-book"
"Hardbound"
"Paperback"
"Web page"
```
## Topics

### Types

- ``Book``
  1. Add an image called ‘book’ to the resources folder.

Let us have a look at the different things added.

# BookInformation

The ``Book`` type represents a single book.

First up we have the title of the article. Then we have its description. Within the description there is a link for the Book type. Included using the double back ticks. This is a good way to help people reading the documentation to directly go over to the type itself.

## Overview

![Book](book)

The type is built up using several different properties. ``Book/author``, ``Book/genre``, ``Book/pageCount``, ``Book/publishedOn``, ``Book/title``, and ``Book/isbn``. Is a ``Book`` is to be uniquely identified then the ``Book/isbn`` property can be used for the same.

Then we have the overview title with the image of a book. This is followed by a description along with links to properties within the type. Links to such properties are established using The path approach. Where we first mention the Type followed by a slash followed by the property. The rest of the article lists out the protocols that our type conforms to and the output format incase its to be printed.

Similarly we will add an article for the Author. Create a new article called author information. Add the code below.

# ``AmaranthineLibrary/Author``

@Metadata {
@DocumentationExtension(mergeBehavior: override)
}

The ``Author`` type represents the author of the book.

## Overview

![Author](author)

This type is built up using 3 properties: ``Author/name``, ``Author/email``, and ``Author/dateOfBirth``. An instance of ``Author`` is said to be unique if both the ``Author/name`` as well as the ``Author/dateOfBirth`` are unique.

## Output format for description

```shell
Author
----------
Name: ABC
Email: abc@mail.com
Birthday: 23 January 1998
"""
```

## Information
> Note: The ``Author/name`` and ``Author/dateOfBirth`` are deemed to be unique properties.

> Important: Requires Swift 5.x

> Tip: See the article on creating markup comments [Adding formatted Text to Swift](https://arunpatwardhan.com/2017/11/09/adding-formatted-text-to-swift-in-xcode/)

[arun@amaranthine.co.in](mailto:arun@amaranthine.co.in)

## Topics

### Types

``Author``

Most of the items are a the same. Let us look at some new things here.

# ``AmaranthineLibrary/Author``

Notice that the title of the article is now a link to the type within the project rather than a static name.

@Metadata {
@DocumentationExtension(mergeBehavior: override)
}

Next we have the metadata. This tell Xcode how to handle the document creation. Should it merge the auto generated documentation with the contents of our article or should the contents of the article override the information. Here we are saying it overrides.

## Information
> Note: The ``Author/name`` and ``Author/dateOfBirth`` are deemed to be unique properties.

> Important: Requires Swift 5.x

> Tip: See the article on creating markup comments [Adding formatted Text to Swift](https://arunpatwardhan.com/2017/11/09/adding-formatted-text-to-swift-in-xcode/)

[arun@amaranthine.co.in](mailto:arun@amaranthine.co.in)

This bit of code is also different. It creates sections for tips, notes, important information. It renders in the documentation with color highlights.

Build the documentation and see how it looks.

There are many other links and formatting options available.

  • You can control the page layout using tabs, tables.
  • You can add small disclosure text
  • Links can be added to specific properties and functions.
  • Extra top level documents: These are not articles related to a specific type but rather general information about the project.
  • Availability for the documentation
  • Comments: Items that are not rendered but are present for the creator of the documentation to take notes
  • Page appearance

Don’t forget to look at the completed code to see the different kinds of formats that have been used.

Next we will look at creating tutorials.

Creating tutorials

Tutorials as the name suggests are simple guides that walk through the usage of your code. Its a great way to help users of your code to learn how to use the types and functions that you have declared.

Tutorials are easy to create. Lets start of by creating the table of contents file.

  1. Click on File > New > File from the menu bar.
  2. Choose Tutorial Table of Contents.
  3. Give it a name
  4. Create it.
  5. It should come pre-populated with some formatted text to show the table of contents.
  6. Replace that with the code shown below. We will explore the different parts of the text in a moment.
@Tutorials(name: "Using the different types available") {
@Intro(title: "How to use the different types") {
In this tutorial we will look at creating and using the different types.

@Image(source: library.png, alt: "Library")
}

@Volume(name: "Creating types") {

First we will look at how to create instances of the different types.
@Image(source: create.png, alt: "Create")

@Chapter(name: "Author") {
In this chapter we look at how to create objects of type ``AmaranthineLibrary/Author``.
@Image(source: author.png, alt: "Author")
@TutorialReference(tutorial: "doc:AuthorTutorial")
}

@Chapter(name: "Genre") {
In this chapter we look at how to create objects of type ``AmaranthineLibrary/Genre``.
@Image(source: genre.png, alt: "Genre")
@TutorialReference(tutorial: "doc:GenreTutorial")
}

@Chapter(name: "Book") {
In this chapter we look at how to create objects of type ``AmaranthineLibrary/Book``.
@Image(source: book.png, alt: "Book")
@TutorialReference(tutorial: "doc:BookTutorial")
}

@Chapter(name: "Library") {
In this chapter we look at how to create objects of type ``AmaranthineLibrary/Library``.
@Image(source: library.png, alt: "Library")
@TutorialReference(tutorial: "doc:LibraryTutorial")
}
}

@Volume(name: "Working with the library") {
Next we will look at how all the types work together as a part of the library.

@Image(source: assemble.png, alt: "Assemble")

@Chapter(name: "Working with the library") {
In this chapter we look at how to use the ``AmaranthineLibrary/Library`` object.
@Image(source: library.png, alt: "Library")
@TutorialReference(tutorial: "doc:UsingTheLibraryTutorial")
}
}

@Resources {
Explore more resources for learning about the different features that we have used in Swift.

@Videos(destination: "https://www.youtube.com/channel/UC127UHd8V7bxPQYnd9QrN8w") {
To view various blog articles and videos.

- [My Blog](https://www.arunpatwardhan.com/)
}

@SampleCode(destination: "https://github.com/AmaranthineTech") {
Download and explore sample code projects.

- [Sample code](https://github.com/AmaranthineTech/)
}

@Documentation(destination: "https://amaranthinerandomgenerators.github.io/documentation/amaranthinerandomgenerators/") {
Browse and search documentation for ``AmaranthineLibrary`` project online.

- [AmaranthineLibrary](https://amaranthinelibrary.github.io/documentation/amaranthinelibrary/)
}
}
}

Let us examine each statement block line by line.

@Tutorials(name: "Using the different types available") 

Right at the top we have the title for the tutorial. All the chapter and volume listings are within this block.

@Intro(title: "How to use the different types") {
In this tutorial we will look at creating and using the different types.

@Image(source: library.png, alt: "Library")
}

Then we have the introduction for the tutorial. Here we can give a brief introduction about the tutorial itself. We can add artwork to help illustrate things for the user.

@Volume(name: "Creating types") {

First we will look at how to create instances of the different types.
@Image(source: create.png, alt: "Create")

@Chapter(name: "Author") {
In this chapter we look at how to create objects of type ``AmaranthineLibrary/Author``.
@Image(source: author.png, alt: "Author")
@TutorialReference(tutorial: "doc:AuthorTutorial")
}

...
}

Next we provide the list of chapters. We can directly provide the list of chapters, or, if our tutorial covers different sections we can have multiple volumes each with a list of chapters. That is what I have done in this example.

The name of the volume, some text explains what is covered in this volume. With the chapter blocks in it. The chapters have a similar structure with name, text, image, and a link to the tutorial document.

@Resources {
Explore more resources for learning about the different features that we have used in Swift.

@Videos(destination: "https://www.youtube.com/channel/UC127UHd8V7bxPQYnd9QrN8w") {
To view various blog articles and videos.

- [My Blog](https://www.arunpatwardhan.com/)
}

@SampleCode(destination: "https://github.com/AmaranthineTech") {
Download and explore sample code projects.

- [Sample code](https://github.com/AmaranthineTech/)
}

@Documentation(destination: "https://amaranthinerandomgenerators.github.io/documentation/amaranthinerandomgenerators/") {
Browse and search documentation for ``AmaranthineLibrary`` project online.

- [AmaranthineLibrary](https://amaranthinelibrary.github.io/documentation/amaranthinelibrary/)
}
}

At the end there is a resources block. This is a great place to put links to other resources that the reader may find useful. These can be categorized to give the reader more information. Here are some of the categories:

  • Documentation
  • Sample code
  • Videos
  • Forums
  • Downloads

Each of these can contain multiple links. Before we build the documentation let us add a tutorial document. In order to do that let us remove the extra volumes and chapters from the table of contents for the moment. This can be added later. Your final code should look like:

@Tutorials(name: "Using the different types available") {
@Intro(title: "How to use the different types") {
In this tutorial we will look at creating and using the different types.

@Image(source: library.png, alt: "Library")
}

@Volume(name: "Creating types") {

First we will look at how to create instances of the different types.
@Image(source: create.png, alt: "Create")

@Chapter(name: "Book") {
In this chapter we look at how to create objects of type ``AmaranthineLibrary/Book``.
@Image(source: book.png, alt: "Book")
@TutorialReference(tutorial: "doc:BookTutorial")
}
}

@Resources {
Explore more resources for learning about the different features that we have used in Swift.

@Videos(destination: "https://www.youtube.com/channel/UC127UHd8V7bxPQYnd9QrN8w") {
To view various blog articles and videos.

- [My Blog](https://www.arunpatwardhan.com/)
}

@SampleCode(destination: "https://github.com/AmaranthineTech") {
Download and explore sample code projects.

- [Sample code](https://github.com/AmaranthineTech/)
}

@Documentation(destination: "https://amaranthinerandomgenerators.github.io/documentation/amaranthinerandomgenerators/") {
Browse and search documentation for ``AmaranthineLibrary`` project online.

- [AmaranthineLibrary](https://amaranthinelibrary.github.io/documentation/amaranthinelibrary/)
}
}
}

Also add the create image to the resources folder. Now we can create our tutorial document.

  1. Click on File > New > File from the menu bar.
  2. Select tutorial file from the template wizard
  3. Name it ‘BookTutorial’
  4. Replace the prefilled text with the markdown shown below
@Tutorial(time: 10) {
@Intro(title: "Creating an instance of Book.") {
We will look at the steps involved in creating an instance of Book.
}

@Section(title: "Create an Book object") {
@ContentAndMedia {
We will look at the steps involved in creating an instance of Book.

@Image(source: book.png, alt: "Book")
}

@Steps {
@Step {
Create the author object

@Code(name: "BookCodeFile.swift", file: BookCodeFile.swift)
}

@Step {
Create the variable that holds the genre.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-1.swift)
}

@Step {
Create the variable that holds the book style.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-2.swift)
}

@Step {
Gather additional book details.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-3.swift)
}

@Step {
Create the variable that holds the book.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-4.swift)
}
}
}
}

Let us explore what is happening in here.

@Tutorial(time: 10) {

We start off by specifying the time estimate for completing the tasks. This is in minutes.

@Intro(title: "Creating an instance of Book.") {
We will look at the steps involved in creating an instance of Book.
}

Next we provide an introduction for this specific tutorial.

@Section(title: "Create an Book object") {

Then we declare the section for this tutorial. The section contains the steps for a specific task.

@ContentAndMedia {
We will look at the steps involved in creating an instance of Book.

@Image(source: book.png, alt: "Book")
}

We provide a little description for the section along with an image using the ContentAndMedia block.

@Steps {
@Step {
Create the author object

@Code(name: "BookCodeFile.swift", file: BookCodeFile.swift)
}

@Step {
Create the variable that holds the genre.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-1.swift)
}

@Step {
Create the variable that holds the book style.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-2.swift)
}

@Step {
Gather additional book details.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-3.swift)
}

@Step {
Create the variable that holds the book.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-4.swift)
}
}

Then we have the steps within the @Steps block. Each step is in its own @Step block. Note the difference between the two. The outer one is @Steps to indicate it holds a series of steps. Inside this is the @Step which represents a single step.

Each step contains the description for that step along with its @Code block. The way the tutorial works is that it walks the reader through a series of tasks that it performs. What is to be done is described in the text and a sample preview for the code is generated through the code file mentioned in the code block.

We will need to upload a series of code files. Each file contains additional code. Listing them in sequence generates the flow. Add the following files to the resources folder of your documentation. Name them BookCodeFile.swift, BookCodeFile-1.swift, BookCodeFile-2.swift, BookCodeFile-3.swift, and BookCodeFile-4.swift.

BookCodeFile.swift

//
//  BookCodeFile.swift
//  
//
//  Created by Arun Patwardhan on 09/08/23.
//

import AmaranthineLibrary

let authorName      : String    = "Arun"
let authorEmail     : String    = "arun@mail.com"
let authorDOB       : Date      = Date(timeIntervalSince1970: 123456789)
let authorPhone     : String    = "9182736450"
let authorLink      : URL       = URL(string: "https://arunpatwardhan.com")

let arun            : Author    = Author(name: authorName,
                                     email: authorEmail,
                                     dateOfBirth: authorDOB,
                                     phone: authorPhone,
                                     photo: nil,
                                     website: authorLink)

BookCodeFile-1.swift

//
//  BookCodeFile.swift
//
//
//  Created by Arun Patwardhan on 09/08/23.
//

import AmaranthineLibrary

let authorName      : String    = "Arun"
let authorEmail     : String    = "arun@mail.com"
let authorDOB       : Date      = Date(timeIntervalSince1970: 123456789)
let authorPhone     : String    = "9182736450"
let authorLink      : URL       = URL(string: "https://arunpatwardhan.com")

let arun            : Author    = Author(name: authorName,
                                     email: authorEmail,
                                     dateOfBirth: authorDOB,
                                     phone: authorPhone,
                                     photo: nil,
                                     website: authorLink)

let bookGenre       : Genre     = Genre.educational

BookCodeFile-2.swift

//
//  BookCodeFile.swift
//
//
//  Created by Arun Patwardhan on 09/08/23.
//

import AmaranthineLibrary

let authorName      : String    = "Arun"
let authorEmail     : String    = "arun@mail.com"
let authorDOB       : Date      = Date(timeIntervalSince1970: 123456789)
let authorPhone     : String    = "9182736450"
let authorLink      : URL       = URL(string: "https://arunpatwardhan.com")

let arun            : Author    = Author(name: authorName,
                                     email: authorEmail,
                                     dateOfBirth: authorDOB,
                                     phone: authorPhone,
                                     photo: nil,
                                     website: authorLink)

let bookGenre       : Genre     = Genre.educational

let bookStyle       : BookStyle = BookStyle.paperback

BookCodeFile-3.swift

//
//  BookCodeFile.swift
//
//
//  Created by Arun Patwardhan on 09/08/23.
//

import AmaranthineLibrary

let authorName      : String    = "Arun"
let authorEmail     : String    = "arun@mail.com"
let authorDOB       : Date      = Date(timeIntervalSince1970: 123456789)
let authorPhone     : String    = "9182736450"
let authorLink      : URL       = URL(string: "https://arunpatwardhan.com")

let arun            : Author    = Author(name: authorName,
                                     email: authorEmail,
                                     dateOfBirth: authorDOB,
                                     phone: authorPhone,
                                     photo: nil,
                                     website: authorLink)

let bookGenre       : Genre     = Genre.educational

let bookStyle       : BookStyle = BookStyle.paperback

let bookTitle       : String    = "Introduction to Swift"
let bookISBN        : String    = "34243-3433-2"
let pageCount       : Int       = 987
let publicationDate : Date      = Date(timeIntervalSince1970: 9876543210)

BookCodeFile-4.swift

//
//  BookCodeFile.swift
//
//  BookCodeFile.swift
//
//
//  Created by Arun Patwardhan on 09/08/23.
//

import AmaranthineLibrary

let authorName      : String    = "Arun"
let authorEmail     : String    = "arun@mail.com"
let authorDOB       : Date      = Date(timeIntervalSince1970: 123456789)
let authorPhone     : String    = "9182736450"
let authorLink      : URL       = URL(string: "https://arunpatwardhan.com")

let arun            : Author    = Author(name: authorName,
                                     email: authorEmail,
                                     dateOfBirth: authorDOB,
                                     phone: authorPhone,
                                     photo: nil,
                                     website: authorLink)

let bookGenre       : Genre     = Genre.educational

let bookStyle       : BookStyle = BookStyle.paperback

let bookTitle       : String    = "Introduction to Swift"
let bookISBN        : String    = "34243-3433-2"
let pageCount       : Int       = 987
let publicationDate : Date      = Date(timeIntervalSince1970: 9876543210)

let swiftTextBook   : Book      = Book(title: bookTitle,
                                       author: arun,
                                       publishedOn: publicationDate,
                                       isbn: bookISBN,
                                       pageCount: pageCount,
                                       genre: bookGenre,
                                       format: bookStyle)

Build the documentation and see how it renders the tutorial. It should look like this:

You can add preview images to your tutorial too to give a visual preview for your code. This is really useful when you are creating tutorials for UI based elements.

Adding assessments

One nice feature of tutorial is the ability to add assessments.

Assessments are a good way of helping readers determine if they have understood specific aspects of the code well. Itโ€™s also a good way to drive home key concepts related to the code.

Assessments are added to the tutorial and is located at the bottom of the tutorial section. Add the following to our Book Tutorial:

@Assessments {
@MultipleChoice {

Which of the following types is not used while creating an instance of a `Book`?

@Choice(isCorrect: false) {
`float`

@Justification(reaction: "Try again!") {
Have a look at the `Book` type to see what has been used.
}
}

@Choice(isCorrect: false) {
`Data`

@Justification(reaction: "Try again!") {
Have a look at the `Book` type to see what has been used.
}
}

@Choice(isCorrect: true) {
`Author`

@Justification(reaction: "That's right!") {
A `Book` has an `Author`.
}
}

@Choice(isCorrect: false) {
`Bool`

@Justification(reaction: "Try again!") {
Have a look at the `Book` type to see what has been used.
}
}
}
}

Let us explore each item in this.

@Assessments {

First we have our assessments block. All our multi choice questions go in here.

@MultipleChoice {

Which of the following types is not used while creating an instance of a `Book`?

Then we have the multi choice block along with the question itself.

@Choice(isCorrect: false) {
`float`

@Justification(reaction: "Try again!") {
Have a look at the `Book` type to see what has been used.
}
}

A MulitpleChoice block contains 2-4 choices. Each choice is represented with its own @Choice block. A choice block has a boolean flag indicating if its the right answer, the choice value, and a hint in the form of a justification to guide the reader to the correct value in case the choice isn’t correct.

Your complete tutorial should now look like:

@Tutorial(time: 10) {
@Intro(title: "Creating an instance of Book.") {
We will look at the steps involved in creating an instance of Book.
}

@Section(title: "Create an Book object") {
@ContentAndMedia {
We will look at the steps involved in creating an instance of Book.

@Image(source: book.png, alt: "Book")
}

@Steps {
@Step {
Create the author object

@Code(name: "BookCodeFile.swift", file: BookCodeFile.swift)
}

@Step {
Create the variable that holds the genre.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-1.swift)
}

@Step {
Create the variable that holds the book style.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-2.swift)
}

@Step {
Gather additional book details.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-3.swift)
}

@Step {
Create the variable that holds the book.

@Code(name: "BookCodeFile.swift", file: BookCodeFile-4.swift)
}
}
}

@Assessments {
@MultipleChoice {

Which of the following types is not used while creating an instance of a `Book`?

@Choice(isCorrect: false) {
`float`

@Justification(reaction: "Try again!") {
Have a look at the `Book` type to see what has been used.
}
}

@Choice(isCorrect: false) {
`Data`

@Justification(reaction: "Try again!") {
Have a look at the `Book` type to see what has been used.
}
}

@Choice(isCorrect: true) {
`Author`

@Justification(reaction: "That's right!") {
A `Book` has an `Author`.
}
}

@Choice(isCorrect: false) {
`Bool`

@Justification(reaction: "Try again!") {
Have a look at the `Book` type to see what has been used.
}
}
}
}
}

Build the documentation. Explore the tutorial and its assessment. It should look like this. The incorrect answers are highlighted in red while the correct one is in green.

That’s it. That covers the basic elements of creating documentation and tutorials for your code. Don’t forget to look at the completed code below.

Top level documentation and other markdown attributes

There are many kinds of attributes available for markdown. We have already seen some of them above. Lets look at a few more.

@MetaData

This attribute allows us to specify how DocC should build this document. Here are some of the items you can mention in there:

AttributeDescriptionPossible values
@DocumentationExtensionUsed to indicate if the contents of the article should override the default documentation or be appended to it.override, append
@PageColorUsed to specify the color to be used for the banner at the top of the pageblue, gray, green, orange, purple, red, yellow
@TechnologyRootUsed to indicate that this is a top level document and that it is not related to any specific type or code in the framework. This is useful when we want to provide some other information not related to the API in question.
@AvailableIndicates the availability of the documentation itself.Platform: iOS, macOS, watchOS, tvOS
@CallToActionThis is used to provide links to resources or downloads associated with that particular page.Purpose argument can have: download, link
@PageKindUsed to specify if the page added is an article or a sample code that is being displayed.article, samplecode
@PageImageUsed to provide an image for the page.Purpose argument can have: icon, card
@DisplayNameUsed to provide a custom name for a page rather than the symbol’s name.String
@SupportedLanguageUsed to specify which programming language supports the specific feature.swift, objc, objective-c

@Options

Similarly we can configure some options for the documentation. This controls how the documentation is rendered. It could be for a specific page or for all the pages in the API. Here are some of the options that we can configure.

AttributeDescriptionpossible values
@AutomaticSeeAlsoUsed to indicate if the see also section is automatically created or not.enabled, disabled
@AutomaticTitleHeadingUsed to indicate if the title head is automatically created or not.enabled, disabled
@TopicsVisualStyleUsed to specify how the topics on a page should be shown.list, compactGrid, detailedGrid, hidden
@AutomaticArticleSubheadingUsed to indicate if the article subheading is automatically created or not.enabled, disabled

@Row

We can add rows and columns too.

@Row {
@Column {
Metadata
}


@Column {
<doc:Author>,
<doc:LibraryInformation>
}
}

@Row {
@Column {
Options
}


@Column {
<doc:AmaranthineLibrary>
}
}

@Row {
@Column {
Tip, Note, Important
}


@Column {
<doc:Author>
}
}

@Row {
@Column {
Tutorials
}


@Column {
<doc:Tutorial-Table-of-Contents>, <doc:AuthorTutorial>, <doc:BookTutorial>, <doc:GenreTutorial>, <doc:UsingTheLibraryTutorial>
}
}

@Row {
@Column {
Assessemnts
}


@Column {
<doc:BookTutorial>
}
}

The above code generates a systematic structure like this:

@TabNavigator

We can offer information on a page with the help of a tab navigator too. This allows us to quickly show multiple options or related information in a structured way.

@TabNavigator {
@Tab("add") {
![add](add)
}

@Tab("assemble") {
![assemble](assemble)
}

@Tab("author") {
![author](author)
}

@Tab("book") {
![book](book)
}

@Tab("checkout") {
![checkout](checkout)
}

@Tab("create") {
![create](create)
}

@Tab("find") {
![find](find)
}

@Tab("genre") {
![genre](genre)
}

@Tab("library") {
![library](library)
}
}

This renders it as:

If there are fewer tabs then it renders slightly differently.

@Links

We can add a list of links too.

@Links(visualStyle: list) {
- <doc:AuthorTutorial>
- <doc:BookTutorial>
- <doc:GenreTutorial>
- <doc:LibraryTutorial>
}

This renders into a simple list of links. You can choose to have it in a compactGrid style or detailedGrid style.

@Small

There is also a way to add small disclaimer or licensing text using the @Small block.

@Small {
MIT License

Copyright (c) 2015 Amaranthine

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
}

This renders it as:

@Comment

Just like we can have comments in our code, we can have comments for our documentation too. The documentation builder does not render them and it is only meant for the author(s) of the documentation. This is a good way to put notes in for things that need to be done.

@Comment {
Dont forget to change the name of this file.
}

Exporting documentation

Now that we have seen different ways of documenting our code its time to start sharing it with our users. Of course when ever users of our package add the package to their project they can simply build the documentation as we have been doing so far. But in some situations users would like to go through the documentation before hand or would like to access it to check something. It is possible to export our documentation to make it accessible to them.

There are a couple of ways of exporting our documentation:

  • Directly export the documentation from the graphical user interface
  • Using the docc command from the command line interface

Let us look at both.

Exporting the documentation via the GUI

  1. First build the documentation for your project.
  2. Select the top level documentation file from the documentation window.
  1. Hover over the right hand side of the documentation name. You should see a more button with 3 dots appear.
  2. Click on the 3 dots and choose “Export”
  1. Choose where you wish to save the archive.
  2. Export it.
  3. Now open the archive by double clicking on the file.
  4. You should see the same documentation but under the imported catalog section.

Export using the command line

  1. First make sure that your project is allready pushed and commited to the github archive.
  2. Now we will be using the Swift-DocC plugin to generate the documentation. We need to add it as a dependency to the Swift Package. Update the Package.swift file to include the dependency.
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "AmaranthineLibrary",
    platforms: [
        .iOS(.v14),
        .macOS(.v11),
      ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "AmaranthineLibrary",
            targets: ["AmaranthineLibrary"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "AmaranthineLibrary",
            dependencies: []),
        .testTarget(
            name: "AmaranthineLibraryTests",
            dependencies: ["AmaranthineLibrary"]),
    ]
)
  1. Next we will run the swift command to generate documentation. Run the following command in your package folder.
swift package generate-documentation --source-service github --source-service-base-url https://github.com/AmaranthineTech/AmaranthineLibrary/blob/main --checkout-path /Users/instructor/Developer/AmaranthineLibrary/

Update the paths to match your own implementation. I have cloned the git repository in the /Users/instructor/Developer/ folder.

When you run the command it will tell you where the doccarchive is saved.

  1. Copy the doccarchive and share it.
  2. Open it to view the links to the different files. These links are generated thanks to the --source-service and --source-service-base-url options.

The links to the files should look like this:

This is one of the big advantages of generating the archive via the command line. You could also use the xcodebuild and xcrun to generate the documentation too.


Hosting the documentation

Exporting documentation is one way of sharing the documentation with users. But it would be even better if we could publish it as a webpage. Let us look at how to do that.

There are a couple of ways of publishing the documentation to a website.

  • File server
  • Web server with custom routing
  • Static pages on github

We will look at how to host them as static pages on github.

In order to host static pages on Github you will need a Github account. You can create one for free if you want.

There are 3 broad steps involved in hosting our documentation webpage on Github.

  1. Creating the Github repository for hosting the webpages
  2. Generating the publishable version of our documentation
  3. Uploading the documentation to Github.

Let’s look at those 3 steps in detail.

Step 1: Creating a Github repository for hosting the web pages.

We are going to use a feature called Github Pages. As explained on the website:

GitHub Pages is a static site hosting service that takes HTML, CSS, and JavaScript files straight from a repository on GitHub, optionally runs the files through a build process, and publishes a website.

Github Documentation

There are 3 types of sites that can be hosted:

TypeDescriptionSample URL
ProjectThe site is connected to a project on Github
UserThe site is hosted in a repository owned by a personal user account.<username>.github.io
OrganisationThe site is hosted in a repository owned by an organisation account.<organisation>.github.io

Depending on your needs you can go in for any one of those. For this demo we will be going in for an Organisation site.

The name of the repository will be in the format mentioned in the sample url above. So let us go ahead and create one.

  1. Create an organisation on Github if needed. You can use an existing one if you want.
  2. Next we will create a repository to host our website. Click on repository and create new.
  3. We need to provide the name of our repository. It should follow the format: <organisation name>.github.io .
  4. Provide a description, this is optional.
  5. Set the site as public or private depending on your requirements.
  6. Create the repository.
  7. Navigate to the repository
  8. Go to settings
  9. Select the pages tab.
  10. Make sure “Deploy from a branch” is sected under source.
  11. Under branch select main and the folder as /root.
  12. Your pages screen should look like this:
  1. Go back to your code section of the repository.
  2. Add a new file called readme.md. Put some basic text in it.
  3. Switch back to the settings > pages section of the repository.
  4. You should see a link to the repository.
  1. Click on Visit site. You should see your readme.md file open in your browser. We will be replacing it with our docc documentation.
  2. Let us clone this repository on our computer.
  3. Now we can add our files there. Run the following commands. I will be creating the repository in the ~/Developer folder on my computer.
cd ~/Developer
git clone https://github.com/AmaranthineLibrary/amaranthinelibrary.github.io
cd amaranthinelibrary.github.io
mkdir docs

Step 2: Generating a publishable version of our documentation

Now that our repository is ready we will generate the documentation.

There are couple of ways of generating that documentation. However, we will simply extract it from the archive we created previously.

  1. Copy the archive from the exporting section where we used the Swift-DocC plugin.
  2. Save it within the ~/Developers folder.
  3. View the contents of the archive by control clicking on it and selecting “Show Package Contents”.
  1. Copy the contents of that folder.
  2. Paste them in the website repository we cloned on our computer, within the docs folder.

Step 3: Uploading the documentation and publishing

The next and final step is to upload this to github and its ready. Use the following commands:

cd ~/Developer/amaranthinelibrary.github.io
git add docs
git commit -m "New files"
git push

Thats it. We have uploaded our documentation. Now we will modify our page to ue the docs folder as source.

Go back to github.com and settings for your repository. Update the pages sestion to use the source as docs.

To view it use the following link format:

https://<repository link/documentation/target

Here are the links to the documentation that I created.

https://amaranthinelibrary.github.io/documentation/bloginformation

https://amaranthinelibrary.github.io/documentation/amaranthinelibrary

There are 2 because i added a top level documentation.


Final thoughts

There you go. We have successfully created, viewed, exported, and hosted documentation for our API using Swift DocC.

As you can see the process is fairly simple and straightforward. Yes, it does appear like its a lot of work, but building this practice will go a long way in making your code more useful, and easy to understand for anyone that’s using your code.

The best way to work with DocC is to start writing the comments, availability attributes, articles, and tutorials as you develop your code. This is far better than leaving it as a standalone activity.

So go ahead, use DocC in as many places as possible, even app projects. It will make life very simple.

Download

You can download the Swift Package Manager project here.


Video

Creating custom operators in Swift

What are custom operators?

Custom operators are operators that are defined by us and are not part of the programming language natively.

We are all aware of the built in operators in the Swift Language. 

Operators like: + – * % > == ! to name a few.

These operators are defined by the system. It is also possible for us to overload some of these operators. However there are situations where we would like to create our own operators that perform operations not defined by the system. 

Thats exactly what Custom operators are. They are operators defined by the developer. These are not overloaded operators but completely new operators that donโ€™t exist otherwise.

These operators are used within the project that we are working on. Though it is possible for us to share these operators using Swift Packages or XCFrameworks.

These operators are typically associated with a specific type and their behavior is also defined by us.

Why do we need them?

There are many reasons why we would want custom operators:

  1. Allow for more compact and concise syntax.

Using custom operators allows our code to be more compact. Entire function calls can be condensed into a single operator.

  1. Make the code more readable

This also improves the readability of our code. Properly chosen symbols can convey the message immediately and easily. 

  1. Allow for consistency in design of code

One of the other things that custom operators help us achieve is consistency. By using standard operations as operators we make our code more familiar and consistent to others who may read it. Programmers are familiar with the concept of operators and using them for different operations. So even if they may not immediately recognise the operator they would understand that there is some task for them to perform.

And finally it encourages reusability.

What do we need to create custom operators?

There are a couple of things that we need to create custom operators:

  1. A logic for the action being performed by the operator
  2. A list of valid symbols
  3. Information about the operators attributes like prefix, postfix, infix.
  4. The precedence of the operator if it is an infix operator

Operator Rules

There are some rules that must be followed when we are constructing the symbol for our operator. Most of the requirements are rather straightforward. However, choosing the right symbol is a very important task. There are a set of symbols that are allowed. 

There are rules as far as whitespace around operators is concerned.

And finally there are certain symbols are allowed only in combination with other symbols. 

Operator types
TypeDescription
PrefixOperators that appear before a variable or value. These are unary operators.
PostfixOperators that appear after a variable or value. These are unary operators.
InfixOperators that appear in between variables or values. These are binary operators.

Allowed Characters

This is the important bit. Which characters are allowed for usage as an operator. 

We can have ASCII symbols that are used for builtin operators.

There are also many mathematical symbols that can be used as operators.

Note that the list of symbols show in the slide are not complete. 

TypeExamples of different symbols
ASCII Characters/, =, -, +, !, *, %,<, >, &, |, ^, ?, ~
Mathematical Operators,
Miscellaneous symbols, dingbats*
โˆ, โˆš, โІ, โ‰ฟ, โˆซ

Here are some more

U+00A1โ€“U+00A7U+2190โ€“U+23FF
U+00A9 or U+00ABU+2500โ€“U+2775
U+00AC or U+00AEU+2794โ€“U+2BFF
U+00B0โ€“U+00B1U+2E00โ€“U+2E7F
U+00B6U+3001โ€“U+3003
U+00BBU+3008โ€“U+3020
U+00BFU+3030
U+00D7U+0300โ€“U+036F
U+00F7U+1DC0โ€“U+1DFF
U+2016โ€“U+2017U+20D0โ€“U+20FF
U+2020โ€“U+2027U+FE00โ€“U+FE0F
U+2030โ€“U+203EU+FE20โ€“U+FE2F
U+2041โ€“U+2053U+E0100โ€“U+E01EF
U+2055โ€“U+205E

Whitespace

The next important bit is the whitespace around the operator.

If an operator has a whitespace on both the sides or doesnโ€™t have whitespace on both the sides then it is interpreted as a binary operator. This is what would appear for infix operator.

If an operator has whitespace only on the left then it is a prefix unary operator.

If an operator has whitespace only on the right then it is a postfix unary operator.

If an operator does not have whitespace on the left but is followed by a dot then it is treated as a postfix unary operator.

Finally, any round, brace, square brackets appearing before or after the operator along with comma, colon, & semicolon are treated as whitespace

Making sure that we put the whitespace in the correct place while using these operators is very important.

No.RuleExample code
1If an operator has a whitespace on both the sides or doesnโ€™t have whitespace on both the sides then it is interpreted as a binary operatora**b 
or 
a ** b
2If an operator has whitespace only on the left then it is a prefix unary operator**a
3If an operator has whitespace only on the right then it is a postfix unary operatora**
4If an operator does not have whitespace on the left but is followed by a dot then it is treated as a postfix unary operatora**.b is treated as a** .b
5(, {, [ before the operator and ), }, ] after the operator along with ,, :, ; are treated as whitespace

There are some exceptions to the rules we just saw. Especially with exclamation mark & question mark.

  1. ! & ? which are predefined are always treated as postfix if there is no whitespace on the left
  2. If we wish to use ? In optional chaining then it must not have whitespace on the left
  3. To use it as a ternary conditional operator ?: it must have whitespace on both the sides
  4. Operators with a leading or trailing <, > are split into multiple tokens. For example, in Dictionary<String, Array<Int>> the last 2 arrows are not interpreted as shift operator.

Operator grammar

There are rules for constructing operators. Only certain combinations are allowed.

Each operator contains a symbol which forms the operator head. The head is the first character in the operator. 

The head may or may not be followed by 1 or more characters which are operator characters. 

The head and the optional characters combined together form the operator. 

The head itself can contain a one out of a set of valid symbols. Or it can contain a period.

These are some of the symbols allowed for usage as the head of the operator. You can choose any one of those.

/, =, -, +, !, *, %,<, >, &, |, ^, ?, ~U+2055โ€“U+205E
U+00A1โ€“U+00A7U+2190โ€“U+23FF
U+00A9 or U+00ABU+2500โ€“U+2775
U+00AC or U+00AEU+2794โ€“U+2BFF
U+00B0โ€“U+00B1U+2E00โ€“U+2E7F
U+00B6U+3001โ€“U+3003
U+00BBU+3008โ€“U+3020
U+00BFU+3030
U+00D7
U+00F7
U+2016โ€“U+2017
U+2020โ€“U+2027
U+2030โ€“U+203E
U+2041โ€“U+2053

For the successive characters you can use any of the symbols allowed for the head plus some additional allowed symbols. The list above contains all the allowed symbols.

/, =, -, +, !, *, %,<, >, &, |, ^, ?, ~U+2055โ€“U+205E
U+00A1โ€“U+00A7U+2190โ€“U+23FF
U+00A9 or U+00ABU+2500โ€“U+2775
U+00AC or U+00AEU+2794โ€“U+2BFF
U+00B0โ€“U+00B1U+2E00โ€“U+2E7F
U+00B6U+3001โ€“U+3003
U+00BBU+3008โ€“U+3020
U+00BFU+3030
U+00D7U+0300โ€“U+036F
U+00F7U+1DC0โ€“U+1DFF
U+2016โ€“U+2017U+20D0โ€“U+20FF
U+2020โ€“U+2027U+FE00โ€“U+FE0F
U+2030โ€“U+203EU+FE20โ€“U+FE2F
U+2041โ€“U+2053U+E0100โ€“U+E01EF
Examples
.+.
โ‰ˆ
โˆš
**

Operator Precedence

As far as infix operators are concerned there is also the question of precedence. Precedence is used to determine the operator priority when there are multiple operators in a single statement. 

precedencegroup <#precedence group name#> {
    higherThan: <#lower group names#>
    lowerThan: <#higher group names#>
    associativity: <#associativity#>
    assignment: <#assignment#>
}

While the first 2 values are straightforward, they simply help determine the exact position of the newly created precedence as compared to existing precedences, the associativity and assignment are extra items that are not immediately clear.

TypeDescriptionValues
AssociativityDetermines order in which a sequence of operators with the same precedence are evaluated in the absence of grouping bracketsleft, right, none
AssignmentSpecifies priority when used with optional chaining. 
TRUE: Same grouping rules as assignment operator from standard libraryFALSE: Same rules as operators that donโ€™t perform assignment
true, false

The assignment of a precedence group specifies the precedence of an operator when used in an operation that includes optional chaining. When set to true, an operator in the corresponding precedence group uses the same grouping rules during optional chaining as the assignment operators from the standard library. Otherwise, when set to false or omitted, operators in the precedence group follows the same optional chaining rules as operators that donโ€™t perform assignment.

Determines order in which a sequence of operators with the same precedence are evaluated in the absence of grouping brackets. so for example 4 – 6 – 7 has the minus sign which has left associativity. The operation 4-6 is grouped and then the – 7 operation is performed.

Nonassociative operators of the same precedence level canโ€™t appear adjacent to each to other.

The priority for the built in precedences can be seen in Apple’s documentation.

Creating the operators

It is fairly easy to create our own operators. You can try the code in a playground. We will be creating 1 operator of each type: postfix, prefix, infix.

  1. Create a new playground.
  2. Declare the creation of the prefix operator as shown. This will be used as a squaring operator.
prefix operator **
  1. Now we will provide a generic version of the operator implementation.
prefix func **<T:Numeric> (inputValue : T) -> T {
    return inputValue * inputValue
}

That’s it. It is that simple to create our own prefix operator. Now let us test it.

  1. Create a variable of type Float and use the operator we have just created.
var lengthOfSideOfSquare : Float = 1.1

var areaOfSquare : Float = **lengthOfSideOfSquare

print("The area of a square whose side is \(lengthOfSideOfSquare) centimeters long is \(areaOfSquare) square centimeters")

  1. Similarly declare a postfix operator. This one will perform conversion to a string.
postfix operator ~>
  1. Now we will implement this operator. To do that let us make a simple type which will have the to string operator capability.
struct Person {
    var name : String = ""
    var age : Int = 0
}

extension Person {
    static postfix func ~> (inputValue : Person) -> String {
        return "NAME: \(inputValue.name)\nAGE: \(inputValue.age)"
    }
}
  1. Let us try this operator out and see.
var developer : Person = Person(name: "Arun Patwardhan",
                                age: 35)

var description : String = developer~>

print(#line, description)
  1. Now let us implement an infix operator. The one that we are going to implement is a similarity operator which can be used to determine the degree of similarity between objects of the same type. To do that let us start off by declaring an enum which holds the values for the degree of similarity.
enum DegreeOfSimilarity {
    case exactly_the_same
    case almost_the_same
    case slightly_similar
    case completely_different
}
  1. Infix operator can also have a precedence associated with it. Let us declare our own precedence and use it for our operator.
precedencegroup DegreeOfSimilarityPrecedence {
    higherThan: AdditionPrecedence
    lowerThan: MultiplicationPrecedence
    associativity: none
    assignment: true
}

Let us examine the values we have given:

higherThan: This indicates that our precedence has higher priority than the Addition precedence

lowerThan: This indicates that our precedence has lower priority than the Multiplication precedence

Associativity: This indicates that our operator is not associative. So we cannot combine multiple occurrences of our operator in one statement.

assignment: This indicates that out operators has the same behaviour, as other operators that assign, when it comes to optional chaining.

  1. Now we can declare our infix operator.
infix operator โ‰ˆ : DegreeOfSimilarityPrecedence

It is useful to save your new operator symbols as code snippets to easily use them. You can read this article if you don’t know how to create a code snippet.

  1. Let us look at the implementation. I am going to use the same person type we used earlier.
extension Person {
    static func โ‰ˆ (lhsValue : Person, rhsValue : Person) -> DegreeOfSimilarity {
        guard lhsValue.name == rhsValue.name else {
            return DegreeOfSimilarity.completely_different
        }
        
        guard lhsValue.age == rhsValue.age else {
            return DegreeOfSimilarity.almost_the_same
        }
        
        return DegreeOfSimilarity.exactly_the_same
    }
}
  1. Now we will test them and see.
var employee1 : Person = Person(name: "Jack",
                                age: 22)

var employee2 : Person = Person(name: "John",
                                age: 21)

var employee3 : Person = Person(name: "Jack",
                                age: 23)

var employee4 : Person = Person(name: "Jack",
                                age: 23)

print(#line, employee1 โ‰ˆ employee2)

print(#line, employee1 โ‰ˆ employee3)

print(#line, employee3 โ‰ˆ employee4)
  1. Run the code and see the end result.

Feel free to create more operators and play around. You could also package these operators in a swift package and share them around. I have shared links to

Summary the new operator

Creating operators is very easy. Most of the requirements are rather straightforward. However, choosing the right symbol is a very important task.

The one thing that we should keep in mind is not to over use these. It can be tempting to do this. But abstracting everything can make the code look a little too vague.

So that is how you can create operators. 

Download the sample project

I have uploaded some of the custom operators, that I have shown above, as a Swift Package. You can download the package as well as a demo project, which shows how to use them, from the links below.

Video

Here is the video describing what we discussed above.

Creating Code Snippets in Xcode

What are code snippets?

Code snippets are as the name suggests, short pieces of code that can quickly be inserted into your code file. This is done either by dragging the snippet or by typing out the completion. Code snippets are very easy to create and use and can be applied in a wide variety of situations.

We will look at how you can create & use snippets. The following example is done in a playground, but this could be done from anywhere within Xcode.

Note: The example below was performed on Xcode 11.7

How do we create code snippets?

  1. Start off by writing the code or text that you want to convert into a snippet. For example, I have a set of comments that I add at the start of every function. Write it down.
/**
 This function performs a comparison of the 2 objects
 - important: This function does not perform data validation.
 - returns: `Bool`.
 - requires: iOS 13 or later
 - Since: iOS 13
 - parameter lhsValue: This holds the value on the lhs of the operator
 - parameter rhsValue: This holds the value on the rhs of the operator
 - Example: `var answer =  venueAddress == hotelAddress`
 - author: Arun Patwardhan
 - copyright: Copyright (c) Amaranthine 2020
 - date: 14th September 2020
 - version: 1.0
 */

2. Select it.
3. From the menu bar select Editor > Create Code Snippet.

This brings up the snippet editor.
4. Give your snippet the following details.

OptionDescription
NameThis is the name of your code snippet.
PlatformThis determines whether your snippet is available only for certain platforms: say only for iOS.
AvailabilityThis determines the place where the snippet can be added.
CompletionThis is the word that we will be typing in the Xcode editor to trigger the implementation of the snippet
LanguageThis specifies the language for which the snippet will be applied.

Name: Func Documentation

Language: Swift

Platform: All

Availability: All scopes

Completion: doc

Note that the values for Name and Completion can be whatever you want.

This is how the snippet should look.

5. Now we will try to use it in the editor. Start typing the completion word in the Xcode editor.

6. Select the snippet with your name and completion.
7. Hit enter. You should see the comments you want appearing in the editor.

Placeholder

We can make our snippet above even better by using placeholders. Placeholders are pieces of text that can be replaced by the user. They also give information about what is expected in the placeholder.

We can add place holders by simply typing the hint inside placeholder brackets. Placeholder brackets are nothing but open <# and closing #>. For example:

<# some text #>

Which appears as

The user will simply click on the “some text” placeholder.

There are plenty of places in our comments where we can use placeholders. When we use the code snippet it should put comments with place holders in them.

  1. Let us change the comments in our Xcode editor first. We will edit the snippet later on. Make the changes as shown below.
/**
 <# put the description of your function here #>
 - important: <# mention some important points here #>
 - returns: `<# return type #>`.
 - requires: iOS  <#iOS Version#>  or later
 - Since: iOS  <#iOS Version#>
 - parameter <#param 1#>: This holds the value on the lhs of the operator
 - parameter <#param2#>: This holds the value on the rhs of the operator
 - Example: `<#put some example code here#>`
 - author: Arun Patwardhan
 - copyright: Copyright (c) Amaranthine 2020
 - date: <#day#>  <#month#>  <#year#>
 - version: 1.0
 */

We have made the following items into comments.

  • Description
  • OS Version
  • Return type
  • Important comments
  • Parameter 1 & 2 names
  • Sample code
  • Day, Month, & Year

Of course, there are other things we could change too. Feel free to make any other changes you can think of.

2. Let us now copy these changes to the code snippet we created. Copy the code from the Xcode editor.

To bring the snippet editor again simply click on the add object button in the upper right hand corner of Xcode.

4. Select the snippet from the list on the left and click edit.
5. Paste the code that you just copied. Your snippet editor should look like this:

6. Click on ‘Done’ once you are finished making changes. Your snippet will now be ready.

7. Try adding the snippet into your editor just like before. Simply type in the completion for your snippet.

Dragging snippets

We can use the autocompletion we saw earlier. But it is also possible for us to drag snippets.

Exporting code snippets

Once created it is possible to export/import code snippets too. All the snippets are located in the following folder.

~/Library/Developer/Xcode/UserData/CodeSnippets/

Any snippets you have created will be located there.

Any new snippets to be added will have to be added there.

Summary

Code snippets are easy to create and have several advantages:

  1. They improve the developers experience
  2. Promote consistent code
  3. Speeds up the process of writing code
  4. Encourages developers to use each others snippets and gain the first 3 advantages.

Creating and using snippets is very very easy and has a lot of benefits. So go ahead and create snippets.

Creating custom templates for iOS App Development

What are Xcode templates?

Xcode templates are basically pre-created files which we use when we create new projects or project files. So every time you go through the process of creating a new project File > New > Project > iOS > Single View App you are using the Single View App template.

While most of the templates are good enough we can easily create our own templates.

Why do we need custom templates?

The templates available out of the box are good for common situations. But we find that most of the times we end up creating a lot of file in our project. Sometime we implement common design patterns and architectures on a regular basis.

In such situations creating out own custom templates will help us save a lot of time during development.

The other advantage is that this promotes a more consistent development experience in any organisation.

Now that we know what templates are and why we may need custom templates let us look at how we can create them.

Template Types

Before we go ahead and create templates let us examine what a typical template includes.

Navigate to the following path:

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

Notice that there are 2 folders already created out here. File Templates & Project Templates. Let us browse through these folders.

File Templates

These are the templates used when a developer wishes to add a new file to an existing project. Under file templates you should see different folders in there. Each folder represents a certain category of templates. For example, User Interface is one category. Select it.

You should see multiple folders under it. The screenshot above shows the View template. As we can see the template itself is a folder with multiple files inside. The template ends with an extensions xctemplate. Let us look at those files.

  • ___FILEBASENAME___.xib
  • TemplateIcon.png
  • TemplateIcon@2x.png
  • TemplateInfo.plist

The first one is the XIB file which will be generated by this template. The ___FILEBASENAME___ placeholder will be replaced with an actual name when it is created.

The next 2 are simply images that will be used as icons for the template when we bring up the template wizard in Xcode.

The last one is the more important one. The TemplateInfo.plist. This is where we describe how the file creation process works. This is also where we configure options which will be presented to the user. We will look at this file in greater depth later on when we try to create our own templates.

Project Templates

These are the templates that are used when a developer decides to create a new project. Under project templates you should see different folders in there. Each folder represents a certain category of templates. For example, Application is one category. Select it.

I have the single view app template inside it. This is the most commonly used template when starting out with iOS App Development. You should see other familiar project templates. Feel free to examine the files in the folder. Let us have a look inside the Single View App template folder. You should see these items:

  • ContentView.swift
  • Main.storyboard
  • TemplateIcon.png
  • TemplateIcon@2x.png
  • Preview Assets.xcassets folder
  • TemplateInfo.plist

The first 2 files are the UI related files. One of the 2 will be selected based on the users choice between Storyboard and SwiftUI.

The next 2 are simply images that will be used as icons for the template when we bring up the template wizard in Xcode.

The Preview Assets folder is used with SwiftUI for previewing purposes.

Here too we have the TemplateInfo.plist file which configures the template options at the time of creation. We will explore this file in greater depth when we try to create our own project template.

How can we create them?

In this article we will look at creating 2 types of templates.

  1. File Templates
  2. Project Templates

Warning: It may be a good idea to try this out on a test computer so that you do not break anything on the computer you use everyday.

Preparation

Before we get started let us prepare the folders where we will be storing our custom templates.

  1. Navigate to the following folder.
~/Library/Developer/Xcode/Templates/

Note, you may have to create this folder.

  1. There should be 2 folders inside: File Templates, Project Templates. If these folders are not there then go ahead and create them.

We will be placing our templates in these folders.


TopicPage
Creating File templates2
Creating Project templates3

Download

You can download the templates from these links.

Note

This code has been tested on Xcode 11.3.1 on macOS Catalina 10.15.3

Creating iOS Apps without Storyboard – Part 2

Autolayout Programmatically

This article continues from the previous article. Earlier we saw how we can make iOS Apps without using the storyboard file. In this article we will explore how to implement Autolayout programmatically. We will continue from the previous article.

The code that I will be showing in the article will not be covering all the possible cases. The point of this article is to give you an idea on how to implement the different Autolayout solutions. Feel free to play around with the code to cover all the cases & situations.

Programmatic Constraints

We have 3 options when it comes to applying constraints programmatically:

  1. StackViews
  2. Layout Anchors
  3. NSLayoutConstraints class
  4. Visual Format Language (VFL)

Handling Size Classes in code

Handling Size classes in code is fairly easy. It is a simple question of overriding the correct function. We will look at this in greater detail when we cover the topic later in the article.

TopicPage
Implementing UIStackViews2
Implementing Layout Anchors3
NSLayoutConstraints class4
Implementing Visual Format Language5
Size Classes6
Summary & Video7

This article has been written using Xcode 10.3.

Creating iOS Apps without Storyboard – Part 1

What are “nibless” apps?

Apps which are designed without the help of Storyboard are called as “Nibless” apps. Normally we design an app with the help of a Storyboard file. Earlier they were called Xib files or Nib files. Hence the term “Nibless”.

Why should we create Apps without storyboard?

There are a number of reasons.

  1. It makes for a better experience when implementing along with version control.
  2. Allows us to create UI elements dynamically.
  3. Makes reusable UI Components easier to distribute and reuse.

How can we create Apps without Storyboard?

There are a couple of things that need to be done. Firstly the Main.storyboard file needs to be removed and the project settings need to be updated to reflect this change.. We are doing this since we won’t be using the storyboard file.
Everything will now have to be started up by us manually. Many of these tasks were taken care of by storyboard, but since that was removed we will have to do it. This means we have to manually create the window, create the view controller set it as a the root view controller.
We also have to manually create each and every component on our own. That is the very thing we were trying to achieve.

This example is implemented on Xcode 10.3 on macOS 10.14.5. We are not implementing auto layout in this article. We will look at implementing that programmatically in the next article.

  1. Let us start with an empty project. Open Xcode.
  2. Select File > New > Project
  3. Give it any name. Select the language as Swift & leave the checkboxes unchecked.
  4. Once the project loads select the Main.storyboard file and delete it.
  5. Switch to the Project settings file.
  6. Remove the entry for the main interface.
  7. It is a good idea to leave the LaunchScreen.storyboard file. The reason for this is to give the launch process a reference of the screen size it needs to produce. Else it will default down to the 0,0,320,480 which is the old iPhone size.
  8. Switch to the AppDelegate.swift file.
  9. Add the following property below the UI Window declaration.
      
    let mainScreenController : ViewController = ViewController() 
    
  10. Add the code to create the window and set root view controller in the didFinishLaunchingWithOptions method
       
    //1. Create the UIWindow object   
    self.window = UIWindow(frame: UIScreen.main.bounds)   
    
    //2. Set the root view controller   
    self.window?.rootViewController = self.mainScreenController   
    
    //3. Make the window key and visible  
    self.window?.makeKeyAndVisible()  
    
  11. Switch to the ViewController.swift file.
  12. Declare the following variables
      
    //UI Variables  
    var labelDemo   : UILabel?  
    var imageDemo   : UIImageView?  
    var buttonDemo  : UIButton = UIButton(type: UIButton.ButtonType.roundedRect) 
    var dataField   : UITextField?
    
  13. Implement the function to create labels. The process of creating a view programmatically is fairly straightforward. Barring a few variations depending on the view component nothing is drastically different.
      
    func createLabel() 
    {      
         //1. Specify the dimensions      
         let labelRect : CGRect   = CGRect(x: 100.0, y: 50.0, width: self.view.frame.size.width - 130.0, height: 60.0)     
    
         //2. Create the view object      
         labelDemo                = UILabel(frame: labelRect)      
    
         //3. Customise the view attributes      
         labelDemo?.text          = "This is my first Programmatic App."                
         labelDemo?.textColor     = UIColor.yellow      
         labelDemo?.textAlignment = NSTextAlignment.left  
         labelDemo?.numberOfLines = 0      
         labelDemo?.font          = UIFont.boldSystemFont(ofSize: 20.0)      
    
         //4. Add the view to the subview      
         self.view.addSubview(labelDemo!) 
    } 
    
    Let us examine the steps one by one.
     
    //1. Specify the dimensions 
    let labelRect : CGRect = CGRect(x: 100.0, y: 50.0, width: self.view.frame.size.width - 130.0, height: 60.0)
    
    This will define the dimensions of the view. As we are not implementing auto layout we will need to do this manually.
     
    //2. Create the view object
    labelDemo = UILabel(frame: labelRect) 
    
    Now that we have the dimensions we can go ahead and instantiate an instance of the label object using those dimensions. These 2 parts are the same as dragging a label from the object library onto the storyboard and placing it onto the storyboard per our requirements.
    //3. Customise the view attributes 
    labelDemo?.text          = "This is my first Programmatic App."     
    labelDemo?.textColor     = UIColor.yellow 
    labelDemo?.textAlignment = NSTextAlignment.center      
    labelDemo?.numberOfLines = 0 
    labelDemo?.font          = UIFont.boldSystemFont(ofSize: 20.0)
    
    This part is the same as changing the attributes in the attributes inspector. This is where we customise the label.
     
    //4. Add the view to the subview 
    self.view.addSubview(labelDemo!) 
    
    This last part also forms one part of dragging the label on to the storyboard. When we drag a view on to the storyboard it is placed within the main view that belongs to the ViewController. This statement completes the above process.
  14. Repeat the above steps for showing an image.
    func createImage()
    {
         //1. Specify the dimensions
         let imageRect  : CGRect  = CGRect(x: 30.0, y: 50.0, width: 60.0, height: 60.0)
    
         //2. Create the image model
         let imageModel : UIImage = UIImage(named: "logo.png")!
    
         //3. Create the view object
         imageDemo                = UIImageView(frame: imageRect)
    
         //4. Customise the view attributes
         imageDemo?.image         = imageModel
         imageDemo?.contentMode   = UIView.ContentMode.scaleAspectFit
    
         //5. Add the view to the subview
         self.view.addSubview(imageDemo!)
    }
    
    The code above is almost similar to the one created for labels except for the fact that we had to explicitly create a model object for the view. Images being different from strings, require this process to be done explicitly.
  15. Similarly let us implement the code for creating buttons
    func createButton()
    {
         //1. Specify the dimensions
         let buttonRect : CGRect = CGRect(x: 30.0, y: 220.0, width: 100.0, height: 50.0)
    
         //2. Provide the frame to the button
         buttonDemo.frame = buttonRect
    
         //3. Customise the view attributes
         buttonDemo.setTitle("Click Me", for: UIControl.State.normal)
         buttonDemo.addTarget(self, action: #selector(ViewController.clickMeTapped), for: UIControl.Event.touchDown)
    
         //4. Add the view to the subview
         self.view.addSubview(buttonDemo)
    }
    
    @objc func clickMeTapped(
    {
         print("Click me tapped!")
    }
    
    Again just minor variations here. Mainly the step to add a target function to be invoked when the button is tapped. We also need to write the target function itself.
  16. We will also implement the code to create a text field.
    func createTextField()
    {
        //1. Provide dimensions for the view
        let tfRect : CGRect             = CGRect(x: 30.0, y: 140.0, width: self.view.frame.size.width - 60.0, height: 50.0)
            
        //2. Create the view object
        dataField                       = UITextField(frame: tfRect)
            
        //3. Customise the attributes of the view
        dataField?.placeholder          = "Enter Name"
        dataField?.borderStyle          = UITextField.BorderStyle.roundedRect
        dataField?.keyboardType         = UIKeyboardType.namePhonePad
        dataField?.keyboardAppearance   = UIKeyboardAppearance.dark
        dataField?.returnKeyType        = UIReturnKeyType.go
            
        //4. Add the view to the subview
        self.view.addSubview(dataField!)
    }
    
  17. Next we need to call all these functions. I have implemented a single creator function for that.
    func createUIElements()
    {
         self.createLabel()
         self.createImage()
         self.createButton()
         self.createTextField()
    }
    
  18. Lastly we will call this function in the viewDidLoad method. Add the following lines to the viewDidLoad method.
    self.view.backgroundColor = UIColor.lightGray
    self.createUIElements()
    
    I have also added code to change the background colour so that we can see the background clearly.
  19. Run the project. Everything should appear normally.

Are there any benefits of creating apps without storyboard?

The points mentioned in the “why should we make programmatic apps?” section are some of the advantages. Beyond that there aren’t too many.
If you are looking at a team based project development then this approach is good.
There is no difference in terms of memory or performance when it comes down to apps design with or without storyboard.

Are there any drawbacks?

As can be seen from the example above, there are a couple of drawbacks

  1. The main drawback is that you can’t get a quick preview of how your app looks. You have to run the simulation every time you wish to see the end result.
  2. There is a lot more coding involved. Which can be daunting to those who are overly accustomed to designing with the help of storyboards

Note

A small point. I have left the LaunchScreen.storyboard file. I did not delete it. The reason I did that was to allow the app to allow the system to determine the dimensions on the device. If we do delete the file then the UIScreen.main.bounds return (0.0, 0.0, 320.0, 480.0) which are the old iPhone screen size settings.
While you can go ahead and make changes programmatically it is a lot easier to just leave the LaunchScreen.storyboard file there.

Carrying on from the previous point. It actually is okay if you leave the Main.storyboard file as is too. In which case you will have to skip steps 5,6,8,9,10. The code is still running programmatically but you do not have to create the main ViewController manually.

Download the Source Code

You can download the Xcode Project from this link.