When I'm working with SwiftUI, I usually have a different Xcode project open where I experiment with the most minimal impl of my attempted UI with just Rectangles as content. Right now I'm working on a 3-pane layout for macOS where I have basically two experiments. Create a new single-window macOS app project from the Xcode template. Build and run to see that you get a single window with nothing in it. Create a new NSView subclass in a new Swift file. There should be a template for this in the dialog that comes up when.
I'm trying to read the contents of a file on the filesystem in a macOS Swift app (Xcode 9 / Swift 4).
I'm using the following snippet for it:
My problem is the following:
1. This works in a Playground
2. This works when I use the Command Line Tool macOS app template
3. This terminates in a permission error when I use the Cocoa App macOS app template
The permission error is the following:
I guess it's related to sandboxing but I found no information about it.
1. How can I read from the filesystem in a sandboxed app? I mean there are so many GUI apps which need an Open File dialog, it cannot be a realistic restriction of sandboxed apps to not read files from outside the sandbox.
2. Alternatively, how can I switch off sandboxing in Build Settings?
3. Finally, I tried to compare the project.pbxproj files between the default Cocoa Apps and Command Line Tool template and I didn't see any meaningful difference, like something about security or sandbox. If not here, where are those settings stored?
Connect the UI to Code
In this lesson, you’ll connect the basic user interface (UI) of the FoodTracker app to code and define some actions a user can perform in that UI.
Learning Objectives
At the end of the lesson, you’ll be able to:
Explain the relationship between a scene in a storyboard and the underlying view controller
Create outlet and action connections between UI elements in a storyboard and source code
Process user input from a text field and display the result in the UI
Make a class conform to a protocol
Understand the delegation pattern
Follow the target-action pattern when designing app architecture
Connect the UI to Source Code
Elements in a storyboard are linked to source code. It’s important to understand the relationship that a storyboard has to the code you write.
In a storyboard, a scene represents one screen of content and typically one view controller. View controllers implement your app’s behavior. A view controller manages a single content view with its hierarchy of subviews. View controllers coordinate the flow of information between the app’s data model, which encapsulates the app’s data, and the views that display that data, manage the life cycle of their content views, handle orientation changes when the device is rotated, define the navigation within your app, and implement the behavior to respond to user input. All view controller objects in iOS are of type UIViewController
or one of its subclasses.
You define the behavior of your view controllers in code by creating and implementing custom view controller subclasses. You can then create a connection between those classes and scenes in your storyboard to get the behavior you defined in code and the user interface you defined in your storyboard.
Xcode already created one such class that you looked at earlier, ViewController.swift
, and connected it to the scene you’re working on in your storyboard right now. In the future, as you add more scenes, you’ll make this connection yourself in the Identity inspector. The Identity inspector lets you edit properties of an object in your storyboard related to that object’s identity, such as what class the object belongs to.
At runtime, your storyboard creates an instance of ViewController
, your custom view controller subclass. The scene from your storyboard appears on the device’s screen, and the user interface’s behavior is defined in ViewController.swift
.
Although the scene is connected to ViewController.swift
, that’s not the only connection that needs to be made. To define interaction in your app, your view controller source code needs to be able to communicate with the views in your storyboard. You do this by defining additional connections—called outlets and actions—between the views in the storyboard and the view controller source code files.
Create Outlets for UI Elements
Outlets provide a way to reference interface objects—the objects you added to your storyboard—from source code files. To create an outlet, Control-drag from a particular object in your storyboard to a view controller file. This operation creates a property for the object in your view controller file, which lets you access and manipulate that object from code at runtime.
You’ll need to create outlets for the text field and label in your user interface to be able to reference them.
To connect the text field to the ViewController.swift code
Open your storyboard,
Main.storyboard
.Click the Assistant button in the Xcode toolbar near the top right corner of Xcode to open the assistant editor.
If you want more space to work, collapse the project navigator and utility area by clicking the Navigator and Utilities buttons in the Xcode toolbar.
You can also collapse the outline view.
In the editor selector bar, which appears at the top of the assistant editor, change the assistant editor from Preview to Automatic >
ViewController.swift
.ViewController.swift
displays in the editor on the right.In
ViewController.swift
, find theclass
line, which should look like this:Below the
class
line, add the following:You just added a comment to your source code. Recall that a comment is a piece of text in a source code file that doesn’t get compiled as part of the program but provides context or useful information about individual pieces of code.
A comment that begins with the characters
//MARK:
is a special type of comment that’s used to organize your code and to help you (and anybody else who reads your code) navigate through it. You’ll see this in action later. Specifically, the comment you added indicates that this is the section of your code that lists properties.In your storyboard, select the text field.
Control-drag from the text field on your canvas to the code display in the editor on the right, stopping the drag at the line below the comment you just added in
ViewController.swift
.In the dialog that appears, for Name, type
nameTextField
. Leave the rest of the options as they are.Click Connect.
Xcode adds the necessary code to
ViewController.swift
to store a reference to the text field and configures the storyboard to set up that connection.
Take a minute to understand what’s happening in this line of code.
The IBOutlet
attribute tells Xcode that you can connect to the nameTextField
property from Interface Builder (which is why the attribute has the IB
prefix). The weak
keyword indicates that the reference does not prevent the system from deallocating the referenced object. Weak references help prevent reference cycles; however, to keep the object alive and in memory you need to make sure some other part of your app has a strong reference to the object. In this case, it’s the text field’s superview. A superview maintains a strong reference to all of its subviews. As long as the superview remains alive and in memory, all of the subviews remain alive as well. Similarly, the view controller has a strong reference to its content view—keeping the entire view hierarchy alive and in memory.
The rest of the declaration defines an implicitly unwrapped optional variable of type UITextField
named nameTextField
. Pay careful attention to the exclamation point at the end of the type declaration. This exclamation point indicates that the type is an implicitly unwrapped optional, which is an optional type that will always have a value after it is first set. When you access an implicitly unwrapped optional, the system assumes it has a valid value and automatically unwraps it for you. Note that this causes the app to terminate if the variable’s value has not yet been set.
When a view controller is loaded from a storyboard, the system instantiates the view hierarchy and assigns the appropriate values to all the view controller’s outlets. By the time the view controller’s viewDidLoad()
method is called, the system has assigned valid values to all of the controller’s outlets, and you can safely access their contents.
Now, connect the label to your code in the same way you connected the text field.
To connect the label to the ViewController.swift code
In your storyboard, select the label.
Control-drag from the label on your canvas to the code display in the editor on the right, stopping the drag at the line just below your
nameTextField
property inViewController.swift
.In the dialog that appears, for Name, type
mealNameLabel
. Leave the rest of the options as they are.Click Connect.
Again, Xcode adds the necessary code to ViewController.swift
to store a reference to the label and configures the storyboard to set up that connection. This outlet is similar to the text field, except for its name and its type (which is UILabel
, to match the type of object that’s in the storyboard).
You only need an outlet to an interface object if you plan to either access a value from the interface object or modify the interface object in your code. In this case, you need to set the text field’s delegate
property and set the label’s text
property. You won’t be modifying the button, so there’s no reason to create an outlet for it.
Outlets let you refer to your interface elements in code, but you still need a way to respond whenever the user interacts with the elements. That’s where actions come in.
Define an Action to Perform
iOS apps are based on event-driven programming. That is, the flow of the app is determined by events: system events and user actions. The user performs actions in the interface that trigger events in the app. These events result in the execution of the app’s logic and manipulation of its data. The app’s response to user action is then reflected back in the user interface. Because the user, rather than the developer, is in control of when certain pieces of the app code get executed, you want to identify exactly which actions a user can perform and what happens in response to those actions.
An action (or an action method) is a piece of code that’s linked to an event that can occur in your app. When that event takes place, the system execute’s the action’s code. You can define an action method to accomplish anything from manipulating a piece of data to updating the user interface. You use actions to drive the flow of your app in response to user or system events.
You create an action the same way you create an outlet: Control-drag from a particular object in your storyboard to a view controller file. This operation creates a method in your view controller file that gets triggered when a user interacts with the object that the action method is attached to.
Start by creating a simple action that sets the label to Default Text
whenever the user taps the Set Default Text button. (The code to set the label to the text in the text field is a bit more involved, so you’ll write that in the Process User Input section.)
To create a setDefaultLabelText action in the ViewController.swift code
In
ViewController.swift
, just above the last curly brace (}
), add the following:This comment indicates that this is the section of your code that lists actions.
In your storyboard, select the Set Default Label Text button.
Control-drag from the Set Default Label Text button on your canvas to the code display in the editor on the right, stopping the drag at the line below the comment you just added in
ViewController.swift
.In the dialog that appears, for Connection select Action.
For Name, type
setDefaultLabelText
.For Type, select
UIButton
.You may have noticed that the value of the Type field defaults to
AnyObject
. In Swift,AnyObject
is a type used to describe an object that can belong to any class. Specifying the type of this action method to beUIButton
means that only button objects can connect to this action. Although this isn’t significant for the action you’re creating right now, it’s important to remember for later.Leave the rest of the options as they are.
Click Connect.
Xcode adds the necessary code to ViewController.swift
to set up the action method.
@IBActionfuncsetDefaultLabelText(_sender: UIButton) {
}
The sender
parameter refers to the object that was responsible for triggering the action—in this case, a button. The IBAction
attribute indicates that the method is an action that you can connect to from your storyboard in Interface Builder. The rest of the declaration declares a method by the name of setDefaultLabelText(_:)
.
Right now, the method declaration is empty. The code to reset the value of the label is quite simple.
To implement the label reset action in the ViewController code
In
ViewController.swift
, find thesetDefaultLabelText
action method you just added.In the method implementation, between the curly braces (
{}
), add this line of code:As you might guess, this code sets the label’s
text
property to Default Text.Notice that you didn’t have to specify the type of Default Text, because Swift’s type inference can see that you’re assigning to something of type
String
and can infer the type correctly.
iOS handles all of the redrawing code for you, so this is actually all the code you need to write for now. Your setDefaultLabelText(_:)
action method should look like this:
@IBActionfuncsetDefaultLabelText(_sender: UIButton) {
mealNameLabel.text = 'Default Text'
}
Checkpoint: Test your changes by running the simulator. When you click the Set Default Label Text button, your setDefaultLabelText(_:)
method is called, and the mealNameLabel
object’s text
value changes from Meal Name
(the value set in your storyboard) to Default Text
(the value set by the action). You should see the change in your user interface.
While changing the meal’s name to “Default Text” isn’t particularly useful, it does illustrate an important point. The behavior you just implemented is an example of the target-action pattern in iOS app design. Target-action is a design pattern where one object sends a message to another object when a specific event occurs.
In this case:
The event is the user tapping the Set Default Text button.
The action is
setDefaultLabelText(_)
.The target is
ViewController
(where the action method is defined).The sender is the Set Default Label Text button.
The system sends the message by calling the action method on the target and passing in the sender object. The sender is usually a control—such as a button, slider, or switch—that can trigger an event in response to user interaction such as a tap, drag, or value change. This pattern is extremely common in iOS app programming, and you’ll be seeing much more of it throughout the lessons.
Process User Input
At this point, users can reset the meal name label to a default value, but you really want to let users enter their own meal names using the text field. To keep things simple, you’ll update the mealNameLabel
object’s text
value whenever the user enters text into the text field and taps Return.
When you work with accepting user input from a text field, you need some help from a text field delegate. A delegate is an object that acts on behalf of, or in coordination with, another object. The delegating object—in this case, the text field—keeps a reference to the other object—the delegate—and at the appropriate time, the delegating object sends a message to the delegate. The message tells the delegate about an event that the delegating object is about to handle or has just handled. The delegate may respond by for example, updating the appearance or state of itself or of other objects in the app, or returning a value that affects how an impending event is handled.
A text field’s delegate communicates with the text field while the user is editing the text, and knows when important events occur—such as when a user starts or stops editing text. The delegate can use this information to save or clear data at the right time, dismiss the keyboard, and so on.
Any object can serve as a delegate for another object as long as it conforms to the appropriate protocol. The protocol that defines a text field’s delegate is called UITextFieldDelegate
. It is very common to make a view controller the delegate for objects that it manages. In this case, you’ll make your ViewController
instance the text field’s delegate.
First, ViewController
needs to adopt the UITextFieldDelegate
protocol. You adopt a protocol by listing it as part of the class declaration line.
To adopt the UITextFieldDelegate protocol
If the assistant editor is open, return to the standard editor by clicking the Standard button.
Expand the project navigator and utility area by clicking the Navigator and Utilities buttons in the Xcode toolbar.
In the project navigator, select
ViewController.swift
.In
ViewController.swift
, find theclass
line, which should look like this:After
UIViewController
, add a comma (,
) andUITextFieldDelegate
to adopt the protocol.classViewController: UIViewController, UITextFieldDelegate {
By adopting the UITextFieldDelegate
protocol, you tell the compiler that the ViewController
class can act as a valid text field delegate. This means you can implement the protocol’s methods to handle text input, and you can assign instances of the ViewController
class as the delegate of the text field.
To set the ViewController object as the delegate of its nameTextField property
In
ViewController.swift
, find theviewDidLoad()
method, which should look like this:overridefuncviewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
The template implementation of this method includes a comment. You don’t need this comment in your method implementation, so go ahead and delete it.
Below the
super.viewDidLoad()
line, add a blank line and the following:// Handle the text field’s user input through delegate callbacks.
nameTextField.delegate = self
The
self
refers to theViewController
class, because it’s referenced inside the scope of theViewController
class definition.You can add your own comments to help you understand what’s happening in your code.
Your viewDidLoad()
method should look like this:
overridefuncviewDidLoad() {
super.viewDidLoad()
// Handle the text field’s user input through delegate callbacks.
nameTextField.delegate = self
}
When a ViewController
instance is loaded, it sets itself as the delegate of its nameTextField
property.
The UITextFieldDelegate
protocol defines eight optional methods. Just implement the ones you need to get the behaviors you desire. For now, you’ll need to implement two of these methods:
functextFieldShouldReturn(_textField: UITextField) -> Bool
functextFieldDidEndEditing(_textField: UITextField)
To understand when these methods get called and what they need to do, it’s important to know how text fields respond to user events. When the user taps a text field, it automatically becomes the first responder. In an app, the first responder is an object that is first on the line for receiving many kinds of app events, including key events, motion events, and action messages, among others. In other words, many of the events generated by the user are initially routed to the first responder.
As a result of the text field becoming the first responder, iOS displays the keyboard and begins an editing session for that text field. What a user types using that keyboard gets inserted into the text field.
When a user wants to finish editing the text field, the text field needs to resign its first-responder status. Because the text field will no longer be the active object in the app, events need to get routed to a more appropriate object.
This is where your implementation of UITextFieldDelegate
methods comes in. You need to specify that the text field should resign its first-responder status when the user taps a button to end editing in the text field. You do this in the textFieldShouldReturn(_:)
method, which gets called when the user taps Return (or in this case, Done) on the keyboard.
To implement the UITextFieldDelegate protocol method textFieldShouldReturn(_:)
In
ViewController.swift
, right above the//MARK: Actions
section, add the following:This comment is used to organize your code and to help you (and anybody else who reads your code) navigate through it.
You’ve added several of these comments so far. Xcode lists each of these comments as a section title in the source code file’s Functions menu, which appears if you click the name of the file at the top of the editor area. The Functions menu lets you jump to a section in your code quickly. You’ll notice the sections you denoted by
//MARK:
listed here. You can click on one of the section titles to jump to that section in the file.Below the comment, add the following method:
functextFieldShouldReturn(_textField: UITextField) -> Bool {
}
In this method, add the following code to resign the text field’s first-responder status, and a comment to describe what the code does:
// Hide the keyboard.
textField.resignFirstResponder()
Try typing the second line instead of just copying and pasting. You’ll find that code completion is one of the great time-saving features of Xcode. When Xcode brings up the list of potential completions, scroll through the list until you find the one you want and then press Return. Xcode inserts the whole line for you.
In this method, add the following line of code:
This method returns a Boolean value that indicates whether the system should process the press of the Return key. In this case, you always want to respond to the user pressing the Return key, so just return
true
.
Your textFieldShouldReturn(_:)
method should look like this:
functextFieldShouldReturn(_textField: UITextField) -> Bool {
// Hide the keyboard.
textField.resignFirstResponder()
returntrue
}
The second method that you need to implement, textFieldDidEndEditing(_:)
, is called after the text field resigns its first-responder status. Because you resign first responder status in textFieldShouldReturn
, the system calls this method just after calling textFieldShouldReturn
.
The textFieldDidEndEditing(_:)
method gives you a chance to read the information entered into the text field and do something with it. In your case, you’ll take the text that’s in the text field and use it to change the value of your label.
To implement the UITextFieldDelegate protocol method textFieldDidEndEditing(_:)
Xcode Macos App
In
ViewController.swift
, after thetextFieldShouldReturn(_:)
method, add the following method:functextFieldDidEndEditing(_textField: UITextField) {
}
In this method, add the following line of code:
That’s all you need to do to see the result. Your textFieldDidEndEditing(_:)
method should look like this:
functextFieldDidEndEditing(_textField: UITextField) {
mealNameLabel.text = textField.text
}
Macos Xcode Version
Checkpoint: Test your changes by running the simulator. You can select the text field and type text into it. When you click the Done button on the keyboard, the keyboard is dismissed and the label text changes to display the text in the text field. When you click the Set Default Label Text button, the label changes from what’s currently displayed in the label to Default Text
(the value set by the action you defined earlier).
Wrapping Up
In this lesson, you’ve used the assistant editor to add outlets and actions to your source code. You’ve also added code to update the user interface as the user interacts with the controls. The project is still just a relatively simple, single scene, but you will continue to add features and increase its complexity over the remaining lessons.
Note
To see the completed sample project for this lesson, download the file and view it in Xcode.
Macos Xcode Install
Copyright © 2018 Apple Inc. All rights reserved. Terms of Use | Privacy Policy | Updated: 2016-12-08