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:
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.
Make the code more readable
This also improves the readability of our code. Properly chosen symbols can convey the message immediately and easily.
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:
A logic for the action being performed by the operator
A list of valid symbols
Information about the operators attributes like prefix, postfix, infix.
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
Type
Description
Prefix
Operators that appear before a variable or value. These are unary operators.
Postfix
Operators that appear after a variable or value. These are unary operators.
Infix
Operators 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.
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.
Rule
Example code
1
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
a**b or a ** b
2
If an operator has whitespace only on the left then it is a prefix unary operator
**a
3
If an operator has whitespace only on the right then it is a postfix unary operator
a**
4
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
a**.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.
! & ? which are predefined are always treated as postfix if there is no whitespace on the left
If we wish to use ? In optional chaining then it must not have whitespace on the left
To use it as a ternary conditional operator ?: it must have whitespace on both the sides
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+00A7
U+2190–U+23FF
U+00A9 or U+00AB
U+2500–U+2775
U+00AC or U+00AE
U+2794–U+2BFF
U+00B0–U+00B1
U+2E00–U+2E7F
U+00B6
U+3001–U+3003
U+00BB
U+3008–U+3020
U+00BF
U+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+00A7
U+2190–U+23FF
U+00A9 or U+00AB
U+2500–U+2775
U+00AC or U+00AE
U+2794–U+2BFF
U+00B0–U+00B1
U+2E00–U+2E7F
U+00B6
U+3001–U+3003
U+00BB
U+3008–U+3020
U+00BF
U+3030
U+00D7
U+0300–U+036F
U+00F7
U+1DC0–U+1DFF
U+2016–U+2017
U+20D0–U+20FF
U+2020–U+2027
U+FE00–U+FE0F
U+2030–U+203E
U+FE20–U+FE2F
U+2041–U+2053
U+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.
Type
Description
Values
Associativity
Determines order in which a sequence of operators with the same precedence are evaluated in the absence of grouping brackets
left, right, none
Assignment
Specifies 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.
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.
Create a new playground.
Declare the creation of the prefix operator as shown. This will be used as a squaring operator.
prefix operator **
Now we will provide a generic version of the operator implementation.
That’s it. It is that simple to create our own prefix operator. Now let us test it.
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")
Similarly declare a postfix operator. This one will perform conversion to a string.
postfix operator ~>
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)"
}
}
Let us try this operator out and see.
var developer : Person = Person(name: "Arun Patwardhan",
age: 35)
var description : String = developer~>
print(#line, description)
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
}
Infix operator can also have a precedence associated with it. Let us declare our own precedence and use it for our operator.
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.
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.
Let us look at the implementation. I am going to use the same person type we used earlier.
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)
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.
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?
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.
Option
Description
Name
This is the name of your code snippet.
Platform
This determines whether your snippet is available only for certain platforms: say only for iOS.
Availability
This determines the place where the snippet can be added.
Completion
This is the word that we will be typing in the Xcode editor to trigger the implementation of the snippet
Language
This 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.
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:
They improve the developers experience
Promote consistent code
Speeds up the process of writing code
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.
This article lists out different macOS terminal commands you might encounter. You can use this list as a starting point in your search for a command to perform a specific task. This list is by no means exhaustive.
Many of the commands have also been used in the article I wrote some time back. You can have a look at the scripts to see some of the commands being used.
To get more information about the commands simply run the following command from within Terminal Application. For example, to view the manual page for tmutil simply type:
man tmutil
For fdesetup
man fdesetup
Here is a nice command to quickly open the man page in the Preview App.
man -t tmutil | open -f -a /System/Applications/Preview.app
Note
This is not a complete list of commands
Some commands are available through the macOS Recovery Volume only
Some commands required other resources such as the OS installer
Some commands are available with certain versions of the OS only
Please read the documentation for more details. Use the commands with care. Improper use of commands may result in loss of data or damage to the computer.
Commands
Installation
Command
Description
startosinstall
Used to start the installation of macOS from the command line.
createinstallmedia
Used to create an external install disk.
Security
Command
Description
fdesetup
Manage FileVault configuration.
security
Manage keychain and security settings
spctl
Manage security assessment policy
csrutil
Configure System Integrity Protection (SIP) settings
resetpassword
Password reset utility located in the Recovery Partition
File System
Command
Description
hdiutil
Used to manipulate and manage disk images.
diskutil
Used to modify, verify, & repair local disks.
Data Management
Command
Description
tmutil
Used to configure Time Machine settings in macOS
screencapture
Takes screenshot of the specified screen and saves the image at the specified location.
mdls
Used to get metadata attributes for a given file
mdutil
Used to manage metadata stores that are used by Spotlight
Settings
Command
Description
defaults
Used to modify plist files. Typically used to update preference files.
ioreg
Used to view the I/O kit registry
system_profiler
Used to generate system hardware & software reports.
plutil
Used to check syntax of property lists or covert property lists from one format to another
AssetCacheManagerUtil
Used to configure content caching settings.
open
Used to open documents from within the command line.
networksetup
Perform network configuration.
systemsetup
Used to configure machine settings in System Preferences.
launchctl
Used to manage and inspect daemons, agents, & XPC Services
Applications
Command
Description
codesign
Used to create, check, display code signatures.
pkgbuild
Used to build installer packages
productbuild
Builds a product archive
installer
System software and package installer tool
User Account Management
Command
Description
dscl
This is a command line Directory service utility that allows us to create, read, and manage Directory Service data.
sysadminctl
User account management
passwd
Change user password
login
Used to login to another user account.
Server & Device Management
Command
Description
profiles
Used to install, remove, list, or manage Configuration profiles.
serveradmin
Used to manage the services in macOS
mdmclient
Located in /usr/libexec/mdmclient it is used to manage interactions with the MDM.
asr
Apple Software restore: Used to copy volumes.
Scripting
Command
Description
osascript
Used to execute the given AppleScript
Share any commands you may know of in the comments window.
Disclaimer
The information 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 information provided Or The Use Or Other Dealings In The information.
One of the advantages with scripts is the fact that you can easily automate many tasks. Here is an article that walks you through that process.
If you come across a situation where you want to perform a set of tasks on multiple computers then scripts come in very handy.
I will be providing the Shell Script version of the task. Feel free to make changes to the scripts as required. I will try to provide an AppleScript version of the tasks a little later.
This is not the only way to implement the scripts. There may be multiple approaches towards achieving the same result. You will have to explore and examine the correct approach.
This is not a comprehensive list. The scripts should give you some ideas and act as a useful reference when you are creating your own scripts.
I have tested these scripts on macOS Catalina 10.15
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.
WARNING
Please try these scripts on a test computer. Some of the scripts do make changes to the system. Always test before using these scripts.
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.
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.
File Templates
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.
Navigate to the following folder.
~/Library/Developer/Xcode/Templates/
Note, you may have to create this folder.
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.
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:
StackViews
Layout Anchors
NSLayoutConstraints class
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.
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.
It makes for a better experience when implementing along with version control.
Allows us to create UI elements dynamically.
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.
Let us start with an empty project. Open Xcode.
Select File > New > Project
Give it any name. Select the language as Swift & leave the checkboxes unchecked.
Once the project loads select the Main.storyboard file and delete it.
Switch to the Project settings file.
Remove the entry for the main interface.
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.
Switch to the AppDelegate.swift file.
Add the following property below the UI Window declaration.
let mainScreenController : ViewController = ViewController()
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()
Switch to the ViewController.swift file.
Declare the following variables
//UI Variables
var labelDemo : UILabel?
var imageDemo : UIImageView?
var buttonDemo : UIButton = UIButton(type: UIButton.ButtonType.roundedRect)
var dataField : UITextField?
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.
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.
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.
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!)
}
Next we need to call all these functions. I have implemented a single creator function for that.
I have also added code to change the background colour so that we can see the background clearly.
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
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.
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.
Disk images are a means of archiving data. They are created using a tool called Disk Utility which is a File System Management Utility of macOS. Disk Images follow the extension ‘.dmg‘ and are only compatible with macOS.
Disk Images are a popular way of distributing applications for macOS. They provide the capability of compressing large files and make delivery over the internet very easy.
In this article we are going to look at how we can create disk images for application distribution.
Creating the DMG Folder for distribution
Create a Background image. This can have any design. It’s a good idea to have arrows or other visual aids to assist others during installation.
Create a new Disk Image. Open Disk Utility.
Click on File > New Image > Blank Image
Leave the default settings as is. Choose the size that you desire.
Mount the Disk Image.
Create a folder called background in the mounted volume.
Save the background image in the folder we just created.
Now we will hide the background folder. Switch to terminal and run the following command.
cd /Volume/InstallDMG/
mv background .background
Here we are simply renaming the background folder with a ‘.’ before it. This hides the folder from the GUI.
Now we will prepare the payload. This can be any file or folder we wish to install. For the sake of this demo I will be choosing Mozilla FireFox. In reality you would be distributing your own application.
Copy the FireFox app into the mounted volume.
Open “Show View Options“.
Restrict the mounted volume to icon view only. Feel free to customise the other settings as you wish. This includes icon size.
Drag and arrange the icons in your mounted window to match the background.
Eject the disk image.
Make a duplicate copy of the image file. This can act as a reference for future images you wish to create.
Now we will convert the disk image into a read only compressed disk image. This will be the one that we will use for distribution. Open Disk Utility.
Click on Images > Convert
Select the InstallerDMG.dmg from Desktop or wherever you had saved it.
Give it a new name and convert it to compressed format.
That’s it. You now have your own drag drop window ready for distribution.
In an earlier article I had discussed how we can create our own frameworks to easily share reusable code. In this article we will take this a little further and create our own reusable UI Components.
Points to Note:
The reusable component we will be creating is based on UIKit. For that reason this component can only be used in iOS Apps. However, you can follow the same steps to create a reusable component for macOS using Cocoa.
UI Components, distributed through a framework, do not render in the project storyboard file.
You should be familiar with creating Embedded Binaries (your own Framework). If you aren’t then please read this article first.
These projects are created on Xcode 10 with Swift 4.2
Getting Started
We will complete the implementation as a 2 step process. Here is the screen that we are planning to implement.
Creating the Reusable Framework
Open Xcode and create a new Framework project.
Name the project “UIVIdentityCard”.
Save the project in any folder.
Create a new Swift file File > New > File > Swift.
Name the file “GenderType.swift”. This is where we will declare the enum that holds the Gender type that we are creating.
Add the following code to the file.
import Foundation
/** Possible scores that can be given.
*values*
`Male`
`Female`
`NotSpecified`
*functions*
`func toString() -> String`
Used to get the `String` version of the value
- Author: Arun Patwardhan
- Version: 1.0
*/
public enum GenderType
{
case Male
case Female
case NotSpecified
}
/** This extension adds the Enum to String converions capability
- Author: Arun Patwardhan
- Version: 1.1
*/
extension GenderType
{
/** This function converts from enum value to `String`
- important: This function does not do validation
- returns: `String`.
- requires: iOS 11 or later
- Since: iOS 11
- author: Arun Patwardhan
- copyright: Copyright (c) Amaranthine 2015
- version: 1.0 */
@available(iOS, introduced: 11.0, message: "convert to String")
func toString() -> String
{
switch self
{
case .Male:
return "Male"
case .Female:
return "Female"
case .NotSpecified:
return "Not Specified"
}
}
}
Create a new Swift file called “PersonDetailsModel.swift”.
Add the following code to the file.
import Foundation
/** This struct represents the data that is to be shown in the ID card
**Variables**
`personName`
`personIcon`
`personDob`
Date of Birth
`personAddress`
`personPhone`
`personEmail`
`personCompany`
`personHeight`
`personWeight`
`personGender`
**Important** There is a variable with the name `entryCount`. This variable keeps tracks of the number of stored properties that exist. The value of this variable will be used to determine the number of rows in the table.The computed property `numberOfRows` is the property used to access the value of `entryCount`.
- Author: Arun Patwardhan
- Version: 1.0
*/
public struct PersonDetailsModel
{
internal var entryCount : Int = 7
public var personName : String = ""
public var personIcon : UIImage
public var personDob : Date
public var personAddress: String = ""
public var personPhone : String = ""
public var personEmail : String = ""
public var personCompany: String = ""
public var personHeight : Double? = 0.0
{
willSet
{
if newValue == nil & personHeight != nil
{
entryCount -= 1
}
else if newValue != nil & personHeight == nil
{
entryCount += 1
}
}
}
public var personWeight : Double? = 0.0
{
willSet(newValue)
{
if newValue == nil & personWeight != nil
{
entryCount -= 1
}
else if newValue != nil & personWeight == nil
{
entryCount += 1
}
}
}
public var personGender : GenderType?
{
willSet
{
if newValue == nil & personGender != nil
{
entryCount -= 1
}
else if newValue != nil & personGender == nil
{
entryCount += 1
}
}
}
public var numberOfRows : Int
{
return entryCount
}
public init(withName newName : String, icon newIcon : UIImage, birthday newDob : Date, address newAddress : String, phone newPhone : String, email newEmail : String, Company newCompany : String, height newHeight : Double?, weight newWeight : Double?, andGender newGender : GenderType?)
{
personName = newName
personIcon = newIcon
personDob = newDob
personAddress = newAddress
personPhone = newPhone
personEmail = newEmail
personCompany = newCompany
if newGender != nil
{
entryCount += 1
}
if newWeight != nil
{
entryCount += 1
}
if newHeight != nil
{
entryCount += 1
}
personHeight = newHeight
personWeight = newWeight
personGender = newGender
}
}
/** This extension adds protocol conformance for the `CustomStringConvertible` protocol.
- Author: Arun Patwardhan
- Version: 1.1
*/
extension PersonDetailsModel : CustomStringConvertible
{
public var description: String
{
return """
NAME: \(self.personName)
DATE OF BIRTH:\(self.personDob)
ADDRESS: \(self.personAddress)
EMAIL:\(self.personEmail)
PHONE:\(self.personPhone)
"""
}
}
Now we will focus out attention on the View. Create a new file File > New > File > View.
Name the view “UIVIdentityCard.swift”.
Design the view as shown in the screenshot below.
Create the corresponding“UIVIdentityCard.swift” file.
Make the IBOutlet & IBAction connections for the different UI elements.
Add the following code. This is how your file should look after its completed.
/** The UIVIdentityCard class
**Functions**
`public func load(data newPerson : PersonDetailsModel)`
Used to load the data for the view.
- Author: Arun Patwardhan
- Version: 1.0
*/
@IBDesignableopen class UIVIdentityCard: UIView, UITableViewDelegate, UITableViewDataSource
{
//IBOutlets --------------------------------------------------
@IBOutlet public weak var personIcon : UIImageView!
@IBOutlet public weak var personName : UILabel!
@IBOutlet public weak var personDetails : UITableView!
//Variables --------------------------------------------------
public var localTableData : PersonDetailsModel!
let nibName : String = "UIVIdentityCard"
var view: UIView!
let cellIdentifier : String = "IDCard"
//Functions --------------------------------------------------
/** This function does the initial setup of the view. There are multiple things happening in this file.
1) The first thing that we do is to load the Nib file using the `nibName` we saved above. The UNIb object contains all the elements we have within the Nib file. The UINib object loads the object graph in memory but does not unarchive them. To unarchive them and get the ibjects loaded completely for use we have to instatiate the object and get the arry of top level objects. We are however interested in the first object that is there in the array which is of type `UIView`. The reference to this view is assigned to our local `view` variable.
2) Next we specify the bounds of our view
3) Finally we add this view as a subview
- important: This function does not do validation
- requires: iOS 11 or later, the varibale that contains the name of the nib file.
- Since: iOS 11
- author: Arun Patwardhan
- copyright: Copyright (c) Amaranthine 2015
- version: 1.0
*/
@available(iOS, introduced: 11.0, message: "setup view")
func setup()
{
//1)
self.view = UINib(nibName: self.nibName, bundle: Bundle(for: type(of: self))).instantiate(withOwner: self, options: nil)[0] as! UIView
//2)
self.view.frame = bounds
//3)
self.addSubview(self.view)
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if let count = localTableData?.entryCount
{
return count - 2
}
return 0
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell
{
var cell : UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
if nil == cell
{
cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
}
switch indexPath.row
{
case 0:
let formatter = DateFormatter()
formatter.dateStyle = .medium
cell?.textLabel?.text = "Birthday\t: "+ formatter.string(from: (localTableData?.personDob)!)
case 1:
cell?.textLabel?.text = "Email\t: " + localTableData.personEmail
case 2:
cell?.textLabel?.text = "Phone\t: " + localTableData.personPhone
case 3:
cell?.textLabel?.text = "Address\t: " + localTableData.personAddress
case 4: cell?.textLabel?.text = "Company\t: " + localTableData.personCompany
case 5:
cell?.textLabel?.text = "Gender\t: " + \(localTableData.personGender?.toString())!
case 6:
cell?.textLabel?.text = "Height\t: \((localTableData.personHeight)!)"
case 7:
cell?.textLabel?.text = "Weight\t: \((localTableData.personWeight)!)"
default:
print("error")
}
cell?.textLabel?.font = UIFont.boldSystemFont(ofSize: 12.0)
cell?.textLabel?.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
return cell!
}
//Inits --------------------------------------------------
override public init(frame: CGRect)
{
super.init(frame: frame)
self.setup()
}
required public init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.setup()
}
override open func layoutSubviews()
{
super.layoutSubviews()
}
}
/** This extension adds the function to load data
- Author: Arun Patwardhan
- Version: 1.1
*/
extension UIVIdentityCard
{
/**
This function loads the data for the view
- important: This function does not do validation
- parameter newPerson: This is the object representing the person whose information will be displayed on the screen.
- requires: iOS 11 or later
- Since: iOS 11
- author: Arun Patwardhan
- copyright: Copyright (c) Amaranthine 2015
- version: 1.0
*/
@available(iOS, introduced: 11.0, message: "load data")
public func load(data newPerson : PersonDetailsModel)
{
self.localTableData = newPerson
self.personIcon.image = localTableData.personIcon
self.personName.text = localTableData.personName
self.personDetails.reloadData()
}
}
Add the placeholder image for the image view.
Select any of the simulators from the list.
Press ⌘ + B to build the project.
From the Project navigator select the Framework file.
Control click and select “Show in Finder”.
Copy the framework to the “Desktop”.
We are done creating the reusable framework. We will not shift our focus towards testing this framework.
Using the Framework in a project
Let us now test the framework we created. We will do this by incorporating the code in our iOS App.
Create a new project. Call it “IdentityCardTest”.
Save the file in a folder of your choice.
Select the Project file and Embed the Framework into your project.
Add an image to your project, this will be the image that will be displayed in your custom view.
Switch to the Main.storyboard file. Drag a UIView into the ViewControllers view.
Set its identity to the UIVIdentityCard in the identity inspector. Also set its module to UIVIdentityCard.
Create an IBOutlet for this custom view.
Switch to the ViewController.swift file. Import the UIVIdentityCard framework at the top of the file.
Add the following code to the file. We will be creating test data and displaying it on the screen using the Custom view we just designed.
//Functions --------------------------------------------------
/**
This function prepares and loads the data that is to be shown in the custom view
- important: This function does not do validation
- requires: iOS 11 or later, the UIVIdentityCard framework.
- Since: iOS 11
- author: Arun Patwardhan
- copyright: Copyright (c) Amaranthine 2015
- version: 1.0
*/
@available(iOS, introduced: 11.0, message: "prepares data to be shown on the ID card")
func prepareIDCard()
{
let displayData : PersonDetailsModel = PersonDetailsModel(withName: "Arun Patwardhan", icon: UIImage(named: "iconHolder.png")!, birthday: Date(timeIntervalSince1970: 44_97_12_000), address: "Mumbai, Maharashtra, India", phone: "91-22-26486461", email: "arun@amaranthine.co.in", Company: "Amaranthine", height: 5.11, weight: nil, andGender: GenderType.Male)
myIDCard.load(data: displayData)
}
Your completed ViewController.swift should look like this.
Run the project. See if the view loads the way we wish.