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!