Skip to main content

Day 23

About 8 minSwiftcrashcoursepaul-hudsonswifthacking-with-swiftxcodeappstore

Day 23 관련


100 Days of Swift - Day 23

Milestone: Projects 1-3

Milestone: Projects 1-3

It’s time for another consolidation day, because we’ve covered a lot of ground in the first three topics and it’s important you review them if you want them to stick in your head.

However, this will also be the first day you’re asked to create a complete app from scratch. Don’t worry: I outline all the components required to make it work, and also provide hints to give you a head start.

As you’ll see, creating an app from scratch is a very different experience to adding modifications to an existing app: you get blank page paralysis, which is where your brain knows where you want to get to but you’re just not sure how to start.

A common reason to get stuck is that folks try to write flawless code first time. As Margaret Atwood once said, “if I waited for perfection, I would never write a word.” So, dive in and see where you get – these milestone challenges will help you learn to get comfortable starting fresh projects, and to get real functionality up and running quickly.

Today you have three topics to work through, one of which of is your challenge.

What you learned

What you learned
100 Days of Swift - Day 23 - What you learned

What you learned

You’ve made your first two projects now, and completed a technique project too – this same cadence of app, game, technique is used all the way up to project 30, and you’ll start to settle into it as time goes by.

Both the app and the game were built with UIKit – something we’ll continue for two more milestones – so that you can really start to understand how the basics of view controllers work. These really are a fundamental part of any iOS app, so the more experience I can give you with them the better.

At this point you’re starting to put your Swift knowledge into practice with real context: actual, hands-on projects. Because most iOS apps are visual, that means you’ve started to use lots of things from UIKit, not least:

  • Table views using UITableView. These are used everywhere in iOS, and are one of the most important components on the entire platform.
  • Image views using UIImageView, as well as the data inside them, UIImage. Remember: a UIImage contains the pixels, but a UIImageView displays them – i.e., positions and sizes them. You also saw how iOS handles retina and retina HD screens using @2x and @3x filenames.
  • Buttons using UIButton. These don’t have any special design in iOS by default – they just look like labels, really. But they do respond when tapped, so you can take some action.
  • View controllers using UIViewController. These give you all the fundamental display technology required to show one screen, including things like rotation and multi-tasking on iPad.
  • System alerts using UIAlertController. We used this to show a score in project 2, but it’s helpful for any time you need the user to confirm something or make a choice.
  • Navigation bar buttons using UIBarButtonItem. We used the built-in action icon, but there are lots of others to choose from, and you can use your own custom text if you prefer.
  • Colors using UIColor, which we used to outline the flags with a black border. There are lots of built-in system colors to choose from, but you can also create your own.
  • Sharing content to Facebook and Twitter using UIActivityViewController. This same class also handles printing, saving images to the photo library, and more.

One thing that might be confusing for you is the relationship between CALayer and UIView, and CGColor and UIColor. I’ve tried to describe them as being “lower level” and “higher level”, which may or may not help. Put simply, you’ve seen how you can create apps by building on top of Apple’s APIs, and that’s exactly how Apple works too: their most advanced things like UIView are built on top of lower-level things like CALayer. Some times you need to reach down to these lower-level concept, but most of the time you’ll stay in UIKit.

If you’re concerned that you won’t know when to use UIKit or when to use one of the lower-level alternatives, don’t worry: if you try to use a UIColor when Xcode expects a CGColor, it will tell you!

Both projects 1 and 2 worked extensively in Interface Builder, which is a running theme in this series: although you can do things in code, it’s generally preferable not to. Not only does this mean you can see exactly how your layout will look when previewed across various device types, but you also open the opportunity for specialized designers to come in and adjust your layouts without touching your code. Trust me on this: keeping your UI and code separate is A Good Thing.

Three important Interface Builder things you’ve met so far are:

  1. Storyboards, edited using Interface Builder, but used in code too by setting storyboard identifiers.
  2. Outlets and action from Interface Builder. Outlets connect views to named properties (e.g. imageView), and actions connect them to methods that get run, e.g. buttonTapped().
  3. Auto Layout to create rules for how elements of your interface should be positioned relative to each other.

You’ll be using Interface Builder a lot throughout this series. Sometimes we’ll make interfaces in code, but only when needed and always with good reason.

There are three other things I want to touch on briefly, because they are important.

First, you met the Bundle class, which lets you use any assets you build into your projects, like images and text files. We used that to get the list of NSSL JPEGs in project 1, but we’ll use it again in future projects.

Second, loading those NSSL JPEGs was done by scanning the app bundle using the FileManager class, which lets you read and write to the iOS filesystem. We used it to scan directories, but it can also check if a file exists, delete things, copy things, and more.

Finally, you learned how to generate truly random numbers using Swift’s Int.random(in:) method. Swift has lots of other functionality for randomness that we’ll be looking at in future projects.

Key points

Key points
100 Days of Swift - Day 23 - Key points

Key points

There are five important pieces of code that are important enough they warrant some revision. First, this line:

let items = try! fm.contentsOfDirectory(atPath: path)

The fm was a reference to FileManager and path was a reference to the resource path from Bundle, so that line pulled out an array of files at the directory where our app’s resources lived. But do you remember why the try! was needed?

When you ask for the contents of a directory, it’s possible – although hopefully unlikely! – that the directory doesn’t actually exist. Maybe you meant to look in a directory called “files” but accidentally wrote “file”. In this situation, the contentsOfDirectory() call will fail, and Swift will throw an exception – it will literally refuse to continue running your code until you handle the error.

This is important, because handling the error allows your app to behave well even when things go wrong. But in this case we got the path straight from iOS – we didn’t type it in by hand, so if reading from our own app’s bundle doesn’t work then clearly something is very wrong indeed.

Swift requires any calls that might fail to be called using the try keyword, which forces you to add code to catch any errors that might result. However, because we know this code will work – it can’t possibly be mistyped – we can use the try! keyword, which means “don’t make me catch errors, because they won’t happen.” Of course, if you’re wrong – if errors do happen – then your app will crash, so be careful!

The second piece of code I’d like to look at is this method:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return pictures.count
}

That was used in project 1 to make the table view show as many rows as necessary for the pictures array, but it packs a lot into a small amount of space.

To recap, we used the Single View App template when creating project 1, which gave us a single UIViewController subclass called simply ViewController. To make it use a table instead, we changed ViewController so that it was based on UITableViewController, which provides default answers to lots of questions: how many sections are there? How many rows? What’s in each row? What happens when a row is tapped? And so on.

Clearly we don’t want the default answers to each of those questions, because our app needs to specify how many rows it wants based on its own data. And that’s where the override keyword comes in: it means “I know there’s a default answer to this question, but I want to provide a new one.” The “question” in this case is “numberOfRowsInSection”, which expects to receive an Int back: how many rows should there be?

The last two pieces of code I want to look at again are these:

let cell = tableView.dequeueReusableCell(withIdentifier: "Picture", for: indexPath)

if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
}

Both of these are responsible for linking code to storyboard information using an identifier string. In the former case, it’s a cell reuse identifier; in the latter, it’s a view controller’s storyboard identifier. You always need to use the same name in Interface Builder and your code – if you don’t, you’ll get a crash because iOS doesn’t know what to do.

The second of those pieces of code is particularly interesting, because of the if let and as? DetailViewController. When we dequeued the table view cell, we used the built-in “Basic” style – we didn’t need to use a custom class to work with it, so we could just crack on and set its text.

However, the detail view controller has its own custom thing we need to work with: the selectedImage string. That doesn’t exist on a regular UIViewController, and that’s what the instantiateViewController() method returns – it doesn’t know (or care) what’s inside the storyboard, after all. That’s where the if let and as? typecast comes in: it means “I want to treat this is a DetailViewController so please try and convert it to one.”

Only if that conversion works will the code inside the braces execute – and that’s why we can access the selectedImage property safely.

Challenge

Challenge
100 Days of Swift - Day 23 - Challenge

Challenge

You have a rudimentary understanding of table views, image views, and navigation controllers, so let’s put them together: your challenge is to create an app that lists various world flags in a table view. When one of them is tapped, slide in a detail view controller that contains an image view, showing the same flag full size. On the detail view controller, add an action button that lets the user share the flag picture and country name using UIActivityViewController.

To solve this challenge you’ll need to draw on skills you learned in tutorials 1, 2, and 3:

  1. Start with a Single View App template, then change its main ViewController class so that builds on UITableViewController instead.
  2. Load the list of available flags from the app bundle. You can type them directly into the code if you want, but it’s preferable not to.
  3. Create a new Cocoa Touch Class responsible for the detail view controller, and give it properties for its image view and the image to load.
  4. You’ll also need to adjust your storyboard to include the detail view controller, including using Auto Layout to pin its image view correctly.
  5. You will need to use UIActivityViewController to share your flag.

As always, I’m going to provide some hints below, but I suggest you try to complete as much of the challenge as you can before reading them.

Hints:

  • To load the images from disk you need to use three lines of code: let fm = FileManager.default, then let path = Bundle.main.resourcePath!, then finally let items = try! fm.contentsOfDirectory(atPath: path).
  • Those lines end up giving you an array of all items in your app’s bundle, but you only want the pictures, so you’ll need to use something like the hasSuffix() method.
  • Once you have made ViewController build on UITableViewController, you’ll need to override its numberOfRowsInSection and cellForRowAt methods.
  • You’ll need to assign a cell prototype identifier in Interface Builder, such as “Country”. You can then dequeue cells of that type using tableView.dequeueReusableCell(withIdentifier: "Country", for: indexPath).
  • The didSelectItemAt method is responsible for taking some action when the user taps a row.
  • Make sure your detail view controller has a property for the image name to load, as well as the UIImageView to load it into. The former should be modified from ViewController inside didSelectItemAt; the latter should be modified in the viewDidLoad() method of your detail view controller.

Bonus tip: try setting the imageView property of the table view cell. Yes, they have one. And yes, it automatically places an image right there in the table view cell – it makes a great preview for every country.

Note

Don’t worry if you don’t complete challenges in the day they were assigned – in future days you’ll find you have some time to spare here and there, so challenges are something you can return back to in the future.


이찬희 (MarkiiimarK)
Never Stop Learning.