Categories
Uncategorized

How to parse JSON in Swift 5

In this tutorial, we talk about a very important concept when it comes to working with APIs: How to parse JSON in Swift. We’ll talk about how to download JSON data from an URL and how to make use of it by encoding it. We learn all of this the easy way with using the Codable protocol!

What we want to achieve 🚀

Supposed we have an API that allows us to download images via their url’s and the usernames of the uploader, we want to create an app that displays that image with the according username. It should look similar to this:

The JSON data we use for this looks like this:

In this tutorial, we start with learning how to download the JSON data and how to parse it so we can extract the image URL’s and the usernames.

Creating the Swift data model for our JSON data 🛠

Take a look at the JSON data above. It contains three entries (001, 002, and 003), where each has one imageURL and one uploaderName. To parse the JSON, we first need to define suitable data models for storing the parsed data later on.

Our data model we start with is the one we use for the entries’ content, the image data. We use a struct containing an imageURL and uploaderName property for this.

struct Image: Codable {
    let imageURL: String
    let uploaderName: String
}

Our second data model is for the entries itself. The only property of this model is a dictionary, where the key is the name of the entry (e.g. 001) and the value an instance of our just created Image model.

struct Entry: Codable {
    let images: [String: Image]
}

You see that we needed to create two different “model layers”, just as our JSON data has two “layers”.

Tip: Especially when it comes to more complex JSON data, you can use a tool such as quicktype, which automatically creates data models out of your JSON, ready for being used in your Swift project.

Downloading the JSON data ⬇️

Now we are ready to download our JSON data. I uploaded my sample JSON data to the web using this free tool. You can do this too if you want and then use the generated URL for retrieving the data. In real life you would use the API url from now on.

We start with creating a Swift URL object out of our URL.

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
    //
}

We can then retrieve the JSON data from the URL like this:

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
   URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        //...
       }
   }.resume()
}

Parsing the JSON data in Swift💡

To decode the JSON data we have to initialize a JSONDecoder.

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
   URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        let jsonDecoder = JSONDecoder()
       }
   }.resume()
}

We can now parse the JSON by using the Decoder and our created data models. We do this inside a try-catch block to print out the error when something goes wrong with the parsing process.

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
   URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        let jsonDecoder = JSONDecoder()
        do {
            let parsedJSON = try jsonDecoder.decode(Entry.self, from: data)
        } catch {
            print(error)
        }
       }
   }.resume()
}

At this point the parsedJSON property is assigned to an instance of our Entry class. This means that the parsedJSON contains a dictionary, where the values are instances of the Image class.

Therefore we can cycle through every Image instance of our Entry’s dictionary and grab the imageURL and uploaderName of each one.

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
   URLSession.shared.dataTask(with: url) { data, response, error in
     if let data = data {
         let jsonDecoder = JSONDecoder()
         do {
             let parsedJSON = try jsonDecoder.decode(Entry.self, from: data)
             for image in parsedJSON.images {
                 print(image.value.imageURL)
                 print(image.value.uploaderName)
            }
        } catch {
            print(error)
        }
       }
   }.resume()
}

We could now use this data for downloading the images via the image URLs and displaying the according uploader user names. How to do this with SwiftUI is explained in our Mastering SwiftUI book where we use the Flickr API to parse image data from the user’s location.

Conclusion 🎊

Great, we just learned how to retrieve and parse JSON data from the web by using the Swift Codeable protocol and our own data models. You are now able to work with API’s that provide data via JSON.

I hope you enjoyed this tutorial! If you want to learn more about SwiftUI, make sure you check out our free SwiftUI Basics eBook and our other tutorials! Also make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Categories
Uncategorized

Drawing in SwiftUI – A Comprehensive Guide

Hello and welcome to a new tutorial! Today’s tutorial is all about drawing in SwiftUI. We’ll learn how to draw custom graphics, vectors and forms by using the SwiftUI graphics API. First, we are going to take a look at SwiftUI’s built-in shapes and how we can modify them. Then we’re going to compose our own shapes by using custom paths. By the end of this step-by-step tutorial, you’ll be able to:

  • Use and modify prebuilt shapes like Rectangles, Circles etc. for your SwiftUI app
  • Draw your own, custom shapes by using SwiftUI paths
  • Design complex shapes, for example to draw your app icon or use custom badges for your SwiftUI app

Here are some of the designs we’re going to achieve:

Basic Shapes 🔵⬛️

SwiftUI provides us with some basic shapes we can use for drawing in our app. These are:

  • Rectangle
  • RoundedRectangle
  • Ellipse
  • Circle
  • Capsule 

You can use them by simply initialising them within your SwiftUI view. 

struct ShapeSandbox: View {
    var body: some View {
        RoundedRectangle(cornerRadius: 20)
    }
}

By default however, the Shape doesn’t know anything about its supposed size and position. This is why as long as we provide the shape with a .frame it tries to fill out the whole view.

So let’s provide our RoundedRectangle with a certain width and height. We also define another fill color than the default black. 

RoundedRectangle(cornerRadius: 20)
            .frame(width: 250, height: 100)
            .foregroundColor(.purple)

Maybe you want your shape not to be filled out with a certain but rather want to create some kind of border out of it. You can do this by using the .stroke modifier (as the first one!) with choosing a certain lineWidth for your border.

RoundedRectangle(cornerRadius: 20)
            .stroke(lineWidth: 10)
            .frame(width: 250, height: 100)
            .foregroundColor(.purple)

It’s even possible to use another stroke style, for example to use a dashed-line as your border. For example like this:

RoundedRectangle(cornerRadius: 20)
            .stroke(style: StrokeStyle(lineWidth: 7, lineCap: .square, dash: [15], dashPhase: 2))
            .frame(width: 250, height: 100)
            .foregroundColor(.purple)

Of course, you can use the discussed “techniques” for all other shapes!

Drawing own shapes by using paths 🖌

Let’s take a quick look at what paths in SwiftUI are. In a nutshell, you can imagine a path like a set of drawing instructions, including lines, curves and other segments like arcs. This why a shape is doing nothing different than using a specific path to define its appereance. 

Okay, let’s use this knowledge to draw a square by using a Path. To do this, create a new SwiftUI view and call it, for intance, PathSandbox.swift.

Let’s insert a Path instance followed by its corresponding closure. Inside the closure we can actually define how our Path should go. By default again, SwiftUI will fill out resulting view with a black color. While drawing the Path however, I personally prefer to only see the outer border for having a better overview of the resulting Path. This is why I’m applying the .stroke() we got familiar with before starting drawing the Shape.

struct PathSandbox: View {
    var body: some View {
        Path { path in

        }
            .stroke()
    }
}

Let’s draw our square by adding several lines to our Path. We can do this by using absolute x- and y-coordinates. Before drawing the first line, we move the “cursor” right upper corner of our imaginary square. Then we’re adding a line which points to the lower right corner and another line pointing to the lower left corner.

Path { path in
            path.move(to: CGPoint(x: 200, y: 0))
            path.addLine(to: CGPoint(x: 200, y: 200))
            path.addLine(to: CGPoint(x: 0, y: 200))
        }
            .stroke()

You see that two lines got added to our SwiftUI! Let’s finish our square by adding a third line pointing to the upper left corner. We could close the rectangle by adding a last line pointing to where we started, but we can also closing the Path “automatically” by using the .closeSubPath modifier.

Path { path in
            path.move(to: CGPoint(x: 200, y: 0))
            path.addLine(to: CGPoint(x: 200, y: 200))
            path.addLine(to: CGPoint(x: 0, y: 200))
            path.addLine(to: CGPoint(x: 0, y: 0))
            path.closeSubpath()
        }
            .stroke()

Now that we are done with defining our square path, we can fill it out by deleting the .stroke modifier again!

var body: some View {
        Path { path in
            //...
        }
    }

Which results in the following view:

Since, as you already know, a Shape also consists of a Path. For reusability purposes, we can simply convert our Square Path to such a Shape by declaring a struct which adopts the Shape protocol. 

struct MySquare: Shape {
    
}

The single requirement for the Shape protocol is having a path function which draws the actual shape. We can simply use the Path we just created like this:

struct MySquare: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        path.move(to: CGPoint(x: 200, y: 0))
        path.addLine(to: CGPoint(x: 200, y: 200))
        path.addLine(to: CGPoint(x: 0, y: 200))
        path.addLine(to: CGPoint(x: 0, y: 0))
        path.closeSubpath()

        return path
    }
}

We now use our MySquare Shape inside our SwiftUI view!

struct PathSandbox: View {
    var body: some View {
        MySquare()
    }
}

However, our MySquare Shape still uses the absolute coordinates we defined earlier. Instead, we want to make it dynamic so that we can transform it by adding a .frame modifier to the MySquare instance inside our SwiftUI view.

We can achieve the rect parameter of our MySquare’s path function. The rect is like a invisible scratchpad inside which we can draw our square and which can gets transformed by passing a certain frame to it when initialising the actual shape.

So let’s exchange the fixed coordinates by the corresponding points of the invisible rect.

struct MySquare: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        path.move(to: CGPoint(x: rect.size.width, y: 0))
        path.addLine(to: CGPoint(x: rect.size.width, y: rect.size.width))
        path.addLine(to: CGPoint(x: 0, y: rect.size.width))
        path.addLine(to: CGPoint(x: 0, y: 0))
        path.closeSubpath()

        return path
    }
}

Let’s talk through it very quick. We just move our cursor to the very upper right edge of our invisible rectangle. Then we tell our path to draw a line to another point by using the rect’s width for both (remember, we want to ensure it’s a square) x- and y-coordinates. Then we’re going back to the lower left corner, followed by the upper left corner before closing the subpath. Awesome, now our MySquare Shape is dynamic and we can adjust its size by using the .frame modifier!

struct PathSandbox: View {
    var body: some View {
        MySquare()
            .frame(width: 250, height: 250)
    }
}

Drawing more complex SwiftUI shapes  👨‍🎨

Okay, now that we know how to create our own shapes we practice our SwiftUI drawing skills by creating more complex shapes. Eventually, we’ll rebuild the logo of our website.

To do this, create a new SwiftUI view called BBLogo. The icon should simply consist of a black background with two B letters on it. We start by inserting a ZStack into or BBLogo view so that all views inside it get stacked on top of each other. For the background we use the rounded rectangle shape we got familiar with earlier.

struct BBLogo: View {
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 20)
                .frame(width: 200, height: 200)
        }
    }
}

Next, we draw the B-Letter Shape. Let’s declare a struct conforming to the Shape protocol.

struct LetterB: Shape {
    
    func path(in rect: CGRect) -> Path {
        Path { path in
            
        }
    }
}

To instantly see what we’re drawing we already initialize a LetterB Shape inside our BBLogo view and apply it with a stroke, white color and a certain frame.

ZStack {
            RoundedRectangle(cornerRadius: 20)
                .frame(width: 200, height: 200)
            LetterB()
                .stroke(lineWidth: 12)
                .foregroundColor(.white)
                .frame(width: 100, height: 100)
        }

Let’s start drawing the LetterB Shape! First, we move the “cursor” to the middle of the rect’s upper edge. Then we add a line pointing to the upper left edge of the invisible rectangle. Next, we draw a line to middle of the rect’s left edge followed by a line pointing to center of the rect.

Path { path in
            path.move(to: CGPoint(x: rect.size.width/2, y: 0))
            path.addLine(to: CGPoint(x: 0, y: 0))
            path.addLine(to: CGPoint(x: 0, y: rect.size.width/2))
            path.addLine(to: CGPoint(x: rect.size.width/2, y: rect.size.width/2))
        }

Your preview should look like this now:

Before drawing on, we move our “cursor” back to the middle of the rect’s left edge. Then we draw a line to the lower left edge. Our last line points the middle of the rect’s lower edge.

Path { path in
            path.move(to: CGPoint(x: rect.size.width/2, y: 0))
            path.addLine(to: CGPoint(x: 0, y: 0))
            path.addLine(to: CGPoint(x: 0, y: rect.size.width/2))
            path.addLine(to: CGPoint(x: rect.size.width/2, y: rect.size.width/2))
            path.move(to: CGPoint(x: 0, y: rect.size.width/2))
            path.addLine(to: CGPoint(x: 0, y: rect.size.width))
            path.addLine(to: CGPoint(x: rect.size.width/2, y: rect.size.width))
        }

To complete the letter B we have to add two halved circles to our Shape. We do this by adding two arcs to our Path. When adding arcs, we need to specify the arcs center, as well as the start and end angle. Therefore we write:

Path { path in
            //...
            path.addArc(center: CGPoint(x: rect.size.width/2, y: rect.size.width*(3/4)), radius: rect.size.width/4, startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
            path.addArc(center: CGPoint(x: rect.size.width/2, y: rect.size.width/4), radius: rect.size.width/4, startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
        }

Awesome, we are finished with creating our LetterB Shape!

We complete the BBLogo by stacking another BLetter Shape on top of the background and the existing one and offsetting it a little bit.

ZStack {
            RoundedRectangle(cornerRadius: 20)
                .frame(width: 200, height: 200)
            LetterB()
                .stroke(lineWidth: 12)
                .foregroundColor(.white)
                .frame(width: 100, height: 100)
            LetterB()
                .stroke(lineWidth: 12)
                .foregroundColor(.white)
                .frame(width: 100, height: 100)
                .offset(x: 40)
        }


That’s it! By building our website’s logo we learned how to create more complex shapes.

Drawing curved shapes in SwiftUI ⤴️🎨

Last but not least, we’ll take a look at how to create curved shapes. By doing this, we’ll be able to create cool shapes like this raindrop icon!

Let’s create new SwiftUI view. Below this, declare a struct called Raindrop and conforming to the Shape protocol.

struct Raindrop: Shape {
    
    func path(in rect: CGRect) -> Path {
        Path { path in

        }
    }
}

Again we start with initialising the Raindrop Shape inside our SwiftUI view and applying a .stroke and .frame to it.

var body: some View {
        Raindrop()
            .stroke(lineWidth: 4)
            .frame(width: 200, height: 200)
    }

Inside our Raindrop’s path we start moving our “cursor” to the middle of the rect’s upper edge. 

Path { path in
            path.move(to: CGPoint(x: rect.size.width/2, y: 0))
        }

Next we want to draw a right curve downwards. To do this, we use the addQuadCurve method. This function adds a so-called Bézier curve to the path. For such a Bézier curve we first need to define an ending point for the curve but to actually do the curve we need to provide the Bézier curve with a control point. Such a control point is used to calculate the strength and direction of the curve. You don’t need to know the calculus for this (nor do i). Just take a look at the following infographic to get a feeling for this.

You see that the direction and strength of the curve depends on where we place the corresponding control point. The further away we place the control point, the more the curve is bent.

Let’s us this knowledge for our addQuadCurve function. We tell the path that the curve should end at the middle of the rect’s lower edge and we place the control point at the lower right edge of the rect

Path { path in
            path.move(to: CGPoint(x: rect.size.width/2, y: 0))
            path.addQuadCurve(to: CGPoint(x: rect.size.width/2, y: rect.size.height), control: CGPoint(x: rect.size.width, y: rect.size.height))
        }

Lets finish our Raindrop Shape by drawing another curve pointing to where we started drawing.

Path { path in
            path.move(to: CGPoint(x: rect.size.width/2, y: 0))
            path.addQuadCurve(to: CGPoint(x: rect.size.width/2, y: rect.size.height), control: CGPoint(x: rect.size.width, y: rect.size.height))
            path.addQuadCurve(to: CGPoint(x: rect.size.width/2, y: 0), control: CGPoint(x: 0, y: rect.size.height))
        }

Awesome, we’re done with our Raindrop shape! Now we can fill out this Shape inside our SwiftUI view by exchanging the .stroke modifier. For example, you can fill the shape with a gradient like this:

Raindrop()
            .fill(LinearGradient(gradient: Gradient(colors: [.white, .blue]), startPoint: .topLeading, endPoint: .bottom))
            .frame(width: 200, height: 200)

Now you’re Raindrop Shape should look like this:

Conclusion 🎊

That’s it! You learned how to use prebuilt shapes and how to create your own by working with Paths. You learned how to use dynamic values instead of fixed coordinates and how to add arcs and curves to your Path. With this knowledge you should be able to create your own drawings and use them inside your SwiftUI app!

I hope you enjoyed this tutorial! If you want to learn more about SwiftUI, make sure you check out our free SwiftUI Basics eBook and our other tutorials! Also make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Categories
Uncategorized

How to create and style Buttons in SwiftUI

Wanna know how to create and customize a Button in SwiftUI that triggers specific code when being touched? Here you go! 👇

Step 1: Create the Object you want inside your Button, for example a Text. If you want more than one object inside your Button, you can group them using, for instance, a HStack. You can now apply modifiers for customizing the created objects.

import SwiftUI

struct ContentView : View {
    var body: some View {
        HStack {
            Text("ADD TO CART")
                .font(.custom("Avenir-Medium", size: 20))
                .foregroundColor(.black)
            Image(systemName: "cart")
                .foregroundColor(.black)
        }
    }
}

Step 2: If you nested multiple objects, you have to apply the overall modifiers like the Buttons frame size or background color to the Stack.  If you have only one object, simply apply these modifiers to the object itself.

struct ContentView : View {
    var body: some View {
        HStack {
            Text("ADD TO CART")
                .font(.custom("Avenir-Medium", size: 20))
                .foregroundColor(.black)
            Image(systemName: "cart")
                .foregroundColor(.black)
        }
            .frame(width: 220, height: 30)
            .padding()
            .background(Color.green)
            .cornerRadius(10.0)
    }
}

Step 3: Wrap everything into a Button. Insert the action parameter followed by curly braces. Inside these braces you can execute for example a print statement or a custom function.

import SwiftUI

struct ContentView : View {
    var body: some View {
        Button(action: {print("Button touched!")}) {
            HStack {
                Text("ADD TO CART")
                    .font(.custom("Avenir-Medium", size: 20))
                    .foregroundColor(.black)
            Image(systemName: "cart")
                    .foregroundColor(.black)
            }
                .frame(width: 220, height: 30)
                .padding()
                .background(Color.green)
                .cornerRadius(10.0)
        }
    }
}

Run your app by starting the live mode of the Preview Simulator!

mIf you liked this tutorial, feel free to check out our Mastering SwiftUI eBook. In this book, we also created a To-Do app by using the mentioned Core Data functionalities!

I hope you enjoyed this tutorial! If you want to learn more about SwiftUI, check out our other tutorials! Also make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Categories
Uncategorized

Xcode 11 Tutorial For Beginners

In this series, you will learn everything you need to know to get started with Xcode, quickly & easily.

The whole article was updated for Xcode 11, including the new main feature, SwiftUI.

What you will learn in this part of the series:

  • What Xcode is and what you need it for
  • How Xcode is structured and what you need for running it
  • Tips for an efficient usage of Xcode

What is Xcode used for and what do I need for running it?

Xcode is Apple’s in-house IDE (integrated development environment). When you develop iOS apps, it is the software you work with most often. Therefore, it is very important to have a profound knowledge of Xcode and to master the basics from scratch.

In Xcode you set up the user interface of your app, organize and write the code that makes your app run. Xcode also offers you the possibility to run and test your app on a virtual simulator on your Mac (and of course on a real iOS device).

Fortunately, Xcode is available completely free of charge in the Mac App Store. On Windows PC’s Xcode is not available. Although it may be possible to run OSX on a virtual machine (“Hackintosh”), i strongly recommend you to buy a Mac if you don’t have one yet. This is more than useful in the long run if you want to develop iOS apps.

Xcode is Apple’s in-house IDE for the development of iOS apps. It is free, but only available for Mac.

Creating a Xcode Project and choosing the User Interface

To get in touch with the IDE, just open Xcode and click on “Create a new Xcode Project” (btw: “Playgrounds”, the option below creating a new project, is an option to test new concepts and ideas quickly and easily). Next, click on “Single View App” and then on “Next” and give your app any name you want.

At this point, you can choose which User Interface mode you want to work with. You can either use SwiftUI or Storyboards.

  • Storyboards: Until now, the most common way to build iOS apps was to use storyboards. Simplified, you created the UI by dragging and dropping, arranging and constraining elements and by connecting them to your code.
By working with storyboards you create the UI by dragging and dropping, arranging and constraining elements and by connecting them to your code.
  • SwiftUI: SwiftUI is THE new feature that got shipped with Xcode 11. It’s a completely new way to build iOS apps and follows a declarative approach, meaning that with SwiftUI you describe how your interface should look like and what it should do. To get started with SwiftUI, check out this quick introduction to this new framework and make sure you read our free tutorials!
SwiftUI follows a declarative syntax approach, meaning that we describe in code how our interface should look

Should I choose Storyboards or SwiftUI?

When you are new into iOS development, you are probably struggling whether you should work with SwiftUI or stay with Storyboards. To give you some advice, we wrote a an article for, that might be helpful.

Unterstanding the Interface 💡

To understand the basic layout of Xcode, create a new Single View app by using SwiftUI, as we described above. Don’t worry understanding the basics of Xcode is not depended on the User Interface mode you choose, so even if you want to work with Storyboards, starting with this example is fine.

What you see now may feels a bit overwhelming. Don’t worry, this feeling is perfectly fine and will pass when you understand the basics of how Xcode is constructed.

Fortunately, the interface is designed pretty straight forward. The interface you see now basically consists of 5 sections:

The five main sections: Toolbar(1), Navigator Area(2), Editor Area(3), Utility Area(4), Debug Area(5)

The Toolbar 🛠

Xcodes toolbar
The right three buttons of the toolbar allows you to show/hide the different areas

The right three buttons of the toolbar allows you to show/hide the different areas

The toolbar allows you to access the basic Xcode settings (don’t mix this up with your app projects settings) and perform several operations. On the left side of the toolbar you can select the device on which you want to run your app, for instance on any simulator. The field in the middle tells you when Xcode is working on something. The area on the right is responsible for the Xcode view modes, more on that later.

The Navigator Area 🔍

The Navigator Area helps you finding your way around your project and organize your code and resources. By default the “Project Navigator” is selected, probably the most important mode of the navigator. Here are the different parts of your app’s code listed. The more complex your app becomes, the more files your project will contain. To keep track, you can create „groups“ (Xcodes name for folders) and move the files as you like. Where you are placing files within your project usually has no effect on the logic of your code or the behavior of the app.

The Navigation Area helps you to organize your app project and to find your way around it

When you click on a file, it will open in the Editor Area where you can, you guessed it, edit it. If not already done, click on “ContentView.swift” to display this file in the Editor Area. Swift files are the heart of every iOS App. In these you write the code that makes your app run.

The Editor Area ✍️

Here you write the code and compose the interface of your app. The appearance of the Editor Area depends on which file type is opened, especially and on whether you are working with SwiftUI or storyboards.

The Utility Area 🧰

Similar to the Editor Area, the appearance of the Utility Area depends on what file type you have just selected. Here you can access, for instance meta data, references, etc. of files or/and their components. This area is especially important when editing storyboards.

Especially the Utilty Area often confuses beginners, because the use of this area depends on the particular situation. But the more you work with Xcode the more you get a feeling for it. You’ll see, it’s much easier than it looks at first sight, I promise!

The Debug Area 👷‍♀️

When you run your app, you will find all relevant information about errors etc. that Xcode provides you. This area becomes very important when it comes to finding and fixing errors and bugs of your app.

Often the output is very long, so you can use the filter to easily find certain output.

Basic Shortcuts

If you begin working with Xcode more often, you will notice that different things like frequently showing and hiding different areas can inferiore your workflow. In order to save time you should remember and use at least the most important shortcuts. Soon you will be memorizing them and save a lot of time. Here are some first, important ones to start with:

  • ⌘+0: Show/Hide Navigator Area
  • ⌘+⌥+0: Show/Hide Utility Area
  • ⌘+SHIFT+Y: Show/Hide Debug Area

Shortcuts are enormously helpful to optimize your workflow and save time

Conclusion 🎊

Congratulations! You now know the basics of Xcode.

Don’t feel insecure if you are a little overwhelmed. You’ll see, the more you deal with it, the more comprehensible everything becomes.

“The secret of getting ahead is getting started.”

Mark Twain

Do not hesitate to ask in the comments if anything is unclear. We will reply as soon as possible (:

Categories
Uncategorized

How to parse JSON with Swift 5

In this tutorial, we talk about a very important concept when it comes to working with APIs: Parsing JSON. We’ll talk about how to download JSON data from an URL and how to make use of it by encoding it. We learn all of this the easy way with using the Codable protocol!

What we want to achieve 🚀

Supposed we have an API that allows us to download images via their url’s and the usernames of the uploader, we want to create an app that displays that image with the according username. It should look similar to this:

The JSON data we use for this looks like this:

In this tutorial, we start with learning how to download the JSON data and how to parse it so we can extract the image URL’s and the usernames.

Creating the data model for our JSON data 🛠

Take a look at the JSON data above. It contains three entries (001, 002, and 003), where each has one imageURL and one uploaderName. To parse the JSON, we first need to define suitable data models for storing the parsed data later on.

Our data model we start with is the one we use for the entries’ content, the image data. We use a struct containing an imageURL and uploaderName property for this.

struct Image: Codable {
    let imageURL: String
    let uploaderName: String
}

Our second data model is for the entries itself. The only property of this model is a dictionary, where the key is the name of the entry (e.g. 001) and the value an instance of our just created Image model.

struct Entry: Codable {
    let images: [String: Image]
}

You see that we needed to create two different “model layers”, just as our JSON data has two “layers”.

Tip: Especially when it comes to more complex JSON data, you can use a tool such as quicktype, which automatically creates data models out of your JSON, ready for being used in your Swift project.

Downloading the JSON data ⬇️

Now we are ready to download our JSON data. I uploaded my sample JSON data to the web using this free tool. You can do this too if you want and then use the generated URL for retrieving the data. In real life you would use the API url from now on.

We start with creating a Swift URL object out of our URL.

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
//
}

We can then retrieve the JSON data from the URL like this:

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
   URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
//...
       }
   }.resume()
}

Parsing the JSON data 💡

To decode the JSON data we have to initialize a JSONDecoder.

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
   URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
let jsonDecoder = JSONDecoder()
       }
   }.resume()
}

We can now parse the JSON by using the Decoder and our created data models. We do this inside a try-catch block to print out the error when something goes wrong with the parsing process.

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
   URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
let jsonDecoder = JSONDecoder()
do {
let parsedJSON = try jsonDecoder.decode(Entry.self, from: data)
        } catch {
print(error)
        }
       }
   }.resume()
}

At this point the parsedJSON property is assigned to an instance of our Entry class. This means that the parsedJSON contains a dictionary, where the values are instances of the Image class.

Therefore we can cycle through every Image instance of our Entry’s dictionary and grab the imageURL and uploaderName of each one.

if let url = URL(string: "http://www.json-generator.com/api/json/get/cfpeTpOFrC?indent=2") {
   URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
let jsonDecoder = JSONDecoder()
do {
let parsedJSON = try jsonDecoder.decode(Entry.self, from: data)
for image in parsedJSON.images {
print(image.value.imageURL)
print(image.value.uploaderName)
            }
        } catch {
print(error)
        }
       }
   }.resume()
}

We could now use this data for downloading the images via the image URLs and displaying the according uploader user names. In the next tutorial (dropping in the very next days) we’ll see how to do this in SwiftUI. If you want to know how to download images in UIKit, take a look at this tutorial!

Conclusion 🎊

Great, we just learned how to retrieve and parse JSON data from the web by using the Codeable protocol and our own data models. You are now able to work with API’s that provide data via JSON.

If you want to see more, make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Have any questions about this article? Write it in the comments below 👇

Categories
Uncategorized

How to use SwiftUI in a Playground

Xcode Playgrounds are a great way to test new ideas quickly and easily. You might have wondered if it is also possible to use SwiftUI views in a playground. And yes, it’s actually very straightforward 👇

Step 1: After you have created a playground, the first step is to import the necessary frameworks.

import UIKit
import PlaygroundSupport
import SwiftUI

Step 2: Next, you can create your SwiftUI view.

struct MyView: View {
    var body: some View {
        VStack {
            Text("Hello World!")
                .font(.title)
            Text("This a SwiftUI view")
        }
    }
}

Step 3: Before we can display this view, we need to convert it into an UIViewController. We do this by using an UIHostingController.

let viewController = UIHostingController(rootView: MyView())

Step 4: Finally, we use this view controller for the live view of our playground.

PlaygroundPage.current.liveView = viewController

If we run our playground now, we get our SwiftUI view displayed in a kind of preview simulator!

If you want to learn more about SwiftUI, make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips!

Categories
Uncategorized

How to prevent the keyboard from hiding your SwiftUI view

To deliver a good user experience, you have to make sure that the user can see what he is typing. Therefore, it’s crucial to prevent the toggled keyboard from hiding your view’s content.


We can achieve this by offsetting the whole SwiftUI view by the height of the toggled keyboard. We could use a fixed value for this, but keep in mind that for example, the keyboard height of an iPhone 8 differs tremendously from the height of an iPad Pro. So how can we access the keyboard’s heigh and use it for offsetting our SwiftUI view? Well, here you go:

If you want to learn how to create a login screen like the one you see in the preview video, take a look at this tutorial or download the finished project.

Step 1: Create a new Swift file called KeyboardResponder and import the SwiftUI framework. Then create a class that adopts the ObservableObject protocol. Our KeyboardResponder needs to be observable so we can update the observing views when the keyboard got toggled.⠀

import Foundation
import SwiftUI

class KeyboardResponder: ObservableObject {

}

Step 2: For keeping track of the keyboard’s current height, we declare a @Published property and assign it to 0 by default. 0 represents that the keyboard is not opened. Whenever the keyboard shows up we will update the published property with the actual keyboard height which will cause the observing views to update themselves.⠀

class KeyboardResponder: ObservableObject {

    @Published var currentHeight: CGFloat = 0

}

Step 3: To achieve this, we can use the system’s built-in keyboardWillShowNotification and keyboardWillHideNotifaction. To handle these notifications, we need to initialise a NotificationCenter first.⠀

class KeyboardResponder: ObservableObject {
//...
    
var _center: NotificationCenter

    init(center: NotificationCenter = .default) {
        _center = center
    }
}

Step 4: Then we tell our notification center to listen to the system’s keyboardWillHide/Show notifications. Whenever the keyboards gets shown/hidden now, the Notification Center will execute the function targeted by the specific selector.⠀

init(center: NotificationCenter = .default) {
        _center = center
    //4. Tell the notification center to listen to the system keyboardWillShow and keyboardWillHide notification
        _center.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        _center.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }

Step 5: Until now, we didn’t implement the functions called by the selectors. Let’s change this by adding these to our KeyboardResponder. Whenever the keyboard gets toggled now, our Notification Center notices and calls the corresponding function. Inside this function, we update the currentHeight property by referring to the keyboard size. As said this will cause all observing views to update themselves.

@objc func keyBoardWillShow(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            withAnimation {
               currentHeight = keyboardSize.height
            }
        }
    }
@objc func keyBoardWillHide(notification: Notification) {
        withAnimation {
           currentHeight = 0
        }
    }

Step 6: To observe the KeyboardResponder we have to initialise it inside our SwiftUI view as an @ObservedObject.

struct ContentView: View {
//...
    
@ObservedObject var keyboardResponder = KeyboardResponder()
var body: some View {
//...
    }
}

Step 7: Finally we tell our view to have the same offset as the keyboard is high by referring to the currentHeight variable of the observed KeyboardResponder.

var body: some View {
        NavigationView {
//...
        }
            .offset(y: -keyboardResponder.currentHeight*0.9)
    }

If you now run your app and open the keyboard, for example by tapping on a text field, the whole content moves up so we’re able to read what we’re writing.

That’s it! I hope you enjoyed this brief SwiftUI tutorial! 🍀

You can download the full source code here.

By the way, did you already download our FREE SwiftUI Basics eBook? Make sure you click the link in the bio to check it out 🚀

Categories
Uncategorized

Stretchy Header and Parallax Scrolling in SwiftUI

Welcome! In this tutorial, we are going to create a sticky/stretchy header in SwiftUI. At the same time, we are learning how to equip certain views with a parallax scrolling effect. We are using those features to create a nice looking blog post screen as you see them in several news apps.

This is what we are going to achieve in this tutorial:


If not already done, create a new Xcode 11 project and choose Single View app. Make sure you’ve selected SwiftUI as the interface mode and create a new project. For the following steps, you can use the default ContentView.swift file.

We’ll use some images for our project, which you can download here and here. You can also usa a portrait image like this one. Note that the format and dimensions of the header image can affect the parallax effect, which you maybe already noticed in the preview video above.

Import the images into your Assets.xcassets folder and make sure you name them correctly.

Setting up the basic content layout 👨‍🎨

Before we get started with implementing our sticky header, we are going to design the basic layout of our blog post screen.

The whole content in our ContentView should be scrollable. For this purpose, we replace the default “Hello World” Text view with a ScrollView.

struct ContentView: View {
var body: some View {
        ScrollView {
        }
    }
}

Inside the ScrollView, we will place the headline and the content texts of our blog post screen. All of those objects should be aligned vertically. Therefore, we insert a VStack into our ScrollView and choose .leading as the alignment mode.

ScrollView {
    VStack(alignment: .leading) {

    }
}

Above the headline of our article, we want to contribute the author by naming him and showing his picture. To do this, we insert a HStack into our VStack. We fill this HStack with a small, rounded image and another VStack containing the author’s name as a Text.

VStack(alignment: .leading) {
                HStack {
                    Image("journalist")
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 60, height: 60)
                        .clipped()
                        .cornerRadius(10)
VStack(alignment: .leading) {
                Text("Article by")
                            .font(.custom("AvenirNext-Regular", size: 15))
                            .foregroundColor(.gray)
Text("Johne Doe")
                            .font(.custom("AvenirNext-Demibold", size: 15))
                    }
                }
                  .padding(.top, 20)
            }

Hint: We are using “Avenir-Next” as a custom font family.To learn more about using custom fonts in SwiftUI, check out this instagram post.

This is what your app preview should show so far:

Below this HStack, we can now insert the headline and some meta information, for instance the date when the post was published and its reading length.

VStack(alignment: .leading) {
                HStack {
                    //...
                }
                    .padding(.top, 20)
                Text("Lorem ipsum dolor sit amet")
                    .font(.custom("AvenirNext-Bold", size: 30))
                    .lineLimit(nil)
                    .padding(.top, 10)
                Text("3 min read • 22. November 2019")
                    .font(.custom("AvenirNext-Regular", size: 15))
                    .foregroundColor(.gray)
                    .padding(.top, 10)
            }

Now we can insert the actual content into our VStack. For this purpose, we declare a constant outside of our ContentView that holds the post’s content as a multi-lined string.

let articleContent =

"""
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
At vero eos et accusam et justo duo dolores et ea rebum.
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
"""

We can now insert a Text by referring to that constant.

//...
                Text("3 min read • 22. November 2019")
                    .font(.custom("AvenirNext-Regular", size: 15))
                    .foregroundColor(.gray)
                    .padding(.top, 10)
                Text(articleContent)
                    .font(.custom("AvenirNext-Regular", size: 20))
                    .lineLimit(nil)
                    .padding(.top, 30)

Let’s define a smaller width for the whole VStack:

VStack(alignment: .leading) {
                //...
            }
                .frame(width: 350)

Great, we are already done with setting up the basic layout of our blog post section. This is how your app should look like so far:

Next, we are going to implement our header image.

Implementing the Parallax Scrolling Header ↕️

On top of the VStack in our ScrollView, we wanna add our article’s header image. For making it stretchy and for implementing the parallax effect when scrolling we need to keep track of to current ScrollView’s position. For this purpose we can use a GeometryReader.

ScrollView {
            GeometryReader { geometry in
                
            }
            VStack(alignment: .leading) {
                //...
            }
                .frame(width: 350)
        }

The GeometryReader not only reads out the dimensions of its parent views (the ScrollView) but also allows us to keep track of the current ScrollView’s position. Inside the GeometryReader we insert an image view. The image should be as high and as wide as the GeometryReader.

GeometryReader { geometry in
                Image("header")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .clipped()
            }

Thus, we have to provide the GeometryReader with a certain frame.

GeometryReader { geometry in
                Image("header")
                    //...
            }
                .frame(height: 400)

Hint: When we are just determining the height, the GeometryReader view will be as wide as possible.

In order for our header image to fill out the whole screen, we have to tell our ScrollView that it should reach to the top, meaning that it should exceed to so-called safe area. We can do this, by adding the following modifier to our ScrollView:

ScrollView {
            //...
        }
            .edgesIgnoringSafeArea(.top)

Awesome, this is what your preview should now look like

When scrolling down the header remains static, meaning that we’ve not created any parallax effect yet. Fortunately, doing this is pretty simple. We just need to tell our header image view to continuously offset its position while we are scrolling up and down. We can do this by adding the following modifier right before the .clipped modifier.

Image("header")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .offset(y: geometry.frame(in: .global).minY/9)
                    .clipped()

We are now determining the vertical offset of our header image by checking the currently (vertical) position of our ScrollView. We are accessing the current vertical position by referring to the minY value of our GeometryReader. By doing this, we make sure our parallax view “moves” accordingly while we are scrolling. By the way: You can divide the minY value by a certain factor to control the magnitude of the parallax effect. In our example, we are dividing by the value 7.⠀

When we now run our app in live mode and scroll down, we notice that the header is also “moving” continuously. A pretty neat parallax effect!

Making our header stretchy 🦒

To make our header image stretchy, we need to find a way to expand the height of it and pushing it to the top when scrolling “over the top” of our ScrollView.

The first thing we have to do is to recognize when the upper boundary of the ScrollView is exceeded. We can this out by using the minY value of our GeometryReader again. If the upper boundary of the ScrollView is exceeded, the vertical scroll position must be positive. In the initial state, the minY value is zero and as we scroll down it gets more and more negative.

We only want to show our existing header image with the parallax effect while scrolling downwards. So only if the minY of our GeometryReader is negative or zero .

GeometryReader { geometry in
                VStack {
                    if geometry.frame(in: .global).minY <= 0 {
                        Image("header")
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: geometry.size.width, height: geometry.size.height)
                        .offset(y: geometry.frame(in: .global).minY/9)
                        .clipped()
                    }
                }
            }

Hint: We have to wrap the if-statement into a container view in order to work. In our example, we use a VStack, but you could also use any other container view types.

If where are scrolling “over the top”, meaning when the GeometryReader’s minY is positive, we want to show our image header as well but with some modifications, we need for making it stretchy.

if geometry.frame(in: .global).minY <= 0 {
                        //...
                    } else {
                        Image("header")
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width: geometry.size.width, height: geometry.size.height)
                            .clipped()
                    }

The first modification we need is to make our header image the higher the further we scroll “over the top”. We can achieve this by adding the current minY value of our ScrollView’s GeometryReader to the height of our Image’s .frame modifier.

Image("header")
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width: geometry.size.width, height: geometry.size.height + geometry.frame(in: .global).minY)
                            .clipped()

If we now run our app in the live preview and start scrolling over the upper boundary of the ScrollView, our header begins stretching. However, it also reaches into our blog posts content.


Instead we want our header to be “glued” to the top of our screen. We can do this by offsetting it depending on the ScrollView’s position like this:

Image("header")
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width: geometry.size.width, height: geometry.size.height + geometry.frame(in: .global).minY)
                            .clipped()
                            .offset(y: -geometry.frame(in: .global).minY)

If we now run our app again, the header not only stretches while scrolling over the upper boundary but also stays on the very top. In addition, when the maximum size of the header’s image is reached, it begins zooming in. This is due to the .fill option we have chosen for our Image’s .aspectRatio modifier.

Conclusion 🎊

Awesome, we just learned how to apply a scrolling parallax effect to our SwiftUI app. By working with the GeometryReader’s minY value, that we used for reading out the current vertical scroll position, we also saw how to add a stretchy header to our app.

The complete source code for the app can be found here.

I hope you enjoyed this tutorial! If you want to learn more about SwiftUI, check out our other tutorials! Also make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Categories
Uncategorized

Floating action button with an animated menu in SwiftUI

Welcome to a new SwiftUI tutorial! Today we will learn how to create a floating action button with an animated floating Menu.

This is what our app will look like at the end:

If you want to skip the tutorial, you can simply view the relevant source code here.

First, we create a new SwiftUI Single View App in Xcode. Then we add a new File-New-File to our project and create a new SwiftUI View. We call this FloatingMenu. In this view, we will implement the floating action button with its corresponding menu. We will then use the finished menu inside our actual ContentView.

Creating the Floating Button ☁️

For the floating button, we insert a corresponding system icon into our FloatingMenu‘s body.

struct FloatingMenu: View {
    var body: some View {
        Image(systemName: "plus.circle.fill")
    }
}

We enlarge the image and give the icon a purple color.

Image(systemName: "plus.circle.fill")
            .resizable()
            .frame(width: 80, height: 80)
            .foregroundColor(Color(red: 153/255, green: 102/255, blue: 255/255))

To give the icon a certain plasticity, we would like to add a slight drop shadow to it. To do this we use the .shadow modifier and create a slightly shifted, gray shadow with a small radius.

Image(systemName: "plus.circle.fill")
            .resizable()
            .frame(width: 80, height: 80)
            .foregroundColor(Color(red: 153/255, green: 102/255, blue: 255/255))
            .shadow(color: .gray, radius: 0.2, x: 1, y: 1)

With a slight drop shadow, we create some plasticity to our button

Finally, we wrap the image into a button. For now, we use a dummy print statement as the button’s action.

Button(action: {
            print("Show Menu")
        }) {
            Image(systemName: "plus.circle.fill")
                //...
        }

Adding the Floating Menu Items ✍️

When the user taps the button, we want to display the menu items above the floating action button. To do this, we wrap the button into a VStack.

VStack {
            Button(action: {
                print("Show Menu")
            }) {
                //...
            }
        }

Each menu item consists of a circle containing a certain icon. To stack these two elements on top of each other we use a ZStack inside the VStack.

ZStack {
                Circle()
                    .foregroundColor(Color(red: 153/255, green: 102/255, blue: 255/255))
                    .frame(width: 55, height: 55)
                Image(systemName: "camera.fill")
                    .imageScale(.large)
                    .foregroundColor(.white)
            }

The menu item itself should have a drop shadow as well, so we reuse the .shadow modifier for the ZStack itself.

ZStack {
                //...
            }
                .shadow(color: .gray, radius: 0.2, x: 1, y: 1)

We can now CMD-Click on the ZStack and select “Extract as Subview”. We call this view MenuItem. Since each MenuItem should have a different icon, we add a property to the MenuItem struct that we use for the icon image.

struct MenuItem: View {
    
    var icon: String
    
    var body: some View {
        ZStack {
            Circle()
                .foregroundColor(Color(red: 153/255, green: 102/255, blue: 255/255))
                .frame(width: 55, height: 55)
            Image(systemName: icon)
                .imageScale(.large)
                .foregroundColor(.white)
        }
        .shadow(color: .gray, radius: 0.2, x: 1, y: 1)
    }
}

We then initialize the icon from our FloatingMenu view.

VStack {
            MenuItem(icon: "camera.fill")
            //...
        }

Then, we add two more MenuItems with different icons. We push the whole menu down by using a Spacer.

VStack {
            Spacer()
            MenuItem(icon: "camera.fill")
            MenuItem(icon: "photo.on.rectangle")
            MenuItem(icon: "square.and.arrow.up.fill")
            //...
        }

The MenuItems should only be displayed if the user has tapped on the button. Thus, we create three different States in our FloatingMenu view for the three according MenuItems

struct FloatingMenu: View {
    
    @State var showMenuItem1 = false
    @State var showMenuItem2 = false
    @State var showMenuItem3 = false
    
    var body: some View {
        //...
    }
}

Only if these States are true, we want to display the MenuItems.

VStack {
            Spacer()
            if showMenuItem1 {
                MenuItem(icon: "camera.fill")
            }
            if showMenuItem2 {
                MenuItem(icon: "photo.on.rectangle")
            }
            if showMenuItem3 {
                MenuItem(icon: "square.and.arrow.up.fill")
            }
            //...
        }

We can now toggle the States from our action button. For this purpose, we create a function called showMenu.

struct FloatingMenu: View {
    
    //...
    
    var body: some View {
        //...
    }
    
    func showMenu() {
        showMenuItem3.toggle()
        showMenuItem2.toggle()
        showMenuItem1.toggle()
    }
}

We can now call this function from our button:

Button(action: {
                self.showMenu()
            }) {
                Image(systemName: "plus.circle.fill")
                    //...
            }

Execute the app in the preview simulator and click on the floating button. Great, our menu items show up! When we click on the button again, our menu disappears again.


However, we want the different MenuItems to appear one after the other rather than at the same time. They should also move in from the right side of the screen.

Animating the Items 🚀

To display the different MenuItems delayed, we have to toggle the corresponding states in our showMenu function with a delay as well.

To execute code with a delay, we use the DispatchQueue.main.asyncAfter method.

func showMenu() {
        showMenuItem3.toggle()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
            self.showMenuItem2.toggle()
        })
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
            self.showMenuItem1.toggle()
        })
    }

If we now click on the action button, the showMenu1 state will be toggled immediately. After one-tenth of a second the showMenuItem2 state and after another tenth of a second the showMenuItem3 state gets toggled.


Now we want the MenuItems to move in from the right side. To animate the toggling of the MenuItems we have to use the withAnimation statement within our showMenu function.

func showMenu() {
        withAnimation {
            showMenuItem3.toggle()
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
            withAnimation {
                self.showMenuItem2.toggle()
            }
        })
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
            withAnimation {
                self.showMenuItem1.toggle()
            }
        })
    }

Now we can add a transition to our MenuItem view.

ZStack {
            //...
        }
        .shadow(color: .gray, radius: 0.2, x: 1, y: 1)
        .transition(.move(edge: .trailing))

Hint: The transition may not be displayed correctly in the preview simulator. We will change this in a moment by adding the FloatingMenu to our ContentView. Then we will be able to run our app in the normal simulator.

Inserting the Floating Menu into our ContentView 🖼

We would like to add the FloatingMenu to the bottom right of our ContentView. To make this possible, we first have to create a transparent view that covers the entire screen. For this purpose, we use a Rectangle object.

struct ContentView: View {
    var body: some View {
        Rectangle()
            .foregroundColor(.clear)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

To stack the FloatingMenu on top of this transparent rectangle, we wrap the rectangle into a ZStack and use the .bottomTrailing alignment option.

ZStack(alignment: .bottomTrailing) {
            Rectangle()
                .foregroundColor(.clear)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        }

Now we can insert our FloatingMenu. We also apply some padding to it

ZStack(alignment: .bottomTrailing) {
            Rectangle()
                .foregroundColor(.clear)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            FloatingMenu()
                .padding()
        }

If we now run the app in the regular simulator, we see our floating action button in the lower right corner. If we tap on it, the individual menu items move in from the right side of the screen!

Conclusion 🎊

Awesome! We learned how to create our own floating action button with an animated menu in SwiftUI. You can now use this knowledge to add your own floating menus to your SwiftUI apps.

The complete source code for the app can be found here.

I hope you enjoyed this tutorial! If you want to learn more about SwiftUI, check out our other tutorials! Also make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Categories
Uncategorized

Voice Recorder app in SwiftUI – #2 Playing the audios

Welcome to a new SwiftUI tutorial. In this article, we will create our own dictation app. We will learn how to record audios, how to save audio files and how to play them.

In the last part of this tutorial, we dealt with how we record and save audio. In this part, we will learn how to playback the recorded audios. We will also enable the user to delete old recordings.

Preparing the AudioPlayer 🎵

Similar to what we already did with the AudioRecorder, we create our own ObservableObject for the playback functionality. For this purpose, we create a new Swift file called AudioPlayer.

In this file, we import the SwiftUI, Combine and the AVFoundation framework. Then we create a class called AudioPlayer which adapts the ObservableObject protocol.

import Foundation
import SwiftUI
import Combine
import AVFoundation

class AudioPlayer: ObservableObject {
    
}

Again, we need a PassthroughObject to notify observing views about changes, especially if an audio is being played or not.

class AudioPlayer: ObservableObject {
    
    let objectWillChange = PassthroughSubject<AudioPlayer, Never>()
    
}

Accordingly, we implement a variable isPlaying which we set to false by default. If the value of the variable gets changed, we inform observing views using our objectWillChange property.

var isPlaying = false {
        didSet {
            objectWillChange.send(self)
        }
    }

And for the playback functionality, we need an AVAudioPlayer instance from the AVFoundation framework.

var audioPlayer: AVAudioPlayer!

You can see that the structure of our AudioPlayer is very similar to the AudioRecorder.

Now we can insert the start and stop buttons into each RecordingRows.

Updating the RecordingRows ✍️

Each RecordingRow needs its own AudioPlayer for the respective audio recording. To do this, we initialise one separate AudioPlayer instance as an ObservedObject for each RecordingRow.

struct RecordingRow: View {
    
    var audioURL: URL
    
    @ObservedObject var audioPlayer = AudioPlayer()
    
    var body: some View {
        //...
    }
}

If the audioPlayer is not playing, we want to display a play button that allows the user to listen to the recording.

HStack {
            Text("\(audioURL.lastPathComponent)")
            Spacer()
            if audioPlayer.isPlaying == false {
                Button(action: {
                    print("Start playing audio")
                }) {
                    Image(systemName: "play.circle")
                        .imageScale(.large)
                }
            }
        }

If an audio is currently playing, we need to display a button to stop the playback.

            if audioPlayer.isPlaying == false {
                Button(action: {
                    print("Start playing audio")
                }) {
                    Image(systemName: "play.circle")
                        .imageScale(.large)
                }
            } else {
                Button(action: {
                    print("Stop playing audio")
                }) {
                    Image(systemName: "stop.fill")
                        .imageScale(.large)
                }
            }

When you run the app in the regular simulator it should look like this:

Now we can implement the functions for playing and stopping the audio in our AudioPlayer, which we will call from the buttons of the RecordingRows.

Setting up the playback functionality

In our AudioPlayer we start by adding a function called startPlayback. This function should accept a URL, i.e. a file path for the audio to be played.

func startPlayback (audio: URL) {
        
    }

Similar to the recordingSession from the last part of the tutorial, we start by initializing a playbackSession inside this function.

func startPlayback (audio: URL) {
        
        let playbackSession = AVAudioSession.sharedInstance()
        
    }

By default, sounds are played through the device’s earpiece. However, we want the audio to be played through the loudspeaker. To achieve this, we have to overwrite the output audio port accordingly.

do {
            try playbackSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
        } catch {
            print("Playing over the device's speakers failed")
        }

Now we can start playing the audio with the help of the given file path and inform the observing views about this. If this does not work, we will output a corresponding error.

do {
            audioPlayer = try AVAudioPlayer(contentsOf: audio)
            audioPlayer.play()
            isPlaying = true
        } catch {
            print("Playback failed.")
        }

To stop the playback, we add the following function to our AudioPlayer:

func stopPlayback() {
        audioPlayer.stop()
        isPlaying = false
    }

We can now call the two functions from our RecordingRow’s start and stop buttons.

if audioPlayer.isPlaying == false {
                Button(action: {
                    self.audioPlayer.startPlayback(audio: self.audioURL)
                }) {
                    Image(systemName: "play.circle")
                        .imageScale(.large)
                }
            } else {
                Button(action: {
                    self.audioPlayer.stopPlayback()
                }) {
                    Image(systemName: "stop.fill")
                        .imageScale(.large)
                }
            }

Run the app and tap on the play button to listen to your recorded audio! You may have noticed that although an audio was played to the end, the stop button is still being displayed. This is because we have not yet updated our isPlaying variable accordingly.

To be notified when an audio has finished playing, we need the audioDidFinishPlaying function. This function is part of the AVAudioPlayerDelegate protocol that our AudioPlayer has yet to adapt. Hint: To adapt this delegate protocol, the AudioRecorder must also adapt the NSObject protocol.

class AudioPlayer: NSObject, ObservableObject, AVAudioPlayerDelegate {
    
    //...
    
}

When an audio is played, we need to set the AudioPlayer itself as the delegate of the AVAudioPlayer.

func startPlayback (audio: URL) {
        
        //...
        
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: audio)
            audioPlayer.delegate = self
            audioPlayer.play()
            isPlaying = true
        } catch {
            print("Playback failed.")
        }
    }

Now we can add the audioDidFinishPlaying function to our AudioPlayer. If the audio was successfully played, we set the playing properties value back to false.

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        if flag {
            isPlaying = false
        }
    }

When we now run the app and play a recording, the AudioPlayer will call the audioDidFinishPlaying function as its own delegate after the audio has been finished playing. This will cause the playing attribute to be false again, which will eventually cause the particular RecordingRow to update itself and display the play button again.

Deleting recordings 🗑

Finally, we would like to allow the user to delete individual recordings. For this purpose, we add the default edit button to the navigation bar of our ContentView.

.navigationBarTitle("Voice recorder")
.navigationBarItems(trailing: EditButton())

This button enables the user to select individual RecordingRows from the RecordingList that he wants to delete. To do this, the Edit button expects us to implement a delete function. We have to add this function to our RecordingsList.

struct RecordingsList: View {
    
    //...
    
    var body: some View {
        //...
    }
    
    func delete(at offsets: IndexSet) {
        
        
    }
}

The offsets argument represents a set of indexes of recording rows that the user has chosen to delete. With these, we create an array of the file paths of the recordings to be deleted.

func delete(at offsets: IndexSet) {
        
        var urlsToDelete = [URL]()
        for index in offsets {
            urlsToDelete.append(audioRecorder.recordings[index].fileURL)
        }

    }

We can now add a function within our AudioRecorder that accepts an array of urls and deletes the corresponding files from the document folder. When the deletion is completed we update our recordings array using the fetchRecording function.

func deleteRecording(urlsToDelete: [URL]) {
        
        for url in urlsToDelete {
            print(url)
            do {
               try FileManager.default.removeItem(at: url)
            } catch {
                print("File could not be deleted!")
            }
        }
        
        fetchRecordings()
        
    }

We now call this function from the delete function of our RecordingsList.

func delete(at offsets: IndexSet) {
        
        var urlsToDelete = [URL]()
        for index in offsets {
            urlsToDelete.append(audioRecorder.recordings[index].fileURL)
        }
        audioRecorder.deleteRecording(urlsToDelete: urlsToDelete)
    }

Finally, we have to apply the delete functionality to every RecordingRow in the RecordingList by writing:

List {
            ForEach(audioRecorder.recordings, id: \.createdAt) { recording in
                RecordingRow(audioURL: recording.fileURL)
            }
                .onDelete(perform: delete)
        }

Run the app to see if it works. We can now either swipe a recording to the right or tap the edit button to delete it.

Conclusion 🎊

That’s it, We are finished with our own voice recorder app! We learned how to record and save audios and how to play and delete them.

You can look up the complete source code of the app here.

I hope you enjoyed this tutorial! If you want to learn more about SwiftUI, check out our other tutorials! Also make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!