Ultimate Portfolio App

Learn to build one complete app using SwiftUI, Core Data, CloudKit, StoreKit, Widgets, Spotlight, and more.

Part 1: Building the core app

The first step of our project will be to build the core of the app itself, providing all the fundamental features. Everyone needs to follow these steps.


Ultimate Portfolio App: Introduction


1. Ultimate Portfolio App: Introduction

While I’m sure you’re keen to get started programming immediately, please give me a few minutes to outline the goals of this course and explain why it’s different from other courses I’ve written.

Setting up the basics

Designing a great model


2. Designing a great model

Almost always, the key to getting a great app is getting a great data model – deciding as early as you can what data you want to store, and how each piece relates to other pieces. So, we’re going to dive straight into Core Data!

First steps in UI


3. First steps in UI

Now that we have our basic data model configured and coded, we can put it to use by building a simple user interface to help make sure our data is in place and working correctly.

Storing your code somewhere safe


4. Storing your code somewhere safe

At this point we have something very simple working, so now is a great time to stash your code away somewhere safe using source control. If you already know how to use Git then you’re welcome to skip this part.

Cleaning up Core Data


5. Cleaning up Core Data

Core Data’s optionals are quite different from Swift’s optionals, which makes them a little uncomfortable to work with. In this article I’m going to show you two ways of fixing this, which will help clear up our code nicely.

Storing tab selection


6. Storing tab selection

Although apps can live in the background for quite a while, eventually they will be terminated. But when a user relaunches them, it’s a good idea to bring them roughly back to where they were, and with state restoration we can do just that.

Editing our data

Editing items


7. Editing items

Our next step is to build a simple form so the user can edit items, which in itself isn’t too hard. However, along the way I want to show you some useful tips for tracking changes and updating the UI – it’s not quite as straightforward as you might expect.

Instant sync and save


8. Instant sync and save

Rather than calling update() when our view disappears, what we really want to do is update the object before the disappear happens. In this article I’ll show you the SwiftUI native way of doing this, then walk you through an alternative that I prefer.

Adding a section header


9. Adding a section header

Now that item editing works well, we can add a screen to edit projects. But before we can even think about that we need to add a custom section header to let users select a project to edit.

Editing projects


10. Editing projects

Editing projects is much like editing items, but because a good portfolio project should show off a range of skills we’re going to bring in grids, alerts, tap gestures, and more.

Questions and answers, part 1


11. Questions and answers, part 1

As folks have worked their way through the series so far, they’ve sent in various questions about implementation choices and more. In this article I want to address the eight most common questions asked so far, so that everyone can benefit.

Filling out the functionality

Adding and deleting


12. Adding and deleting

At this point you can get a basic idea for the UI of our app, but it has a fatal flaw: although we can add test data, we can’t do the same for user data. Let’s fix that now – there’s more to it than you might think!

Custom sorting for items


13. Custom sorting for items

Our default sort for project items works well enough, but with a little extra work we can let users choose how to sort them. In this article I’m going to walk you through several different ways of approaching this problem, some that I think work well, and others not so much…

Finishing ProjectsView


14. Finishing ProjectsView

At this point our main list view is almost done, but before we’re finished we’re going to add some icons, make landscape mode work better, and even fix a rather nasty deletion bug.

Reading awards JSON


15. Reading awards JSON

Parsing data into your app is the single most common task any iOS developer needs to do, so in this article we’ll start to build out an Awards tab using JSON.

Counting Core Data results


16. Counting Core Data results

Now that we’ve designed a basic awards UI, we can bring it to life with some Core Data queries to determine which awards have actually been earned.

Creating the Home view


17. Creating the Home view

So far our home view has simply been a host for adding test data, but that changes now: we’re going to make the home view a summary of all their project progress, plus the most important items coming up next.

Beginning the clean up

Now for the real work…


18. Now for the real work…

Everything we’ve done so far has produced a serviceable app, although it does have a few bugs that we’ll address later. But before we address those, I want to change gear and focus on making our existing code better. This is where the real work begins!

Internationalization and localization


19. Internationalization and localization

Our app was designed to work in English, and although you might not want to change that your should at least be able to change. Let’s start with that…

Making your app accessible


20. Making your app accessible

It is my firm belief that every iOS app should be usable to everyone, and putting in the work to make your app function well no matter who is using it says a lot about the kind of developer you are.

Cleaning up view code


21. Cleaning up view code

Although our SwiftUI layouts conform to the View protocol, if you were to try to think about them in MVC terms I’d say they were more like controllers. And like controllers from UIKit, we need to put in some work to keep SwiftUI views lean – let’s look at this now…

Questions and answers, part 2


22. Questions and answers, part 2

I’ve had a whole bunch more questions sent in from readers, covering Core Data, property wrappers, localization, and more, so let’s dive into them with some code examples.

Core Data delete rules and predicates


23. Core Data delete rules and predicates

One of the least obvious but most important clean ups lies in our use of Core Data, because right now we’re leaking data and also showing flat out wrong data. To fix these we need to use some more advanced Core Data, so let’s get into it…

Linting our code


24. Linting our code

Code is designed to be read far more often than it is written, and one of the simplest ways of making code easier to read is to make it uniform – to make sure your code follows a simple style, so your brain can focus less on spacing and naming and more on understanding how the code actually works.

Documenting our code


25. Documenting our code

Good documentation describes not only what code does, but provides context on why it works a certain way, what assumptions you made, any optimizations you made, as well as describing subtleties in the implementation if you’re dealing with difficult code. In this article we’re going to be documenting our project for other developers and beyond!

Organizing the project itself


26. Organizing the project itself

We have one last easy task before we look at something trickier, which is to organize the Xcode project itself. Here I’m going to show you two different approaches so you can contrast them yourself, then explain which I prefer and why.

Testing our project

Testing the basics


27. Testing the basics

At last it’s time to start writing tests for our project, which means a little bit of setup work backed by writing our first couple of tests – we’ll take this slow initially, but lay down a good foundation for future tests.

Testing Core Data


28. Testing Core Data

Our app relies extensively on user data, so if there’s one part of it that absolutely must be bullet proof it’s our Core Data stack. In this article we’ll explore writing tests for our data, including projects, items, and awards.

Testing development data


29. Testing development data

In previous tests we relied upon our sample data creating 5 projects and 50 items, but that isn’t set in stone right now – it’s an implementation detail, meaning that it’s a behavior that happens to be the case but isn’t explicitly guaranteed. This is a common cause of bugs, so in this article we’re going to write tests for our development code, and along the way discover and resolve some interesting quirks…

Testing extensions


30. Testing extensions

Our project has three extensions that add functionality to Apple’s own code, and we need to treat these carefully – we don’t own them, so their behavior might change at any point in the future. So, in this article we’re going to write tests for the extensions we created, making sure they work correctly every time.

Measuring performance


31. Measuring performance

Although our project doesn’t do anything particularly performance intensive, having a great portfolio app means you should at least attempt to demonstrate you understand how performance tests work. In this chapter we’ll add just such a test to our project, to make sure our award counting work is fast.

UI Testing with SwiftUI


32. UI Testing with SwiftUI

Even after writing stacks of unit tests, chances are your test coverage is still well below 40%. Those units tests are really important, but if you really want great test coverage you need to add some UI tests and that’s exactly what we’re going to work on here.

Pragmatic MVVM

Bringing MVVM into our SwiftUI project, part 1


33. Bringing MVVM into our SwiftUI project, part 1

The final major change we’re going to make to our project is to look at how it fits in with the MVVM design pattern. I left this one to last because it’s quite a jump from our previous work and in some respects SwiftUI even fights against it, but I do think it’s worth exploring so you can be sure your code is sound.

Bringing MVVM into our SwiftUI project, part 2


34. Bringing MVVM into our SwiftUI project, part 2

In this article we’re going to continue with our move towards MVVM, this time converting another view that works well, but also looking at code that works less well so you can get a better idea of how SwiftUI and MVVM really work.

Final tweaks

Wrap up, part one


35. Wrap up, part one

We’ve just put in place the last major code to complete part one of this app. Let’s clean up just a tiny bit, then look over what we’ve made so far.

Part 2: Integrating with the system

Once our core app is built, we can explore optional extras such as integrating with StoreKit, Apple Pay, and more. These steps are optional and you can follow any of them in any order you want.

System integration

Adding haptics


36. Adding haptics

In this article I’m going to walk you through adding haptics to your app, to make it feel a little more alive in the user’s hand.

Integrating with Spotlight


37. Integrating with Spotlight

In this article we’re going to make Spotlight store our app’s data, meaning that the user can search for items right from their iOS Home Screen. If you intend to follow the Widget or shortcut sections of this course later on, you should follow this article first.

Adding local notifications


38. Adding local notifications

Local notifications allow us to set reminders for the user without ever needing to send data off the device. In this article we’re going to add these notifications to our app, so that users can be reminded to work on specific projects.

Offering in-app purchases, part 1


39. Offering in-app purchases, part 1

Although many apps work great when paid for up front, many more work better when using a freemium model – you get lots of downloads of a free app, then charge for some kind of premium service. In this article we’re going to prepare to limit our app unless the user has paid for an unlock, but we’ll be using a flexible approach you can adapt easily.

Offering in-app purchases, part 2


40. Offering in-app purchases, part 2

Previously we added all the back-end work to make in-app purchases possible in our app. In this article we’re going to continue that work by implementing the user interface for our store, limiting the app itself, and also asking for user reviews.

Questions and answers, part 3


41. Questions and answers, part 3

I’ve had a whole bunch more questions sent in from readers, covering testing, haptics, Spotlight, and more, so let’s dive into them with code examples.

Adding a Quick Action to our icon


42. Adding a Quick Action to our icon

Quick Actions let users long-press on our app’s icon on the Home Screen to show a list of actions they can perform immediately. In this article we’re going to add a quick action to create a new project in one step – it’s more work than you might think!

Integrating with Shortcuts


43. Integrating with Shortcuts

Shortcuts let users access quick commands from our app elsewhere in the system, as well as chaining them to build complex commands, or even asking Siri to trigger one directly. In this article we’re going to add one to our app, and I think you’ll be amazed how little work it takes!

Creating a simple widget


44. Creating a simple widget

Widgets allow users to place parts of our app right on their Home Screen, which is both deeply powerful and deeply personal. In this article we’re going to start by doing all the app configuration required to make widgets function, then add a simple widget for our app to show that everything works.

Creating a second widget


45. Creating a second widget

In this follow-on article we’re going to up our widget game by adding a second, more complex widget and exploring some configuration options that help our widgets work better on-screen.

Connecting to Apple

Upgrading iCloud


46. Upgrading iCloud

We’re about to do a fair chunk of work integrating CloudKit, Sign in with Apple, and Apple Pay into our app, but the first step to success is to make a handful of small changes to our app to ensure it will all work well, and also fix a few long-standing bugs.

Storing data in iCloud


47. Storing data in iCloud

Now that our project is all ready for expansion, our first step will be to let users upload projects to iCloud so later on other users can view them and even comment on them. We’ll approach this in a simple way at first, but we’ll come back for improvements later.

Querying data from iCloud


48. Querying data from iCloud

In the previous step we added the ability to upload projects and items to iCloud, then used the iCloud Dashboard to check the data had arrived safely. In this step we’re going to load shared projects, and let users browse them.

Adding Sign in with Apple


49. Adding Sign in with Apple

We’re attaching an owner name to projects, but right now it’s always hard-coded to “TwoStraws”. In this step we’re going to fix that using Sign in with Apple, which authenticates users securely. This needs to be done carefully, but the end result is really nice as you’ll see!

Posting comments through CloudKit


50. Posting comments through CloudKit

The last major piece of CloudKit work we’re going to add will let users post comments on shared projects – hopefully encouraging ones! This will combine querying and writing CloudKit data in a single part of our app, and also demonstrate how to write single records rather than several at once.

Cleaning up CloudKit, part 1


51. Cleaning up CloudKit, part 1

We’ve added quite a bit of iCloud functionality in the last few tutorials, so before we’re done we need to clean up what we have so it’s a solid foundation we can build on. To start with, that means tackling removing data as well as we handling adding data.

Cleaning up CloudKit, part 2


52. Cleaning up CloudKit, part 2

The second part of cleaning up CloudKit involves tackling error handling head on, and along the way I’ll show you a useful trick for making this process easier. I’ve said it before, but it bears repeating that getting error handling right is the key to a great CloudKit app!

Cleaning up CloudKit, part 3


53. Cleaning up CloudKit, part 3

The last part of cleaning up CloudKit involves upgrading our Awards to include chat messages, updating our localization to include all the new UI we’ve added, and fixing a small SwiftUI bug – just enough to leave CloudKit in good shape before we move on.

Part 3: Going cross-platform

The final step of our project will be to see how we can take our code to macOS, tvOS, and even watchOS. These are best watched in sequence, to avoid confusion.


Moving over to macOS


54. Moving over to macOS

Here’s where things start to get really interesting: taking the code we wrote and making it cross-platform. We’ll ultimately be porting to macOS, tvOS, and watchOS, but no matter which platform you want to build for you’ll need to follow this part as we do a bunch of important set up work.


Cleaning up Awards


55. Cleaning up Awards

To kick off the process of cleaning up our code to work well on macOS, we’re going to tackle the easiest one of our views: Awards. This means fixing up its navigation and button styles so it looks and feels great on Mac.

Making projects and items feel at home on the Mac


56. Making projects and items feel at home on the Mac

Now we need to turn our eyes to the first significant piece of work porting to macOS: adjusting the Open and Closed tabs so they look and work better on macOS. This means adding some Mac-only modifiers and views, but it gives us a big step forward as you’ll see.

Time to start forking


57. Time to start forking

So far we’ve tried very hard to look for ways we can implement platform adjustments and workarounds to enable maximum code reuse between iOS and macOS, but now we need to go further because getting the Home view to work requires something quite custom.