Making a WatchOS app with SwiftUI from scratch, with data fetching

Fer Cervantes Alvarez
Mac O’Clock
Published in
19 min readMay 6, 2020
Image by Fabian Albert, https://unsplash.com/photos/bl_NUhqLC8o

Hi everyone!

In this post, I’ll guide you step by step on creating a WatchOS app using SwiftUI and a couple of libraries to handle network requests and JSON responses. In 35 minutes, we will have a beautiful and functional, independent app for your Apple Watch.

Prerequisites

  • Xcode 11 (You can download it from the Mac App Store)
  • Cocoapods (https://guides.cocoapods.org/using/getting-started.html)
  • An API key for using NewsAPI.org (Get yours for free from https://newsapi.org/)
  • Visual Studio Code or some text editor if you’re unfamiliar with terminal-based code editors.

For previewing your App

  • iPhone
  • Apple Watch
  • Lightning cable to connect your iPhone to your Mac

We will create an app that fetches data from NewsAPI (https://newsapi.org/) and displays the different articles we fetch in a list, so we can tap and read some bits of news on the go.

We’ll be using the AlamoFire library (https://github.com/Alamofire/Alamofire) for making network requests quickly, as well as SwiftyJSON (https://github.com/SwiftyJSON/SwiftyJSON) to handle the response easily and turn it into usable elements on our UI, and KingFisher (https://github.com/onevcat/Kingfisher/wiki/Installation-Guide) to download and cache images easily on to our App.

So, if you have all the prerequisites ready, let’s begin!

To start, open Xcode and tap on Create a new Xcode project.

On the WatchOS tab, select Watch App

I’ll name my App WristNews, but feel free to call it however you want! Make sure you have Swift as Language and SwiftUI as User Interface and save it in an easy to access directory since we’ll access to it later via the terminal.

Now that we created our App, let’s jump to code!

On the right side, tap on the Resume button to quickly build your App and trigger the preview on the canvas.

Now, you have the code on the left side and the preview on the right side. We will edit code and rebuild the preview so we can see our App come to life with each step.

Replace the “Hello, World” text on the text element by “WristNews” and add the .font(.title) and .fontWeight(.thin) modifiers at the end.

import SwiftUIstruct ContentView: View {
var body: some View {
Text("WristNews")
.font(.title)
.fontWeight(.thin)

}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Notice how the preview updates as you change the code. You can also tap on each element in the preview to get a visual interface to make changes to your UI. This interface to the right side is called The Inspector.

Wrap your Text element in a VStack. A VStack is a container that stacks elements vertically. It is useful for keeping consistency and order in your UI. It also will let you apply alignment and padding properties to the whole container instead of trying to accommodate each element on the screen manually.

import SwiftUIstruct ContentView: View {
var body: some View {
VStack{
Text("WristNews")
.font(.title)
.fontWeight(.thin)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

After having done this, let’s add some more elements to our View.

Let’s add another Text element below the one we already have.

import SwiftUIstruct ContentView: View {
var body: some View {
VStack{
Text("WristNews")
.font(.title)
.fontWeight(.thin)

Text("Daily news, delivered on your wrist.")
.fontWeight(.thin)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Now, we’re going to use the inspector to make a change in our UI.

Tap on any place of the word VStack, and notice how a blue box appears around the Text elements on your preview, as well as the inspector populating with different options. Now, tap on the second to last button on the alignment options to align the content on our VStack to the left.

Then, on your preview, tap the second Text we added, and apply a left alignment to it as well, so our preview ends up looking like this:

Now, we’re going to create another view that takes us to the List of articles.

Lookin’ good!

Press CMD+N to create a new file, and select SwiftUI View from the List of options.

Let’s save the View with the name ArticlesView, and make sure that it’s saved on the WristNews WatchKit Extension Group, as well as the same Target checked. It must look like on the image below, except with ArticlesView instead of SwiftUIView.

I took the screenshot before changing the file name, sorry!

Replace the “Hello World” text with “Articles” and wrap it in a VStack. We may remove it later, but let’s leave it like that for now. We’ll come back to this file later.

import SwiftUIstruct ArticlesView: View {
var body: some View {
VStack{
Text("Articles")
}

}
}
struct ArticlesView_Previews: PreviewProvider {
static var previews: some View {
ArticlesView()
}
}

Now, before we start adding articles, we first need to define what an article is, and what will it contain, in order to tell the UI what to display.

This is a sample of the data we’re going to get from NewsAPI, so we’ll base on this sample to define the Article object.

{
"status": "ok",
"totalResults": 38,
"articles": [
{
"source": {
"id": null,
"name": "Vogue.com"
},
"author": "Estelle Tang",
"title": "Rosalía Shares Her All-Time Favorite Songs in a Met Gala–Themed Playlist - Vogue",
"description": "\"This playlist is a celebratory playlist for the Met, with nods to my all-time favorite songs, sounds, artists, and references. It has not been possible to celebrate the Met as God intended, but we can celebrate it in our own way from home.\"",
"url": "https://www.vogue.com/article/rosalia-met-gala-about-time-playlist",
"urlToImage": "https://assets.vogue.com/photos/5eb0a75cd5f359c964b7e0e4/16:9/w_1280,c_limit/ GettyImages-1162355675.jpg",
"publishedAt": "2020-05-05T00:20:29Z",
"content": "To commemorate a very different first Monday in May this year, Vogue asked some of our favorite stars to create a playlist featuring timeless songs inspired by the theme of this year's Met gala and the upcoming Costume Institute exhibition, About Time: Fashio… [+2158 chars]"
},
]
}

Create a new file, but this time, select Swift File from the List (it should already be selected) and name it Article.

On this file, we’ll start by defining the Article struct like this:

import Foundationstruct Article: Hashable, Identifiable {
public var id: String
public var title: String
public var description: String
public var author: String
public var link: String
public var imageLink: String
public var publishedAt: String
public var content: String
}

Each Article will contain:

  • A unique identifier
  • A Title
  • A short description or headline
  • The author’s name
  • A link to the article on its source
  • A link to the headline image
  • Time and date of publication
  • A short snippet of the content (unless you paid for the NewsAPI service)

If you paid for the NewsAPI service, you would be able to get the full article content here.

Now, we’re going to add the initializer method, which will let us create Article objects and represent them visually.

import Foundationstruct Article: Hashable, Identifiable {
public var id: String
public var title: String
public var description: String
public var author: String
public var link: String
public var imageLink: String
public var publishedAt: String
public var content: String

init(title: String, description: String, author: String, link: String, imageLink: String, publishedAt: String, content:String){
self.id = UUID().uuidString
self.title = title
self.description = description
self.author = author
self.link = link
self.imageLink = imageLink
self.publishedAt = publishedAt
self.content = content
}

}

The initializer method takes everything that an Article requires and transforms it into a usable object.

On this moment, we will proceed to install the libraries we will require for our App, so save and close Xcode, and open your terminal

cd into the folder of your App, and on the root folder (where your .xcodeproj file is), run the following command

pod init

This will create a new file called Podfile in your root folder. Now open that file with your preferred text editor, and add the following lines under

# Pods for WristNews WatchKit Extension

And add a # before all the use_frameworks! statements. It should look like this:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'WristNews' do
# Comment the next line if you don't want to use dynamic frameworks
#use_frameworks!
# Pods for WristNewsendtarget 'WristNews WatchKit App' do
# Comment the next line if you don't want to use dynamic frameworks
#use_frameworks!
# Pods for WristNews WatchKit Appendtarget 'WristNews WatchKit Extension' do
# Comment the next line if you don't want to use dynamic frameworks
#use_frameworks!
# Pods for WristNews WatchKit Extension
pod ‘Kingfisher/SwiftUI’, ‘~> 5.0’
pod 'Alamofire', '~> 5.1'
pod 'SwiftyJSON', '~> 4.0'

end

You should grab those three lines from their respective webpages (links on the top), since they may have changed by the time you check this tutorial.

Save and close the file, and on the terminal, enter:

pod install

And wait for it to finish. After that, to open this folder in Finder, enter:

open .

You will notice we now have a new file that ends in .xcworkspace, and we will continue with that file from now on.

Before you open it, make sure all instances of Xcode are closed. You can now close the terminal.

On the project navigator, you will find your working files under WristNews > WristNews WatchKit Extension.

New file structure

Now, we’re going to create a UI element that represents an Article.

Create a new file, select SwiftUI View from the List, and name it ArticleRow.

Since we’re going to use the KingFisher library here to fetch our image, let’s import it at the top with the statement:

import struct Kingfisher.KFImage

Add a variable called article of type Article on to our View. This tells SwiftUI that this View requires an Article object to work.

struct ArticleRow: View {
var article: Article

var body: some View {
Text("Hello, World!")
}
}

This, of course, will trigger an error on the preview struct below, since we have to provide an Article to the View to work with.

You can remove the ArticleRow_Previews struct and replace it by copying and pasting this one, since its too much to type by hand:

struct ArticleRow_Previews: PreviewProvider {
static var previews: some View {
ArticleRow(article: Article(
title: "Rosalía Shares Her All-Time Favorite Songs in a Met Gala–Themed Playlist - Vogue",
description: "This playlist is a celebratory playlist for the Met, with nods to my all-time favorite songs, sounds, artists, and references. It has not been possible to celebrate the Met as God intended, but we can celebrate it in our own way from home.",
author: "Estelle Tang",
link:"https://www.vogue.com/article/rosalia-met-gala-about-time-playlist",
imageLink: "https://assets.vogue.com/photos/5eb0a75cd5f359c964b7e0e4/16:9/w_1280,c_limit/ GettyImages-1162355675.jpg",
publishedAt: "2020-05-05T00:20:29Z",
content: "To commemorate a very different first Monday in May this year, Vogue asked some of our favorite stars to create a playlist featuring timeless songs inspired by the theme of this year's Met gala and the upcoming Costume Institute exhibition, About Time: Fashio… [+2158 chars]"
))
}
}

Now, we will take some elements from there to form our Article Row

Wrap the Text element in an HStack and replace the “Hello, World!” string with article.title in order to use the title of the provided article

var body: some View {
HStack{
Text(article.title)
}
}

Let’s add some modifiers to make the text occupy as small as possible, so we can fit more text into our ArticleRow. You can add these via code or via the inspector. If you use the inspector, you should choose the following options:

Otherwise, if you’re adding the modifiers directly on the code, it should end up like this

Text(article.title)
.font(.caption)
.fontWeight(.ultraLight)
.lineLimit(2)

And now, we’re going to add a small thumbnail image to the left side of our row, so this will leave less space for the article title, but having reduced the text size will help here.

We’re going to add a KFImage object, which takes a URL object in order to download the image. The resizable() and .frame modifiers will provide an optimal size for the UI element.

HStack{
KFImage(URL(string: article.imageLink))
.resizable()
.frame(width: 50, height: 50)


Text(article.title)
.font(.caption)
.fontWeight(.ultraLight)
.lineLimit(2)
}

You may notice that the image isn’t being shown on the preview, no worries, when we push the image to our watch, we will be able to see it. In the meantime, let’s add a placeholder, so we at least have some visual cue to indicate that the image is being downloaded, as well as some style touches, like .cornerRadius(3.0), which applies rounded corners to our image, so it looks a little bit nicer, as well as adding vertical padding to our HStack so our image has a little bit of vertical space around it.

Notice the .scaledToFill() modifier below .resizable(), this modifier takes our image and scales it enough to fit the frame below, while keeping its aspect ratio.

HStack{
KFImage(URL(string: article.imageLink))
.placeholder {
Image(systemName: "arrow.2.circlepath.circle")
.font(.largeTitle)
.opacity(0.3)
}

.resizable()
.scaledToFill()
.frame(width: 50, height: 50)
.cornerRadius(3.0)
Text(article.title)
.font(.caption)
.fontWeight(.ultraLight)
.lineLimit(2)
}.padding(.vertical)

Now, we’re going to get our hands dirty and work on the logic for fetching the actual articles.

Create a new swift file and name it ArticleFetcher

At the very top, let’s import SwiftUI plus the two libraries we’re gonna use to fetch and handle the data easily

import SwiftUI
import Alamofire
import SwiftyJSON

Let’s create a public class with the same file name, and let’s make it an ObservableObject, so our ArticleView can use it to fetch the articles from NewsAPI

import Foundation
import SwiftUI
import Alamofire
import SwiftyJSON
public class ArticleFetcher: ObservableObject{

}

Let’s add two published variables. One that will store the List of articles, and another that will tell us if there was an error while fetching the articles.

A published variable can be accessed by other views, and the views will update when their value changes.

import Foundation
import SwiftUI
import Alamofire
import SwiftyJSON
public class ArticleFetcher: ObservableObject{
@Published var articles: [Article] = []
@Published var fetchError: Bool = false
}

Now, we’re going to create the function that fetches the articles. For this, we’ll need our NewsAPI API Key. If you didn’t copy and paste it before closing News API after you created your account, you could just go to https://newsapi.org/ and login to get your API Key.

Let’s create a variable containing our API Key

import Foundation
import SwiftUI
import Alamofire
import SwiftyJSON
public class ArticleFetcher: ObservableObject{
@Published var articles: [Article] = []
@Published var fetchError: Bool = false

let myApiKey = “yourAPIkeyHere“
}

Below that, we’re going to start our function. Create a function named fetchArticles(), and in the body, of the function, we’re going to add an AlamoFire request, containing the NewsAPI URL that will deliver the top headlines.

import Foundation
import SwiftUI
import Alamofire
import SwiftyJSON
public class ArticleFetcher: ObservableObject{
@Published var articles: [Article] = []
@Published var fetchError: Bool = false

let myApiKey = “yourAPIkeyHere”

func fetchArticles(){
AF.request("
https://newsapi.org/v2/top-headlines?language=en&apiKey="+myApiKey)
}

}

Now, we need to take the response as a JSON object and use SwiftyJSON to access the value of the response. We’re going to add a switch statement in there to catch a possible failure, and we will update our published variable fetchError so we can reflect the error in the UI.

func fetchArticles(){
AF.request("https://newsapi.org/v2/top-headlines?language=en&apiKey="+myApiKey).responseJSON{ response in
switch response.result {
case .success(let value):
let json = JSON(value)

case .failure(let error):
print(error)
self.fetchError = true
}
}

}

For what comes next, we will do the following

  • We will take the array of articles contained in the JSON variable
  • We will create an array that will hold Article type objects
  • We will iterate over each article, and use the data to create an Article object
  • That Article object will be appended to the array that holds our Articles
  • When we finish iterating, we will copy the contents of our Article array into the published variable that also holds Articles.
func fetchArticles(){
AF.request("https://newsapi.org/v2/top-headlines?language=en&apiKey="+myApiKey).responseJSON{ response in
switch response.result {
case .success(let value):
let json = JSON(value)

let articles = json["articles"].array ?? []
var articleArray: [Article] = []
for item in articles {
let title = item["title"].string ?? "Untitled Article"
let author = item["author"].string ?? "No author data"
let description = item["description"].string ?? "No description"
let link = item["url"].string ?? ""
let imageLink = item["urlToImage"].string ?? "https://via.placeholder.com/50x50.png?text=IMG"
let publishedAt = item["publishedAt"].string ?? "No version data"
let content = item["content"].string ?? "No article content"
let articleItem = Article(title: title, description: description, author: author, link: link, imageLink: imageLink, publishedAt: publishedAt, content: content)
articleArray.append(articleItem)
}
self.articles = articleArray

case .failure(let error):
print(error)
self.fetchError = true
}
}
}

As you can see, we provided some fallback values in case that an Article doesn’t contain what we need.

At the end, ArticleFetcher should look like this

import Foundation
import SwiftUI
import Alamofire
import SwiftyJSON
public class ArticleFetcher: ObservableObject{
@Published var articles: [Article] = []
@Published var fetchError: Bool = false

let myApiKey = "yourApiKeyHere"

func fetchArticles(){
AF.request("https://newsapi.org/v2/top-headlines?language=en&apiKey="+myApiKey).responseJSON{ response in
switch response.result {
case .success(let value):
let json = JSON(value)

let articles = json["articles"].array ?? []
var articleArray: [Article] = []
for item in articles {
let title = item["title"].string ?? "Untitled Article"
let author = item["author"].string ?? "No author data"
let description = item["description"].string ?? "No description"
let link = item["url"].string ?? ""
let imageLink = item["urlToImage"].string ?? "https://via.placeholder.com/50x50.png?text=IMG"
let publishedAt = item["publishedAt"].string ?? "No version data"
let content = item["content"].string ?? "No article content"
let articleItem = Article(title: title, description: description, author: author, link: link, imageLink: imageLink, publishedAt: publishedAt, content: content)
articleArray.append(articleItem)
}
self.articles = articleArray

case .failure(let error):
print(error)
self.fetchError = true
}
}
}
}

Now, we’re going to get back on to some UI stuff. Let’s go back to ContentView, we’re going to add a button that takes us to ArticlesView.

Below the second Text element, let’s add a Spacer() and a NavigationLink with ArticlesView as the destination, wrapping a Text element that says “Start”, or something similar.

struct ContentView: View {
var body: some View {
VStack(alignment: .trailing){
Text("WristNews")
.font(.title)
.fontWeight(.thin)

Text("Daily news, delivered on your wrist.")
.fontWeight(.thin)
.multilineTextAlignment(.trailing)

Spacer()

NavigationLink(destination: ArticlesView()){
Text("Start")
}

}
}
}

That Start button will take us to our ArticlesView, where we will now add the List of Articles to populate the View, so let’s go to ArticlesView.

Right after the struct opens, we’re going to add an ObservedObject variable and name it articleManager, which will be an instance of ArticleFetcher. With it, we can trigger the article fetching from our View, and also access both the List of articles and the possible error that can be displayed.

struct ArticlesView: View {
@ObservedObject var articleManager = ArticleFetcher()

var body: some View {
VStack{
Text("Articles")
}
}
}

Let’s add an onAppear modifier to our VStack, which will let us execute functions when that component appears on the screen. On the onAppear body, we will execute the function that fetches the articles.

struct ArticlesView: View {
@ObservedObject var articleManager = ArticleFetcher()

var body: some View {
VStack{
Text("Articles")
}.onAppear{
self.articles.fetchArticles()
}

}
}

Now, we’re going to add our List of articles into the View. Let’s create a list and pass on to it, the List of articles that articleManager contains. In the body of the List, we will iterate over each Article object, and create an ArticleRow with the article data inside. Just like when we were working on ArticleRow, this time, ArticleRow will take the real data instead of the hardcoded data we used on the Preview struct.

struct ArticlesView: View {
@ObservedObject var articleManager = ArticleFetcher()

var body: some View {
VStack{
Text("Articles")
List(self.articleManager.articles) { article in
ArticleRow(article: article)
}
}.onAppear{
self.articleManager.fetchArticles()
}
}
}

We’re going to add some UI elements to catch errors, and provide an option to let the user try to fetch the articles again.

For that, we’re going to add an if statement right under our first Text element. That statement will check for the value of fetchError, which will be false until an error comes up.

If fetchError is true, that means that an error occurred while trying to fetch the articles, so we’ll add a Text element in there to tell the user that something happened.

struct ArticlesView: View {
@ObservedObject var articleManager = ArticleFetcher()

var body: some View {
VStack{
Text("Articles")
if(self.articleManager.fetchError == true){
Text("There was an error while fetching your news")
} else {
List(self.articleManager.articles) { article in
ArticleRow(article: article)
}
}

}.onAppear{
self.articleManager.fetchArticles()
}
}
}

Let’s also add in there a button to let the user try to fetch the articles again.

struct ArticlesView: View {
@ObservedObject var articleManager = ArticleFetcher()

var body: some View {
VStack{
Text("Articles")
if(self.articleManager.fetchError == true){
Text("There was an error while fetching your news")
Button(action:{
self.articleManager.fetchArticles()
}){
Text("Try Again")
}

} else {
List(self.articleManager.articles) { article in
ArticleRow(article: article)
}
}
}.onAppear{
self.articleManager.fetchArticles()
}
}
}

Now, let’s go into ArticleFetcher and we’ll switch the value of fetchError to true, so we can check how our error message looks like in the preview.

public class ArticleFetcher: ObservableObject{
@Published var articles: [Article] = []
@Published var fetchError: Bool = true

After doing so, let’s go back to ArticlesView, and let’s remove the first Text element that contains “Articles”, since it’s no longer needed and is somewhat redundant. Resume the preview, and you will see the error message in there.

To make this look a little bit better, let’s align the text to the center. You can do so via the inspector by tapping on the text, or via code. It should end up like this on code:

if(self.articleManager.fetchError){
Text("There was an error while fetching your news.")
.multilineTextAlignment(.center)
Button(action:{
self.articleManager.fetchArticles()
}){
Text("Try Again")
}
} else {
List(self.articleManager.articles) { article in
ArticleRow(article: article)
}
}

Let’s go back to ArticleFetcher, and turn the fetchError value back to false. Also, at the very start of the fetchArticles function, let’s set the value to false, so the error clears everytime we try to fetch the articles. Don’t worry, it’ll turn back to true if we come across an error again.

public class ArticleFetcher: ObservableObject{
@Published var articles: [Article] = []
@Published var fetchError: Bool = false

let myApiKey = "yourApiKeyHere"

func fetchArticles(){
self.fetchError = false
AF.request("https://newsapi.org/v2/top-headlines?language=en&apiKey="+myApiKey).responseJSON{ response in
...

Now, we’re almost there! The last step will be creating a detailed view so we can tap on our articles and see them in detail, and we will be ready to wrap this up and push the App to our Apple Watch!

Create a new SwiftUI View and name it ArticleDetail. Same as with ArticleRow, we will require an Article object here, so let’s add that in there, and let’s copy the same hardcoded data we used on the ArticleRow preview over here as well. We also will import Kingfisher to show an image in here as well, but this time, it’s going to be a little bit bigger.

import SwiftUI
import Kingfisher
import struct Kingfisher.KFImage
struct ArticleDetail: View {
var article: Article

var body: some View {
Text("Hello, World!")
}
}
struct ArticleDetail_Previews: PreviewProvider {
static var previews: some View {
ArticleDetail(article: Article(
title: "Rosalía Shares Her All-Time Favorite Songs in a Met Gala–Themed Playlist - Vogue",
description: "This playlist is a celebratory playlist for the Met, with nods to my all-time favorite songs, sounds, artists, and references. It has not been possible to celebrate the Met as God intended, but we can celebrate it in our own way from home.",
author: "Estelle Tang",
link:"https://www.vogue.com/article/rosalia-met-gala-about-time-playlist",
imageLink: "https://assets.vogue.com/photos/5eb0a75cd5f359c964b7e0e4/16:9/w_1280,c_limit/ GettyImages-1162355675.jpg",
publishedAt: "2020-05-05T00:20:29Z",
content: "To commemorate a very different first Monday in May this year, Vogue asked some of our favorite stars to create a playlist featuring timeless songs inspired by the theme of this year's Met gala and the upcoming Costume Institute exhibition, About Time: Fashio… [+2158 chars]"
))
}
}

Replace “Hello, world!” by article.title, and, below it, add another Text element that contains the author’s name. Let’s make this last text element ultraThin, so it differentiates itself from the title. Wrap everything in a VStack, and then, wrap the VStack inside a ScrollView. This will let us scroll up or down a view that overflows the screen.

var body: some View {
ScrollView{
VStack(alignment: .leading){
Text(article.title)
.font(.headline)
.fixedSize(horizontal: false, vertical: true)

Text("By \(article.author)")
.fontWeight(.ultraLight)

}
}
}

Below the author’s name, we’ll add the image. This time, I won’t add a placeholder, since it’s not immediately obvious we’re expecting an image here, different from the ArticleRow View. It’s up to you though, if you want to add a placeholder, you can take some code from ArticleRow.

After that, let’s add a Divider(), and both the article description and content. We’ll add some modifiers to both of them, to make the text occupy as less space as possible, and the last one to not truncate the text.

var body: some View {
ScrollView{
VStack(alignment: .leading){
Text(article.title)
.font(.headline)
.fixedSize(horizontal: false, vertical: true)

Text("By \(article.author)")
.fontWeight(.ultraLight)
Divider()

Text(article.description)
.font(.caption)
.fontWeight(.ultraLight)
.fixedSize(horizontal: false, vertical: true)

Text(article.content)
.font(.caption)
.fontWeight(.ultraLight)
.fixedSize(horizontal: false, vertical: true)
}
}
}

Finally, let’s add another divider, and two more text elements. One to show the date of publication, and the last one to provide a link to the full story.

var body: some View {
ScrollView{
VStack(alignment: .leading){
Text(article.title)
.font(.headline)
.fixedSize(horizontal: false, vertical: true)

Text("By \(article.author)")
.fontWeight(.ultraLight)

KFImage(URL(string: article.imageLink))
.resizable()
.scaledToFit()

Divider()

Text(article.description)
.font(.caption)
.fontWeight(.ultraLight)
.fixedSize(horizontal: false, vertical: true)

Text(article.content)
.font(.caption)
.fontWeight(.ultraLight)
.fixedSize(horizontal: false, vertical: true)

Divider()

Text("Published at \(article.publishedAt)")
.font(.caption)
.fontWeight(.ultraLight)
.fixedSize(horizontal: false, vertical: true)

Text("Read the full story in: \(article.link)")
.font(.caption)
.fontWeight(.ultraLight)
.fixedSize(horizontal: false, vertical: true)
.padding(.top)
}
}
}

Now, for the final act, we’re going to link the ArticleRow View to the ArticleDetail View, and we will have completed the App!

Let’s go back on to ArticlesView. On the List of articles, we’ll wrap ArticleRow inside a NavigationLink, and let’s set it’s destination to ArticleDetail, while providing the same article we provide to ArticleRow.

List(self.articleManager.articles) { article in
NavigationLink(destination: ArticleDetail(article: article)){
ArticleRow(article: article)
}
}

And we have finished our App! Now, get your Apple Watch, iPhone, Lightning Cable and plug your iPhone to your Mac.

Wait a little bit until it’s detected by Xcode.

Right above the editor, tap on the active scheme

And at the very top of the List, you should see your Apple Watch via your iPhone.

Then, tap on the Play button to finally push the App on to your Apple Watch!

If this is the first time you do this, most likely you will get an error. Worry not! The App is installed on your Apple Watch, but you have to allow it to run. Press the Digital Crown to open the app drawer, and look for a suspicious icon that looks like a White Crosshair on Gray Background, or like some concentric circles with a cross in the middle. That’s your App.

When you open it, you should see a small message asking for permission to run the App. Tap on Allow, and enjoy!

I hope you’ve enjoyed this tutorial as much as I enjoyed writing it. Building WatchOS apps with SwiftUI is fun and easy, and it’s amazing how we can do a lot of stuff on a tiny computer that sits on our wrist.

If you have time, feel free to expand your news app! Add more views, change the ones we have a bit, alter the order of the Text elements, and experiment with it!

Thanks for getting to the end of it with me!

--

--

Fer Cervantes Alvarez
Mac O’Clock

Lead UI Developer @ SunPower. I love designing beautiful things, creating exciting apps, and sharing the joy of it with the world!