Book Image

Learn Swift by Building Applications

By : Emil Atanasov, Giordano Scalzo, Emil Atanasov
Book Image

Learn Swift by Building Applications

By: Emil Atanasov, Giordano Scalzo, Emil Atanasov

Overview of this book

Swift Language is now more powerful than ever; it has introduced new ways to solve old problems and has gone on to become one of the fastest growing popular languages. It is now a de-facto choice for iOS developers and it powers most of the newly released and popular apps. This practical guide will help you to begin your journey with Swift programming through learning how to build iOS apps. You will learn all about basic variables, if clauses, functions, loops, and other core concepts; then structures, classes, and inheritance will be discussed. Next, you’ll dive into developing a weather app that consumes data from the internet and presents information to the user. The final project is more complex, involving creating an Instagram like app that integrates different external libraries. The app also uses CocoaPods as its package dependency manager, to give you a cutting-edge tool to add to your skillset. By the end of the book, you will have learned how to model real-world apps in Swift.
Table of Contents (14 chapters)
5
Adding Interactivity to Your First App

Functions

In this section, we will learn how to define functions and how to use them in our code. They help us to reuse sequences of statements with ease. We can define a general solution of a problem and then apply it, customized, to different parts of our app. This approach saves time, reduces the potential of bugs in the code, and simplifies huge problems to small ones.

The first function, which we already saw in use, is print(). It's used to display text on the screen. We will experiment with this in the next chapter, once we get our hands dirty with Xcode and Swift.

Now let's define our first function, which executes a sequence of statements in its body:

func printSum() {
let a = 3
let b = 4
print("Sum \(a) + \(b) = \(a + b)")
}

When defining a function, we start with the special word func. Then the name of the function follows and the list of the arguments in brackets ( ) and its returned type. After that comes the body of the function in curly braces { }.

The name can start with any letter or underscore and can be followed by a letter, digit, underscore, or dollar sign. A function name shouldn't match any keyword from the Swift language.

This definition doesn't do anything if we don't call (execute) the function. How is this done?

We have to call the function using its own name as follows:

printSum()

Once a function is called, we may think that the same sequence of code is executed where the function call is made. It's not exactly the same, but we can think of having the body of the function executed line by line.

Now let's see the general form of a function:

func functionName(argumentLabel variableName:String) -> String {
let returnedValue = variableName + " was passed"
return returnedValue
}
//here is the function invocation and how the result is returned
let resultOfFunctionCall = functionName(argumentLabel: "Nothing")

Each function may have no arguments, one argument, or many arguments. Until now, we have seen some without arguments and with a single argument. Every argument has an argument label and a parameter name. The argument label is used when the function is called. This is really useful when we have many parameters. It gives us a clue what data should be passed to that specific parameter when using the function. The parameter name (variable name) is the name which will be used in the function body to refer to the passed value. All parameters should have unique parameter names; otherwise there is ambiguity, and we won't be able to say which one is which.

A function may return a value from a specific type, or it may be void (nothing will be returned). When we want to return something, we have to define that, and this is done with -> and the type of the result. In the preceding code, we see -> String, and this means that the function returns a value of the String type. This obliges/binds us to using the keyword return in the function body at least once. The return keyword immediately stops the execution of the function and returns the value passed. If a function doesn't return anything, we can still use return in its body, and it will work similarly to break in a loop.

We can use _ if we want to skip the label of an argument. Here is a simple piece of code that illustrates that:

func concatenateStrings(_ s1:String, _ s2:String) -> String {
return s1 + s2
}
let helloSwift = concatenateStrings("Hello ", "Swift!")
// or
concatenateStrings("Hello ", "Swift!")

When we don't use the _ (underscore), then the argument name is the same as the parameter name (variable name).

Similar to what we have seen with the labels, we can ignore the returned value once the function is called.

What happens if we want to return multiple values? We can use tuples to return multiple values when executing a function. The following code is an example of this:

//define a function which finds the max element and its index in an 
array of integers
func maxItemIndex(numbers:[Int]) -> (item:Int, index:Int) {
var index = -1
var max = Int.min
//use this fancy notation to attach an index to each item
for (i, val) in numbers.enumerated() {
if max < val {
max = val
index = i
}
}

return (max, index)
}

let maxItemTuple = maxItemIndex(numbers: [12, 2, 6, 3, 4, 5, 2, 10])
if maxItemTuple.index >= 0 {
print("Max item is \(maxItemTuple.item).")
}
//prints "Max item is 12."

What is a tuple?

A tuple is a bundle of different types (they may be the same) which have short names. In the preceding code, we have a tuple of two Int statements. The first one is named item, and the second one is named index. After the execution of the function, we will store the maximum item and its index in the tuple. If there are no items in the array, then the index will be -1.

It's possible to return an optional tuple type if there is a chance to return nil in some cases. The previous function may return nil if there are no items, and a valid result otherwise.

Each parameter may have a default value set. To set a default value, you have to declare it and add it right after the parameter's type. The following code is an example of this:

func generateGreeting(greet:String, thing:String = "world") -> String {
return greet + thing + "!"
}

print(generateGreeting(greet: "Hello "))
print(generateGreeting(greet: "Hello ", thing: " Swift 4"))

We can easily define a function which accepts zero or more variables of a specified type. This is called a variadic parameter. Each function definition could have, at most, one variadic parameter. It's denoted with ... after its type. In the body of the function, the type of this parameter is converted to an array. This array contains all passed values:

func maxValue(_ numbers:Int...) -> Int {
var max = Int.min
for v in numbers {
if max < v {
max = v
}
}

return max
}

print(maxValue(1, 2, 3, 4, 5))
//prints 5

One specific thing that we should know about function parameters is that they are constants. We can't mutate these by mistake. We should express this explicitly. To do so, we have to use the special word inout to mark the parameter. The inout parameters is added before the type of the parameter. We can pass variables to the inout parameters, but we can't pass constants. To pass a variable, we should mark this with & when calling the function. The inout parameters can't have default values. Also, variadic parameters can't be marked as such. In general, we can use the inout parameters to return values from a function, but this is not the same as returning values using return. This is an alternative way to let a function affect the outer world in the matrix. Check out the following code:

func updateVar(_ x: inout Int, newValue: Int = 5) {
x = newValue
}

var ten = 10
print(ten)
updateVar(&ten, newValue: 15)
print(ten)

What is the guard statement?

The guard statement has similar behavior to an if statement. This statement checks the condition, and if it's not met, then the else clause is triggered. In the else clause, the developer should finish the current function or program, because the prerequisites won't be met. Take a look at the following code:

func generateGreeting(_ greeting: String?) -> String {
guard let greeting = greeting else {
//there is no greeting, we return something and finish
return "No greeting :("
}
//there is a greeting and we generate a greeting message
return greeting + " Swift 4!"
}

print(generateGreeting(nil))
print(generateGreeting("Hey"))

This is a tiny example, showing us code that illustrates the regular usage of the guard statement. We can combine it with the where clause, or we can make the check really complex. Usually, it's used when the code depends on several if...let checks. The guard statement keeps the code concise.

How to tackle huge problems – bottom-up versus top-down

Step-by-step through this book, we will start solving problems until we can write a fully-working mobile app. It's not an easy task, but we can take two different approaches when trying to solve a huge problem (such as writing a mobile app). The first one is top-down. This technique starts from the top with the main problem, and breaks it down into smaller problems and functions. Once we reach something unclear, something which is not well defined that we should implement, then we define a new function, but we won't continue developing the exact implementation of this part of the app immediately. Let's assume that we are trying to develop a mobile app with three screens. The first one displays a list of news. The second one renders specific news, and the last one shows information about our application.

If we apply the top-down approach, then we will have the following abstract process. We start from the biggest problem: how to develop an app with three screens. Then, we break this huge task down into three sub-tasks with their respective functions. Those functions are empty functions. The first one will be responsible for creating the first screen, the second one should create the detailed news presentation, and the third should define the last screen. By doing this, we have decomposed the main problem into three smaller ones. These new functions are empty, but at a later phase we will implement each one of them. We can start with the first one: we define another help function which creates the list of news, and another function which fetches the news from an internet address. Now it doesn't look really hard to define those functions. We will learn how to do this throughout the book, but the general idea is to break down each problem into smaller ones until you reach a state where you can solve them without any hassle. In the end, the main problem will be solved, because all parts that have been decomposed are already working, and the final result will be a fully-working mobile application.

The other approach is bottom-up, which does things in reverse. It's more like working with Lego, but you first go and build many small building blocks, which you combine together until you manage to build a solution to the whole problem; in our case, until you build a working mobile app. Abstractly, we develop simple enough functions that we can implement to solve small problems. Then we combine those into bigger chunks. Those bigger chunks are put together in even bigger and more complex functions or app parts, until we define the final working app.

Neither of these two approaches is the best. Every developer prefers to use a nice mixture of both techniques, which leads to the final result—a working app.


If top-down, or bottom-up, is used on its own, it is not a silver bullet. Try to use top-down and bottom-up together and you will find the solution easier.

Just tweak your approach based on what you know at the moment, and what you have.