In this chapter, we are going to have a look at some of the updates to Swift (Swift 3.0), Xcode, and Interface Builder. We will start with Swift and some of the really exciting features that have been added to it since you read the last edition of this cookbook.
Let’s say that you want to create a method that takes in a first name and last name as two arguments and returns a full name. The first name and the last name have to each at least be one character long for this method to work. If one or both have 0 lengths, we are going to want to throw an exception.
The first thing that we have to do is define our errors of type Error
:
enum
Errors
:
Error
{
case
emptyFirstName
case
emptyLastName
}
And then we are going to define our method to take in a first and last name and join them together with a space in between:
func
fullNameFromFirstName
(
_
firstName
:
String
,
lastName
:
String
)
throws
->
String
{
if
firstName
.
characters
.
count
==
0
{
throw
Errors
.
emptyFirstName
}
if
lastName
.
characters
.
count
==
0
{
throw
Errors
.
emptyLastName
}
return
firstName
+
" "
+
lastName
}
The interesting part is really how to call this method. We use the do
statement like so:
do
{
let
fullName
=
try
fullNameFromFirstName
(
"Foo"
,
lastName
:
"Bar"
)
(
fullName
)
}
catch
{
(
"An error occurred"
)
}
The catch
clause of the do
statement allows us to trap errors in a fine-grained manner. Let’s say that you want to trap errors in the Errors
enum differently from instances of NSException
. Separate your catch
clauses like this:
do
{
let
fullName
=
try
fullNameFromFirstName
(
"Foo"
,
lastName
:
"Bar"
)
(
fullName
)
}
catch
let
err
as
Errors
{
// handle this specific type of error here
(
err
)
}
catch
let
ex
as
NSException
{
// handle exceptions here
(
ex
)
}
catch
{
// otherwise, do this
}
The guard
syntax allows you to:
Let’s have a look at a method that takes an optional piece of data as the NSData
type and turns it into a String
only if the string has some characters in it and is not empty:
func
stringFromData
(
_
data
:
Data
?)
->
String
?{
guard
let
data
=
data
,
let
str
=
NSString
(
data
:
data
,
encoding
:
String
.
Encoding
.
utf8
.
rawValue
)
,
data
.
count
>
0
else
{
return
nil
}
return
String
(
str
)
}
And then we are going to use it like so:
if
let
_
=
stringFromData
(
nil
){
(
"Got the string"
)
}
else
{
(
"No string came back"
)
}
We pass nil
to this method for now and trigger the failure block (“No string came back”). What if we passed valid data? And to have more fun with this, let’s create our NSData
instance this time with a guard
. Because the NSString
constructor we are about to use returns an optional value, we put a guard
statement before it to ensure that the value that goes into the data
variable is in fact a value, and not nil
:
guard
let
data
=
NSString
(
string
:
"Foo"
)
.
data
(
using
:
String
.
Encoding
.
utf8
.
rawValue
),
data
.
count
>
0
else
{
return
}
if
let
str
=
stringFromData
(
data
){
(
"Got the string
(
str
)
"
)
}
else
{
(
"No string came back"
)
}
So we can mix guard
and conditions in the same statement. How about multiple let
statements inside a guard? Can we do that? You betcha:
func
example3
(
firstName
:
String
?,
lastName
:
String
?,
age
:
UInt8
?){
guard
let
firstName
=
firstName
,
let
lastName
=
lastName
,
let
_
=
age
,
firstName
.
characters
.
count
>
0
&&
lastName
.
characters
.
count
>
0
else
{
return
}
(
firstName
,
" "
,
lastName
)
}
Anything that you put inside a defer
block inside a method is guaranteed to get executed before your method returns to the caller. However, this block of code will get executed after the return call in your method. The code is also called when your method throws an exception.
Let’s say that you want to define a method that takes in a string and renders it inside a new image context with a given size. Now if the string is empty, you want to throw an exception. However, before you do that, we want to make sure that we have ended our image context. Let’s define our error first:
enum
Errors
:
Error
{
case
emptyString
}
Then we move on to our actual method that uses the defer
syntax:
func
imageForString
(
_
str
:
String
,
size
:
CGSize
)
throws
->
UIImage
{
defer
{
UIGraphicsEndImageContext
()
}
UIGraphicsBeginImageContextWithOptions
(
size
,
true
,
0
)
if
str
.
characters
.
count
==
0
{
throw
Errors
.
emptyString
}
// draw the string here...
return
UIGraphicsGetImageFromCurrentImageContext
()
!
}
I don’t want to put print()
statements everywhere in the code because it makes the code really ugly. So to see whether this really works, I suggest typing this code into your Xcode—or even better, grab the source code for this book’s examples from GitHub, where I have already placed breakpoints in the defer
and the return
statements so that you can see that they are working properly.
You can, of course, then call this method like so:
func
imageForString
(
_
str
:
String
,
size
:
CGSize
)
throws
->
UIImage
{
defer
{
UIGraphicsEndImageContext
()
}
UIGraphicsBeginImageContextWithOptions
(
size
,
true
,
0
)
if
str
.
characters
.
count
==
0
{
throw
Errors
.
emptyString
}
// draw the string here...
return
UIGraphicsGetImageFromCurrentImageContext
()
!
}
We’ve all been waiting for this for a very long time. The days of having to call the respondsToSelector:
method are over (hopefully). Now we can just use #available
to make sure a specific iOS version is available before making a call to a method.
Let’s say that we want to write a method that can read an array of bytes from an NSData
object. NSData
offers a handy getBytes:
method to do this, but Apple decided to deprecate it in iOS 8.1 and replace it with getBytes:length:
, an improved version that minimizes the risk of buffer overflows. So assuming that one of our deployment targets is iOS 8 or older, we want to ensure that we call this new method if we are on iOS 8.1 or higher and the older method if we are on iOS 8.0 or older:
enum
Errors
:
Error
{
case
emptyData
}
func
bytesFromData
(
_
data
:
Data
)
throws
->
[
UInt8
]{
if
(
data
.
count
==
0
){
throw
Errors
.
emptyData
}
var
buffer
=
[
UInt8
](
repeating
:
0
,
count
:
data
.
count
)
if
#available
(
iOS
8.1
,
*
){
(
data
as
NSData
).
getBytes
(&
buffer
,
length
:
data
.
count
)
}
else
{
(
data
as
NSData
).
getBytes
(&
buffer
)
}
return
buffer
}
And then we go ahead and call this method:
guard
let
data
=
"Foo"
.
data
(
using
:
String
.
Encoding
.
utf8
)
else
{
return
}
do
{
let
bytes
=
try
bytesFromData
(
data
)
(
"Data =
(
bytes
)
"
)
}
catch
{
(
"Failed to get bytes"
)
}
For this recipe, I will create three packs of assets, each with three images in them. One pack may run for x3 screen scales, another for iPhone 6, and the last for iPhone 6+, for instance. I am taking very tiny clips of screenshots of my desktop to create these images—nothing special. The first pack will be called “level1,” the second “level2,” and the third “level3.”
Use the GitHub repo of this book for a quick download of these resources. Also, for the sake of simplicity, I am assuming that we are going to run this only on x3 scale screens such as iPhone 6+.
Place all nine images (three packs of three images) inside your Assets.xcassets file and name them as shown in Figure 5-1. Then select all the images in your first asset pack and open the Attributes inspector. In the “On Demand Resource Tags” section of the inspector, enter level1
and do the same thing for other levels—but of course bump the number up for each pack.
Now, in your UI, place three buttons and three image views, hook the buttons’ actions to the code, and hook the image view references to the code:
@IBOutlet
var
img1
:
UIImageView
!
@IBOutlet
var
img2
:
UIImageView
!
@IBOutlet
var
img3
:
UIImageView
!
var
imageViews
:
[
UIImageView
]{
return
[
self
.
img1
,
self
.
img2
,
self
.
img3
]
}
To find out whether the resource pack that you need has already been downloaded, call the conditionallyBeginAccessingResourcesWithCompletionHandler
function on your resource request. Don’t blame me! I didn’t name this function. This will return a Boolean of true
or false
to tell you whether you have access to the resource. If you don’t have access, you can simply download the resources with a call to the beginAccessingResourcesWithCompletionHandler
function. This will return an error if one happens, or nil
if everything goes well.
var
currentResourcePack
:
NSBundleResourceRequest
?
func
displayImagesForResourceTag
(
_
tag
:
String
){
OperationQueue
.
main
.
addOperation
{
for
n
in
0.
.<
self
.
imageViews
.
count
{
self
.
imageViews
[
n
].
image
=
UIImage
(
named
:
tag
+
"-
(
n
+
1
)
"
)
}
}
}
func
useLevel
(
_
lvl
:
UInt32
){
let
imageViews
=
[
img1
,
img2
,
img3
]
for
img
in
imageViews
{
img
?.
image
=
nil
}
let
tag
=
"level
(
lvl
)
"
if
let
req
=
currentResourcePack
{
req
.
endAccessingResources
()
}
currentResourcePack
=
NSBundleResourceRequest
(
tags
:
[
tag
])
guard
let
req
=
currentResourcePack
else
{
return
}
req
.
conditionallyBeginAccessingResources
{
available
in
if
available
{
self
.
displayImagesForResourceTag
(
tag
)
}
else
{
req
.
beginAccessingResources
{
error
in
guard
error
==
nil
else
{
//
TODO:
you can handle the error here
return
}
self
.
displayImagesForResourceTag
(
tag
)
}
}
}
}
@IBAction
func
useLevel3
(
_
sender
:
AnyObject
)
{
useLevel
(
3
)
}
@IBAction
func
useLevel2
(
_
sender
:
AnyObject
)
{
useLevel
(
2
)
}
@IBAction
func
useLevel1
(
_
sender
:
AnyObject
)
{
useLevel
(
1
)
}
We keep a reference to the request that we send for our asset pack so that the next time our buttons are tapped, we don’t have to check their availability again, but release the previously downloaded resources using the endAccessingResources
function.
Run the code now in your simulator. When Xcode opens, go to the Debug Navigator (Cmd-6 key) and then click the Disk section. You will see results similar to that shown in Figure 5-2.
Note how none of the asset packs are in use. Now in your UI, click the first button to get the first asset pack and watch how the first asset pack’s status will change to “In Use.” Once you switch from that pack to another, the previously chosen pack will be set to “Downloaded” and be ready to be purged.
Follow these steps:
Bitcode is Apple’s way of specifying how the binary that you submit to the App Store will be downloaded on target devices. For instance, if you have an asset catalog with some images for iPad and iPhone and a second set of images for iPhone 6 and 6+ specifically, users on iPhone 5 should not get the second set of assets. This is the default functionality in Xcode, so you don’t have to do anything special to enable it. If you are working on an old project, you can enable bitcode from Build Settings in Xcode.
If you are writing an app that has a lot of device-specific images and assets, I suggest that you use this method, before submitting your app to the store, to ensure that the required images and assets are indeed included in your final build. Remember, if bitcode is enabled in your project, Apple will detect the host device that is downloading your app from the store and will serve the right binary to that device. It’s not necessary to separate your binaries when submitting to Apple—simply submit a big, fat, juicy binary and Apple will take care of the rest.
I remember working on a project where we had a really messy storyboard and we had to separate the view controllers. What we ended up doing was putting the controllers on separate storyboards manually, after which we had to write code to link our buttons and other actions to the view controllers, instantiate them manually, and then show them. Well, none of that anymore. Apple has taken care of that for us!
As an exercise, create a single view controller project in Xcode and then open your main storyboard. Then choose the Editor menu, and navigate to Embed In->Navigation Controller. Now your view controller has a navigation controller. Place a button on your view controller and then place another view controller on your storyboard. Select the button on the first view controller, hold down the Control button on your keyboard, drag the line over to the second view controller, and then choose the Show option. This will ensure that when the user taps your button, the system will push the second view controller onto the screen, as Figure 5-3 shows.
Now select your second view controller and then, from the Editor menu, choose the “Refactor to Storyboard” item. In the dialog, enter Second.storyboard
as the file name and save. That’s really it. Now run your app and see the results if you want.
If you prefer to do some of this stuff manually instead of embedding things like this, you can always drag the new item called Storyboard Reference from the Object Library onto your storyboard and set up the name of the storyboard manually. Xcode will give you a drop-down box so that you don’t have to write the name of the storyboard all by yourself. You will also be able to specify an identifier for your storyboard. This identifier will then be useful when you are working with the segue. You of course have to set up this ID for your view controller in advance.
In Xcode, you can now add multiple bar button items to your navigation bar. Simply open the Object Library and search for “bar button.” Once you find the buttons, drag and drop them onto your navigation bar and then simply reference them in your code if you have to. For instance, Figure 5-4 shows two bar buttons on the righthand side of the navigation bar. In previous versions of Xcode, we could add only one button to each side. If we wanted more buttons, we had to write code to add them.
Prior to the latest Xcode, you could not place multiple bar button items next to each other on your navigation bar. Well, now you can. You can also access these buttons just as you would expect, by creating a reference to them in your code. And you can always find them using the barButtonItems
property of your navigation bar.
final
for classes, methods, and variables that aren’t going to be overridden.CFAbsoluteTimeGetCurrent
function to profile your app inside your code.Let’s have a look at an example. Let’s say that we have a Person
class like so:
class
Person
{
let
name
:
String
let
age
:
Int
init
(
name
:
String
,
age
:
Int
){
self
.
name
=
name
self
.
age
=
age
}
}
Now we will write a method that will generate 100,000 instances of this class, place them inside a mutable array, and then enumerate the array. We will time this operation using the CFAbsoluteTimeGetCurrent
function. We’ll then be able to tell how many milliseconds this took:
func
example1
(){
var
x
=
CFAbsoluteTimeGetCurrent
()
var
array
=
[
Person
]()
for
_
in
0.
.<
100000
{
array
.
append
(
Person
(
name
:
"Foo"
,
age
:
30
))
}
// go through the items as well
for
n
in
0.
.<
array
.
count
{
let
_
=
array
[
n
]
}
x
=
(
CFAbsoluteTimeGetCurrent
()
-
x
)
*
1000.0
(
"Took
(
x
)
milliseconds"
)
}
When I ran this code, it took 41.28 milliseconds to complete; it will probably be different in your computer. Now let’s create a struct similar to the class we created before but without an initializer, because we get that for free. Then do the same that we did before and time it:
struct
PersonStruct
{
let
name
:
String
let
age
:
Int
}
func
example2
(){
var
x
=
CFAbsoluteTimeGetCurrent
()
var
array
=
[
PersonStruct
]()
for
_
in
0.
.<
100000
{
array
.
append
(
PersonStruct
(
name
:
"Foo"
,
age
:
30
))
}
// go through the items as well
for
n
in
0.
.<
array
.
count
{
let
_
=
array
[
n
]
}
x
=
(
CFAbsoluteTimeGetCurrent
()
-
x
)
*
1000.0
(
"Took
(
x
)
milliseconds"
)
}
Don’t suffix your struct names with “Struct” like I did. This is for demo purposes only, to differentiate between the class and the struct.
When I run this code, it takes only 35.53 milliseconds. A simple optimization brought some good savings. Also notice that in the release version these times will be massively improved, because your binary will have no debug information. I have tested the same code without the debugging, and the times were around 4 milliseconds. Also note that I am testing these on the simulator, not on a real device. The profiling will definitely report different times on a device, but the ratio should be quite the same.
You will also need to determine which parts of your code are final and mark them with the final
keyword. This will tell the compiler that you are not intending to override those properties, classes, or methods and will help Swift optimize the dispatch process. For instance, let’s say we have this class hierarchy:
class
Animal
{
func
move
(){
if
"Foo"
.
characters
.
count
>
0
{
// some code
}
}
}
class
Dog
:
Animal
{
}
And we create instances of the Dog
class and then call the move
function on them:
func
example3
(){
var
x
=
CFAbsoluteTimeGetCurrent
()
var
array
=
[
Dog
]()
for
n
in
0.
.<
100000
{
array
.
append
(
Dog
())
array
[
n
].
move
()
}
x
=
(
CFAbsoluteTimeGetCurrent
()
-
x
)
*
1000.0
(
"Took
(
x
)
milliseconds"
)
}
When we run this, the runtime will first have to detect whether the move
function is on the superclass or the subclass and then call the appropriate class based on this decision. This checking takes time. For instance, if you know that the move
function won’t be overridden in the subclasses, mark it as final:
class
AnimalOptimized
{
final
func
move
(){
if
"Foo"
.
characters
.
count
>
0
{
// some code
}
}
}
class
DogOptimized
:
AnimalOptimized
{
}
func
example4
(){
var
x
=
CFAbsoluteTimeGetCurrent
()
var
array
=
[
DogOptimized
]()
for
n
in
0.
.<
100000
{
array
.
append
(
DogOptimized
())
array
[
n
].
move
()
}
x
=
(
CFAbsoluteTimeGetCurrent
()
-
x
)
*
1000.0
(
"Took
(
x
)
milliseconds"
)
}
When I run these on the simulator, I get 90.26 milliseconds for the non-optimized version and 88.95 milliseconds for the optimized version. Not that bad.
I also recommend turning on whole module optimization for your release code. Just go to your Build Settings and under the optimization for your release builds (App Store scheme), simply choose “Fast” with Whole Module Optimization, and you are good to go.
Use Xcode’s new Generated Interface Assistant Editor. This is how you do it: open your Swift file first and then, in Xcode, use Show Assistant Editor, which you can find in the Help menu if you just type that name. After you open the assistant, you will get a split screen of your current view. Then in the second editor that opened, on top, instead of Counterparts (which is the default selection), choose Generated Interface. You’ll see your code as shown in Figure 5-5.
The Generated Interface functionality of the assistant editor is quite handy if you want to get an overview of how clean your code is. It probably won’t be day-to-day functionality that you use all the time, but I cannot be sure—maybe you will love it so much that you will dedicate a whole new monitor just to see your generated interface all the time. By the way, there is a shortcut to the assistant editor in Xcode: Command-Alt-Enter. To get rid of the editor, press Command-Enter.
Let’s say that I have a structure that keeps track of iPhone models. I want to be able to create a set of this structure’s values so that I can say that I have an iPhone 6, iPhone 6+, and iPhone 5s (fancy me!). Here is the way I would do that:
struct
IphoneModels
:
OptionSet
,
CustomDebugStringConvertible
{
let
rawValue
:
Int
init
(
rawValue
:
Int
){
self
.
rawValue
=
rawValue
}
static
let
Six
=
IphoneModels
(
rawValue
:
0
)
static
let
SixPlus
=
IphoneModels
(
rawValue
:
1
)
static
let
Five
=
IphoneModels
(
rawValue
:
2
)
static
let
FiveS
=
IphoneModels
(
rawValue
:
3
)
var
debugDescription
:
String
{
switch
self
{
case
IphoneModels
.
Six
:
return
"iPhone 6"
case
IphoneModels
.
SixPlus
:
return
"iPhone 6+"
case
IphoneModels
.
Five
:
return
"iPhone 5"
case
IphoneModels
.
FiveS
:
return
"iPhone 5s"
default
:
return
"Unknown iPhone"
}
}
}
And then I can use it like so:
func
example1
(){
let
myIphones
:
[
IphoneModels
]
=
[.
Six
,
.
SixPlus
]
if
myIphones
.
contains
(.
FiveS
){
(
"You own an iPhone 5s"
)
}
else
{
(
"You don't seem to have an iPhone 5s but you have these:"
)
for
i
in
myIphones
{
(
i
)
}
}
}
Note how I could create a set of my new type and then use the contains
function on it just as I would on a normal set. Use your imagination—this is some really cool stuff.
Use protocol extensions. Swift allows protocol extensions to contain code.
Let’s say that you want to add a method on any array in Swift where the items are integers. In your extension, you want to provide a method called canFind
that can find a specific item in the array and return yes
if it could be found. I know that we can do this with other system methods. I am offering this simple example to demonstrate how protocol extensions work:
extension
Sequence
where
Iterator
.
Element
==
Int
{
public
func
canFind
(
_
value
:
Iterator
.
Element
)
->
Bool
{
return
contains
(
value
)
}
}
Then you can go ahead and use this method like so:
func
example1
(){
if
[
1
,
3
,
5
,
7
].
canFind
(
5
){
(
"Found it"
)
}
else
{
(
"Could not find it"
)
}
}
As another example, let’s imagine that you want to extend all array types in Swift (Sequence
) that have items that are either double or floating point. It doesn’t matter which method you add to this extension. I am going to add an empty method for now:
extension
Sequence
where
Iterator
.
Element
:
FloatingPoint
{
// write your code here
func
doSomething
(){
//
TODO:
code this
}
}
And you can, of course, use it like so:
func
example2
(){
[
1.1
,
2.2
,
3.3
].
doSomething
()
}
However, if you try to call this method on an array that contains non–floating-point data, you will get a compilation error.
Let me show you another example. Let’s say that you want to extend all arrays that contain only strings, and you want to add a method to this array that can find the longest string. This is how you would do that:
extension
Sequence
where
Iterator
.
Element
==
String
{
var
longestString
:
String
{
var
result
=
""
for
value
in
self
{
if
value
.
characters
.
count
>
result
.
characters
.
count
{
result
=
value
}
}
return
result
}
}
Calling it is as simple as:
func
example3
(){
([
"Foo"
,
"Bar"
,
"Vandad"
].
longestString
}
Build your equality functionality into the protocols to which your types conform. This is the way to go!
Let me give you an example. Let’s say that we have a protocol called Named
:
protocol
Named
{
var
name
:
String
{
get
}
}
We can build the equality functionality into this protocol. We can check the name
property and if the name is the same on both sides, then we are equal:
func
==(
lhs
:
Named
,
rhs
:
Named
)
->
Bool
{
return
lhs
.
name
==
rhs
.
name
}
Now let’s define two types, a car and a motorcycle, and make them conform to this protocol:
struct
Car
{}
struct
Motorcycle
{}
extension
Car
:
Named
{
var
name
:
String
{
return
"Car"
}
}
extension
Motorcycle
:
Named
{
var
name
:
String
{
return
"Motorcycle"
}
}
That was it, really. You can see that I didn’t have to build the equality functionality into Car
and Motorcycle
separately. I built it into the protocol to which both types conform. And then we can use it like so:
func
example1
(){
let
v1
:
Named
=
Car
()
let
v2
:
Named
=
Motorcycle
()
if
v1
==
v2
{
(
"They are equal"
)
}
else
{
(
"They are not equal"
)
}
}
This example will say that the two constants are not equal because one is a car and the other is a motorcycle, but what if we compared two cars?
func
example2
(){
let
v1
:
Named
=
Car
()
let
v2
:
Named
=
Car
()
if
v1
==
v2
{
(
"They are equal"
)
}
else
{
(
"They are not equal"
)
}
}
Bingo. Now they are equal. So instead of building the equality functionality into your types, build them into the protocols that your types conform to and you are good to go.
Use the new for x in y where
syntax, specifying a where
clause right in your for
loop. For instance, here I will go through all the keys and values inside a dictionary and only get the values that are integers:
let
dic
=
[
"name"
:
"Foo"
,
"lastName"
:
"Bar"
,
"age"
:
30
,
"sex"
:
1
,
]
as
[
String
:
Any
]
for
(
k
,
v
)
in
dic
where
v
is
Int
{
(
"The key
(
k
)
contains an integer value of
(
v
)
"
)
}
In older versions of Swift, you’d have to create your conditions before you got to the loop statement—or even worse, if that wasn’t possible and your conditions depended on the items inside the array, you’d have to write the conditions inside the loop. Well, no more.
Here is another example. Let’s say that you want to find all the numbers that are divisible by 8, inside the range of 0 to 1,000, inclusively:
let
nums
=
0.
.<
1000
let
divisibleBy8
=
{
$0
%
8
==
0
}
for
n
in
nums
where
divisibleBy8
(
n
){
(
"
(
n
)
is divisible by 8"
)
}
And of course you can have multiple conditions for a single loop:
let
dic
=
[
"name"
:
"Foo"
,
"lastName"
:
"Bar"
,
"age"
:
30
,
"sex"
:
1
,
]
as
[
String
:
Any
]
for
(
k
,
v
)
in
dic
where
v
is
Int
&&
v
as
!
Int
>
10
{
(
"The key
(
k
)
contains the value of
(
v
)
that is larger than 10"
)
}
Use storyboards while designing your UI, and after you are done, put your code inside an actual class. In IB, you can detach a view so that it is always visible in your playground while you are working on it, and any changes you make will immediately be shown.
Create a single view app and add a new playground to your project, as shown in Figure 5-6.
Write code similar to this to create your view:
import
UIKit
var
view
=
UIView
(
frame
:
CGRect
(
x
:
0
,
y
:
0
,
width
:
300
,
height
:
300
))
view
.
backgroundColor
=
UIColor
.
green
view
.
layer
.
borderColor
=
UIColor
.
blue
.
cgColor
view
.
layer
.
borderWidth
=
10
view
.
layer
.
cornerRadius
=
20
view
Now on the righthand side of the last line of code that you wrote, you should see a + button. Click that (see Figure 5-7).
By clicking that button, you will get a live preview of your view inside your playground. Now you can continue changing your view’s properties and once you are done, add a new preview of your view, so that you can compare the previous and the new states (see Figure 5-8). The first view shown has only the properties you assigned to it up to the point that view was drawn. The second view has more properties, such as the border width and color, even though it is the same view instance in memory. However, because it is drawn at a different time inside IB, it shows different results. This helps you compare how your views look before and after modifications.
Use the fallthrough
syntax. Here is an example:
let
age
=
30
switch
age
{
case
1.
..
10
:
fallthrough
case
20.
..
30
:
(
"Either 1 to 10 or 20 to 30"
)
default
:
(
age
)
}
This is just an example. There are better ways of writing this code than to use fallthrough
. You can indeed batch these two cases together into one case
statement.
In Swift, if you want one case
statement to fall through to the next, you have to explicitly state the fallthrough
command. This is more for the programmers to look at than the compiler, because in many languages the compiler is able to fall through to the next case
statement if you just leave out the break
statement. However, this is a bit tricky because the developer might have just forgotten to place the break
statement at the end of the case
and all of a sudden her app will start behaving really strangely. Swift now makes you request fall-through explicity, which is safer.
Follow these steps:
In the iPhone RTF I’ve written “iPhone Says Hello,” and the iPad one says “iPad Says Hello”; the words iPhone and iPad are bold (attributed texts). I am then going to load these as attributed strings and show them on the user interface (see Figure 5-13).
NSDataAsset
class’s initializer.data
property of your asset to access the data.Place a label on your UI and hook it up to your code under the name lbl
(see Figure 5-12).
Then create an intermediate property that can set your label’s text for you:
import
UIKit
class
ViewController
:
UIViewController
{
@IBOutlet
var
lbl
:
UILabel
!
var
status
=
""
{
didSet
{
lbl
.
text
=
status
}
}
...
When the view is loaded, attempt to load the custom data set:
guard
let
asset
=
NSDataAsset
(
name
:
"rtf"
)
else
{
status
=
"Could not find the data"
return
}
The name of the data asset is specified in the asset catalog (see Figure 5-11).
Because data assets can be of any type (raw data, game levels, etc.), when loading an attributed string we need to specify what type of data we are loading in. We do that using an options dictionary that we pass to NSAttributedString
’s constructor. The important key in this dictionary is NSDocumentTypeDocumentAttribute
, whose value in this case should be NSRTFTextDocumentType
. We can also specify the encoding of our data with the NSCharacterEncodingDocumentAttribute
key:
let
options
=
[
NSDocumentTypeDocumentAttribute
:
NSRTFTextDocumentType
,
NSCharacterEncodingDocumentAttribute
:
String
.
Encoding
.
utf8
.
rawValue
]
as
[
String
:
Any
]
Last but not least, load the data into our string and show it (see Figure 5-13):
do
{
let
str
=
try
NSAttributedString
(
data
:
asset
.
data
,
options
:
options
,
documentAttributes
:
nil
)
lbl
.
attributedText
=
str
}
catch
let
err
{
status
=
"Error =
(
err
)
"
}