Blogs – Beyers Appshttp://beyersapps.com
Sat, 05 Aug 2017 16:27:48 +0000 en-US
hourly
1 https://wordpress.org/?v=5.1.1http://beyersapps.com/wp-content/uploads/2017/07/cropped-Site_Icon-1-32x32.pngBlogs – Beyers Appshttp://beyersapps.com
323282113791Implementing Handoff In iOS and macOShttp://beyersapps.com/implementing-handoff-ios-macos/
http://beyersapps.com/implementing-handoff-ios-macos/#respondThu, 03 Aug 2017 13:26:18 +0000http://beyersapps.com/?p=168Handoff is a neat feature that was introduced in iOS 8 and macOS (then OS X) v10.10. This capability allows an app to pass data across macOS and iOS devices so that a task started on one device can be completed on another device. In this tutorial we will implement Handoff on both iOS and macOS platforms.

]]>Handoff is a neat feature that was introduced in iOS 8 and macOS (then OS X) v10.10. This capability allows an app to pass data across macOS and iOS devices so that a task started on one device can be completed on another device. The difference between this and continuing an activity by saving a file to a place like iCloud and resuming the activity on another device is context. Meaning that when you use handoff, unlike other methods, the user of your app will be in the exact same location, when the app is opened on a second device, as they were on the first device.

An example of this behavior is a user typing in your app on a screen that takes a few steps to get to, continuing an app on the second device will automatically take that user to the same screen where they will see the same text and even have the keyboard visible and ready for them to continue typing. The alternative is for the user to wait for the data to sync between all devices then navigating to the same place in the app and tapping on the text box to make the keyboard show. This may sound trivial but the user’s experience is much better when the app knows what behavior is desired and automatically does as much of the work as possible.

Introduction

In this tutorial, we will walk through setting up handoff on both iOS and macOS. We are going to use a simple app that stores time stamps. On iOS there is a button to allow the user to create time stamps while the macOS app will generate a new time stamp every time the app is launched. Both apps use a master-detail controller to show the time stamp on the details screen when one is selected on the master side. When we are finished, any time stamp that is being viewed on the details side of the screen will be available for another app to open and view, even if that time stamp does not exist on the other device. The starter project can be found on GitHub.

We will implement Handoff on both platforms simultaneously and will re-use as much of the code as possible so that the two implementations are nearly identical. Please note that for handoff to work between a macOS app and an iOS app, the developer’s Team ID must be the same. Also, the mac app must be signed. In the project settings under the macOS app target on the “General” tab, look for the “Signing” section as seen here.

If your app is a document-based app, Handoff is automatically supported. Because of this, we will not look at document-based apps. Rather, we will implement the manual way of supporting Handoff. To do this, we will handle call backs in the AppDelegate and we will manually create and update NSUserActivity objects.

Getting Started With Handoff

The first step is to declare the activities that can be continued by an app. This lets the OS know that if another device is broadcasting an NSUserActivity of the same type, it can be continued using this app. So, in the Info.plist file, make a new entry where the key is NSUserTypes and the value is an array of strings. Each string represents one activity and should be in reverse DNS style formatting. For example, our activity will be “com.chariotsolutions.ListHandoff.viewTimestamp”, as seen in the following image.

Working with NSUserActivity

The next step is to create the User Activity (NSUserActivity). The User Activity is used to describe what actions can be passed to another device. We are only going to pass the viewing of an individual timestamp. However, we could create a separate NSUserActivity for viewing a list or for editing a timestamp if we wanted to support those activities. Since all of our user interaction is filtered through ListTableDataSource, and this code is shared by both iOS and macOS, this is the perfect place to manage the NSUserActivity objects. Therefore, in ListTableDataSource we are going to add a property to hold the NSUserActivity:

var currentActivity: NSUserActivity?

The currentActivity will then be managed as the user selects Events in the table view. Continuing, add a method to handle user selection before the event is passed back to the delegate:

Next, we will update the mac app to call this by opening ListTableDataSource+macOS.swift, which has an extension of ListTableDataSource that is available in the macOS app only, and replace line 80 with:

userDidSelect(event: event, notifyDelegate: true)

In addition, the iOS app needs to be updated. Currently, selecting a UITableViewCell is automatically transitioning the app to the next screen so the user interaction is not being handled at all. However, we will fix this by implementing the following method in ListTableDataSource+iOS.swift, which is an extension of ListTableDataSource that is available in the iOS app only:

Now, any time the user selects a timestamp from the list, we will know about it. Although, we still need to update the app on the iPhone so that when the user presses the back button to return to the list, the user activity gets deselected. To do so, open the iOS app’s MasterViewController class, locate viewWillAppear(_:) and add the following just before the call to super.

In the previous code, if the event is not nil, we check to see if a user activity already exists. If it does, we update the user data so that it is current. But, if an activity does not exist, then we create the new user activity. If, on the other hand, the event is nil, then we invalidate the activity and nil it out to free up the memory. We must include currentActivity?.becomeCurrent() in order for the activity to be the one published by the OS as a handoff activity.

Until now, the iOS app’s MasterViewController has had no need to know about event selections because the navigations happen automatically. However, during handoff, there is no user interaction so we must provide a way for the MasterViewController to navigate properly when handoff occurs. Consequently, we must make two changes. First, in viewDidLoad() set the delegate on the data source.

dataSource.delegate = self

Second, implement the protocol such that when an event is selected, the index path for that event is determined, that index path is selected on the table view and, finally, the segue to show the details view is programmatically initiated. To do this, create an extension to the iOS app’s MasterViewController that implements ListTableDataSourceDelegate.

Note that this method is already implemented in the macOS app because that app does not automatically transition to the details view on user interaction. Therefore, this behavior was already set up so that we could handle user interaction appropriately.

Continuing An Activity With Handoff

At this point, the part of the app that publishes an activity is complete and if user activities are continued, the UI will transition to the details view. The user activities that are ready for Handoff will be automatically advertised to near-by devices. Next, we will set up the apps to continue an activity by handling events that are passed to the delegate.

In the iOS app’s AppDelegate we need to add two methods. The first tells iOS that it, not the app, should handle notifying the user about an activity, if necessary. For example, if the data is taking too long to transfer from one device to the next. The second handles the user activity once the data has been received.

The final part of the iOS app is to handle failures. Naturally, we want to gracefully notify the user that the activity is not able to be continued so that they are not waiting unnecessarily. But, if we do not implement this method, iOS will notify the user for us. Add the following to AppDelegate.

Switching back to the macOS app, we need to implement the AppDelegate methods. Again, there are three methods to be implemented. Similar to the iOS app, the first method will tell the OS to handle notifying the user of activity that should be reported. The second method handles continuing an activity. Finally, the third method handles gracefully notifying the user when something goes wrong.

That is it! runt he app on your iPhone, iPad and/or Mac and enjoy Handoff! The completed project can be found on GitHub.

Debugging Handoff

There are a couple things that might be helpful to know if this doesn’t work for you. First, as mentioned before, the macOS app must be signed. Second, Handoff makes sure the devices are close by each other so keep the devices close. There are also a few requirements for your device’s set up. Both Bluetooth and Wi-Fi must be turned on. If the devices are connected to the same Wi-Fi network that is even better, although it is not technically required. Each device also has to have Handoff turned on. On both iOS and macOS devices, this setting is in Settings under “General”.

]]>http://beyersapps.com/implementing-handoff-ios-macos/feed/0168NSAttributedString Enhancements in iOS 11http://beyersapps.com/nsattributedstring-enhancements-ios-11/
http://beyersapps.com/nsattributedstring-enhancements-ios-11/#respondThu, 03 Aug 2017 13:25:48 +0000http://beyersapps.com/?p=209Attributed strings can be a pain to configure the way you want them. The attributes dictionary has always been a dictionary comprised of String-Any pairs. There have been some nice enhancments in iOS 11, though.

]]>An NSAttributedString can be a pain to configure in the way that you want. The attributes dictionary has always been a dictionary comprised of String–Any pairs. There have been some nice enhancments in iOS 11, though.

In iOS 10, the keys, which are defined in the NSAttributedString, looked something like NSForegroundColorAttributeName. The keys are simply constants for a String. It could be difficult to find the one you want though, because auto-complete did not work great unless you knew what you were looking for. If the names looked more like NSAttribuedNameForegroundColor, code completion would have worked much better.

Now, in iOS 11, the NSAttributedString changes have gone even further to become easy to work with. The attributes dictionary has been changed to NSAttributedStringKey–Any pairs. NSAttributedStringKey is a struct and all of the keys that you can work with in an NSAttributedString have static properties on that struct. So, when trying to find a attribute, we can now get code completion help very easily.

NSAttributedString Code Comparison

The code for building an attributes dictionary does not look very different, but for a quick comparison, here is an attributes dictionary created in Xcode 8 for iOS 10.

]]>http://beyersapps.com/nsattributedstring-enhancements-ios-11/feed/0209Implementing Drag and Drop in iOS 11 Part 2http://beyersapps.com/implementing-drag-drop-ios-11-part-2/
http://beyersapps.com/implementing-drag-drop-ios-11-part-2/#respondFri, 28 Jul 2017 13:19:53 +0000http://beyersapps.com/?p=179At WWDC Apple announced that iOS 11 will now support Drag and Drop. This works not only within an app, but also from one app to another. Dragging between apps is only supported on iPads. This tutorial will walk through how to implement exporting data via Drag and Drop.

For this demo, we will start with a basic contact list app that can interact with the contacts app in iOS. A starter project can be found here.

Drag and Drop – Source App

Just like the destination app that we worked on previously, Apple has provided built in support for a UITextView, UITableView and UICollectionView from the source side. As we consider implementing Drag and Drop as a source app, we will implement this feature both from the UITableView and from the UIImageView that we used before. When we are done this, we will have an app that can move contacts back and forth with the Contacts app and images back and forth with the Photos app.

Drag and Drop – UITableView

Returning to the MasterViewController‘s table view, we will start implementing the ability to start a Drag and Drop activity. The first thing that we need to do, much like we did when making the UITableView act as the destination, is to set a dragDelegate. In the viewDidLoad() method, add the following.

tableView.dragDelegate = self

Next, we need to make MasterViewController conform to the UITableViewDragDelegate protocol. Do this by creating another extension in the MasterViewController.swift file.

extension MasterViewController: UITableViewDragDelegate {
}

There is just one method that needs to be implemented in this extension, as seen below. The purpose of this method is to create one or more UIDragItem objects and return them in an array. Each UIDragItem should be representative of one piece of data that is available through this drag action. For example, when I drag a contact, I could choose to make a CNContact and a UIImage available so that an app that accepts either of those items can accept my Drag and Drop action.

Our implementation will first, determine which contact is represented by the UITableViewCell that is being dragged. Next, it will create a CNContact, which is the object type that can be saved in the Contacts app. Continuing, we will serialize that contact into a Data object. Then, we need to create an NSItemProvider to provide the data. The NSItemProvider is the object that determines which type of data we are allowing to be dragged. When the NSItemProvider‘s completion block is called, it will return the Data object that was previously created. Finally, we create and return an Array of one UIDragItem which holds the NSItemProvider.

Notice that we are calling the method cnContact() on an instance of Contact. This is a helper method that has been created in the Contact class to create a CNContact object. Now, we can run the app. If we open the Contacts app next to our app, we can Drag and Drop a row from the UITableView over to the Contacts app and a new contact card will be created with the data that we have provided.

Drag and Drop – UIImageView

Our final task in this tutorial is to implement Drag and Drop as the source app using a UIView that does not have built in support for Drag and Drop as the UITableView does. For this task we will be focussing, once again, on DetailViewController. When we finish, we will be able to Drag and Drop the UIImage of a contact into the Photos app or the Contacts app to set an image on an existing contact.

The first step is to create a UIDragInteraction for the UIImageView much like we did with the drop interaction. Open DetailViewController and add the following to the bottom of viewDidLoad().

Once again, there is a protocol that we must conform to for the purpose of becoming a delegate for this drag interaction. There is only one method to implement in this delegate and by this point it is probably going to start looking very familiar. In this method we start off by checking to see if the UIImageView contains a UIImage. If it does not, we have nothing to allow the user to drag so we return an empty Array. This will disable dragging. However, if there is a UIImage, we want to allow that UIImage to be dragged. So, we will create an NSItemProvider and pass our image in the initializer. Second, we will create a UIDragItem and pass our NSItemProvider into the initializer. Next, we assign a localObject to the UIDragItem. The localObject is only visible to our app and is not required. We could remove this line but it may become helpful if the app is enhanced to allow a UIImage to be dropped elsewhere. Finally, we return an Array with our instance of UIDragItem.

That is all. That quickly we have prepared our UIImageView to support Drag and Drop. Launch the app and open Photos, Contacts, or any other app that will accept a UIImage and use Drag and Drop to move the image.

There are still two odd behaviors when using our app as the Drag and Drop source. First, if you drag a UITableViewCell, you will be allowed to drop the cell on the same UITableView from which the drag started. The result of this will be a second copy of the same contact. Similarly, if we start a Drag and Drop action from the UIImageView, the same UIImageView recognizes this and will allow us to drop the UIImage back where it started. This is not as big of an issue, because the image will be set to a new copy of the same image with which it started. However, it does require the device to do extra work that is completely unnecessary.

To view the final project, including the fixes for the two issues mentioned above, check out the final project.

]]>http://beyersapps.com/implementing-drag-drop-ios-11-part-2/feed/0179Implementing Drag and Drop in iOS 11 Part 1http://beyersapps.com/implementing-drag-drop-ios-11/
http://beyersapps.com/implementing-drag-drop-ios-11/#respondFri, 28 Jul 2017 13:19:18 +0000http://beyersapps.com/?p=174At WWDC Apple announced that iOS 11 will now support Drag and Drop. This works not only within an app, but also from one app to another. Dragging between apps is only supported on iPads. This tutorial will walk through how to implement receiving data from Drag and Drop.

]]>At WWDC Apple announced that iOS 11 will now support Drag and Drop. This works not only within an app, but also from one app to another. Dragging between apps is only supported on iPads. Drag and Drop between apps works if the iPad is in split view mode, with both the source and destination apps open side by side, or even if the destination app is completely closed.

As the iPad becomes more of a “Pro” device and increases in power, it is going to become a desktop replacement for more and more people. And, this feature will be an extremely useful. Hopefully, your app excels at it’s main goal. However, most users do not want content to be available to only one app. If your app is a photo editing app then you will want your users to be able to take that photo to another app to have it printed or share it with friends and family online.

Even if your business is a restaurant, you can make use of Drag and Drop. Think about the user who wants to keep receipts of everything purchased. Your app is awesome and allows orders to be placed online. But then the user needs to have that receipt emailed to them so that it can be uploaded to the note taking app that they love. Your user would be thrilled if they could open the note taking app right next to your app and when they Drag and Drop, your app generates a PDF file that can be dropped right into that notes app. This interaction is easier than the previous method and you have a happy customer.

Getting Started

In this tutorial we are going to learn how to implement Drag and Drop as the destination app. This means that we will accept data coming from other apps. However, we will not be able to drag data out of our app to another app just yet. Watch for part 2 of this tutorial to learn how to handle dragging. Now, let’s dive in to see how we can implement Drag and Drop as the destination app.

For this demo, we will start with a basic contact list app that can interact with the contacts app in iOS. A starter project can be found here.

Drag and Drop – Destination App

As usual, Apple has tried to help developers where possible. UITextView, UITableView and UICollectionView all have built in support for Drag and Drop. So, we will learn how to implement Drag and Drop both to a UITableView and to a UIImageView, which does not have built in support.

Drag and Drop – UITableView

To enable the drop action from a Drag and Drop activity in a UITableView, we need to set the UITableView‘s dropDelegate property. In MasterViewController‘s viewDidLoad() add the following line of code at the bottom.

tableView.dropDelegate = self

Initially, you will see an error on this line of code because MasterViewController does not conform to the UITableViewDropDelegate protocol. So, let’s fix that next. At the bottom of MasterViewController.swift, create an extension of MasterViewController.

extension MasterViewController: UITableViewDropDelegate {
}

There are three methods that must be implemented. Next, we will go through each one. The first method will give us, as the consumer, the option to ignore a Drag and Drop action if it is representative of data that we can’t use. For example, if we want to allow text only, we can check the UIDropSession to see if it contains text. Additionally, we could ignore data based on the state of our app or any other parameters we choose. For our app, we will accept any contact from the device’s contact list, but we will only allow one item to be dropped.

This will show errors because kUTTypeVCard. To fix this, we need to import MobileCoreServices. Later, we will also need to import the contacts framework so we will import both of these frameworks now. At the top of the file add the following import statements.

import Contacts
import MobileCoreServices

Next, we want to tell the system if and how we want to accept data. We have four options, which are called UIDropOperations.

We can choose to move the data. In order perform a move, the UIDropSession must have it’s allowsMoveOperation set to true. Otherwise, the move will fail and this will be treated as canceling the Drag and Drop activity. This option is only valid if you are supporting Drag and Drop within an application.

We can choose to copy the data. This is most likely the option that we want to choose if data is coming from another app. This option can also be used with Drag and Drop within the same application.

We can choose to cancel the activity. This option will be used when we do not want to accept the data. This might happen if we returned true in the previous step, but, because of the context of the app, we are not able to accept the data right now.

We can decide to forbid the data from being handled. This option would be used when, under normal circumstances, we want to accept the data. However, because of some special situation, we are not able to.

We also need to tell the system how the drop will take place if we are accepting the drop content. This is done through a UIDropIntent. Again, there are four options.

The drop intent can be unspecified. This would indicate that the location in which the data will be placed is not known. It is recommended to make some kind of visual change, manually, to make it clear to the user that the drop is being accepted until the data can be positioned correctly.

We can specify that the data will be inserted at the index path
(insertAtDestinationIndexPath) over which the user is hovering. If this is returned, the table view will adjust data to leave a space to make this obvious to the user.

We can specify that the data will be inserted into the index path (insertIntoDestinationIndexPath) over which the user is hovering. This would be used if the row is a container for multiple pieces of data and the dropped data should be inserted intot he same container. In this case, the UITableViewCell will be highlighted so that the user gets visual indication of what is happening.

The drop intent can be automatic. This would mean that the system will determine if this should be inserted at an index path or inserted into an index path. The decision will be made based on the precise location of the drop activity. Also, this option should only be used if the UITableView supports both insertAtDestinationIndexPath and insertIntoDestinationIndexPath.

In order to tell the system which combination of these options is applicable, we must implement the next delegate method. In this delegate method we will return a new UITableViewDropProposal which includes both a UIDropOperation and a UIDropIntent. For our purposes, we will always use the .copy operation and the .insertAtDestinationIndexPath intent.

Finally, when the user lifts their finger, thereby committing to the drop activity, we must handle the drop and update our data according to the data given to us. When working in the method that handles the drop activity, we want to return as quickly as possible. To aid us in this goal, the methods that help us get the data are asynchronous.

Next, we will complete the drop activity. During that process, we will copy the data and update our UI as needed.

Breaking this down step by step we can see exactly what is happening here.

We need to determine the index path at which the user is dropping the content. If the index path can’t be determined, we will set the index path to be the last row of the table

Loop through each of the UIDragItems given to us.

For each item in the loop, we are going to load the CNContact that it represents asynchronously. To accomplish that, we are going to call loadDataRepresentation(forTypeIdentifier:completionHandler:) and pass in kUTTypeVCard as the type of data we want to handle.

Once our completion handler is called, we verify that a non-nil data object was returned.

Making use of CNContactVCardSerialization we parse the data into CNContact objects.

We could have multiple CNContacts at this point. We will loop through each one.

We create our custom Contact object using a convenience initializer which accepts the CNContact object.

Using the index path that we determined earlier, we insert the new Contatct into our array of Contacts.

We need to update the UITableView so we call insertRows(at:with:) on tableView so that it displays the new data. Be sure that this is done on the main thread.

We are now set up to accept Drag and Drop from the contacts app or any other app that creates CNContact object and allows the user to drag them around. For further exploration, you can try removing the requirement that only one contact can be dropped into our list at a time.

Drag and Drop – UIImageView

In this section, we want to allow the user to update a contact’s picture by dragging in an image from another app. For example, the Photos app. Although, any app that allows the user to drag and drop and image will work. For this, we will mostly be working in DetailViewController.

The first thing that needs to be done when working with a view that does not have Drag and Drop built in, like the UITableView does, is to create a drop interaction. This is done using the UIDropInteraction class and adding an instance of it to the view that should support the drop interaction. Also, the view that is going to accept the Drag and Drop action must have user interaction enabled. Add the following code in viewDidLoad().

Obviously, DetailViewController does not implement UIDropInteractionDelegate so there is an error. We will fix this by adding an extension to DetailViewController with the required protocol declaration.

extension DetailViewController: UIDropInteractionDelegate {
}

This implementation is going to require us to implement three methods from the delegate protocol. These three methods are going to look very familiar after going through the implementation of UITableView. However, we will still step through these methods one at a time.

The first method is an opportunity for the app to decide if it wants to handle the Drag and Drop activity. This activity can be canceled based on the state of your app, the content type that is available or any other logic that you wish. For our app, we will make sure the data is a UIImage and there is only one.

Note that we are using canLoadObjects(ofClass:) to determine if the object is a UIImage as apposed to hasItemsConforming(toTypeIdentifiers:), which was used in the UITableView implementation. Either of these methods can be used when we are checking for an UIImage. However, when we were working with CNContact objects, we could not use canLoadObjects(ofClass:) because CNContact does not conform to NSItemProviderReading.Type. So, some types of data give us some flexibility while others limit our options. You should be comfortable handling data both ways.

Next, we need to provide the system with a UIDropProposal. We will use this, as we did before, to verify that the drop can happen. First, we will check to see if the user is holding the Drag and Drop item over our UIImageView view. Then, we will determine if the location is over the UIImageView, in which case we will use .copy as the UIDropOperation. Otherwise, we will use .cancel.

The last step is to accept the data and update the data model and UI based on the data that comes in. We start this process by calling an asynchronous method that will load the UIImages for us. Remember, we want to exit out of this method as quickly as possible so that the UI looks responsive. Second, when we get our asynchronous callback, we will save off the UIImage in the model and display it on the screen.

At this point we can build and run the app. After navigating to the contact details screen in this app, open an app like Photos and use any method of Drag and Drop to move an image from that app to our contact app. When you drag an image over the UIImageView on the details screen you will see a green circle with a plus icon indicating that the image can be copied there.

It is important to note that the image change is only local to the details screen and if you look at the list of contacts, the original image will still be displayed. Since updating the image everywhere in the app is not related to drag and drop, it will be a task for you to implement on your own if you choose to do so.

You can find the complete source code for implementing Drag and Drop as a destination app on GitHub. You can also continue on to part two of this tutorial.

]]>http://beyersapps.com/implementing-drag-drop-ios-11/feed/0174Large Titles For Navigation Bars In iOS 11http://beyersapps.com/large-titles-navigation-bars-ios-11/
http://beyersapps.com/large-titles-navigation-bars-ios-11/#respondWed, 26 Jul 2017 18:24:19 +0000http://beyersapps.com/?p=198If you have played with iOS 11 either on a simulator or on a real device, you have probably noticed that most of the first party apps, such as Mail and Settings, use large titles in the navigation bar. We are going to look at how to implement the same navigation bar behavior in our apps.

]]>If you have played with iOS 11 either on a simulator or on a real device, you have probably noticed that most of the first party apps, such as Mail and Settings, use large titles in the navigation bar. We are going to look at how to implement the same navigation bar behavior in our apps. Unsurprisingly, Apple has made this very easy for us.

But first, if you have not seen this look yet, here are screen shots of the settings app.

The first screen shot shows how the app looks when it is first launched. It is probably not too difficult to recognize the change here. The navigation bar appears much larger than it had in previous versions of iOS and the title of this screen is large and to the left of the screen, instead of centered at the top of the screen as it was before.

The second screen shot shows the look of the app if you pull down on the screen. The screen looks mostly the same except the search bar is now also visible.

The third screen shot shows the look of the app once the user begins scrolling. The search bar is hidden, the navigation bar shrinks back down to the size to which we are accustomed and the title is back in the center of the navigation bar.

Guidelines

Before we dive into how to implement the large titles, it is important to think about when to use large titles. Using large titles is a convenient way of making it very obvious to the user where he or she is in the app. However, it also takes a lot of extra screen real estate. Because of the extra space taken, we need to be sure that it is not over used.

A general rule of thumb is that large titles should be used on a UINavigationController‘s rootViewController only. After that, each screen would have the standard sized UINavigationBar. Although, there are exceptions to every rule. Apple’s own Mail app uses large titles on the first two screens. Once the user drills down into a specific email, though, the navigation bar shrinks down.

It is up to you to decide where it should be used in your app. Just remember that over use could negatively effect the user’s experience.

Large Titles

The first thing we are going to do is open MasterViewController and in viewDidLoad() add the following line of code at the bottom.

navigationController?.navigationBar.prefersLargeTitles = true

That is all you need. Build and run and you will see the UINavgiagionBars with large text. There is, of course, a lot more customization that can, and should, be done.

Customization

At this point, if you select an item from the list, the details screen will also have a large title. I think this is a bit of over-use so we want to remove this from the details view.

Limiting Large Title Usage

To remove large text from the details view, open DetailViewController and add the following line of code at the bottom of configureView().

navigationItem.largeTitleDisplayMode = .never

This will make the UINavigationBar display in the standard size, regardless of how it looked on the previous screen. There are two other options for largeTitleDisplayMode.

automatic – Keep the UINavigationBar in whichever state it was for the previous screen.

always – Show the UINavigationBar with large titles regardless of which state it was in for the previous screen.

There is one important note to make with regard to the largeTitleDisplayMode property. If the UINavigationController‘s UINavigationBar does not have the prefersLargeTitles property set to true, as we did in MasterViewController, then the largeTitleDisplayMode property will have no effect at all.

Searching and Refreshing

Searching and refreshing data inside of a UINavigationController are very common tasks. Many screens have a UISearchController and a UIRefreshControl that hide under the navigation bar. If iOS did not help us out, these are two controls that could get tricky to implement because of the UINavigationBar‘s changing size. However, iOS does help us and these two common controls are still very easy to use.

In MasterViewController we need to create a UISearchController. Add this property at the top of the class.

Run the app now and notice that as you pull down two things will happen. First, the search bar will appear. Second, as you continue to pull down, the refresh controller will appear above the large title. Obviously, with the code that we added here, we do not actually have searching or refreshing implemented. However, with the easy integration of large titles with searching and refreshing, there is nothing new about implementing those two features.

Another customized behavior that you may want with the search bar is to always keep it visible, even when scrolling. Again, this is very easy to do. Add the following line of code at the bottom of viewDidLoad.

navigationItem.hidesSearchBarWhenScrolling = false

Run the app now, and see that iOS, again, handles all of the animation as we scroll around while always keeping the search bar visible.

Customizing Large Title Text

We also have the ability to set attributes on the large title via the largeTitleTextAttributes property. Suppose, we want the large text to be blue and to use the Papyrus font with the size set to 30. To do so, simply add the following line of code at the bottom of viewDidLoad.

This is just like setting attributes on an NSAttributedString. Now, when we run the app we see our custom color and font for the large title.

Appearance API

The two new properties for the UINavigationBar that we have looked at are compatible with the appearance API. So, if we want all of our UINavigationBars to look the same way, we could remove the assignment of prefersLargeTitles and largeTitleTextAttributes from MasterViewController. We would then set them up wherever we are configuring our global appearance settings.

For example, you could add the following code into AppDelegate‘s application(_:didFinishLaunchingWithOptions:) just before the return statement.

There you have it. You can now implement the new, iOS 11 style large title navigation bars in your app. And, you can do it without much work.

The final project can be found on GitHub if you would like to take a look. As always, if you have any comments or questions I would love to hear from you in the comments below or on Twitter (@beyerss).

]]>http://beyersapps.com/large-titles-navigation-bars-ios-11/feed/0198Enabling Siri Integration With SiriKithttp://beyersapps.com/enabling-siri-integration-sirikit/
http://beyersapps.com/enabling-siri-integration-sirikit/#commentsWed, 26 Jul 2017 11:54:35 +0000http://beyersapps.com/?p=161SiriKit was created for developers in iOS 10. However, there were, and still are, a limited number of intent domains with which you can integrate an app. An intent domain is simply a category of app that can take advantage of Siri. With the release of iOS 11, a few new domains are being made available. This tutorial will walk through implementing SiriKit with the "Lists and Notes" domain, although, the core concepts can be applied to any domain.

]]>SiriKit was created for developers in iOS 10. However, there were, and still are, a limited number of intent domains with which you can integrate an app. An intent domain is simply a category of app that can take advantage of Siri. With the release of iOS 11, a few new domains are being made available.

Possibly the most intriguing intent domain for most developers is “Lists and Notes”. In the past, Siri has been available for messaging apps, VoIP calling apps and some others. While, having Siri available to these apps as a user is a great thing, most apps are not able to fit inside of those domains. Although, many more apps will be able to fit into the lists and notes domain.

Because of the lists and notes being suitable for many more apps, this tutorial is going to look into how to use SiriKit so that an app can take advantage of Siri. We will work on a simple note app. Even if your app is not a list or note taking app, most of what is discussed here is generic to SiriKit and not specific to the domain. So, the information here can easily be applied to other domains. To get started, download the starter project from GitHub.

Requesting Authorization For Siri

Siri, like many other iOS features, requires the user to grant permission before it will work. To that effect, we must first set up the app to request permission. Note, though, that this permission is only requested from the iOS app. If you have a watchOS app, it will automatically be granted or denied permission based on the iOS app’s permission.

First, we must declare that we are going to use Siri in the app’s Info.plist file. In Info.plist, add a new entry with the key NSSiriUsageDescription. The value for the key should be a string that describes what information is going to be shared with Siri. Enter “Notes will be shared with Siri.”

Next, we need to prompt the user for permission. Your app might have a setting to turn this on so that you can prompt the user only when the feature is enabled. For this app, we will request as soon as the app finished launching. In AppDelegate, add import Intents to the list of import statements. Then, in application(_: didFinishLaunchingWithOptions:) add the following code.

Enabling Siri

The first step to enabling Siri is to turn on the Siri capability. This will add an entitlement to your project. Open the project details and select the “Capabilities” tab. There, you can turn on Siri.

Creating A Siri Extension

Next, we need to create an app extension. This is where all interactions with Siri take place. Create a new target and select “Intents Extension” as the type.

Give it an appropriate name, such as “SiriNotes_SiriExtension”. By keeping the “Include UI Extension” checkbox selected, we will have an opportunity to customize the Siri interface if we so choose. If you look at the list of targets, you will now see three. They correspond to the iOS app, the intents extension, and the UI extension that is used for customization.

Specifying Intents For Siri

The extension’s Info.plist file lets SiriKit know which intents are supported. Next, we will be working in this file. Do not confuse this file with the Info.plist file that we were previously working on. This Info.plist file will be found under the folder that was created in Xcode for your new extension. The file should look something like this:

Take note of two keys: “IntentsSupported” and “IntentsRestrictedWhileLocked”. These are both arrays of strings. Each string should match a class name. In the “IntentsSupported” array, we need to list the intents that we are going to support. In the “IntentsRestrictedWhileLocked” array, we list the intents that we want the user to unlock the device before using. We would want that if there is a security concern with the data our app holds. For example, apps that support financial transactions would require the device to be unlocked before committing a transaction.

We need to remove the default intents that are listed and add the ones that we actually want to support. After removing the three supported intents under “IntentsSupported”, add the new supported intent: “INCreateNoteIntent”.

There are two important things to mention at this point. First, there are more intents that we could support with our notes app, “INSearchForNotebookItemsIntent” and “INAppendToNoteIntent”. We are choosing not to support these. Second, the order of the intents is, sometimes, important. If, when speaking to Siri, the user is not clear about exactly which action is desired, Siri will use the first intent in the list that might work.

Setting Up The Intent Handler For Siri

In the extension that was created, there is an IntentHandler class. This class is used by SiriKit to provide an object that can handle an intent. This object can be any type and could even be a different type of object based on the type of intent that needs to be handled. For example, I could have one class that only handles the “INSearchForNotebookItemsIntent” and a separate class that only handles “INCreateNoteIntent. If this is my setup, then I can inspect the intent to decide which class I should instantiate and return. However, since we are only supporting one intent, we will use the IntentHandler class to do all of the work instead of creating a new class.

IntentHandler currently has a lot of code for handling messaging intents, which we do not need. Remove all the code from this class except the handler(for:) method. Your class should look like this:

This will tell Siri that this instance of IntentHandler is going to be the handling object for any intents that are received. Next, we need to make sure our class conforms to INCreateNoteIntentHandling since that corresponds to the intent type that we handle.

Resolving Parameters From Siri

The first thing we need to do when handling an intent is to make sure the data given to the app is valid. To do this, we work with SiriKit by implementing the methods that start with “resolve”. For note creation there are three:

resolveTitle(forCreateNote:with:)

resolveContent(forCreateNote:with:)

resolveGroupName(forCreateNote:with:)

For each of these we need to inspect the intent and see if the data is valid. Once complete, we call the completion closure with the results. There are six options as the results.

Success – The intent has the information we need.

Not required – Our app does not need this data.

Needs disambiguation – There are multiple results that could be accurate but we do not know which one the user wants. So, more information is needed from the user.

Needs confirmation – We think that we know what the user wants but we need to confirm.

Value required – There is no value for this parameter but we need a value.

Unsupported – The value supplied represents something that our app is not able to handle.

Since our app is fairly simple, the only results we will need to use are Success and Not required. Lets start by implementing the three “resolve” methods. First, we will implement resolveTitle(forCreateNote:with:). Implement this method as follows.

Since this value is required, we are checking for the existence of the title. If it exists, we return success. Otherwise, we return needsValue. Take not that success also takes a string. This is because if we wanted to alter the title we could. For example, we could make sure that the title always starts with a capital letter.

Next, we will implement resolveContent(forCreateNote:with:). For this property, we will either return success or notRequired. Add the following method to IntentHandler.

Again, we check to see if the content exists. If it does, we return success. Unlike the title, however, this is not required. So, if the content is not valid, we will return notRequired so that Siri will continue anyway.

Continuing, we must implement resolveGroupName(forCreateNote:with:). We are not grouping our notes into categories, so the group name does not make sense for us. We need a way of telling Siri that we do not need a value for this but we can still proceed. For this, we will always return unsupported.

Confirming Details For Siri

After all of the parameters have been resolved. SiriKit will ask us to confirm that we are ready to handle the intent. This is our point to make sure any dependencies we have are prepared. For example, if we need access to the user’s location, we can make sure that is active and we have been granted permission. For this app, there is nothing more that we need so we are simply going to tell SiriKit that the intent is confirmed.

Handling The Intent From Siri

Finally, we get one more method called in our extension to let us know that all of the data is ready. That method is handle(createNote:completion:). In this method we will create a new note and call the completion handler with the result of our actions. We will go through this implementation one step at a time. First, take a look at the code below.

We get the context so that we can save to CoreData. Note that we must make changes to our DatabaseHelper so that our data is in an “App Group”. Otherwise, the extension will not see the same data as the app. This step is not covered here as it is not anything new.

Create a new Note and populate the properties based on the intent we are given.

Save the new Note.

Create an INCreateNoteIntentResponse to pass to the completion block. The completion block must be called. Since we have been successful in creating the new note, we will pass success in as the response code. We use nil for the user activity since our app is not configured to handle user activities.

Create an INNote to represent the Note that we created. The only part of this that we are interested in relaying back to the user is the title. So, the title is added from the intent, the contents are an empty array and everything else is nil. We could fill in more of these parameters to give the user more data but it is not necessary.

Call the completion block with the INCreateNoteIntentResponse that was created in step 4.

If there is an error along the way, we will respond by calling the completion block and telling Siri that we failed.

We have now implemented SiriKit to allow us to create a new note! Build and run the app, close it and trigger Siri to create a new app by saying something like “Create a new note called dogs in SiriNotes”. Then, close the Siri screen and launch the app. You should see your new note!. If you do not see your note at this point, kill the app and re-open.

Testing Siri Integration

In order to test, which can be done on a device or simulator, select the scheme for your intents extension and select run. You will be shown a prompt to select which app you would like to run. Select “Siri”.

If you do this, and issue a command to Siri, but your app is not recognized, it does not immediately mean that there is a problem. There can be up to a few minutes of delay between launching this and Siri recognizing your app. The same is true if you make changes to the extension’s Info.plist file. For example, you may add or remove an intent. In any of these situations, wait for a few minutes and try again. Apple does not give us any direction on the maximum amount of time that this could take.

Issues And Improvements

There is currently one bug and one very big improvement that could be made. First, I will address the bug. As I alluded to previously, there is a chance that you will not see the new note in the app immediately. This happens if the new note is created via Siri with the app running in the background. This is because the NSFetchedResultsController is not told about changes made to CoreData while the app is in the background. There are multiple ways to fix this issue but I will leave that up to you.

Second, if an NSUserActivity is given to the INCreateNoteIntentResponse that is sent to the completion handler in the method handle(createNote:completion:) then this process will be more powerful. This would enable the user to continue with the note immediately after creating it via Siri. To do this, you would also have to handle continuing from an NSUserActivity in the AppDelegate class.

Wrap Up

You have now seen how to fully handle intents in the notes domain with SiriKit. As mentioned previously, the implementation for other domains would be very similar. Since many of these methods are very specific to notes, it is clear that methods will change. However, the concepts remain the same. You will need to resolve each parameter, confirm that the intent can be handled and handle the intent.

]]>http://beyersapps.com/enabling-siri-integration-sirikit/feed/1161Handling Keyboard Shortcuts In iOShttp://beyersapps.com/handling-keyboard-shortcuts-in-ios-2/
http://beyersapps.com/handling-keyboard-shortcuts-in-ios-2/#commentsThu, 20 Jul 2017 16:56:06 +0000http://beyersapps.com/?p=152The debate as to whether or not the iPad can replace a computer is on going. We do not need to get into the discussion of who this might work for or what kind of work can be done with an iPad. The fact is that for some people this is already happening. Even if […]

]]>The debate as to whether or not the iPad can replace a computer is on going. We do not need to get into the discussion of who this might work for or what kind of work can be done with an iPad. The fact is that for some people this is already happening. Even if people are not using the iPad as a computer replacement, many people are at least using it as a light weight computer. It is a computer that has a pointing device (finger, Apple Pencil or stylus), a spacious screen and a keyboard, which allows us to use shortcuts.

Introduction

The keyboard is an enormously important piece of hardware. Leaving aside the iPad for a minute, when people have an application that they use heavily, they tend to squeak every bit of of efficiency from it. Keeping both hands on the keyboard and not moving one of the hands to a mouse or the screen is one great way to keep things efficient. Personally, I have chosen to use software that I know lacks certain features that are available from other apps simply because I already know exactly what to do with the app I am comfortable with and I know every shortcut key I need.

Returning to the iPad, the same idea applies. People want to use whatever apps are available as efficiently as possible. Especially if the app is used for business. As these devices become more powerful and people are doing more with them, we, as developers, need to make sure we are doing everything possible to give the user the best experience possible with the apps we create. As I looked through the apps on my iPad Pro, I was shocked by how few of them supported keyboard shortcuts, or at least discoverable keyboard shortcuts.

Implementing Keyboard Shortcuts

If you prefer video, I did a quick screencast on what it takes to set up keyboard shortcuts which can be found here. For a more in-depth and realistic look, though, we are going to re-visit a list app that I made while writing Enabling Siri Integration With SiriKit. Using this app as a starting point, we will implement keyboard shortcuts to create and delete notes. To get started you can download the code here.

Keyboard Shortcuts with a UISplitViewController

In a simple app, as you may have seen if you watched the screencast that accompanies this post, supporting keyboard shortcuts is very simple. However, in most apps there is something about the app’s architecture that causes complications. For this app, those complications come from the split view controller. We are not able to simply support keyboard shortcuts directly in the MasterViewController and DetailViewController because on an iPad, where these shortcuts are most likely to be used, these views can both be on screen at the same time. In this situation, the system doesn’t know which UIViewController‘s keyboard shortcuts to make available. Additionally, we may want shortcuts from both the master and the detail to be available at the same time. This is going to require us to subclass the UISplitViewController and add some logic there.

First, create a new class called SiriSplitViewController and make it a subclass of UISplitViewController. In this class, we must override the keyCommands property. keyCommands is an array of UIKeyCommand objects and that array tells the system what keyboard shortcuts are available. The keyCommands property is available to all UIResponder subclasses. It is a read-only property, though, so it must be overridden.

Notice that, unlike UIButtons, there is no target in a UIKeyCommand. That means that the Selector is called on the object that creates the UIKeyCommand. Since we do not really want SiriSplitViewController to be deciding which shortcuts are available or to be handling the keyboard shortcuts, we must create a way to communicate with the classes inside of our SiriSplitViewController instance that should be doing this work. We will do this by creating a protocol. Above SiriSplitViewController, create a protocol like this:

Now, when SiriSplitViewController is asked by the system for supported shortcutKeys, this request can be, in effect, passed on to the proper classes. Before going any further, we are going to create a couple of helper methods that are going to be used a few times in SiriSplitViewController. The first helper method is going to find a class that conforms to KeyCommandProvider, if one exists.

With these two helper methods in place, we can return to the keyCommands property. A UISplitViewController, of which SiriSplitViewController is a subclass, has up to two UIViewControllers that are accessible via the viewControllers property. We are going to use that property to get the commands that are supported on the master and detail side of the split view controller. Add the following code inside of keyCommands implementation:

Next, we need to merge these two arrays together to get one master list of UIKeyCommands that are supported. If only one of the arrays is non-nil, though, the master list will be comprised of that array only. Under the two lines of code that were just added, add the following:

As I mentioned earlier, when the UIKeyCommand is created there is no target property. So, if we return this list, the system will figure out that there must be a mistake and the list will be ignored. So, we need to create our own UIKeyCommand objects that mirror the keyboard shortcuts that the KeyCommandProvider wants to support. We will iterate through each of the UIKeyCommands that are supposed to be supported and create a related UIKeyCommand with the same input, modifierFlags and discoverabilityTitle but a new Selector.

As you can see, each of these new UIKeyCommands will call handleKeyCommand(_:) so that needs to be implemented. We will need to check both the master and the detail side of the SiriSplitViewController to find a KeyCommandProvider, then we will need to allow that KeyCommandProvider to handle the UIKeyCommand. It is possible, though, that the KeyCommandProvider that we find first, does not know how to handle the UIKeyCommand. So, we will also need a way for the KeyCommandProvider to report if it successfully handled the command. To start this flow, we will add a function to the KeyCommandProvider. Using this new function, we can pass the UIKeyCommand that is being used and the function will return a Bool indicating if it was handled or not. In the KeyCommandProvider add the following under the shortcutKeys property:

func handleShortcut(keyCommand: UIKeyCommand) -> Bool

Next, since we potentially need to run the same exact code on both the master side and the detail side of the SiriSplitViewController, we are going to make another helper method. This helper method will take the UIKeyCommand and a UIViewController. Using the UIViewController it will find an available KeyCommandProvider. If it is able to find a KeyCommandProvider, it will call handleShortcut(keyCommand:) and return the result.

With the helper method in place, we can now implement handleKeyCommand(_:), the function that we created a Selector for when we created the UIKeyCommands. In this function, we will first call handleKeyCommand(_:withBaseController:) on the last UIViewController that is in SiriSplitViewController‘s viewControllers array. If that call returns false, meaning the the UIKeyCommand was not handled, then we will call the same helper method on the first UIViewController in SiriSplitViewController‘s viewControllers array.

Updating the Storyboard

Now that we have the new SiriSplitViewController fully implemented, we need to make use of it in our app. Open main.storyboard and locate “Split View Controller Scene”. Select the UISplitViewController inside of this screen. Open the identity inspector on the right and change the “class” to SiriSplitViewController.

Keyboard Shortcuts with a KeyCommandProvider

Since our app is simple, we only need to implement KeyCommandProvider in two places. First, we will modify MasterViewController. At the bottom of this class, create an extension that declares an implementation of KeyCommandProvider.

extension MasterViewController: KeyCommandProvider {
}

Next, we need to implement the shortcutKeys property so that we can provide the keyboard shortcuts that we are going to support. In this class, we will only support one keyboard shortcut, command+n. Additionally, we want to have the discoverabilityTitle set to “New Note”. The discoverabilityTitle is used so that the system can “advertise” your keyboard shortcuts. We will talk about this more later. For now, add the following inside of the new extension:

Remember that because of our implementation in SiriSplitViewController, the action specified in the constructor does not matter at all. However, since all of our UIKeyCommands will be passed through the handleShortcut(keyCommand:) function, we will use that here.

Continuing, we need to handle the handleShortcut(keyCommand:) function call. We need to check that the input is “n” and the modifierFlags is .command. If both of these are true, then the user pressed command+n, which is what we want to handle. In this case, we will call insertNewObject(_:), which is the same method that gets called when the user taps the “+” button on the top right of the screen. Also, in this case we need to return true so that SiriSplitViewController does not try to pass this keyboard shortcut to another KeyCommandProvider as well. If the user did not press command+n, we will return false.

Now, we need to take the same exact steps in the DetailViewController class. Open DetailViewController.swift and at the bottom of the file, create an extension that implements KeyCommandProvider.

extension DetailViewController: KeyCommandProvider {
}

Then, implement shortcutKeys. In this class we will supply two ways to delete a note. The first, is by pressing command+d and the second is by pressing the delete key. Note that unicode characters can be used and “\u{8}” is the unicode character for the delete key.

One Last Step

We are now done with all of the code we need for handling shortcuts. However, if you build and run now you will notice that the delete shortcuts do not actually do anything. This is because in the previous version of the app, we did not worry about saving updates to a note. So, when the segue was performed after selecting a note in MasterViewController, the managed object context was not passed in. Therefore, the managed object context is currently nil when we try to delete the note and nothing is happening.

You can fix this by opening MasterViewController and adding the following line under line 54:

Now, you can build and run. All of the supported keyboard shortcuts will work like a charm.

Discovering Keyboard Shortcuts

I mentioned that I was surprised by the lack of apps supporting discoverable keyboard shortcuts. But, I did not mention how to find keyboard shortcuts that are discoverable. To do this, simply hold command key down while any app is open. If you do this with our app you will now see:

This interface is given to us for free on the iPad. It does not show on the iPhone at all, although the shortcuts are still supported if the user attaches a keyboard to an iPhone. The fact that this display is given to us by iOS is great. It means that anyone who is familiar with the keyboard can quickly find out what keyboard shortcuts your app supports.

Making Discoverable Keyboard Shortcuts

The way that we created all of our UIKeyCommand objects makes them discoverable by default. If you do not want your UIKeyCommand object to be discoverable, for some reason, simply remove the discoverabilityTitle from the end of the constructor. Then, when the user holds down the command key, that keyboard shortcut will not get displayed.

I should note that as of iOS 11 beta 2, UIKeyCommands that do not have a discoverabilityTitle are still getting displayed. This is a bug, however, and will most likely be resolved before the release of iOS 11. You can run on iOS 10 or earlier to verify the behavior you want.

Designated Keyboard Shortcuts

There are some standard keyboard shortcuts that most people are familiar with. There are also some that are standard that not as many people know but are available system wide. Some exaples are:

command+tab – This will let you switch apps without touching the screen

command+c – Copy

command+p – Paste

None of these can be overridden. If you try to use one of these keyboard shortcuts, the event will never be passed through to your app.

Conclusion

Now you know how to support keyboard shortcuts in your apps. You can find the completed implementation for this project on GitHub. This is an easy feature to implement and your users will love it. If you have any questions feel free to let me know in the comments below.

]]>http://beyersapps.com/handling-keyboard-shortcuts-in-ios-2/feed/1152Bible Binder On Google Playhttp://beyersapps.com/bible-binder-on-google-play/
Tue, 18 Jul 2017 18:11:35 +0000http://beyersapps.com/?p=117After a very long wait, Bible Binder is finally available for Android™. At this point it does not have many of the features that are available in iOS. However, the missing features will be added to the app over time until both platforms support the same features. We hope you enjoy using Bible Binder on […]

]]>After a very long wait, Bible Binder is finally available for Android. At this point it does not have many of the features that are available in iOS. However, the missing features will be added to the app over time until both platforms support the same features.

]]>117New App: Fraction Quizzerhttp://beyersapps.com/new-app-fraction-quizzer/
Wed, 31 Aug 2016 19:45:30 +0000http://beyersapps.com/?p=107For the past 6 weeks we have been lucky enough to partner with a group of math teachers/researchers from a University in North Eastern U.S. These researchers are trying to find new and effective ways of teaching young students who are falling behind in math. As part of their strategy, they wanted to create an […]

]]>For the past 6 weeks we have been lucky enough to partner with a group of math teachers/researchers from a University in North Eastern U.S. These researchers are trying to find new and effective ways of teaching young students who are falling behind in math. As part of their strategy, they wanted to create an app that quizzes the students about fractions using proportions.
Today we are excited to release Fraction Quizzer. While it is an app aimed at being used by teachers to guide students through a short quiz, it can also be used by individuals who want to quiz themselves. The app has a “Student Mode” for this purpose.

We are very thankful for the opportunity to give back to the community and help children gain a solid understanding of math. It is our hope to do more work of this kind in the near future.

]]>107Swift Initializershttp://beyersapps.com/swift-initializers/
http://beyersapps.com/swift-initializers/#respondFri, 22 Jan 2016 13:26:49 +0000http://beyersapps.com/?p=71Swift is full of exciting features and it certainly takes a long time of in-depth use to find them all and to build a true appreciation for them. Recently, I have been working with initializers and have found some new tricks, provided by Swift, that have simplified my work. I’m going to focus on Swift […]

]]>Swift is full of exciting features and it certainly takes a long time of in-depth use to find them all and to build a true appreciation for them. Recently, I have been working with initializers and have found some new tricks, provided by Swift, that have simplified my work. I’m going to focus on Swift 2.1 and share some of the things that have helped me with initializers.

Initializers are one of the most basic functions of any programming language. After all, every object or struct that you use must be initialized! However, it is very easy to just use the generic

init()

method that every

NSObject

inherits. With Swift variables that are non-optional this leads to people frequently making one of two options. The first, initializing the non-optional variables with defaults, which does not work in every situation. For example, take the classic programming example of a car object. A make and model of the car is always required so they might be defaulted to an empty

String

. That does not really make sense, though, because that will never be valid. Second, many people mark the variable as a force unwrapped variable using the exclamation point (also known as “crash operator”). This would look something like

var carModel: String!

. This would work but it is error prone. If the car is accidentally created without ever assigning a model then the app will crash as soon as the

carModel

property is read. Here are a few ways to address problems like these.

I have prepared a sample app, Garage Keeper, to help demonstrate the points made in this post. The app can be found on GitHub. Garage Keeper will allow you to add all of the garages you have using a name (i.e. “Home Garage” or “Vacation House”) along with a capacity so that you know how many cars fit in that garage. The master branch is the starting point for this post.

Convenience Initializers

The first attempt that many people make at fixing a problem like this is to use convenience initializers. In the master branch of Garage Keeper if you look at the Garage class, this is exactly what was done. A convenience initializer was create that takes a name and capacity as the input parameters. It looks like this:

convenience init(name: String, capacity: Int)

This works, but still leaves us with a problem. Since our new initializer is only a convenience initializer anyone can still create a

Garage

object by calling

Garage()

so then we must override the

init()

method and give default values for these properties. In the case of a garage, this option might not be too bad. the name doesn’t really matter so we can default the name to “default” and we can set the capacity to 1 by default so the garage can always hold at least 1 car. I still think this can be better.

Designated Initializers

By simply removing the

convenience

keyword from the convenience initializer we make it a designated initializer. That has some implications, however. In our convenience initializer for the

Garage

class we were first calling a designated initializer,

self.init()

, then assigning the non-optional values. In a designated initializer, however, the non-optional values must be set first. We must also call a designated initializer from the super class in this instance. Now, we can completely remove the plain

There are a few things going on here so I will explain. First, before we are allowed to return nil in an initializer, we must fully initialize all the variables. So, we start with giving a default name and capacity and call the super class’s designated initializer. This will fully initialize our object. These defaults are not quite the same as when we used the convenience initializer because they are guaranteed to be changed if all validation passes. Next, we make sure that the

name

and

capacity

that were passed in are not nil. If they are nil, initialization fails. The reason that we changed the inputs to be optional is to save other developers who want to initialize this class a little bit of time. Now, they will not even have to make sure they have a non-nil value before attempting to initialize a

Garage

. The third step is a check to make sure name is not empty. Previously, an empty

String

could have been passed in and that would have been acceptable. In fact, even with the code validation that we had in the view controller, this would happen if the user did not enter a name for the garage. Finally, after passing all validation, we store the passed in name and capacity in our object. At this point a valid

Garage

object will be created and returned.

There is still one last problem that can be fixed in this method. A garage is not a garage if it is not able to hold any cars. So, any numbers less than 1 for the capacity should be rejected. We can quickly fix this but adding the following code to our failable initializer:

method was changed to do no validation. Instead, it simply passes the values from the

UITextField

s to the

Garage

initializer. If nil comes back then an error alert is displayed and if a valid

Garage

is returned then it finishes processing successfully.

Required Initializers

Required Initializers are a special type of designated initializer. They work the same as designated initializers except for one important distinction. If you subclass a class with a required initializer you must override that initializer. This can be helpful when certain pieces of data are required to be passed in for every subclass. To demonstrate this, we added

var wheels: Int = 4

to the

Car

object in Garage Keeper. Most often, we are dealing with normal cars so four wheels seems like a reasonable starting point. But what if I purchased a Polaris Slingshot? Thats a 3 wheel car. Of course, I could manually change that number but for this example I’m going to create a subclass of

Car

and name it

UniqueCar

. Now, I know that a Polaris Slingshot is a 3 wheeled car so I want to build that in. In this case i can override the required initializer in my new class. At that point I can check for the make and model that I want and if they match, I will automatically update the wheel count to 3. To do this I put the initializer in