Categories
Uncategorized

What’s new in SwiftUI (Xcode 12 Beta) – Everything you should know

At WWDC20, Apple introduced Xcode 12 coming with an updated version of the SwiftUI framework. This update provides SwiftUI developers with a huge amount of new features and concepts, enhanced workflow, and improved stability. This article summarizes all major updates getting shipped with Xcode 12. We will explore a reworked app hierarchy concept, new and improved views and modifiers, and many more new features.

Hint: The SwiftUI update we’re talking about in this tutorial gets shipped with the new Xcode 12 Beta. Check out this tutorial to learn where to download it and what else you should know about the new Xcode update. 

New app hierarchy concept 🔽💡

The first thing you notice when you create a new SwiftUI project in Xcode 12 is that besides the usual ContentView.swift file, another file is “YourProjectNameApp.swift” (By the way, if you’re wondering why there are multiple folders called “Shared”, “iOS” and “macOS”, check out this post). The code inside this file looks like this:

import SwiftUI

@main
struct YourProjectNameApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}


What is this all about? Well, with the new SwiftUI update, Apple introduced a reworked app hierarchy concept including new APIs like App and Scene

We will dedicate a whole article to talk about this in more detail, but let’s try to explain this new concept as concisely as possible.

As you already know, everything you see on the screen of your app is in some way defined by a View. The more complex the interface of your app gets the more nested Views you create resulting in less or more complex view hierarchies. 

Certain areas of visible Views can be divided into distinct regions. With the new SwiftUI update, we refer to those areas as Scenes. Scenes always consist of one or multiple Views and there are multiple types of Scenes, like WindowGroup or DocumentGroup


All Scenes together form the entire content for our App. An App itself contains Scenes which concludes in the following hierarchy structure of any SwiftUI app.


With this knowledge, you should grasp the rough concept when looking at the default code in your YourProjectApp.swift file. The highest piece in the hierarchy is the App which returns Scenes, which in turn return Views.


Of course, there is much more to know about this, so make sure you read our related article about this concept. What’s cool to know at this point is that with this new concept apps can be written using 100% SwiftUI and furthermore that these apps can be easily constructed for running on multiple platforms like iOS, iPadOS, and macOS.

LazyStacks, Outlines, and Grids 📑🖼

The new SwiftUI update provides us with many new features regarding stacks and lists. Supposed you want to create a long, scrollable collection of elements (imagine something like the Instagram feed), you need to wrap those elements into a VStack embedded into a ScrollView. 

ScrollView {
            VStack {
                //View 1, view 2 etc.
            }
        }

However, the problem with this is that all views inside the VStack get initialized at once regardless of whether they are currently shown on screen. Depending on the number of views inside the stack, this could lead to performance issues.


Apple now remedies this by introducing LazyVStacks and LazyHStacks. By using these, we make sure that the content inside the VStack or HStack only gets loaded once they will be visible on the screen.

ScrollView {
            LazyVStack {
                //View 1, view 2 etc.
            }
        }

Furthermore, Apple equipped Lists with so-called outlines. Supposed you have a data model which allows children instances, for instance, something like this:

struct Film: Identifiable {
    let id = UUID()
    var name: String
    var parts: [Film]?
}

And a corresponding data set containing sub-instances:

let myFilmCollection = [
    Film(name: "Pulp Fiction"),
    Film(name: "Interstellar"),
    Film(name: "Lord of the Rings", parts: [
        Film(name: "The Fellowship of the Ring"),
        Film(name: "The Two Towers"),
        Film(name: "The Return of the King")
    ]),
    Film(name: "The Godfather")
]

And you want to display the data by grouping the children instances into an outline, you can pass your data into the list and indicate the children elements by providing SwiftUI with the corresponding property key path of your data model. 

List(myFilmCollection, children: \.parts) { movie in
            Text(movie.name)
        }

How the outline is presented depends on the device the app runs on and the .listStyle you apply to the List. On iOS and with no custom .listStyle applied the resulting List looks something like this: 



Finally, with the new update, we have a proper alternative to UICollectionView we know from the UIKit framework: Grid views. Those Grids are super easy to implement. Just embed a LazyVGrid or LazyHGrid (yes, they are lazy by default) into a ScrollView. Specify the number and appearance of the rows/columns by passing a GridItem collection. 

struct ContentView: View {
    
    var columns = [
        GridItem(spacing: 5),
        GridItem(spacing: 10),
        GridItem(spacing: 5)
    ]
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 100) {
                
            }
        }
    }
}

Inside the Grid, you can iterate through a specific data set and return a cell for every element inside this data set, like do it with Lists.

ScrollView {
            LazyVGrid(columns: columns, spacing: 100) {
                ForEach(0 ..< 100) { entry in
                    MyCell()
                }
            }
        }

This is how such a Grid looks like in action:


More views, modifiers and customization options 🆕

Apple also introduced many more views, modifiers and customization options for existing modifiers and views. You already got to know the new Grid view. Another cool new view is the ProgressView. By inserting a plain ProgressView instance we get a spinning activity indicator. When passing a value to it, we get a linear progress bar by default.

struct MyProgressView: View {
    
    @State var progress = 0.4
    
    var body: some View {
        ProgressView(value: progress, total: 1.0)
            .padding(10)
    }
}


Another new feature is the Label view. A Label consists of a String and an appended Image and its appearance depends on the context where the label is placed in. For example, let’s create a List with two sections containing several Label views.

NavigationView {
            List {
                Section(header: Text("Drama")) {
                    Label("The Godfather", systemImage: "film")
                    Label("The Shawshank Redemption", systemImage: "film")
                    Label("Green Mile", systemImage: "film")
                }
                Section(header: Text("Comedy")) {
                    Label("Bruce Almighty", systemImage: "film")
                    Label("Night at the Museum", systemImage: "film")
                    Label("Men in Black", systemImage: "film")
                    Label("Jumanji", systemImage: "film")
                }
            }
                .navigationTitle(Text("My Film Collection"))
        }

This is how the resulting preview looks like. 


Note: In specific situations, for example in plain lists, Label views seem to have weird behavior, as you see in the image above (I’m currently not certain if this is a bug in Xcode 12 beta). However, we can change this when applying a certain .listStyle, for example to SidebarListStyle which is new this year and provides us this beautiful layout:

List {
                //...
            }
                .listStyle(SidebarListStyle())
                //...
List {
                //...
            }
                .listStyle(SidebarListStyle())
                //...


Another cool new modifier is .listItemTint which we can apply to views inside a list or to whole sections like this:

List {
                Section(header: Text("Drama")) {
                    //...
                    Label("Green Mile", systemImage: "film")
                        .listItemTint(.gray)
                }
                Section(header: Text("Comedy")) {
                    //...
                }
                    .listItemTint(.gray)
            }
                .listStyle(SidebarListStyle())
//...

Finally, we can also customize the tint color of controls like Toggles like this: 

Toggle("Show favorites only", isOn: $isOn)
                    .toggleStyle(SwitchToggleStyle(tint: .blue))


There are several more views, modifiers and customization options to discover. This is beyond the scope of this article and will be discussed in separate tutorials.

Updated data flow concept 🌊

Apple has also reworked the data flow concept in SwiftUI and especially adapted it to the new App hierarchy we mentioned above and also introduced new property wrappers like @StateObject or @SceneStorage.

This would also go beyond the scope of this article and we will publish a dedicated tutorial on this topic in the near future. 

Good to know is that the data flow structure of your existing SwiftUI apps can be adopted without any further adjustments.

Enhanced system integration 🚀

Finally, let’s talk about to enhanced system integration Apple provides the SwiftUI framework with. 

On the one hand, there are features like Links that you can insert into your SwiftUI view that automatically opens a specified destination, for example, a URL in your browser.

Link(destination: myURL) {
                    Label("IMDB List", systemImage: "star")
                }



On the other hand, third frameworks such as AVKit or SceneKit now provide possibilities to use their functionality directly in SwiftUI apps without having to take the detour via UIHostingController as it was often the case before.

Conclusion 🎊

So you see that SwiftUI has become even more powerful. With more views, more functional modifiers and customization options, a revised app hierarchy concept, and advanced system integration, it is now even easier to write powerful apps with 100% SwiftUI.

As I said, we will write separate tutorials on many topics, in which we will go into more detail. Make sure you subscribe to our newsletter so you don’t miss anything (No spam, I promise!).

Categories
Uncategorized

Xcode 12 – All new features and improvements

The new Xcode 12 beta has just been released. Apple developers get a redesigned development environment, a more efficient workflow and great new features. In this post, we’re going to introduce you to all of the important new features that come with Xcode 12.

You can download the Xcode 12 Beta here. Note that you need to run at least MacOS Catalina for running Xcode 12 properly.

Let’s start with probably the most noticeable novelty.

New Look 🎨

At the WWDC20, Apple presented the new MacOS Big Sur which brings a completely revised design and a new visual aesthetic. The look of Xcode 12 has also been adapted to this, which now looks even cleaner and tidier and we think it’s a pretty cool new feature.

Particularly noticeable is the newly designed toolbar with modernised controls and icons. But also the inspectors have been updated.

With Xcode 12, the font size of the Navigator area automatically matches the system’s general sidebar setting. But you can also change adjust the Navigator area font size by going opening Preferences and choosing the “Navigator Size” under the “General” tab.


What’s also super cool is that while Xcode 12 is in fullscreen, you can easily close the left side menu and extend it by moving the mouse to the left edge of the screen. This allows you to work even more space-efficiently.

Document Tabs  📑

Finally, in Xcode 12, you can now easily work in multiple tabs to speed up your workflow massively.

Let’s say you want to keep a file open by holding it in a new tab. To open a new tab, just double click on the respective file in the navigator area or hold down option while you click on it.


You can simply rearrange your tabs by dragging them and close them again by clicking on the “X”. 

The document tab feature works regardless of the type of content you want to open in a new tab.

You can also create a split editor and load a whole bunch of files as tabs into it. When you’re done working on these, you can close the split editor again.


More Power to SwiftUI 🚀

Xcode 12 ships with a SwiftUI framework that is as fast as never before. New SwiftUI views like Labels, Grids and Toolbars were added. The new features of SwiftUI are a broad topic, therefore, we dedicated an own post to this (coming soon).

What can already be noted at this point is that the SwiftUI previews work much faster. They now also have a revised toolbar with new and improved functionality. 

You can now also design your own views and add them to the View Library.

As said, the functionality of SwiftUI has been greatly expanded and the framework has been significantly improved. Make sure you check out this article to know about all the new SwiftUI features in detail (coming soon).

Another cool thing is the new App API and lifecycle that was added to SwiftUI. No AppDelegate and no storyboards needed anymore are used anymore to code an app. Everything can be done by using 100% SwiftUI

In addition, with Xcode 12 and SwiftUI Apple is encouraging even more to develop apps that run cross-platform on iOS, iPadOS and macOS. 

This is noticeable by the fact that Xcode 12 automatically develops so-called multiplatform app templates. In the Navigator Area you can see a group folder “Shared”, which contains e.g. the view files that are used by all platforms. There are also other folders where system-specific adjustments can be made.

Enhanced Code Completion and Auto-Intendation ☑️

One of the other new cool features of Xcode 12 is that code completion works now even faster. Another cool feature introduced with Xcode 12 is that code completion automatically inserts placeholders for the required arguments. So you don’t need to worry about any compile errors while writing down your code using code completion.


Furthermore, auto-indentation was improved and works faster and more reliable now. Especially when working with SwiftUI and inserting modifiers etc. this difference is especially noticeable.

Developing Universal Apps 🌎

Apple has also announced that in the medium term they will no longer use Intel chips, but will introduce their own hardware, called “Apple Silicon”. 

But that shouldn’t worry you too much. Apps developed with Xcode 12 will run without problems on Intel-based devices as well as on the future Apple Silicon hardware. And even apps developed in the past only need to be recompiled once, so they’re ready for future Mac systems with just one click. 

New Testing Functionality 🧪

[Coming soon]

Conclusion 🎊

As you can see, Xcode 12 introduced some changes, cool new features and improvements. Most noticeable is that Apple is pushing and expanding its SwiftUI framework even further. So it’s crucial to learn SwiftUI, if you haven’t done so yet. You can download our free SwiftUI Basics eBook.

Which new feature do you like best? Does something bother you about the update or do you miss certain features? Let us know in the comments.

Categories
Uncategorized

Creating a Simple Stopwatch in SwiftUI

Welcome to a new SwiftUI tutorial! Today we will take a look at @ObservableObjects and @Published property wrappers, learn what they are used for and how to use them to create a simple SwiftUI stopwatch app. Understanding these property wrappers is especially important for creating functional models for your SwiftUI apps that you can use to communicate with your views.

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



First, create a new Xcode project, select Single View App and make sure that you select SwiftUI as the “User Interface”.

Preparing the UI 🎨

We start by creating the interface for our stopwatch app. For this, we can use the default ContentView.swift file. First, replace the string of the “Hello, World” text with a placeholder for the elapsed seconds. We also use a custom font and make sure there is enough space around the text by using appropriate .paddings.

struct ContentView: View {
    var body: some View {
        Text("0.0")
            .font(.custom("Avenir", size: 40))
            . padding(.top, 200)
            .padding(.bottom, 100)
    }
}


Below the TextView, we will use Buttons to start, pause and stop the stopwatch. Wrap the TextView into a VStack and add a corresponding Button. At the moment, we just use a dummy print statement as placeholder. 

VStack {
            Text("0.0")
                .font(.custom("Avenir", size: 40))
                .padding(.top, 200)
                .padding(.bottom, 100)
            Button(action: {print("Start timer.")}) {
                Text("Start")
                    .foregroundColor(.white)
                    .padding(.vertical, 20)
                    .padding(.horizontal, 90)
                    .background(Color.blue)
                    .cornerRadius(10)
            }
        }


Later, we will use this button design for the pause and stop buttons as well, each with different text and color. To make it reusable, CMD-click on the Text view of our button and select “Extract Subview”. We can call the extracted subview TimerButton.

As already mentioned, the text and the color of the button should be changeable. Therefore, we use dynamic properties in our TimerButton struct instead of fixed values, which we then initialize from our ContentView. So change the TimerButton struct as follows:

struct TimerButton: View {
    
    let label: String
    let buttonColor: Color
    
    var body: some View {
        Text(label)
            .foregroundColor(.white)
            .padding(.vertical, 20)
            .padding(.horizontal, 90)
            .background(buttonColor)
            .cornerRadius(10)
    }
}


Now, we have to initialize the parameters in our ContentView accordingly.

VStack {
            //...
            Button(action: {print("Start Timer")}) {
                TimerButton(label: "Start", buttonColor: .blue)
            }
        }


Finally, we push the views inside our VStack upwards by inserting a Spacer.

VStack {
            Text("0.0")
                //...
           Button(action: {print("Start Timer")}) {
                TimerButton(label: "Start", buttonColor: .blue)
            }
            Spacer()
        }


So much for the preparations for the UI of our stopwatch app. Your SwiftUI preview for your stopwatch app should now look like this:

Setting up our StopWatchManager ⏱

For our SwiftUI stopwatch app, we need an instance that notifies our ContentView every time unit (e.g. every tenth of a second) after we start the timer and adjust the TextView accordingly.

This functionality is ideally placed in a separate class. Therefore, we create a new Swift file, which we call StopWatchManager.swift In this file, we import the SwiftUI framework and create a new class, which we also call StopWatchManager. 

import SwiftUI

class StopWatchManager {
    
}


Our StopWatchManager needs a variable with which we keep track of how much time has passed since the timer was started. This variable will be called secondsElapsed. We also initialize a Swift Timer instance

class StopWatchManager {
    
    var secondsElapsed = 0.0
    var timer = Timer()
}


We also create a function that starts the timer, which adds the value 0.1 to our secondsElapsed every 0.1 seconds. 

class StopWatchManager {
    
    //...
    
    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
            self.secondsElapsed += 0.1
        }
    }
}


We can now initialize the StopWatchManager in our ContentView and access the respective value of the secondsElapsed property for the TextView. We also want to trigger the start function when we tap the corresponding Button.

struct ContentView: View {
    
    var stopWatchManager = StopWatchManager()
    
    var body: some View {
        VStack {
            Text(String(format: "%.1f", stopWatchManager.secondsElapsed))
                //...
            Button(action: {self.stopWatchManager.start()}) {
                TimerButton(label: "Start", buttonColor: .blue)
            }
            //...
        }
    }
}


Now, launch the app in the live preview and tap the start Button. You will see that the preview does not change. This is because although the timer in our StopWatchManager starts to add 0.1 to the secondsElapsed property every tenth of a second, our ContentView is not being notified about these changes and therefore does not show the continuously updated values. 

Using the @ObservableObject and @Published property wrapper functionality 🛠

However, we can easily implement this functionality. To do this, we use the so-called ObservableObject protocol.

class StopWatchManager: ObservableObject {
    
    //...
}


ObservableObjects are similar to State properties which you probably already know. But instead of just (re)rendering a view depending on its assigned data, ObservableObjects are capable of the following things:

  • We can bind one or multiple views to the ObservableObject (or better said, we can make these views observe the object).
  • The observing views can access and manipulate the data inside the ObservableObject. When a change happens to the ObservableObject’s data all observing views get automatically rerendered, similar to when a State changes

We want our ContentView to observe our StopWatchManager to “listen” to changes in it. To do this, we use the @ObservedObject property wrapper in front of the initialisation of the StopWatchManager property.

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


Okay, our ContentView is now able to listen and to react to changes in the stopWatchManager instance. But how do we specifically notify and tell the ContentView to rerender every time the value assigned to the secondsElapsed property changes? Well, that’s quite easy: Just place the @Published property wrapper in front of it!

class StopWatchManager: ObservableObject {
    
    @Published var secondsElapsed = 0.0
    
    //...
}


This property wrapper tells all observing views (including our ContentView!) to reload themselves whenever the value assigned to secondsElapsed changes.

Let’s rerun the preview of our ContentView in live mode and check if that works. 



Awesome! After you tap the “Start” button, the timer in our stopWatchManager every 0.1 seconds adds 0.1 to our secondsElapsed variable. And since its a @Published variable and our ContentView observes the StopWatchManager ObservableObject, our ContentView will rerender every 0.1 seconds as well with always showing exactly how many seconds have already elapsed!

Hint: If it doesn’t work in your ContentView preview, try to run your app in the “regular” simulator.

Completing the timer functionality ⏲

Okay, we now know how @ObservableObject and @Published property wrappers work and have used this knowledge to implement our basic timer in SwiftUI. Let’s finish our SwiftUI stopwatch app by implementing the possibility to pause and stop the timer. 

First, we add a corresponding enum under our StopWatchManager class, so that later on we always know if our stopwatch is currently running, paused or stopped.

enum stopWatchMode {
    case running
    case stopped
    case paused
}


Next, we add another @Published property to our StopWatchManager so that every time our timer is started, paused or stopped, our ContentView can react and adapt accordingly.

class StopWatchManager: ObservableObject {
    
    @Published var mode: stopWatchMode = .stopped
    //...
}


When our timer starts, we set the mode to .running.

func start() {
        mode = .running
        timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
            self.secondsElapsed = self.secondsElapsed + 0.1
        }
    }


Next, we add two more functions that pause and reset the timer and change the mode accordingly.

class StopWatchManager: ObservableObject {
    
    //...
    
    func stop() {
        timer.invalidate()
        secondsElapsed = 0
        mode = .stopped
    }
    
}


Finishing our UI behaviour 👨‍💻

Great, our StopWatchManager can now also pause and stop the timer and furthermore our ContentView always knows whether the timer is running, paused or stopped through the @Published mode property.

Depending on the mode, we want to show different buttons in our ContentView. So if the timer mode is currently .stopped we want to show the start button.

VStack {
            Text(String(format: "%.1f", stopWatchManager.secondsElapsed))
                //...
            if stopWatchManager.mode == .stopped {
                Button(action: {self.stopWatchManager.start()}) {
                    TimerButton(label: "Start", buttonColor: .blue)
                }
            }
            Spacer()
        }


On the other hand, if the timer is running, we want to display a button that pauses the timer, so we write:


VStack {
            Text(String(format: "%.1f", stopWatchManager.secondsElapsed))
                //...
            if stopWatchManager.mode == .stopped {
                //...
            }
            if stopWatchManager.mode == .running {
                Button(action: {self.stopWatchManager.pause()}) {
                    TimerButton(label: "Pause", buttonColor: .blue)
                }
            }
            Spacer()
        }


And when the timer is eventually paused, we want to show two buttons: one to resume the timer and one to stop it.

VStack {
            Text(String(format: "%.1f", stopWatchManager.secondsElapsed))
                //...
            if stopWatchManager.mode == .stopped {
                //...
            }
            if stopWatchManager.mode == .running {
                //...
            }
            if stopWatchManager.mode == .paused {
                Button(action: {self.stopWatchManager.start()}) {
                    TimerButton(label: "Start", buttonColor: .blue)
                }
                Button(action: {self.stopWatchManager.stop()}) {
                    TimerButton(label: "Stop", buttonColor: .red)
                }
                    .padding(.top, 30)
            }
            Spacer()
        }


If we now run our app, we can start, pause and stop our stopwatch!

Conclusion 🎊

That’s it! We learned how to communicate between models and views in SwiftUI by using @ObservableObjects and @Published modifiers and used that knowledge to create a simple stopwatch app in SwiftUI.

You can find the source code for this app here.

We hope you liked this tutorial. Let us know what you think about it in the comments 👇 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 detect shake gestures in SwiftUI

iOS devices are capable of detecting when the device gets shaken. Implementing these shake gestures into your app can be a pretty cool feature. But how do you implement a shake gesture in SwiftUI? Well, here you go! 

In our example app, we want to roll the dice by executing the corresponding function when the device gets shaken.

This is the initial code of our SwiftUI ContentView. If you want to learn how it works, feel free to download our free SwiftUI Basics eBook where we build it from scratch.

import SwiftUI

struct ContentView: View {
    
    @State var rolledNumber = 1
    @State var rolledIt = false
    
    var body: some View {
        VStack {
            Image(rolledIt ? "\(rolledNumber)" : "unknown")
                .resizable()
                .frame(width: 100, height: 100)
                .aspectRatio(contentMode: .fit)
                .clipped()
                .padding(.top, 250)
            Spacer()
            Text("Shake device to roll the dice!")
                .padding(.bottom, 40)
            Spacer()
        }
    }
    
    func rollDice() {
        let randomNumber = Int.random(in: 1 ..< 7)
        self.rolledNumber = randomNumber
        self.rolledIt = true
    }
}

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

struct RollButtonContent: View {
    var body: some View {
        Text("Roll the dice")
            .frame(width: 240, height: 75)
            .foregroundColor(.white)
            .font(.headline)
            .background(Color.orange)
            .cornerRadius(20)
    }
}


Although detecting shake gestures is not possible by relying on merely SwiftUI, there’s a trick to achieve the same result. Just follow these steps:

Step 1: Create a new file and name it ShakeGestureManager.swift. Make sure you import the SwiftUI and the Combine framework.

import SwiftUI
import Combine


Step 2: Inside this file, you need to create a PassthroughSubject that is part of the Combine framework. By using this, we can notify our SwiftUI app when the device was shaken.

let messagePublisher = PassthroughSubject<Void, Never>()


Step 3: Next, create a UIViewController in this file. Inside this class, we can override the motionBegan method that gets executed when the device gets shaken. ⠀

class ShakableViewController: UIViewController {

    override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        
    }
}


Step 4: When the shake gesture event happens, we use our messagePublisher to notify the SwiftUI view later on.⠀

class ShakableViewController: UIViewController {

    override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        guard motion == .motionShake else { return }
        messagePublisher.send()
    }
}


Step 5: Since we can’t directly embed UIViewControllers into SwiftUI views, we need to host it inside a UIViewControllerRepresentable. Use the protocol’s makeUIViewController method to initialise the defined view controller. Since we won’t need it, you can leave the updateUIViewController empty.⠀

struct ShakableViewRepresentable: UIViewControllerRepresentable {
    
    func makeUIViewController(context: Context) -> ShakableViewController {
        ShakableViewController()
    }
    func updateUIViewController(_ uiViewController: ShakableViewController, context: Context) {
        
    }
}


Step 6: Now we’re ready to insert the ShakeableViewRepresentable into our SwiftUI view. We don’t want it to affect the layout of the remaining views of our ContentView, this is why we place it behind the remaining content by using a ZStack. We also prevent the user from interacting with the representable by applying the .allowsHitTesting(false) modifier. ⠀

var body: some View {
        
        ZStack {
            ShakableViewRepresentable()
                .allowsHitTesting(false)
            VStack {
                //...
            }
        }
    }


Step 7: Now, we can make our SwiftUI listen to the messagePublisher by using the .onReceive modifier. Once the publisher gets fired, we execute our rollDice function.⠀

 var body: some View {
        ZStack {
            ShakableViewRepresentable()
                .allowsHitTesting(false)
            VStack {
                //...
            }
                .onReceive(messagePublisher) { _ in
                    self.rollDice()
                }
        }
    }


That’s it! Run your app in the simulator and click on “Device” and “Shake” to test the implemented shake gesture. You see that detecting shake gestures in SwiftUI is not too hard 🎊⠀



We hope you liked this tutorial. Let us know what you think about it in the comments 👇 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

Video Based Onboarding Screen in SwiftUI

Hello and welcome to another SwiftUI tutorial! Today we’re going to learn how we can stream and play videos inside a SwiftUI application. While doing this, we are going to create a dynamic, video-based SwiftUI onboarding screen. 

Hint: If you’re looking for a short guide to embed videos into SwiftUI apps, take a look at this tutorial. (coming soon)

This is how our app will look like at the end of this tutorial:

Preparing our UI 🎨

Let’s start by setting up a new Xcode project. Open Xcode and create a new Xcode project. Select “Single View App”, name your project and make sure you select “SwiftUI” as the User Interface mode. 

For preparing the UI of our app we can stick with the default ContentView.swift file. We want the played video to cover the entire screen. But before we implement the video player, we work with a solid black background as a placeholder for it. Therefore, delete the default “Hello, World!” Text view and insert a black Color view instead. 

struct ContentView: View {
    var body: some View {
        Color.black
    }
}


By default, any SwiftUI view’s content stays inside the so-called safe area. This causes our Color view to not touch the upper and lower edge of the screen. However, we can tell our Color view to exceed the boundaries of the safe area by using the .edgesIgnoringSafeArea modifier. You can determine which safe area edges you want to ignore. In our example, we choose .all.

struct ContentView: View {
    var body: some View {
        Color.black
            .edgesIgnoringSafeArea(.all)
    }
}


This is how your ContentView preview should look like now:


On top of our Color view (remember: we just use this as a temporary placeholder for the video player) we want to place the logo of our app and two Text views describing the benefits of our app.

To stack views on top of other views, we use ZStacks. Therefore, wrap the Color view into a ZStack and place a VStack below the Color view.

ZStack {
            Color.black
                .edgesIgnoringSafeArea(.all)
            VStack {
                
            }
        }


Inside the VStack, we place the logo for of our app and both text views for aligning them vertically. We start with inserting an Image view for our app’s logo. In this example, we use the “paperplane.fill” system icon and apply the common modifiers to frame it properly. 

VStack {
                Image(systemName: "paperplane.fill")
                    .resizable()
                    .frame(width: 70, height: 70)
                    .aspectRatio(contentMode: .fit)
                    .foregroundColor(.white)
                    .padding(.bottom, 30)
            }


Below our Image view, we place both Text views and stylise them like this:

VStack {
                Image(systemName: "paperplane.fill")
                    //...
                Text("Explore the World")
                    .font(.largeTitle)
                    .foregroundColor(.white)
                Text("Discover the most amazing places in the world and share your experience with the No. 1 travel community.")
                    .foregroundColor(.white)
                    .frame(maxWidth: 320)
                    .padding(.top, 20)
                    .padding(.bottom, 50)
            }


And this is how your preview should look like now:


What’s missing is the bottom row of buttons that allow the user to either login or register. We want to visually separate this by using a thin white rectangle which we place below our last Text view. 

VStack {
                //...
                Rectangle()
                    .frame(height: 1)
                    .foregroundColor(.white)
            }


Next, view insert another HStack holding the “Login” and the “Signup” Text view. We distribute both Text views equally by inserting Spacer views as well.

HStack {
                Spacer()
                Text("Login")
                    .foregroundColor(.white)
                    .padding(20)
                Spacer()
                Text("Signup")
                    .foregroundColor(.white)
                    .padding(20)
                Spacer()
            }


We want to push the whole “button row” to the bottom while fixing the icon and Text views at the center of our ContentView. To do this, place a Spacer view right above the thin Rectangle and above the Image view. 

VStack {
                Spacer()
                Image(systemName: "paperplane.fill")
                    //...
                Text("Explore the World")
                    //...
                Text("Discover the most amazing places in the world and share your experience with the No. 1 travel community.")
                    //...
                Spacer()
                Rectangle()
                    //...
                HStack {
                    //....
                }
            }


Awesome, we’re done with preparing the UI of our onboarding screen. This is how your preview should look like now:


Next, we will learn how to create a video player in SwiftUI that we can use as the background of the ContentView instead of our black Color view. 

Setting up a video player in SwiftUI 📼

Unfortunately, SwiftUI does not provide a video player on its own. Therefore, we need to rely on using an AVPlayer (which is part of the AVKit framework) in connection with the UIKit. This means that we have to create an AVPlayer which we insert into an UIView. Then we’ll use this UIView to embed it into our SwiftUI ContentView. Don’t worry, this sounds more complicated than it actually is. 

As said, we need a UIView view to hold an AVPlayer. Therefore, create a new File-New-File and choose Swift file. Name this file UIPlayerView. Make sure you import the SwiftUI and the AVKit framework and create a class that adopts the UIView protocol.

import AVKit
import SwiftUI

class UIVideoPlayer: UIView {
    
}


Inside this class, we start with declaring an AVPlayerLayer which actually holds the visual output of the video we will play.


class UIVideoPlayer: UIView {
    
    var playerLayer = AVPlayerLayer()

}


When initialising our UIVideoPlayer view, we first make sure to have a valid url before trying to stream the video from it. To do this, we use a guard-let statement. So, insert the following overriding init method into the UIVideoPlayer class. 

override init(frame: CGRect) {
        super.init(frame: frame)
        
       guard let url = URL(string: "https://github.com/BLCKBIRDS/Video-Based-Onboarding-Screen-in-SwiftUI/blob/master/video.mp4?raw=true") else { return }
    }


When overriding the init function we also need to provide our UIView with the required init?(coder: NSCoder) method. So, insert this function into your UIVideoPlayer class as well:

required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


In our overriding init function, after we safely unwrapped the url, we can create an AVPlayer instance that streams the video from the provided url. We also mute the AVPlayer and tell it to play the video immediately upon initialisation.

override init(frame: CGRect) {
        super.init(frame: frame)
        
        guard let url = //...

        let player = AVPlayer(url: url)
        player.isMuted = true
        player.play()
    }


Next, we add the created player to the playerLayer we created earlier. 

override init(frame: CGRect) {
        super.init(frame: frame)
        
        guard let url = //...

        let player = AVPlayer(url: url)
        player.isMuted = true
        player.play()
      
        playerLayer.player = player
    }


We also want to play the video in fullscreen. To do this, we tell our playerLayer to resize the video within its bounds (the bounds will be equal to the frame of the player view once we embedded it into our SwiftUI view) by using the aspect fill option.

override init(frame: CGRect) {
        super.init(frame: frame)
        
        guard let url = //...

        let player = AVPlayer(url: url)
        player.isMuted = true
        player.play()
      
        playerLayer.player = player
        playerLayer.videoGravity = AVLayerVideoGravity(rawValue: AVLayerVideoGravity.resizeAspectFill.rawValue)

    }


Finally, we add the playerLayer to the UIView. This results in the following init function:

override init(frame: CGRect) {
        super.init(frame: frame)
        
        guard let url = //...

        let player = AVPlayer(url: url)
        player.isMuted = true
        player.play()
      
        playerLayer.player = player
        playerLayer.videoGravity = AVLayerVideoGravity(rawValue: AVLayerVideoGravity.resizeAspectFill.rawValue)
        
        layer.addSublayer(playerLayer)
    }

We need to lay out all the subviews of our UIVideoPlayer to adapt their frames to the UIVideoPlayer bounds (remember: our playerLayer is such a subview). To do this, we insert the third and last override method into our UIVideoPlayer class:

override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }


That’s it. We successfully created a video player that streams the video from a specific url and that we insert into our SwiftUI view.

Inserting and modifying the PlayerView 👨‍💻

But before we can embed the UIVideoPlayer into our SwiftUI ContentView, we need to embed it into a UIViewRepresentable first since only a representable can be directly inserted into a SwiftUI view.

To do this, create another class called PlayerView adopting the UIViewRepresentable protocol.

struct PlayerView: UIViewRepresentable {

}


The UIViewRepresentable protocol has two mandatory functions: makeUIView and updateUIView. The makeUIView function creates and returns the specified view (in our case that should be the UIViewPlayer) while the updateUIView method gets called every time the state of the view gets updated (roughly speaking). However, we don’t need the functionality of the latter one for our app so we just insert the corresponding protocol stub.

struct PlayerView: UIViewRepresentable {

    func makeUIView(context: Context) -> UIVideoPlayer {
        return UIVideoPlayer()
    }

    func updateUIView(_ uiView: UIVideoPlayer, context: Context) {
        
    }
}


Awesome! The PlayerView now hosts our UIVideoPlayer and we can insert it into our ContentView.

So in your ContentView, replace the Color view with a PlayerView instance.

ZStack {
            PlayerView
                .edgesIgnoringSafeArea(.all)
            VStack {
                //...
            }
        }


Your preview gets filled with a blank color now but if you start a live preview, you’ll notice that the video starts playing!


Let’s customise the appearance of the PlayerView a little bit. First, we want to overlay it with an opaque green color.

PlayerView()
                .overlay(Color.green.opacity(0.4))
                .edgesIgnoringSafeArea(.all)


By doing this, we also see the remaining views of our ContentView while the video does not run.

Finally, we also apply some blur to the PlayerView:

PlayerView()
                .overlay(Color.green.opacity(0.4))
                .blur(radius: 1)
                .edgesIgnoringSafeArea(.all)


If you start a live preview now, you see that the video gets overplayed with a green color and is also a little bit blurred which makes the remaining content stand our more.


Conclusion 🎊

That’s it! You just learned how you can stream and play videos inside your SwiftUI app and how you can use this technique for creating a dynamic onboarding screen. 

Here you can find the source code of this 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 use Local Notifications in SwiftUI

Today we’ll show you a quick way to use local notifications in SwiftUI apps. There a just a few steps you need to follow. In our demo app, we’re creating a simple notification that gets triggered after a specified time interval. Let’s go!

This is how our notification should look like in the end:


Step 1: In your SwiftUI app, create a new class called LocalNotificationManager that adopts the @ObservableObject. Adopting this protocol isn’t a must but can be useful if you want your SwiftUI view to react when a local notifications gets fired.

import Foundation
import SwiftUI

class LocalNotificationManager: ObservableObject {
    

}


Step 2: Inside this class declare an array that holds the Notification objects that get created.

class LocalNotificationManager: ObservableObject {
    
    var notifications = [Notification]()
    
}


Step 3: Before firing notification, we need to ask the user for his permission. We want to do this when initialising the LocalNotificationManager instance.

init() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
            if granted == true && error == nil {
                print("Notifications permitted")
            } else {
                print("Notifications not permitted")
            }
        }
    }


Step 4: Inside our LocalNotificationManager class, we create a function that uses certain parameters to create a Notification instance. 

func sendNotification(title: String, subtitle: String?, body: String, launchIn: Double) {
        //...
    }


Step 5: Inside the function, we define which content we want to use for the Notification. We can even attach media like images to it.
 

func sendNotification(title: String, subtitle: String?, body: String, launchIn: Double) {
        
        let content = UNMutableNotificationContent()
        content.title = title
        if let subtitle = subtitle {
            content.subtitle = subtitle
        }
        content.body = body
           
        let imageName = "logo"
        guard let imageURL = Bundle.main.url(forResource: imageName, withExtension: "png") else { return }
        let attachment = try! UNNotificationAttachment(identifier: imageName, url: imageURL, options: .none)
        content.attachments = [attachment]
    }


Step 6: After that set the trigger for the notification to get fired. In our example, we define a certain time interval. But you can also use an UNCalendarNotificationTrigger or an UNLocationNotificationTrigger.

func sendNotification(title: String, subtitle: String?, body: String, launchIn: Double) {
        
        //...
           
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: launchIn, repeats: false)
        let request = UNNotificationRequest(identifier: "demoNotification", content: content, trigger: trigger)

    }


Step 7: Then, add the resulting request the the UNNotificationCenter. Once the defined time interval has lapsed, the notification gets fired!

func sendNotification(title: String, subtitle: String?, body: String, launchIn: Double) {
        
        //...

        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
    }


Step 8 and 9: In our example SwiftUI app, we want to launch the local notification when tapping on a button. To do this, initialise the LocalNotificationManager and use its sendNotification method.

struct ContentView: View {
    
    //8. Observe the notification manager in your SwiftUI view
    @ObservedObject var notificationManager = LocalNotificationManager()
    
    @State var showFootnote = false
    
    var body: some View {
        NavigationView {
            VStack {
                Button(action: {
                    withAnimation {
                        self.showFootnote.toggle()
                        //9. Use the send notification function
                        self.notificationManager.sendNotification(title: "Hurray!", subtitle: nil, body: "If you see this text, launching the local notification worked!", launchIn: 5)
                    }
                }) {
                    Text("Launch Local Notification 🚀")
                        .font(.title)
                }
                if showFootnote {
                    Text("Notification Arrives in 5 seconds")
                        .font(.footnote)
                }
            }
                .navigationBarTitle("Local Notification Demo", displayMode: .inline)
        }
    }
}


If we now run our SwiftUI app, launch the local notification and close the app, we see that the notification gets fired as expected!


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 embed a SwiftUI view into a UIKit Storyboard

In case you are working with storyboards you maybe wondered how you can embed a SwiftUI view into your UIKit storyboard. This can be very useful if you want to compose your views with SwiftUI with relying on the familiar storyboard functionality as well.

Maybe in your storyboard looks something like this:

And that’s how your SwiftUI view could look like:

Embedding the SwiftUI view into your storyboard is super simple, just follow the three steps below:

Step 1: Inside your storyboard file click on the + button and drag and drop a Hosting View Controller. This View Controller is capable of hosting a SwiftUI view.


Step 2: Next, create a UIHostingController subclass. Inside this class, initialise your specific SwiftUI view by using the “init? (coder aDecoder: NSCoder)” initialiser as you see above. ⠀

//Create a UIHostingController class that hosts your SwiftUI view
class SwiftUIViewHostingController: UIHostingController<MySwiftUIView> {
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder, rootView: MySwiftUIView())
    }
}

struct MySwiftUIView: View {
    var body: some View {
        //..
    }
}


Step 3: Now we’re ready to take this UIHostingController subclass and assign it the the Hosting Controller in our storyboard.⠀


That’s it! If you now run your app, the UIHostingController initialises your SwiftUI view properly. And that’s how you embed your SwiftUI view into your UIKit storyboard 🎊⠀


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

How to limit the number of characters in a SwiftUI TextField

Did you ever ask yourself how to limit the number of characters that are allowed to enter into a SwiftUI TextField? To set the maximum length for a text field, follow these steps 👇


Step 1: First of all, create a new Swift file and make sure you import the SwiftUI framework (and the AudioToolbox framework for letting the device vibrate later on).

import SwiftUI
import AudioToolbox


Step 2: Inside this file, you need to create an @ObservableObject that serves as the TextField’s manager und updates the related ContentView whenever the TextField will get updated. 

import SwiftUI
import AudioToolbox

class TextFieldManager: ObservableObject {
  
    
}


Step 3: Within the the TextfieldManager, we create a constant for setting the maximum length for our TextField.

class TextFieldManager: ObservableObject {
    
    let characterLimit = 4
    
}


Step 4: Next, make sure to create a @Published property for holding the TextField’s input and for updating the related ContentView whenever the input changes.

class TextFieldManager: ObservableObject {
    
    let characterLimit = 4
    
    @Published var userInput = ""
    
}


Step 5: In your ContentView, create a TextFieldManager instance and observe it by using the @ObservedObject property wrapper.

struct ContentView: View {

    @ObservedObject var textFieldManager = TextFieldManager()

    var body: some View {
        //...
    }
}


Step 6: Now we can create our actual TextField and bind it to the @Published property of our TextFieldManager instance

var body: some View {
        TextField("Enter something...", text: $textFieldManager.userInput)
            .padding()
            .background(Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0))
            .cornerRadius(5.0)
            .padding()
    }


Step 7: Back to our TextFieldManager.swift file: We must react every time the TextField gets updated. We do this by using the didSet property observer. 

@Published var userInput = "" {
        didSet {
            
        }
    }


Step 8: So every time the user enters a character we want to check if the userInput already exceeds the character limit. If that’s the case, we crop the new value to the allowed number of characters.

@Published var userInput = "" {
        didSet {
            if userInput.count > characterLimit {
                userInput = String(userInput.prefix(characterLimit))
            }
        }
    }


Step 9: Optionally, you can let the device vibrate when the allowed number gets exceeded by using the following method.

didSet {
            if userInput.count > characterLimit {
                userInput = String(userInput.prefix(characterLimit))
                AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) { return }
            }
        }


And that’s it. This is how you limit the number of characters in a SwiftUI TextField! ✨

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

How to use auto-validating Text Fields in SwiftUI

Hello and welcome to a new SwiftUI tutorial! Today we’ll learn how to use an automatic validator for SwiftUI TextFields and SecureFields. By using auto-validating Text Fields, we can, for instance, automatically check if the user entered correct credentials without needing him to tap on a login button or something similar.

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

Setting up the Xcode project 🛠

To get started, create a new Xcode project. Make sure “Use SwiftUI” is selected. After creating the project, you can create some color sets in your Assets.xcassets folder for creating the neumorphic look as described in this tutorial.

Optionally, you can create some color sets for achieving the neumorphic look we’re going to use in this tutorial.

Next, click on File-New-File on your Xcode toolbar and create a new Swift file. Call this Constants. In this, we are going to store the correct pin the user needs to enter to get access to the home screen.

import Foundation

let pin = "1234"


That’s it! We’re ready to compose the UI of our app.

Composing the UI 🎨

We continue by composing the interface of our auto-validation app. For the look of this, we follow an approach called Neumorphism design. We made a whole tutorial about this design technique, so make sure you check it out.

First, we alter the background color of our ContentView. To do this, replace the default “Hello World” Text view with a ZStack and place a Rectangle inside it. Make sure the Rectangle is colored and covers the whole screen by using the following modifiers:

var body: some View {
        ZStack {
            Rectangle()
                .fill(Color("TextFieldColor"))
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .edgesIgnoringSafeArea(.all)
        }
    }


Note that we created the “TextFieldColor” in our Assets folder earlier, make sure you check it out this tutorial to see how you can do this.

On top of our background Rectangle, we put a VStack that contains the necessary Text views to instruct the user to enter his verification code.

ZStack {
            //...
            VStack(alignment: .leading) {
                Text("Please enter your verification code")
                    .font(.largeTitle)
                    .fontWeight(.semibold)
                    .padding(.top, 70)
                    .padding(.bottom, 20)
                Text("We have sent you a four-digit PIN to verificate your phone number.")
                    .lineLimit(2)
                    .fixedSize(horizontal: false, vertical: true)
                    .padding(.bottom, 20)
            }
        }


Now it’s time to create the Text Field. But before we can do this, we need to declare a @State property that hold’s the user’s input.

struct ContentView: View {
    
    @State var enteredPin = ""
    
    var body: some View {
        //...
    }
    
}


For hiding the user’s input we use a special type of a Text Field, a SecureField. We style this SecureField and set the numeric pad as the default keyboard type. Then, we push the VStack’s views up by using a Spacer.

VStack(alignment: .leading) {
                //...
                SecureField("PIN", text: $enteredPin)
                    .keyboardType(.numberPad)
                    .padding()
                    .background(Color("TextFieldColor"))
                    .cornerRadius(5.0)
                    .shadow(color: Color("LightShadow"), radius: 8, x: -8, y: -8)
                    .shadow(color: Color("DarkShadow"), radius: 8, x: 8, y: 8)
                Spacer()
            }


Finally, we apply some padding to the whole VStack:

VStack(alignment: .leading) {
                //...
            }
                .padding(30)


And that’s it! This is how your preview should look like now:

Creating the View Router 

Before moving on with implementing our Text Field auto-validating functionality, we need to think about what should happen when the user enters the right PIN. In this case, we want to present another view, for instance, the app’s home screen. Let’s create such a sample screen by clicking on File-New-File and creating a new SwiftUI view called HomeView. Inside the HomeView, we replace the “Hello World” Text with “Home”.

struct HomeView: View {
    var body: some View {
        Text("Home")
    }
}


But how can we navigate to this view when the user enters the right PIN?

Note: We made a tutorial about navigating independently in SwiftUI and we’re basically using the same technique as described in this, so make sure you check out.

First, we need to create our view router that manages which view should currently be presented to the user. For this purpose, create a new Swift file and call it ViewRouter. Make sure you import the SwiftUI framework. Inside this file, create a class also called ViewRouter and adopt the ObservableObject protocol so other views in our app can notice when something with the ViewRouter happens.

import Foundation
import SwiftUI


class ViewRouter: ObservableObject {
    
}


As said, our ViewRouter keeps track of which view should currently be shown. For this purpose, we create a @Published variable and call it currentPage. By default, we want to show our ContentView, which we will call “pin”. 

class ViewRouter: ObservableObject {
    
    @Published var currentPage = "pin"
    
}


Next, we need to set up our mother view that holds all views and shows either the ContentView or the HomeView. So let’s create a new SwiftUI view called MotherView. Inside this view, we observe the ViewRouter as an @EnvironmentObject, so every time the currentPage variable of the ViewRouter changes, we can show the corresponding view. We implement this functionality by writing:

struct MotherView: View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    var body: some View {
        VStack {
            if viewRouter.currentPage == "home" {
                Text("Home")
            } else if viewRouter.currentPage == "pin" {
                ContentView()
            }
        }
    }
}


Next, we need to set our MotherView as the root view when the app launches. To do this, we go to our SceneDelegate.swift file and adjust the root view inside the scene function as follows:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        let viewRouter = ViewRouter()
        
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(viewRouter))
            self.window = window
            window.makeKeyAndVisible()
        }
    }


That’s it, now we’re capable of navigating to a new view which we will do when the user enters the correct PIN into the Text Field.

Again: If you didn’t understand what we just did with the ViewRouter, MotherView etc., please make sure you check out this tutorial where we explain everything in more detail.

Finally, we move the enteredPin @State of our ContentView into our ViewRouter class and turn it into a @Published property.

class ViewRouter: ObservableObject {
    
    @Published var enteredPin = ""
    
    //...
    
}

To bind our SecureField to the ViewRouter’s enteredPin property, we need to access it by using the @EnvironmentObject property wrapper inside our ContentView.

struct ContentView: View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    var body: some View {
        //...
    }
    
}

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


Now, we can bind our SecureField to the viewRouter’s enteredPin property.

SecureField("PIN", text: $viewRouter.enteredPin)
                    //...

Validating the Text Field ☑️

Every time the user enters something into the Text Field, we want to compare it to the PIN we stored in our Constants.swift file earlier. To achieve such auto-validating Text Fields, we must react every time the enteredPin property, which is bound to the SecureField, gets updated. In Swift, we do this by using the didSet property observer. 

We can add this observer by placing curly braces followed by the didSet closure behind our viewRouter’s enteredPin property.

@Published var enteredPin = "" {
        didSet {
            
        }
    }

The code we write inside the didSet curly braces gets executed every time the user modifies the enteredPin property by entering a character into the SecureField.

So every time the user enters a character we want to check if the enteredPin equals the stored pin. When this happens, we change the currentPage variable to “home” which causes the observing MotherView to eventually show the user the HomeView.

@Published var enteredPin = "" {
        didSet {
            if self.enteredPin == pin {
                self.currentPage = "home"
            }
        }
    }

So let’s see if that works. Run your app (in the regular simulator!) and enter the correct pin. Awesome! Once you entered the complete pin, the enteredPin’s didSet property observer automatically notices and tells the viewRouter to grant the user access to the home screen. Great! You just learned how to create auto-validating Text Fields in SwiftUI.

Implementing Phone Vibration and limiting the entered TextField Characters 📳

However, we want to prevent the user from entering a value that is more than four digits long. Additionally, we want to erase the Text Field’s content once the user entered a four digit pin that is wrong and give him haptic feedback by letting the phone vibrate.

To do this, we add an else-if statement to our didSet property observer, which gets executed once the user entered a pin that is four or more digits long which is wrong.

@Published var enteredPin = "" {
        didSet {
            if self.enteredPin == pin {
                self.currentPage = "home"
            } else if enteredPin.count >= 4 {
                
            }
        }
    }

Inside this statement, we reset the enteredPin and let the phone vibrate by writing the following code:

enteredPin = ""
           AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) { return }


Let’s try this out by running the app again and entering a wrong code. You see that the SecureField’s content automatically gets emptied again. Of course, the vibration only works if you run the app on a real device.

Conclusion 🎊

That’s it! With the described technique you can create auto-validating Text Fields that automatically react and, for instance, grants access to another view when the user enters the correct input.

We’ve uploaded the whole source code of this app to GitHub.

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

How to show a SwiftUI onboarding screen only when to app launches for the first time

A lot of people recently asked us how to tell SwiftUI that a certain screen, for instance an onboarding view, should only be shown when the app launches the first time. To implement this functionality, follow the steps below👇



Step 1: Make sure you already set up your onboarding view that should be shown when the app launches for the first time. We made a whole tutorial about creating a multi-paged one.

struct OnboardingView: View {
    
    //Compose your Onboarding view, tutorial can be found here:
    //https://blckbirds.com/post/how-to-create-a-onboarding-screen-in-swiftui-1/
    
}

Step 2: Next, make sure you created your home view that should be shown when the app has already launched before.⠀

struct HomeView: View {
    //Compose your home view
}

Step 3: Next, we need to take control of when to show which view. For this purpose, we create an ObservableObject which serves as the “router” for our SwiftUI app’s views. This is basically the same technique we use for navigating independently between several views in SwiftUI.

class ViewRouter: ObservableObject {
    
    @Published var currentPage: String
    
}

Step 4: Next, we create the MotherView which holds both, our onboarding view and our default home view. Based on our ViewRouter’s currentPage property, we either load the onboarding or the home view.

struct MotherView : View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    var body: some View {
        VStack {
            if viewRouter.currentPage == "onboardingView" {
                OnboardingView()
            } else if viewRouter.currentPage == "homeView" {
                HomeView()
            }
        }
    }
}

struct MotherView_Previews: PreviewProvider {
    static var previews: some View {
        MotherView().environmentObject(ViewRouter())
    }
}

Step 5: In our SceneDelegate.swift file’s scene function, we set our MotherView as the root view when the app launches.⠀

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            
            window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(ViewRouter()))
            self.window = window
            window.makeKeyAndVisible()
        }
    }

Step 6: The trick now is to decide which view should be shown when the ViewRouter gets initialised (which is at the app’s launch!). For this purpose, we use UserDefaults. If there is already a key called “didLaunchBefore” stored, we tell our ViewRouter to show the home view at the app’s launch. When no key can be found, we know that it’s the first time the app launches and we tell our ViewRouter to display the onboarding view. Then, we create such a key so that when launching the app the next time, the home view gets chosen instead.⠀

class ViewRouter: ObservableObject {

    init() {
        if !UserDefaults.standard.bool(forKey: "didLaunchBefore") {
            UserDefaults.standard.set(true, forKey: "didLaunchBefore")
            currentPage = "onboardingView"
        } else {
            currentPage = "homeView"
        }
    }
    
    @Published var currentPage: String
    
}

And that’s it! With this technique, you can tell your SwiftUI app to show the onboarding screen only when the app launches for the first time 💡

We’ve uploaded the whole source code of this app to GitHub.

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!