Getting Started with macOS Programming

Getting Started with macOS Programming

Let’s go together to build our first macOS application!

Dear readers, let me welcome you and start by saying that this tutorial is the beginning of a new journey and new explorations, as it’s the first of a series of tutorials on a new topic: macOS programming!

Developing for macOS and creating desktop applications is a wonderful process if you want to go down that road. If you are coming from an iOS background like me, then you will find exciting and challenging at the same time to be able to make apps for both systems. What makes it even better is one simple fact: There are lots of common things between iOS and macOS programming. Several frameworks and SDKs, the language itself (Swift), even the coding style that one has used to remain almost the same. But macOS programming is not meant for people being iOS developers already. New programmers who have started learning Swift and they want to make their own desktop applications can follow along as well!

Starting from this tutorial we are going to meet and learn key concepts on macOS programming. I am planning to present various topics for which I will highlight their most important concepts and I will show techniques, how-to and best practices. It is obvious that it would take a series of books to cover everything about macOS programming, not just a series of tutorials. However, what I’m going to show you starting from this tutorial and onwards not only will get you started, but it will enable you to build full applications and it will put you on the right track so you know how to look further on topics you are interested in. Whenever possible, we will be going through the making process of a new macOS app that will have some usefulness at the end. Something that you can take with you when finish reading each post.

So, once again welcome to a new exciting journey! Let’s go together to build our first macOS application!

Our Roadmap

In this post we are going to see the basics of a macOS app through the creation of a simple, but at the same time useful for many developers app: A font viewer that will let us choose font families and it will be displaying the PostScript names of the available fonts. Clarifying why this is useful: The PostScript name is the font name we write in code when creating a NSFont object. Usually it’s not the same to the font’s file name, and without knowing the PostScript it’s impossible to create font objects in code or to add custom fonts into projects. There’s a manual process to reveal PostScript names as described here, but why not to have a macOS app that will let us get it instantly?

Our menu today contains a lot of key stuff:

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

There are even more things to discuss in this post in addition to those listed above. We will talk about new classes that will be proved extremely valuable for our cause, we will talk about the custom logic that we will implement in the various parts of the app, and we will see small or big details that will produce at the end a complete project.

Here is what you are going to have in your hands by the time you will leave here:

Creating Your First Mac App

Launch Xcode, and then choose to create a new project:

In the first step of the guide that appears next do the following:

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

Once you do so, it’s time to “baptise” the project and set all the rest initial information. Since we’re going to create our custom font viewer, why not to call it just like that, Font Viewer? Set that as the value to the Product Name field, and configure the other properties such as Team, Organization Name, Organization Identifier. Generally, make sure that your window looks like the next one:

Finally, choose a place to save the project on your disk, and finish with the creation process.

Running The App

While having Font Viewer project open in Xcode, press Cmd + R on your keyboard to run the app. Here’s what you’ll see appearing on your screen:

Congratulations! You just ran your first macOS app!

It’s an empty window for now, but it doesn’t matter as we will change that. Look and play around with it. You will notice that standard behaviour and attributes are assigned by default to the window, such as resizing, moving or going full screen. Also, there’s the main menu at the top bar, where some common functionalities are provided already, while some others are just disabled as they don’t trigger any action.

Exploring The Project

If you have prior experience to iOS development, you will find out that a macOS project looks pretty much the same to an iOS project. Project Navigator lists all files and groups, tabs like General, Capabilities, Build Settings, etc are here too, and generally everything looks familiar. Displayed settings and options are specific to macOS though.

In the Project Navigator you will find all files and groups that Xcode creates by default for a Cocoa App. What is new here (comparing again to iOS projects), is an .entitlements file. This is a property list file that contains rights an app is granted with. Most of the times there is no need to edit it manually. Changes made through the Capabilities tab update this file automatically.

Other than that, two code files are created automatically. AppDelegate.swift and ViewController.swift. We will add more code to ViewController.swift in a while. There is also:

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

Looking at the General tab of the Font Viewer target, in the Identity section you can find the Bundle Identifier, as well as the Version and Build Number. But what is more interesting here, is the Application Category popup, where you can find all available app categories also found on App Store. This is the place where we set the category of our app. Which category would be suitable for us here? We could assign it under Developer Tools since it’s going to display font information, but we could also have it in the Utilities category. Right now it doesn’t really matter as we are not going to publish it, but remember to choose a category for your app before you send it to the App Store (it’s still okay if you don’t, you can choose app category on the App Store as well, but why not to do it here?).

Looking a bit lower you will see the Deployment Info section. Here you specify the minimum macOS version required by your app to run. You can have it run on macOS Mojave only (10.14), or enable it running on High Sierra (10.13) or older versions too. Keep that option in mind if you ever want to support older operating systems.

Take your time to go through every aspect of the project. Switch to the Capabilities tab, and see what kind of options are available, or go to Build Settings and see if you find something interesting there too. Once you finish your exploration, let’s take a few moments to see what Main.storybard file contains.

Exploring The Main.storyboard

Clicking on the Main storyboard file in the Project Navigator, the automatically created, default user interface will be revealed in Interface Builder. Any Cocoa based app contains three default scenes:

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

Let’s focus on some interesting details now. Click on the window in the window controller scene, and open Attributes Inspector in the inspectors panel of Xcode. This is what you should be seeing:

For some of them, just enabling or disabling options has immediate visual effect on the window. For some others it has not. Let me highlight the most important ones, but if you find yourself needing information for options not mentioned below, feel free to search for them on the web.

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

Leaving the rest options to their default settings makes sure that a window fits to the general macOS environment and it follows workflows that users are already familiar with. Of course, you can find additional information about them and their purpose if you wish so, I just highlighted the most important ones here.

With the window still being selected, show the Size Inspector. Here, you can set the content size of the window, the allowed maximum and minimum size, as well as the initial place of the window on screen.

Setting the content size (width and height) makes the obvious, it changes the window’s default content size. By leaving the Minimum Content Size and Maximum Content Size unchecked, users can resize the window and make it as small or as big they want. To limit the minimum content size so users cannot decrease it further than that, just enable the Minimum Content Size checkbox and specify values for the min width and height. Similarly, enable the Maximum Content Size checkbox to prevent your window grow more than the values you specify.

To keep a constant content size and disallow resizing, then set the exact same values to both minimum and maximum content size.

By default a window is displayed to the bottom left side of the screen. You can change that by either dragging the window sample in the small screen preview window, or just change the initial position X and Y values. Just remember: coordinates in macOS are not like iOS. The Y=0 point is the bottom side of the screen, not the top.

Windows can also be shown centered on the screen. To do that, click on the Proportional Horizontal and Proportional Vertical popup buttons, and select Center Horizontally and Center Vertically respectively. The screen sample in the Size inspector will be updated, and you can verify that by running the app; the window will be shown on the center of the screen.

Setting Up The UI

After that quick walkthrough to the macOS project, let’s do some real work on our app. For starters, select the default Window and open the Size Inspector in the inspectors panel. Change the Content Size to 370×250. Then, select the view controller’s view and do the same.

Now let’s add the graphical controls to the default view of the view controller.

Begin by clicking on the Library Button on Xcode to open the Objects Library:

In the Objects Library, type popup in the top search field:

Choose the Pop Up Button item, and drag and drop it to the view controller’s view. Select the popup control you just dragged to the view controller, and press Cmd + D on your keyboard to duplicate it.

Select both popup buttons, and add the following constraints: Top: 20, Leading: 20, Trailing: 20, Height: 21.

Following the above steps, let’s add also a push button and a label:

  1. Open the Objects Library again, and type button.
  2. Drag and drop a push button to view controller’s view below the popups.
  3. Set its constraints: Trailing: 20, Bottom: 20, Width: 120, Height: 21.
  4. Double click on the button to change its title to: Display All
  5. Open the Objects Library one last time, and type label in the search field.
  6. Drag and drop a label object into the view controller, under the popups but above the push button.
  7. Set the following constraints for the label: Top: 20, Leading: 20, Trailing: 20, Height: 80.

At the end, your view should be similar to this one:

The first popup button will be displaying all available font families on the system, while the second will contain all available font types (such as bold, italic, etc) for any selected font family in the first popup.

Label will show the PostScript name of the selected font family and type, formatted accordingly.

Lastly, the “Display All” button will be used to show all available font types of a font family at once. More about that later.

IBOutlet Properties And IBAction Methods

We will come to the need to interact with the controls we just added soon, so it’s necessary to create and connect IBOutlet properties and IBAction methods to them. Open the ViewController.swift file, and to the top of the class add the next IBOutlet property declarations:

@IBOutlet weak var fontFamiliesPopup: NSPopUpButton!
 
@IBOutlet weak var fontTypesPopup: NSPopUpButton!
 
@IBOutlet weak var sampleLabel: NSTextField!

Also, define the following IBAction methods:

@IBAction func handleFontFamilySelection(_ sender: Any) {
 
}
 
 
@IBAction func handleFontTypeSelection(_ sender: Any) {
 
}
 
 
@IBAction func displayAllFonts(_ sender: Any) {
 
}

Back to the Main.storyboard file, select the view controller object in the document outline:

In the inspectors panel, select to open the Connections inspector, where you will find all the IBOutlet properties and IBAction methods previously declared. Make the connections as follows:

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

Before we make the simple user interface we just created usable, let’s take a look at the controls we just added. By default, each popup button contains three items, where each one is a menu item (more about menus and menu items in a future post). These can be changed either by double clicking on the popup button and then by double clicking on each item, or by selecting the popup button and spotting each item to the Document Outline.

In this demo application, we will change the menu items programmatically, but Interface Builder is a good option to do that if you have a constant set of items that are not meant to be changed on the fly.

Note: Popup button items can be always updated programmatically regardless of whether an initial set of real items has been set through Interface Builder.

In the Attributes inspector you can find all configurable properties of the popup button (after selecting one). For example, you can define the default selected item, or to set font properties and text alignment.

Similarly, label’s and button’s properties can be changed in the Attributes inspector too if you select the label or button object in the view controller. Feel free to look around and experiment a bit by changing any properties you want. It doesn’t matter if you “damage” any control by accident; just delete it and create a new one by following the steps described above.

Another interesting point is the object hierarchy and the actual contained objects in the Document Outline pane. One would expect to find there only the popup buttons, the label and the push button along with their constraints. However, if you start expanding the objects tree you will notice that there are more objects than those we specifically added. Apart from the menu items of the popups (which is fine to be found there since a menu item is actually another Cocoa control created automatically), you will also find a cell object going in pair with each control. For example, the fontFamiliesPopup is a NSPopUpButton object, but it contains a NSPopUpButtonCell item which in turn contains the menu items. Likewise, the label which is a NSTextField object contains a NSTextFieldCell item.

So, what is the purpose of those “Cell” objects?

Generally speaking, a cell object (any “…Cell” subclass) is responsible for implementing the actual Cocoa control and its functionalities. All those subclasses inherit from the same ancestor, NSCell, which implements several common properties and methods regarding the appearance and behaviour of controls. Even though we will be interacting with the popup buttons, the label and the push button objects directly in this app, behind the scenes any visual changes and updates will be handled by the matching cell subclasses.

Practically speaking now, we don’t have to worry about them as we won’t have to deal with any cell subclass. Just one general warning: make sure to connect IBOutlet properties to the actual controls and not their cell counterparts. It’s a common mistake that can happen if you perform the outlet connection by pressing Control and drag-and-dropping from Interface Builder to the Assistant Editor (code) and you don’t pay attention to what the origin (source) object of the dragging is.

Bringing App To Life

Building the UI, even a simple one, is an achievement. But without code, not much can happen.

Initial Configuration

Open the ViewController.swift file, where we’ll get started by implementing a function in the ViewController class that will perform some initial setup to the UI.

Go after the default view controller methods and add the following:

func setupUI() {
    fontFamiliesPopup.removeAllItems()
    fontTypesPopup.removeAllItems()
    sampleLabel.stringValue = ""
    sampleLabel.alignment = .center
}

In the first two lines we remove all default items from the two popup buttons. In the other two lines we empty the default text of the label, and we center it. Notice that the property to set the label’s text is stringValue; don’t look for a property called text or something like that.

Next step is to populate the first popup button with actual values: All font families existing in the Mac. We will achieve that by getting to know a new class called NSFontManager. It is responsible for several things including the Font panel and Font menu found in many apps, keeping record of the currently selected font, triggering font changes, and more. Among its “duties”, important information about available font families and font attributes is also included!

NSFontManager is used through a shared instance programmatically. A property named availableFontFamilies can give us what we need: A collection (array) with all the available font families!

Right next we define a new method that uses the NSFontManager class and adds the found family names as items to the fontFamiliesPopup:

func populateFontFamilies() {
    fontFamiliesPopup.removeAllItems()
    fontFamiliesPopup.addItems(withTitles: NSFontManager.shared.availableFontFamilies)
}
 

Now, both of methods we implemented above have to be called once the app is started, and before the view gets appeared. Go right after the viewDidLoad() method and add the following:

override func viewWillAppear() {
    super.viewWillAppear()
 
    setupUI()
    populateFontFamilies()
}

6

7

override func viewWillAppear() {

    super.viewWillAppear()

 

    setupUI()

    populateFontFamilies()

}

 

Let’s see what we’ve done so far. Run the app and open the first popup, you will find all available font families in it!

Handling Actions

NSFontManager class seems to be quite useful, as there are more interesting properties and methods to explore and use. One such method that is particularly interesting is called availableMembers(ofFontFamily:). It returns an array where each item is another array ([[Any]]) with four distinct values:

  1. Open the Objects Library again, and type button.
  2. Drag and drop a push button to view controller’s view below the popups.
  3. Set its constraints: Trailing: 20, Bottom: 20, Width: 120, Height: 21.
  4. Double click on the button to change its title to: Display All
  5. Open the Objects Library one last time, and type label in the search field.
  6. Drag and drop a label object into the view controller, under the popups but above the push button.
  7. Set the following constraints for the label: Top: 20, Leading: 20, Trailing: 20, Height: 80.

An item-array is called a member of the font family. The following example taken from Apple Documentation illustrates what availableMembers(ofFontFamily:) returns:

(("Times-Roman", "Roman", 5, 4),
("Times-Italic", "Italic", 6, 5),
("Times-Bold", "Bold", 9, 2),
("Times-BoldItalic", "Bold Italic", 9, 3)
)

Putting the above in different words, with *availableMembers(ofFontFamily:)* we can get all font variations that a font family supports, and display them on the *fontTypesPopup* popup button.

Before we make the actual use of the above method, let’s declare the following two properties to the ViewController class, they’ll become handy a bit later:

var selectedFontFamily: String?
 
var fontFamilyMembers = [[Any]]()

What we need to do now is to make it possible to keep the selected font family and get its contained members every time the fontFamiliesPopup selection gets changed. This must be done in the handleFontFamilySelection(_:) IBAction method:

@IBAction func handleFontFamilySelection(_ sender: Any) {
    if let fontFamily = fontFamiliesPopup.titleOfSelectedItem {
 
        selectedFontFamily = fontFamily
 
        if let members = NSFontManager.shared.availableMembers(ofFontFamily: fontFamily) {
            fontFamilyMembers.removeAll()
            fontFamilyMembers = members
        }
    }
}
 

At first, we get the title of the item that gets selected in the fontFamiliesPopup; it matches to the font family name. We keep it in the selectedFontFamily property.

Next, we get all available members of the selected font family using the availableMembers(ofFontFamily:) method of the NSFontManager class. Those members are kept in the fontFamilyMembers class property. It’s important to remove any previous items existing in that array! We don’t want data from a previously selected font family to remain while we are selecting another one! That’s why removeAll() is called prior to assigning members to fontFamilyMembers.

However, just keeping the selected font family name and its members makes no real difference. We need to populate them to the fontTypesPopup so we see and use the font variations of each family. Why don’t we create a new method to do that? Add the next one to the ViewController class:

func updateFontTypesPopup() {
    fontTypesPopup.removeAllItems()
 
    for member in fontFamilyMembers {
        if let fontType = member[1] as? String {
            fontTypesPopup.addItem(withTitle: fontType)
        }
    }
}

Here is what is going on in the above method:

  1. Open the Objects Library again, and type button.
  2. Drag and drop a push button to view controller’s view below the popups.
  3. Set its constraints: Trailing: 20, Bottom: 20, Width: 120, Height: 21.
  4. Double click on the button to change its title to: Display All
  5. Open the Objects Library one last time, and type label in the search field.
  6. Drag and drop a label object into the view controller, under the popups but above the push button.
  7. Set the following constraints for the label: Top: 20, Leading: 20, Trailing: 20, Height: 80.

You might want to run the app now and see the available font variations after having selected a font family first. Do it, and soon enough you will end up to an unpleasant result: The fontTypesPopup does not get any value at all! But why?

Well, the previous method does, indeed, populate the available members of a font family to the fontFamiliesPopup, but where exactly is it called? Nowhere yet!

Back to the handleFontFamilySelection(_:) IBAction method, which should be updated so it calls the updateFontTypesPopup() when the available members have been retrieved using the availableMembers(ofFontFamily:) method:

@IBAction func handleFontFamilySelection(_ sender: Any) {
    if let fontFamily = fontFamiliesPopup.titleOfSelectedItem {
        selectedFontFamily = fontFamily
 
        if let members = NSFontManager.shared.availableMembers(ofFontFamily: fontFamily) {
            // ...
 
            updateFontTypesPopup()
        }
    }
}

Great, now you can run the app, select a font family, and then see the available members in the second popup!

So far so good, but we still have some more distance to run and get to finish line. When selecting a font member from the second popup, an actual sample should be shown in the sampleLabel label. We can make that happen if we update the font of the sample label every time a font variation is selected. And here is a new thing: How can we create a font (a NSFont object) using the data we have so far?

NSFontManager will be our tool again, as it provides a method named font(withFamily:traits:weight:size:) that returns a NSFont object. If you think about it, we have values for all parameters, as traits and weight are parts of the font member data that we keep in the fontFamilyMembers collection. Also, the selected font family is also kept to the selectedFontFamily property. Regarding size, we’ll set a random value, it doesn’t really matter.

In the handleFontTypeSelection(_:) IBAction method we are going to implement all the logic that will allow the creation of a new font object, which in turn we will set to the sample label along with the PostScript name of the font.

@IBAction func handleFontTypeSelection(_ sender: Any) {
    let selectedMember = fontFamilyMembers[fontTypesPopup.indexOfSelectedItem]
 
    if let postscriptName = selectedMember[0] as? String, let weight = selectedMember[2] as? Int, let traits = selectedMember[3] as? UInt, let fontfamily = selectedFontFamily {
 
        let font = NSFontManager.shared.font(withFamily: fontfamily,
                                             traits: NSFontTraitMask(rawValue: traits),
                                             weight: weight,
                                             size: 19.0)
        sampleLabel.font = font
        sampleLabel.stringValue = postscriptName
    }
}

  1. Open the Objects Library again, and type button.
  2. Drag and drop a push button to view controller’s view below the popups.
  3. Set its constraints: Trailing: 20, Bottom: 20, Width: 120, Height: 21.
  4. Double click on the button to change its title to: Display All
  5. Open the Objects Library one last time, and type label in the search field.
  6. Drag and drop a label object into the view controller, under the popups but above the push button.
  7. Set the following constraints for the label: Top: 20, Leading: 20, Trailing: 20, Height: 80.

Run the app again, and this time you will have both a sample of the selected font, and its PostScript name too!

An Eye To The Details

Our small app is working, but there are a few details left untouched. Let’s go through them.

Regarding the first one, it’s obvious that there is no font sample when the app starts. Moreover, no font members are contained in the fontTypesPopup popup button until we update the selected font family. To get around that, we will go back to one of the first methods created here, the populateFontFamilies(). In it we must make a call to the handleFontFamilySelection(_:) IBAction method and trigger that way the gathering of the font family’s available members:

func populateFontFamilies() {
    // ...
 
    handleFontFamilySelection(self)
}

 This fixes the missing values on the second popup when the app starts, but it doesn’t fix the missing font sample. To make the sample label show the font sample of the first member found in the fontTypesPopup popup button automatically, we need to update the updateFontTypesPopup() method as shown in the next snippet:

func updateFontTypesPopup() {
    // ...
 
    fontTypesPopup.selectItem(at: 0)    
    handleFontTypeSelection(self)
}

First, we auto-select the first item in the popup. Then, we call the handleFontTypeSelection(_:) IBAction method to create the font based on the selected member and update the sample label.

Lastly, one more detail that will show us a general technique beyond the demo app specifics…

Updating The Window title

Our app’s window title is saying “Window” (unless you changed that in Interface Builder), but wouldn’t be nice to make it show the selected font family’s name?

Accessing the window programmatically is easy and it’s done through the view property (main view) of the view controller as you will see right next. Go to the handleFontFamilySelection(_:) IBAction method and add the following line:

@IBAction func handleFontFamilySelection(_ sender: Any) {
    if let fontFamily = fontFamiliesPopup.titleOfSelectedItem {
        // ...
 
        view.window?.title = fontFamily
    }
}

The window now displays the selected font family!

Completing The App With A New Window Controller

Our small application is working perfectly at this point and all steps presented so far have already shown some important how-to stuff regarding macOS programming. However, the “Display All” push button is not functioning yet, and this is the part that we will focus on here. This will give us the chance to explore and talk about more things that will make this post more complete and valuable.

The purpose of the “Display All” push button is to present a new window where font variations of a selected font family will be displayed all together, formatted based on each font’s PostScript name. That might not sound such a big deal given that we can already see them one by one through the popup buttons we have. By doing that though, we will learn:

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

So, if all that sound interesting to you, just keep reading.

Adding A New Window Controller

Back to Main.storyboard file, let’s add a new window controller along with a view controller to our canvas.

Note: Additional window controllers are not required to exist in the Main.storyboard file. You can create new storyboard files to host them. Here we will have them all into the Main storyboard so we keep things simple.

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

You will notice that along with the window controller, an attached view controller is added as well!

What Kind Of Panel Do You Prefer?

Now, let’s see how we can change the default appearance so we can make our new window look like an auxiliary panel.

At first, click on the new window and then show the Identity Inspector. Look at the Custom Class section, where the window’s class is automatically set to NSWindow.

Click on that field, type: NSPanel and hit the Return key on your keyboard.

And now, switch to the Attributes Inspector. A new section has been added to the top, named Panel! The default value in the Style popup is Regular Panel, which means the normal window appearance. Two more panel types are supported:

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

Both of them are meant to exist as auxiliary windows that contain supplementary options or information to the content shown on the main window.

Change the panel style and go through all options; see how the window’s appearance is being changed accordingly. A HUD panel is similar to a Utility panel, with the obvious graphical difference. It’s darker and translucent. Also, only the close button is available on the window’s bar.

Once you finish examining panel style settings, choose HUD as the style for our new window.

You are encouraged to read the Human Interface Guidelines about panels here.

While still being in the Attributes Inspector, set the “Fonts Display” text (without the quotes) as the window’s title.

One last thing before we move to the next step. Show the Identity inspector and in the Identifier text field under the Identity section type the value: fontsDisplayStoryboardID. You will see in a while why we need that.

The Fonts Display View Controller

Along with the window controller, a new view controller was added too to the project. That brings us to the need to create a new NSViewController subclass that we will connect to that new view controller. Press Cmd + N in your keyboard, and select Cocoa Class under the Source section.

Click Next, and change the Subclass of: field to NSViewController. In the Class field, type FontsDisplayViewController. Get finished with the creation of the new file.

Again to the Main.storyboard file, click on the View Controller object, and set the FontsDisplayViewController value to the Class field in the Identity Inspector.

Next, click on the main view of the view controller, and show the Size Inspector. Set its size to 480×400.

Let’s add two new Cocoa controls now to the view.

  1. Open the Objects Library again, and type button.
  2. Drag and drop a push button to view controller’s view below the popups.
  3. Set its constraints: Trailing: 20, Bottom: 20, Width: 120, Height: 21.
  4. Double click on the button to change its title to: Display All
  5. Open the Objects Library one last time, and type label in the search field.
  6. Drag and drop a label object into the view controller, under the popups but above the push button.
  7. Set the following constraints for the label: Top: 20, Leading: 20, Trailing: 20, Height: 80.

We will use the textview to display all available font members of a font family formatted. The button will be used to close the window.

Note: Adding a button to close the window is not really necessary, as the panel itself provides a close button. However, we are doing that here clearly for learning reasons; to see how we can close a window programmatically.

In the FontsDisplayViewController.swift file now, let’s declare an IBOutlet property for the textview, and an IBAction method for the button:

@IBOutlet var fontsTextView: NSTextView!
 
@IBAction func closeWindow(_ sender: Any) {
 
}

Finally, in the Main.storyboard file again, click to select the Fonts Display View Controller object and from the Connections Inspector connect the fontsTextView outlet to the textview, and the closeWindow(_:) action to the button.

There is one disturbing thing in the UI we are being creating, and that is the white textview on the dark and translucent HUD window. That’s okay and tolerable in the storyboard, however who would want it when using the app?

So, open the FontsDisplayViewController.swift file and add the following function:

func setupTextView() {
    fontsTextView.backgroundColor = NSColor(white: 1.0, alpha: 0.0)
    fontsTextView.enclosingScrollView?.backgroundColor = NSColor(white: 1.0, alpha: 0.0)
    fontsTextView.isEditable = false
    fontsTextView.enclosingScrollView?.autohidesScrollers = true
}
 

The above:

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

We will call setupTextView() right before the view gets appeared:

override func viewWillAppear() {
    super.viewWillAppear()
 
    setupTextView()
}

Loading And Presenting The Window Controller

The UI of the auxiliary panel is ready, so let’s see how it looks like when the application is running. Before we load and present the new window controller, we must declare the following two properties in the FontsDisplayViewController.swift file:

var fontFamily: String?
var fontFamilyMembers = [[Any]]()

We need both of them, as we are going to pass the selected font family name and its members from the ViewController class to FontsDisplayViewController. We’ll see how we’ll make use of them in a while. Just declaring them for now is good enough.

So, let’s proceed to the presentation of the new window controller. Open the ViewController.swift file, and go to the displayAllFonts(_:) IBAction method. Remember that this action method is connected to the “Display All” button in the default window of our app, so any code added here will be executed when that button is clicked.

The idea behind presenting a window controller that has been created graphically in a storyboard file, is to initialize a **NSStoryboard** object and ask it to instantiate that window controller. A NSStoryboard object is being initialized by providing the storyboard name, but in a not-so-much straightforward way. Here it is:

let storyboardName = NSStoryboard.Name(stringLiteral: "Main")
let storyboard = NSStoryboard(name: storyboardName, bundle: nil)

As you can see, the NSStoryboard initializer does not accept a string value as an argument (aka the storyboard name). It expects for a NSStoryboard.Name value instead, where Name is String type alias. Value of bundle can be left nil.

With the storyboard object being initialized now, we can instantiate our window controller. For that purpose we will use the storyboard Identifier we had previously set in Interface Builder:

let storyboardID = NSStoryboard.SceneIdentifier(stringLiteral: "fontsDisplayStoryboardID")
 
if let fontsDisplayWindowController = storyboard.instantiateController(withIdentifier: storyboardID) as? NSWindowController {
 
}

instantiateController(withIdentifier:) is the method of the NSStoryboard class that instantiates the window controller in the above code. It returns an Any? value, so it is necessary to:

  • How to create a new macOS project.
  • What the most important parts of the generated project are.
  • What is a window controller and how to configure its contained window, including size, position, title, and a lot more.
  • How to add graphical controls into a view controller.
  • How to interact with those graphical controls, update their values and react on actions triggered by them.
  • How to create additional window controllers.
  • How to change a window’s appearance so it looks like a utility or auxiliary panel.
  • How to load and present and close a window programmatically.

Note: Apart from window controllers, it is also possible to instantiate view controllers using the *instantiateController(withIdentifier:)* method, as long as a storyboard Identifier has been set for them in Interface Builder. That’s why that method returns an *Any* value instead of a *NSWindowController* object. It’s our duty to cast to the proper type.

Also, in the above snippet you see that the window controller’s identifier is not given as a plain String value. Instead, a NSStoryboard.SceneIdentifier value must be provided, where SceneIdentifier is a String type alias.

Supposedly at this point the new window controller has been instantiated. That’s not enough though, we must access its content view controller, and pass the selected font family and its members collection to it. NSWindowController has a property named contentViewController which represents the view controller attached to the window controller. We will use it, and after having accessed it we will pass the values we want:

if let fontsDisplayVC = fontsDisplayWindowController.contentViewController as? FontsDisplayViewController {
    fontsDisplayVC.fontFamily = selectedFontFamily
    fontsDisplayVC.fontFamilyMembers = fontFamilyMembers
}

Finally, we can present the window as follows:

fontsDisplayWindowController.showWindow(nil)

The above are the steps required to instantiate a window controller programmatically, to get its content view controller, and eventually present them all. Right next is the displayAllFonts(_:) method in one piece:

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@IBAction func displayAllFonts(_ sender: Any) {
    let storyboardName = NSStoryboard.Name(stringLiteral: "Main")
    let storyboard = NSStoryboard(name: storyboardName, bundle: nil)
 
    let storyboardID = NSStoryboard.SceneIdentifier(stringLiteral: "fontsDisplayStoryboardID")
 
    if let fontsDisplayWindowController = storyboard.instantiateController(withIdentifier: storyboardID) as? NSWindowController {
 
        if let fontsDisplayVC = fontsDisplayWindowController.contentViewController as? FontsDisplayViewController {
            fontsDisplayVC.fontFamily = selectedFontFamily
            fontsDisplayVC.fontFamilyMembers = fontFamilyMembers
        }
 
        fontsDisplayWindowController.showWindow(nil)
    }
}

Now you can run the app and click on the “Display All” button. There will be no content yet, however you will have your first HUD panel!

Displaying Fonts

What we are going to achieve in this part is to display all font variations contained in a selected font family one after another in the textview. More precisely, the font PostScript names. Furthermore, each displayed font variation is going to be formatted according to the font it represents.

We will achieve that by creating an attributed string that will contain all PostScript names separated by the newline character “\n”, and by adding new font attributes for each displayed line. At the end, that attributed string will be assigned to the textview.

Let’s get started by creating a new method in the FontsDisplayViewController class. We’ll name it showFonts(), and the first thing we’ll do is to make sure that the font family name exists.

func showFonts() {
    guard let fontFamily = fontFamily else { return }    
}
 

Remember that fontFamily is supposed to get its value when the “Display All” push button is clicked on the main window of the app.

Next, let’s declare the following two variables:

var fontPostscriptNames = ""
var lengths = [Int]()

fontPostscriptNames is the string that will hold all PostScript names that will be displayed on the textview. lengths is an array that will keep the length of each PostScript name. I’ll explain why we need it later.

Now, let’s gather all PostScript names from the font family members and let’s build the display string. At the same time, we’ll be storing each name’s length:

for member in fontFamilyMembers {
    if let postscript = member[0] as? String {
        fontPostscriptNames += "\(postscript)\n"
        lengths.append(postscript.count)
    }
}

Once again, the fontPostscriptNames string is composed by appending each font PostScript name and the newline symbol “\n” to it.

Having the string value we want to display, we are able to create the attributed string that will format later:

let attributedString = NSMutableAttributedString(string: fontPostscriptNames)

Here’s what’s coming next: We will go once again through all font family members, and for each one we will be creating a new font (NSFont) object based on each member’s data.

for (index, member) in fontFamilyMembers.enumerated() {
    if let weight = member[2] as? Int, let traits = member[3] as? UInt {            
        if let font = NSFontManager.shared.font(withFamily: fontFamily, traits: NSFontTraitMask(rawValue: traits), weight: weight, size: 19.0) {
 
        }
    }
}

We will use the font object above to create a new attribute which we will add it to the attributedString we initialized previously.

Now, there is a tricky part coming. Any attribute added to an attributed string must specify the range of text that it will be applied to. That range is a NSRange value, which is composed by two distinct values: location and length. Location indicates the index of the starting character a range starts from, and length shows how many characters the range will spread to.

Given the fact that we want each line of our text to have a different attribute because of the different font, we must calculate the range of the text that each font will be applied to. And here is where the lengths array is becoming handy. Each new range will start where the previous range stopped, and its length will be equal to the respective value in the lengths array. First range will start at zero.

The following code shows how we calculate the starting location for each range and how we create a new range:

var location = 0
if index > 0 {
&nbsp;&nbsp;&nbsp;&nbsp;for i in 0..<index {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;location += lengths[i] + 1
&nbsp;&nbsp;&nbsp;&nbsp;}
}
&nbsp;
let range = NSMakeRange(location, lengths[index])

In case index is zero, meaning this is the first member in the fontFamilyMembers array, the location is set to zero too (start of text). In every other case, location is the sum of all previous range lengths, including the “\n” symbol which counts as additional character in each line (that’s why “+ 1” is added to each length).

Last line shows how a range is created. NSMakeRange() function accepts two arguments, the location and the length of the range.

Finally, we can create a new attribute for each font now:

attributedString.addAttribute(NSAttributedString.Key.font, value: font, range: range)

All the above will happen for each single item found in the fontFamilyMembers array, or in other words, in the for (index, member) in fontFamilyMembers.enumerated() { ... } loop. There’s one more attribute we have to add to the entire text, and after the end of the loop. That is the text color. By default, black color is the text color of the textview. But since we are using a HUD panel which is already dark, we have to give a light color to the text.

Here is how we do that for the entire text. As you will see, specifying the range in that case is a lot easier than what we previously did:

attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: NSColor.white, range: NSMakeRange(0, attributedString.string.count))

Finally, the attributed string is ready with all the attributes set. It’s time to assign it to the textview, or more precisely, to the textStorage property of the textview:

fontsTextView.textStorage?.setAttributedString(attributedString)

Here’s the showFonts() method with its code all together:

func showFonts() {
&nbsp;&nbsp;&nbsp;&nbsp;guard let fontFamily = fontFamily else { return }
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;var fontPostscriptNames = ""
&nbsp;&nbsp;&nbsp;&nbsp;var lengths = [Int]()
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;for member in fontFamilyMembers {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if let postscript = member[0] as? String {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fontPostscriptNames += "\(postscript)\n"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lengths.append(postscript.count)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;let attributedString = NSMutableAttributedString(string: fontPostscriptNames)
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;for (index, member) in fontFamilyMembers.enumerated() {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if let weight = member[2] as? Int, let traits = member[3] as? UInt {
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if let font = NSFontManager.shared.font(withFamily: fontFamily, traits: NSFontTraitMask(rawValue: traits), weight: weight, size: 19.0) {
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var location = 0
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if index > 0 {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for i in 0..<index {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;location += lengths[i] + 1
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let range = NSMakeRange(location, lengths[index])
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;attributedString.addAttribute(NSAttributedString.Key.font, value: font, range: range)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: NSColor.white, range: NSMakeRange(0, attributedString.string.count))
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;fontsTextView.textStorage?.setAttributedString(attributedString)&nbsp;&nbsp;&nbsp;&nbsp;
}

Do not forget to call it:

override func viewWillAppear() {
&nbsp;&nbsp;&nbsp;&nbsp;// ...
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;showFonts()
}

We are done! Run the app now, select any font family and click on the “Display All” button. You will get all available font variations in one place:

Closing The Window

There is one last thing to do, and that is to enable the Close button so it’s possible to close the window when clicking on it. Doing so is extremely easy as you can see right next:

@IBAction func closeWindow(_ sender: Any) {
&nbsp;&nbsp;&nbsp;&nbsp;view.window?.close()
}

Summary

We came eventually to the end of the tutorial! Through all previous parts it became quite obvious that creating a macOS application involves a lot of steps and details, and this post highlighted the most important ones given our today roadmap. There are definitely more things to explore and discuss, and in future posts we’ll be focusing on more and new stuff about macOS programming. Here we managed to create a small application with just a few graphical controls on its UI, we implemented code to get the font information we wanted, to make visual updates to controls and to “play” with windows. By going into the making process all these, we had the chance to meet important concepts and to talk about standard techniques and practices. I hope this post will become a guide to your efforts in building your own macOS applications. Don’t forget to visit Apple’s documentation and Human Interface Guidelines if you find yourself stuck, or you just want to make sure that you are playing by the rules. See you soon!

For reference, you can download the full project on GitHub.

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

iOS App Development: How Apple's iOS 13 & Swift 5 Will Change App Development Industry

iOS App Development: How Apple's iOS 13 & Swift 5 Will Change App Development Industry

Want to know how Apple's iOS 13 & Swift 5 will change the iOS app development industry? Refer to this blog that contains in-detail information on it.

In June this year, Apple announced the release of iOS 13 during the annual WWDC event along with a bunch of other releases and launches. The new iOS version- iOS 13, was discussed in detail, and the developer community was given reasons to be excited about the new release.

Dark Mode was one of the most awaited updates that the iPhone users were deprived of. Know more about how to enable dark mode for your iOS application.

Sign in with Apple and updates in background tasks kept the developers counting days. As with every new Apple product, whether it is software or hardware, we all look forward to what’s new in it. The release of iOS13 redefines the way mobile app development occurred today. In fact, the powerful combination of Apple’s iOS 13 and Swift 5 will be akin to Batman and Robin when developing mobile apps for the millennials in a demanding market.

If you have been viewing this space, and looking for an answer to the question – how iOS 13 will change the app development space, then here are some clues.

How iOS 13 and Swift 5 Will Change iOS App Development

  1. Augmented Reality Apps Development with ARKit 2
    Augmented Reality is redefining customer experiences. Remember how Pokemon Go became a rage a few years back? Almost everyone had it installed on their phones. They played it day and night, on the streets, competing to catch virtual Pokemon. In fact, it is the same with all the AR-based app solutions. Apple is making progress with its AR technology and has redefined the ARkit to match the needs of the future.

While the ARKit is getting a refined look, Apple has also announced a new AR technology known as Realitykit which will help the creators of AR apps render photorealistic digital material along with incorporating the right type of animation for the same. The best part of the new AR technology is that it works with Swift 5, which means you can develop AR apps for Mac, iPhone, and iPad with ease.

The company had earlier released headsets for AR app solutions, but the headsets were not connected with iPhone or iPad, however, they were linked to the camera app alone. Things have gradually changed in this department. The announcement of this new technology comes at a time when AR has not only picked up but has also been commanding several applications. This new development technique will enhance how mobile apps were creating experiences until now.

  1. Car Play Apps Development
    This combination will give your car the much-needed upgrade. The new version of CarPlay released with iOS 13 will definitely redefine your in-car experience.

The dashboard is the biggest overhaul this feature has received where you can easily view the current music being played by the stereo, the map showing the route to the destination as well as insights into your destination, all in one place. In fact, the dark/light theme is also available for the car plays dashboard. Basically, the new car play ends an era of switching between two screens and helps enjoy the drive.

The maps have also been revamped for the new version of iOS. You also get a better Siri interface with Carplay, which makes your connection with Siri interesting. Carplay also integrates the calendar app thus, allowing you a quick glance into your day’s appointment. The best part of the upgrade is that your car play screen is independent of the phone screen, which means you can read two different things at the same time.

  1. Machine Learning with Core ML
    The advancements made with CoreML are worth knowing, as it will improve the way you integrate machine learning into your iOS-based mobile apps. The new Core ML 3 will expand the horizons of development and will make it easy for you as a developer to introduce machine learning to the apps.

Apart from ease of development, the next big thing introduced in this version of CoreML is the on-device training. The server side training was done using frameworks such as PyTorch and then these applications were converted to Core ML for better predictions. CoreML3 is the new version that supports both inference and on-device training.

  1. Cloud-Based Apps
    You cannot be slow and demand to become successful at the same time. You ought to accelerate not only your app development process but also how your apps work irrespective of the environment. The cloud-based app solutions are the new feature devised by iOS13 that will work in favour of app developers.

From operations to storage flexibility, the cloud proves to be a brilliant way of getting apps to download faster and improve user experience. iOS13’s introduction of cloud-based apps will not only make app development economical by offering flexible pricing but also improve the functionality of the app solutions.

The new cloud-based app solutions offer an exceptional dashboard thus making data accessible, and pave ways for easy monitoring of the app solutions.

  1. watchOS 6
    With the release of iOS13, the watchOS6 will also be released. The new watchOS will come with shortcuts, custom controls, an upgraded Siri and an audio app. The new OS is also set to launch new health features for the fitness apps that you may develop.

The app store has already defined app solutions that can be used with watchOS6. One is the cycle tracking app which helps women track their menstrual cycles while the other app solution is the noise app which measures the level of noise in your environment.

  1. Increased security
    App developers are always concerned about security within the apps. There is always a fear of user accounts getting hacked or their data being stolen. They want to ensure that there is enough security so that the users feel safe when connecting with the apps. The new version of iOS has strengthened app security. The company will be using App Transport Security to incorporate new security standards whenever they are releasing a new app. The app developers will need to ensure that there is a single channel for all interactions with the mobile app.

Creating an iOS app with user presence using Node.js and Swift

Creating an iOS app with user presence using Node.js and Swift

In this tutorial, we are going to see how you can add user presence to an iOS application using Pusher Channels, Node.js, and Swift.

We will create a sample chat application to demonstrate this feature. However, because we are focusing on just user presence, we will not implement the actual chat feature.

If you are building an application that has a user base, you might need to show your users when their friends are currently online. This comes in handy especially in messenger applications where the current user would like to know which of their friends are available for instant messaging.

Here is a screen recording on how we want the application to work:

If you want a tutorial on how to create a messenger application on iOS, check out this article.


Prerequisites

To follow along you need the following requirements:

  1. Xcode installed on your machine. Download here.
  2. Knowledge of the Swift programming language.
  3. Knowledge of the Xcode IDE.
  4. A Pusher Channels app. Create one here.
  5. Cocoapods installed on your machine. Installation guide.
  6. Node.js and npm installed on your machine. Installation guide.
  7. Basic knowledge of JavaScript and Node.js.

Let’s get started.


Creating the backend of the application

Before creating the iOS application, let’s create the backend application in Node.js. This application will have the necessary endpoints the application will need to function properly. To get started, create a new directory for the project.


Installing the dependencies

In the root of the project, create a new package.json file and paste the following contents into it:

    {
      "name": "presensesample",
      "version": "1.0.0",
      "main": "index.js",
      "dependencies": {
        "body-parser": "^1.18.3",
        "express": "^4.16.4",
        "pusher": "^2.1.3"
      }
    }

Above, we have defined some npm dependencies that the backend application will need to function. Amongst the dependencies, we can see the pusher library. This is the Pusher JavaScript server SDK.

Next, open your terminal application and cd to the root of the project you just created and run the following command:

    $ npm install

This command will install all the dependencies we defined above in the package.json file.


Creating the main application

Next, create a new file called index.js and paste the following code into the file:

    // File: ./index.js
const express = require('express');
const bodyParser = require('body-parser');
const Pusher = require('pusher');
const app = express();

let users = {};
let currentUser = {};

let pusher = new Pusher({
  appId: 'PUSHER_APP_ID',
  key: 'PUSHER_APP_KEY',
  secret: 'PUSHER_APP_SECRET',
  cluster: 'PUSHER_APP_CLUSTER'
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// TODO: Add routes here

app.listen(process.env.PORT || 5000);

In the code above, we imported the libraries we need for the backend application. We then instantiated two new variables: users and currentUser. We will be using these variables as a temporary in-memory store for the data since we are not using a database.

Next, we instantiated the Pusher library using the credentials for our application. We will be using this instance to communicate with the Pusher API. Next, we add the listen method which instructs the Express application to start the application on port 5000.

Next, let’s add some routes to the application. In the code above, we added a comment to signify where we will be adding the route definitions. Replace the comment with the following code:

    // File: ./index.js

// [...]

app.post('/users', (req, res) =&gt; {
  const name = req.body.name;
  const matchedUsers = Object.keys(users).filter(id =&gt; users[id].name === name);

  if (matchedUsers.length === 0) {
    const id = generate_random_id();
    users[id] = currentUser = { id, name };
  } else {
    currentUser = users[matchedUsers[0]];
  }

  res.json({ currentUser, users });
});

// [...]

Above, we have the first route. The route is a POST route that creates a new user. It first checks the users object to see if a user already exists with the specified name. If a user does not exist, it generates a new user ID using the generate_random_id method (we will create this later) and adds it to the users object. If a user exists, it skips all of that logic.

Regardless of the outcome of the user check, it sets the currentUser as the user that was created or matched and then returns the currentUser and users object as a response.

Next, let’s define the second route. Because we are using presence channels, and presence channels are private channels, we need an endpoint that will authenticate the current user. Below the route above, add the following code:

    // File: ./index.js

// [...]

app.post('/pusher/auth/presence', (req, res) =&gt; {
  let socketId = req.body.socket_id;
  let channel = req.body.channel_name;

  let presenceData = {
    user_id: currentUser.id,
    user_info: { name: currentUser.name }
  };

  let auth = pusher.authenticate(socketId, channel, presenceData);

  res.send(auth);
});

// [...]

Above, we have the Pusher authentication route. This route gets the expected socket_id and channel_name and uses that to generate an authentication token. We also supply a presenceData object that contains all the information about the user we are authenticating. We then return the token as a response to the client.

Finally, in the first route, we referenced a function generate_random_id. Below the route we just defined, paste the following code:

    // File: ./index.js

// [...]

function generate_random_id() {
  let s4 = () =&gt; (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}

// [...] 

The function above just generates a random ID that we can then use as the user ID when creating new users.

Let’s add a final default route. This will catch visits to the backend home. In the same file, add the following:

    // [...]

app.get('/', (req, res) =&gt; res.send('It works!'));

// [...]

With this, we are done with the Node.js backend. You can run your application using the command below:

    $ node index.js

Your app will be available here: http://localhost:5000.


Building the iOS application

Launch Xcode and create a new sample Single View App project. We will call ours presensesample.

When you are done creating the application, close Xcode. Open your terminal application and cd to the root directory of the iOS application and run the following command:

    $ pod init

This will create a new Podfile file in the root directory of your application. Open the file and replace the contents with the following:

    # File: ./Podfile
target 'presensesample' do
platform :ios, '12.0'

  use_frameworks!

  pod 'Alamofire', '~&gt; 4.7.3'
  pod 'PusherSwift', '~&gt; 5.0'
  pod 'NotificationBannerSwift', '~&gt; 1.7.3'
end

Above, we have defined the application’s dependencies. To install the dependencies, run the following command:

    $ pod install

The command above will install all the dependencies in the Podfile and also create a new .xcworkspace file in the root of the project. Open this file in Xcode to launch the project and not the .xcodeproj file.


Creating the scenes

The first thing we will do is create the storyboard scenes we need for the application to work. We want the storyboard to look like this:

Open the main storyboard file and delete all the scenes in the file so it is empty. Next, add a view controller to the scene.

TIP: You can use the command + shift + L shortcut to bring the objects library.

With the view controller selected, click on Editor > Embed In > Navigation Controller. This will embed the current view controller in a navigation controller. Next, with the navigation view controller selected, open the attributes inspector and select the Is Initial View Controller option to set the navigation view controller as the entry point for the storyboard.

Next, design the view controller as seen in the screenshot below. Later on in the article, we will be connecting the text field and button to the code using an @IBOutlet and an @IBAction.

Next, add the tab bar controller and connect it to the view controller using a manual segue. Since tab bar controllers come with two regular view controllers, delete them and add two table view controllers instead as seen below:

When you are done creating the scenes, let’s start adding the necessary code.


Adding code to the created scenes

Create a new controller class called LoginViewController and set it as the custom class for the first view controller attached to the navigation controller. Paste the following code into the file:

    // File: ./presensesample/LoginViewController.swift
import UIKit
import Alamofire
import PusherSwift
import NotificationBannerSwift

class LoginViewController: UIViewController {
    var user: User? = nil
    var users: [User] = []

    @IBOutlet weak var nameTextField: UITextField!

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        user = nil
        users = []

        navigationController?.isNavigationBarHidden = true
    }

    @IBAction func startChattingButtonPressed(_ sender: Any) {
        if nameTextField.text?.isEmpty == false, let name = nameTextField.text {
            registerUser(["name": name.lowercased()]) { successful in
                guard successful else {
                    return StatusBarNotificationBanner(title: "Failed to login.", style: .danger).show()
                }

                self.performSegue(withIdentifier: "showmain", sender: self)
            }
        }
    }

    func registerUser(_ params: [String : String], handler: @escaping(Bool) -&gt; Void) {
        let url = "http://127.0.0.1:5000/users"

        Alamofire.request(url, method: .post, parameters: params)
            .validate()
            .responseJSON { resp in
                if resp.result.isSuccess,
                    let data = resp.result.value as? [String: Any],
                    let user = data["currentUser"] as? [String: String],
                    let users = data["users"] as? [String: [String: String]],
                    let id = user["id"], let name = user["name"]
                {
                    for (uid, user) in users {
                        if let name = user["name"], id != uid {
                            self.users.append(User(id: uid, name: name))
                        }
                    }

                    self.user = User(id: id, name: name)
                    self.nameTextField.text = nil

                    return handler(true)
                }

                handler(false)
        }
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let vc = segue.destination as? MainViewController {
            vc.viewControllers?.forEach {
                if let onlineVc = $0 as? OnlineTableViewController {
                    onlineVc.users = self.users
                    onlineVc.user = self.user
                }
            }
        }
    }
}

In the controller above, we have defined the users and user properties which will hold the available users and the current user when the user is logged in. We also have the nameTextField which is an @IBOutlet to the text field in the storyboard view controller, so make sure you connect the outlet if you hadn’t previously done so.

In the same controller, we have the startChattingButtonPressed method which is an @IBAction so make sure you connect it to the submit button in the storyboard view controller if you have not already done so. In this method, we call the registerUser method to register the user using the API. If the registration is successful, we direct the user to the showmain segue.

The segue between the login view controller and the tab bar controller should be set with an identifier of showmain.

In the registerUser method, we send the name to the API and receive a JSON response. We parse it to see if the registration was successful or not.

The final method in the class is the prepare method. This method is automatically called by iOS when a new segue is being loaded. We use this to preset some data to the view controller we are about to load.

Next, create a new file called MainViewController and set this as the custom class for the tab bar view controller. In the file, paste the following code:

    // File: ./presensesample/MainViewController.swift
import UIKit

class MainViewController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.title = "Who's Online"
        navigationItem.hidesBackButton = true
        navigationController?.isNavigationBarHidden = false

        // Logout button
        navigationItem.rightBarButtonItem = UIBarButtonItem(
            title: "Logout",
            style: .plain,
            target: self,
            action: #selector(logoutButtonPressed)
        )
    }

    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        navigationItem.title = item.title
    }

    @objc fileprivate func logoutButtonPressed() {
        viewControllers?.forEach {
            if let vc = $0 as? OnlineTableViewController {
                vc.users = []
                vc.pusher.disconnect()
            }
        }

        navigationController?.popViewController(animated: true)
    }
}

In the controller above, we have a few methods defined. The viewDidLoad method sets the title of the controller and other navigation controller specific things. We also define a Logout button in this method. The button will trigger the logoutButtonPressed method.

In the logoutButtonPressed method, we try to log the user out by resetting the users property in the view controller and also we disconnect the user from the Pusher connection.

Next, create a new controller class named ChatTableViewController. Set this class as the custom class for one of the tab bar controllers child controllers. Paste the following code into the file:

    // File: ./presensesample/ChatTableViewController.swift
import UIKit

class ChatTableViewController: UITableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func numberOfSections(in tableView: UITableView) -&gt; Int {
        return 0
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {
        return 0
    }
}

The controller above is just a base controller and we do not intend to add any chat logic to this controller.

Create a new controller class called OnlineTableViewController. Set this controller as the custom class for the second tab bar controller child controller. Paste the following code to the controller class:

    // File: ./presensesample/OnlineTableViewController.swift
import UIKit
import PusherSwift

struct User {
    let id: String
    var name: String
    var online: Bool = false

    init(id: String, name: String, online: Bool? = false) {
        self.id = id
        self.name = name
        self.online = online!
    }
}

class OnlineTableViewController: UITableViewController {

    var pusher: Pusher!
    var user: User? = nil
    var users: [User] = []

    override func numberOfSections(in tableView: UITableView) -&gt; Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {
        return users.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "onlineuser", for: indexPath)
        let user = users[indexPath.row]

        cell.textLabel?.text = "\(user.name) \(user.online ? "[Online]" : "")"

        return cell
    }
}

In the code above, we first defined a User struct. We will use this to represent the user resource. We have already referenced this struct in previous controllers we created earlier.

Next, we defined the OnlineTableViewController class which is extends the UITableViewController class. In this class, we override the usual table view controller methods to provide the table with data.

You have to set the cell reuse identifier of this table to onlineuser in the storyboard.

Above we also defined some properties:

  • pusher - this will hold the Pusher SDK instance that we will use to subscribe to Pusher Channels.
  • users - this will hold an array of User structs.
  • user - this is the user struct of the current user.

Next, in the same class, add the following method:

    // File: ./presensesample/OnlineTableViewController.swift

// [...]

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.allowsSelection = false

    // Create the Pusher instance...
    pusher = Pusher(
        key: "PUSHER_APP_KEY",
        options: PusherClientOptions(
            authMethod: .endpoint(authEndpoint: "http://127.0.0.1:5000/pusher/auth/presence"),
            host: .cluster("PUSHER_APP_CLUSTER")
        )
    )

    // Subscribe to a presence channel...
    let channel = pusher.subscribeToPresenceChannel(
        channelName: "presence-chat",
        onMemberAdded: { member in
            if let info = member.userInfo as? [String: String], let name = info["name"] {
                if let index = self.users.firstIndex(where: { $0.id == member.userId }) {
                    let userModel = self.users[index]
                    self.users[index] = User(id: userModel.id, name: userModel.name, online: true)
                } else {
                    self.users.append(User(id: member.userId, name: name, online: true))
                }

                self.tableView.reloadData()
            }
        },
        onMemberRemoved: { member in
            if let index = self.users.firstIndex(where: { $0.id == member.userId }) {
                let userModel = self.users[index]
                self.users[index] = User(id: userModel.id, name: userModel.name, online: false)
                self.tableView.reloadData()
            }
        }
    )

    // Bind to the subscription succeeded event...
    channel.bind(eventName: "pusher:subscription_succeeded") { data in
        guard let deets = data as? [String: AnyObject],
            let presence = deets["presence"] as? [String: AnyObject],
            let ids = presence["ids"] as? NSArray else { return }

        for userid in ids {
            guard let uid = userid as? String else { return }

            if let index = self.users.firstIndex(where: { $0.id == uid }) {
                let userModel = self.users[index]
                self.users[index] = User(id: uid, name: userModel.name, online: true)
            }
        }

        self.tableView.reloadData()
    }

    // Connect to Pusher
    pusher.connect()
}

// [...]

In the viewDidLoad method above, we are doing several things. First, we instantiate the Pusher instance. In the options, we specify the authorize endpoint. We use the same URL as the backend we created earlier.

The next thing we do is subscribe to a presence channel called presence-chat. When working with presence channels, the channel name must be prefixed with presence-. The subscribeToPresenceChannel method has two callbacks that we can add logic to:

  • onMemberAdded - this event is called when a new user joins the presence-chat channel. In this callback, we check for the user that was added and mark them as online in the users array.
  • onMemberRemoved - this event is called when a user leaves the presence-chat channel. In this callback, we check for the user that left the channel and mark them as offline.

Next, we bind to the pusher:subscription_succeeded event. This event is called when a user successfully subscribes to updates on a channel. The callback on this event returns all the currently subscribed users. In the callback, we use this list of subscribed users to mark them online in the application.

Finally, we use the connect method on the pusher instance to connect to Pusher.


Allowing local connections on the iOS app

One last thing we need to do before we are done with the iOS application is allowing the application load data from arbitrary URLs. By default, iOS does not allow this, and it should not. However, since we are going to be testing locally, we need this turned on temporarily. Open the info.plist file and update it as seen below:

Now, our app is ready. You can run the application and you should see the online presence status of other users when they log in.


Conclusion

In this tutorial, we learned how to use presence channels in your iOS application using Pusher Channels.

The source code for the application created in this tutorial is available on GitHub.


Learn More

Learn Swift 4: From Beginner to Advanced

Top 10 Node.js Frameworks

Machine Learning In Node.js With TensorFlow.js

Express.js & Node.js Course for Beginners - Full Tutorial

How to Perform Web-Scraping using Node.js

Build a web scraper with Node

Getting started with Flutter

Android Studio for beginners

Building a mobile chat app with Nest.js and Ionic 4

Originally published by Neo Ighodaro at https://pusher.com/

Build an iOS Chat App with Swift

In this tutorial we show you how easy it is to build your own chat application for iOS using Swift and our new API for chat and messaging: https://getstream.io/chat/. Once you've completed this tutorial you'll have created a fully functioning chat app for the iPhone.