Category: iOS

UndoManager Introduction Swift

I recently needed to add undo support for a Mac app after not dealing with undo for several years. I noticed some subtle changes so I’m sharing what I learned here. I have not worked with the undo manager in an iOS app, but most of the material in this article should apply to iOS apps as well as Mac apps.

Accessing the Undo Manager

The first step to adding undo support in your app is to access the undo manager. Document-based Mac and iOS apps have an undo manager for each document. Use the undoManager property to access the document’s undo manager.

Core Data apps also have an undo manager. The managed object context has an undo manager, which you access with the undoManager property.

Registering an Undo Operation

When someone performs an action in your app that can be undone, register an undo operation for that action. Registering an undo operation adds the action to the undo stack so the action can be undone.

The easiest way to register an undo operation is to call the registerUndo method. There are two registerUndo methods: one that takes a closure (unnamed function) as an argument and one that takes a selector as an argument. The method that takes a closure is the better option for Swift developers. Swift’s closure syntax is less painful to work with than its syntax for working with Objective-C selectors.

When calling registerUndo, supply a target for the undo operation and a closure handler that calls the function to perform the undo operation.

Suppose you have a note taking app. In a note taking app people can remove notes. An important undo feature is to undo note removal. If someone accidentally removes a note, they would like to get it back.

To support undoing note removal in the app, you would write two functions: one function to remove a note and a second function to restore a removed note.

In the function to remove a note, call registerUndo. Call the function that restores the note in the handler closure.

In the removeNote function, the code calls registerUndo. The closure is the block that starts with the brace before targetSelf. The name targetSelf is one I created. You can use whatever name you want to indicate the variable is the target. The closure calls the restoreNote function to restore the removed note. When someone removes a note and undoes it, the app restores the note by calling restoreNote.

The last line of code in the function sets an action name for the Undo menu item in the Edit menu. The code sets the menu item to Undo Remove Note.

Now let’s look at the function to restore the removed note.

Like the removeNote function, restoreNote calls registerUndo. The main difference is the handler closure. The handler calls removeNote because removing the note is the undo operation in this case. If someone undoes removing a note and chooses Redo, the code calls removeNote to redo removing the note.

You might be wondering why the action name is Remove Note instead of Restore Note. When you undo an action in a Mac app, a Redo menu item appears in the Edit menu. In this note taking app example, when you redo, you are redoing removing a note. A menu title Redo Remove Note makes more sense than Redo Restore Note.

Creating a Draggable iOS View

Some iOS apps have items like cards or sticky notes that you can drag. Making an iOS view draggable is less difficult than you think. Use a pan gesture recognizer, and you can drag a view around with your finger.

The Steps

Take the following steps to create a draggable view in iOS:

  • Add a view to the view controller scene in your storyboard.
  • Drag a pan gesture recognizer to the view so the view becomes draggable.
  • Create an action (IBAction) for the pan gesture recognizer by making a connection from the storyboard to the view controller’s source code file. Set the type for the sender to UIPanGestureRecognizer.

I have a demo project for draggable views on GitHub.

The Code

To make the view move around when you drag it, you must implement the action for the pan gesture recognizer. The following code shows an example of panning a view:

By setting the type of the IBAction to UIPanGestureRecognizer, you can access the view being dragged through the pan gesture recognizer’s view property.

The first line of code determines how far the view dragged. Using an if-let statement avoids force unwrapping optionals when accessing the dragged view.

The first line of code inside the if-let block moves the center of the view by the amount of the drag. The second line of code clears the translation for future drags.

Learning iOS Development

A lot of people are interested in developing iOS apps but don’t know where to get started. Getting started questions come up frequently on Reddit’s iOS programming forum and the same answers pop up over and over again so I’m compiling them here.

Apple’s iBooks on iOS Development

Apple provides two free introductory books on developing iOS apps on iBooks:

If you search iBooks for Apple Education, you’ll also find books on learning to code as part of Apple’s Everyone Can Code series. If you’re new to programming as well as iOS development, Apple’s books are a good way to start learning.

Stanford’s CS193P Course

If you have experience developing software on other platforms and want to quickly get up to speed developing iOS apps, Stanford has their CS193P course on iOS development available for free on iTunes U. The course is only available on iOS devices. If you want to watch the videos on your Mac, many of the lectures are available on YouTube.

Keep in mind that this is a college course at Stanford that students take in one quarter. This course moves at a much faster pace than the Apple books.

UPDATE

The Stanford course has been updated to use SwiftUI.

Hacking with Swift

Hacking with Swift is a website that teaches Swift and iOS development. They have a free book that teaches the Swift language by building 39 projects. The site also has free 100 day iOS development courses, one that uses UIKit and one that uses SwiftUI.

When you finish the free book, Hacking with Swift has books you can buy to delve deeper into Swift and iOS development.

Angela Yu’s Udemy Course

If you prefer video learning and want something that moves at a slower pace than the Stanford course, check out Angela Yu’s iOS App Development Bootcamp. This course is not free, but it gets recommended a lot when people ask how to get started with iOS development.

Ray Wenderlich

When you go through one or more of the resources I mentioned earlier in this article, you’ll be ready for raywenderlich.com. raywenderlich.com has a vast collection of text and video tutorials on iOS development as well as books to buy. They are moving towards more video tutorials. The tutorials tend to focus on one specific topic, such as table views. After you learn the basics of iOS development and want to go deeper on a specific topic, check out raywenderlich.com.

Additional Questions

If you have more questions on learning iOS development, they may be answered in Reddit’s iOS programming FAQ. The FAQ has a large list of resources for iOS developers including blogs to read, podcasts to listen to, and places to ask questions.

Checking API Availability in Swift

In a previous article I explained how to set up your Xcode projects to support older versions of iOS and macOS. But I didn’t explain how to use new functions on new iOS and macOS versions and older functions on older systems.

Swift provides two statements to help support older OS versions: #available and @available.

#available

The #available keyword works with if and guard statements. Supply an operating system version version, and the code inside the if block runs only on suitable systems.

Let’s work through an example. Apple added the NSPersistentContainer class in iOS 10 and macOS 10.12 to simplify setting up the Core Data stack. You would like to use NSPersistentContainer on systems that have the class and not use it on older versions of iOS and macOS. The following example shows how to use NSPersistentContainer on systems running iOS 10 and later:

The asterisk as the second argument to #available tells the compiler to require the minimum deployment target for other platforms. The minimum deployment target is the earliest version of iOS or macOS that can run your app.

The following code shows how to use NSPersistentContainer on systems running macOS 10.12 and later:

Apple added the #available statement before changing the name of the Mac operating system from OS X to macOS.

If you have some code that should run only on later versions of iOS or macOS, use #available with a guard statement. By using #available with guard, your code can exit if it’s running on an older version of iOS or macOS.

@available

Place @available in front of a function or class to make that function or class available only on supported iOS and Mac versions. Like #available, supply an operating system version. The following code shows how to use @available with a function:

The following code uses @available with a class:

Supporting Older Versions of iOS and macOS

I recently answered a question from someone who wanted to know what version of Xcode and Swift to use to develop an app for iOS 9 and another question from someone who wanted to know what version of Xcode to use to develop an app to run on macOS 10.7. Answering what is basically the same question twice in one week is a sign that people are not sure how to make their apps run on older versions of iOS and macOS. This article should clarify things.

Short version: use the latest version of Xcode and set the deployment target for the project to the earliest version of iOS or macOS you want to support.

What Version of Xcode Should You Use?

You should use the most recent version of Xcode that will run on your Mac. Currently the most recent version of Xcode is version 10.1, which runs on macOS 10.13 and 10.14. The site Xcode Releases has a list of every Xcode release and the operating system requirements.

Why should you use the most recent Xcode version? Each version of Xcode ships with a SDK (Software Development Kit) for iOS and macOS. Xcode 10 ships with the iOS 12 and macOS 10.14 SDKs. The SDK contains the features your app can use. By using the latest SDK, your app can take advantage of the features Apple added in the latest version of iOS and macOS. The macOS 10.14 SDK includes support for dark mode. If you want your Mac app to look good in dark mode, you have to build it with the 10.14 SDK.

Some of you are asking the following question: if I use the iOS 12 SDK, will my app run only on devices running iOS 12? No. The SDK determines the latest features your app can take advantage of. The deployment target determines the operating systems that can run your app. If you use the iOS 12 SDK and set the deployment target to iOS 10, your app can run on devices running iOS 10, 11, and 12 (and future versions of iOS too).

What Version of Swift Should You Use?

All versions of Swift support the same operating systems: iOS 7 and later and macOS 10.9 and later. You can use the latest version of Swift and support older versions of iOS and macOS.

The main reason to use an older version of Swift is if you are using any non-Apple libraries or frameworks that were built with an older version of Swift. In that situation you should use the same version of Swift that was used to build the library or framework.

Setting the Deployment Target for Your Project

To make your app run on older versions of iOS and macOS, change the deployment target for your project by selecting the project file from the left side of the project window.

SetiOSDeploymentTarget

The deployment target is the earliest version of iOS or macOS that can run your app. Set the deployment target to the earliest version of iOS or macOS that you want to support. Keep in mind that Apple makes supporting earlier versions of iOS and macOS difficult. Most app developers support the most recent operating system version and the previous version, such as supporting iOS 11 and 12.

Setting the deployment target is mandatory for your app to run on older versions of iOS and macOS, but it’s not the only thing you have to do. You have to make sure your app isn’t using any technologies or calling any code that Apple introduced in a later iOS or macOS version. As an example Apple added a document browser class, UIDocumentBrowserViewController, in iOS 11 to simplify creating and opening documents. If your app uses the new document browser, it will run only on iOS 11 and later. Setting the deployment target to iOS 9 isn’t going to make your document-based app run on iOS 9 or 10.

Summary

Use the latest version of Xcode so your app can take advantage of the latest features in iOS and macOS. Change the deployment target for your project to support earlier versions of iOS and macOS, making sure not to use any new technologies or functions.

Creating Document-Based iOS Apps Part 2: Make a Text Editor

In Part 1 of this tutorial, you learned the concepts of creating an iOS document-based app. In Part 2 of the tutorial, you will put those concepts into practice by building a simple rich text editor. You must be running Xcode 9 or later to follow along, as Apple added the document-based app project template in Xcode 9.

If you haven’t read Part 1 of the tutorial, you should do so before you read this tutorial. The first part explains some higher-level concepts that this part glosses over.

Create The Project

Let’s start by creating the project. Open Xcode and choose File > New > Project. The New Project Assistant opens.

CreateProjectStep1

Click the iOS button at the top of the window. Select Document Based App. Click the Next button to move to the next step.

CreateProjectStep2Cropped

Name the project in the Product Name text field. Choose your iOS developer profile from the Team menu. Enter your company name or your name in the Organization Name text field. Use that name to fill in the organization identifier. The organization identifier usually takes the form com.CompanyName. This site is called Swift Dev Journal so choose Swift from the Language menu. Click the Next button to choose a location to save the project.

If you look at the project navigator on the left side of the project window, you should see Swift files for the document browser view controller, the document view controller, and the document. You should also see a storyboard. Those are the files you are going to use in the project.

Build the User Interface

Now that you created the project, the next step is to build the user interface, which mostly consists of a text view. Select the Main.storyboard file from the project navigator to open it. The storyboard has two scenes: one for the document browser view controller and one for the document view controller. You’re going to lay out the user interface in the document view controller. Apple creates the document browser user interface for you.

You’re going to perform the following tasks to build the user interface:

  • Remove most of the user interface in the document view controller.
  • Add a text view to the document view controller scene.
  • Configure the text view so you’re editing rich text instead of plain text.
  • Add constraints to the Done button so it’s centered on all iOS devices.
  • Add constraints to the text view so the text isn’t on the edges of the screen.
  • Create an outlet for the text view and connect the outlet so you can access the text view in your code.

Remove Most of the Existing User Interface

The document view controller looks like the following initially:

DocumentViewControllerAtStart

Keep the Done button so people can go back to the document browser when they’re finished editing a document. Move the Done button out of the stack view and place it at the top center of the screen. After moving the Done button out of the stack view, delete the stack view.

Add a Text View

To add a text view, select the text view element from the object library and drag it to the document view controller. If you can’t find the object library, remember that Apple moved the object library in Xcode 10. The button to open the object library is on the right side of the toolbar.

Xcode10LibraryButtonHighlighted

Place the text view below the Done button and have it fill the rest of the view. The document view controller scene should look similar to the following screenshot:

DocumentViewControllerAfterAddingTextView

Configure the Text View

Select the text view and open the attributes inspector. Choose Attributed from the Text menu so the text view shows rich text instead of plain text. You can also remove the Lorem ipsum text if you want. Select the Allows Editing Attributes checkbox.

TextViewAttributesInspector

Make sure the Editable checkbox is also selected. A text editor that doesn’t let you edit text is not much fun to use.

Add Constraints for the Done Button

Add a constraint to center the Done button horizontally at the top of the view so that it’s centered on all iOS devices. Select the Done button. Click the Add Constraints button to open a popover to add an alignment constraint.

DoneButtonConstraint

Select the Horizontally in Container checkbox. Click the Add 1 Constraint button at the bottom of the popover to add the constraint.

Add Constraints for the Text View

The container for the text view fills the entire view by default. This means the text you type is pushed against the left edge of the text view. Add some constraints to add some padding.

Select the text view. Click the Add Constraints button to open a popover to add new constraints.

TextViewConstraints

Click the four struts surrounding the square at the top of the popover to add some spacing for the text view. Enter spacing values in the text fields. I added 16 points of spacing to the left and right edges and added 10 points of spacing for the top and bottom edges. Click the Add 4 Constraints button at the bottom of the popover to finish adding the constraints.

Create and Connect the Text View Outlet

To be able to access the text view in your code, you must add an outlet for the text view in your code and connect the text view in the storyboard to the outlet. Add the following property to the DocumentViewController class to create an outlet:

Now connect the text view in the storyboard to this outlet. Open the assistant editor (choose View > Assistant Editor > Show Assistant Editor) so the source code file and storyboard are both open. Select the text view in the storyboard, hold down the Control key, and drag it to the outlet to make the connection.

Creating the Document

If you run the project at this point, you’ll notice that tapping the Create Document button does nothing. To create a new document when someone taps the Create Document button, you must do the following:

  • Add an empty document file to your project with the same file extension as your document.
  • Edit the document type in Xcode.
  • Add code to load the empty document in the document browser view controller.

If you were creating a new document type, you would also have to add an exported UTI for the document. But this app saves RTF files so you don’t need to add an exported UTI.

Add an Empty Document File

Fortunately Xcode has a RTF file template to use as the empty document file. To add an empty RTF file to the project, choose File > New > File. The Rich Text File is in the Resource section under iOS.

CreateRichTextFile

Name the file. I named the file New Document. You can choose a different name if you want, but remember the name.

Edit the Document Type

Select the project from the project navigator to open the project editor. Select the app target from the left side of the project editor. Click the Info button at the top of the project editor to access the document types. Click the disclosure triangle next to Document Types.

DocumentTypes

  1. Enter RTF in the Name text field.
  2. Enter public.rtf in the Types text field. public.rtf is the standard UTI (Uniform Type Identifier) for RTF files.
  3. Set the CFBundleTypeRole key’s value to Editor so people will be able to edit RTF files in the text editor.
  4. Set the LSHandlerRank key’s value to Alternate. This will let people edit RTF files in the app without forcing them to use the app to edit all RTF files.

Load the Empty Document

Open the DocumentBrowserViewController.swift file and go to the didRequestDocumentCreationWithHandler function. You should see the following line of code at the start of the function:

Change this line of code so it retrieves the empty document file you added to the project.

Make sure the name of the resource matches the name of the empty document file you added to the project. If the names don’t match, the new document won’t be created.

There’s one more change to make to the code.

There’s a problem with the code inside the if block.

If you keep this code, your app will crash the second time you create a document because the empty document file was moved out of the app bundle when you created the first document. When you try to load the empty document file the second time, the app can’t find it in the app bundle, causing a crash. You must copy the file from the app bundle to create multiple documents without crashing.

Update Document When Text View Changes

The user interface consisting mainly of a text view has a potential problem. If you wait for the person using the app to stop editing to update the document contents and save, you won’t be updating and saving until the person taps the Done button. You run the risk of losing data.

For this project you’re going to update the document’s data to match the text view contents when the text view’s contents change. This behavior is not the most efficient, but it minimizes the risk of losing data.

Start by adding the following property to the Document class that stores the document’s text:

Now it’s time to write some code to update the document when the text view’s contents change. Add the following function to the document view controller:

When the contents of the text view change, change the document’s text property so it holds the text view’s contents. Update the change count for the document to trigger autosaving for the document.

Save the Document

To save the document, implement the function contentsForType in the Document class.

The first line of code makes sure the text property is not nil before saving. The second line grabs the document’s text and calls the NSKeyedArchiver method archivedData to save the text.

Open the Document

To open the document, start by implementing the load function in the Document class.

The first line of code in the function makes sure the file has suitable data to load. The second line loads the document text from the file by calling the NSKeyedUnarchiver method unarchiveObject. The last line sets the document’s text property to the text you loaded from the file.

Now you must set the text view’s contents to the document’s text you just loaded. The document view controller’s viewDidAppear method has a block of code to open the file. If the open is successful, set the text view’s attributed text to the document’s text.

Conclusion

I put this project on GitHub for you to look at if you run into problems. If you look at the GitHub project, you can see how to scroll the text view when the onscreen keyboard appears and disappears. Scrolling the text view is important for a text editor so the keyboard doesn’t block what you’re typing, but scrolling the text view doesn’t involve document-based apps so I didn’t write about it in the article.

Creating Document-Based iOS Apps Part 1

iOS support for document-based apps opens up new categories of apps for you to develop. Want to develop screenwriting software? A video editing app? A level editor for a game? By making a document-based app you can make iOS versions of these types of apps.

In iOS 11 Apple simplified creating and opening documents, making it easier for developers to create document-based apps. Unfortunately Apple did not update their documentation to reflect these changes.

This article helps document those changes. To make things more manageable, I divided it into two parts. This part covers the higher-level concepts. The second part walks you through creating a rich text editor.

What Are Document-Based Apps?

Document-based apps let people create documents that they can share. Examples of document-based apps include text editors, spreadsheets, and image editors. The document is the text, spreadsheet, or image that you’re editing.

Document-based apps are more common on Macs than on iOS. But by learning how to create document-based apps in iOS, you can build powerful apps that used to require a Mac to run.

The Classes

There are two classes to study to develop document-based apps. The first class is UIDocumentBrowserViewController, which controls the document browser.

The document browser is where someone using your app creates and opens documents. The following screenshot shows what the document browser looks like initially:

DocumentBrowserForNewProject

The second class is UIDocument, which represents the document. When you create a document-based app project, Xcode includes a UIDocument subclass for you. You use this class to store any data properties related to your document.

Your Tasks

You have to perform the following tasks to develop an iOS document-based app:

  • Create a document-based app project in Xcode.
  • Set up your document type to create new documents.
  • Implement delegate functions in the document browser view controller to handle creating new documents and selecting documents from the document browser.
  • Save the document.
  • Open the document.

It looks like a lot of work to make a document-based app, but there’s much less code to write than you think. In the project I wrote for the second part of this article, I wrote less than ten lines of code to implement the delegate functions, save the document, and open the document. Apple’s document-based app project template does a lot of work for you.

Creating a Document-Based App Project in Xcode

Creating a document-based app project in Xcode gives you a document browser view controller, a view controller for the document, and a UIDocument subclass.

Setting Up Your Document Type to Create New Documents

There’s a surprising amount of work involved in creating a new document when someone taps the Create Document button in the document browser. You must perform the following tasks:

  • Set up the document type.
  • Supply a blank file to create documents.

If you are creating a brand new document type, you must also add an exported UTI for the document. UTI stands for Uniform Type Identifier. A UTI uniquely identifies a file type. The exported UTI lets the operating system and other apps know about the existence of your file type.

Set Up the Document Type

When you create a document-based app project, Xcode creates a document type for you. Use Xcode’s project editor to configure the document type. Start by selecting your project from the project navigator to open the project editor and selecting your app target from the left side of the project editor. Click the Info button at the top of the project editor to access the Document Types section.

Enter the UTIs for your document type in the Types text field. If you are creating a new file type, the UTI should take the form com.CompanyName.FileType. If you are using an existing file type, supply its UTI. Suppose you are developing a plain text editor. The UTI for your document would be public.plain-text. Apple has a list of system-declared UTIs, but it’s not being actively maintained.

The Document Types section has a subsection called Additional document type properties. You must add two document type properties. The first property has the key CFBundleTypeRole. Its type is String. Its value should be Editor if you want people to edit your documents.

The second property has the key LSHandlerRank. Its type is String. For new file types, the value should be Owner because your app owns the file type. For existing file types the value should be Alternate. The alternate handler rank allows your app to edit files of the file type but keeps your app from being the default app to edit files of that type. Suppose you’re making an image editor that lets people edit PNG files. By making your app the alternate editor, you can edit PNG files in your app without forcing people to use your app to edit all PNG files.

Supply a Blank File to Create Documents

You need to create a blank file when someone chooses to create a new document. Otherwise tapping the Create Document button does nothing. The easiest way to create a blank file is to add an empty file to your project whose file extension matches the name of your document’s file extension. Your app will load this file from the app bundle when someone creates a new document.

Add an Exported UTI

If you are creating a new document type, you must add an exported UTI for the document type. The exported UTI lets the operating system and other apps know of the existence of your new file type. Exported UTIs are in the same section as document types in Xcode’s project editor.

Enter your document’s UTI in the Identifier text field. The UTI should take the form com.CompanyName.FileType. Enter any additional UTIs your exported UTI supports in the Conforms To text field. If your document saves its contents in a file package instead of a single file, you would add the UTI com.apple.package in the Conforms To text field.

You also must add an additional exported UTI property. The key is UTTypeTagSpecification, and its type is Dictionary. You must create two dictionary items. The first item has the key public.mime-type. Its type is String. Its value is the UTI for your document type.

The second item has the key public.filename-extension. Its type is String. Its value is the file extension for your document. Do not add a dot in front of the file extension.

Document Browser Delegate Functions

Handling creating a new document requires you to implement two delegate functions in the document browser view controller: didRequestDocumentCreationWithHandler and didImportDocumentAt. You load the empty document file from the app bundle in didRequestDocumentCreationWithHandler when someone creates a new document.

Fortunately the document browser view controller in Xcode’s document-based app project template provides basic versions of these two delegate functions for you. Unless you’re doing something like Pages, where creating a document opens a template chooser, you won’t have to write much code. In the project for the second part of this article, I had to add one line of code in didRequestDocumentCreationWithHandler and change one other line of code.

Handling selecting a document from the document browser requires you to implement the delegate function didPickDocumentURLs in the document browser view controller. The document browser view controller provides a basic version for you to use. In the project for the second part of this article, I didn’t have to change anything in didPickDocumentURLs.

Saving Documents

To save a document you must override the contentsOfType function in the UIDocument subclass. Xcode’s document-based app project template provides an empty function for you to fill in. In most cases you’ll use the NSKeyedArchiver class to save the document data.

Opening Documents

To open a document you must override the load function in the UIDocument subclass. Xcode’s document-based app project template provides an empty function for you to fill in. In most cases you’ll use the NSKeyedUnarchiver class to read the document data from the file.

On to Part 2

Part 2