Categories
Uncategorized

Core Data and SwiftUI 2.0 – Saving, retrieving, updating and deleting persistent data

Updated for Xcode 12.0 and SwiftUI 2.0 ✅

Hello, and welcome to a new tutorial! Today we will learn how to use the Core Data framework with SwiftUI to store and manage persistent data. The integration of Core Data into SwiftUI projects is surprisingly easy. By creating a useful app for a small pizza restaurant, we will talk through all basic CRUD operations (Create, Read, Update, and Delete Data) used in Core Data.

In this tutorial, we will explore:

  • How Core Data and SwiftUI work together
  • Creating and updating Core Data objects
  • How to update views when stored data gets updated
  • Using SwiftUI property wrappers for fetching Core Data objects

We will create a simple app for a pizza restaurant that waiters can use to take and manage orders.

The finished app will look like this:

Setting up Core Data using SwiftUI and Xcode 12

To get started, open Xcode 12 and create a new “App” under “Multiplatform” or “iOS”. You can name your project however you want, for instance, “PizzaRestaurant”. But make sure to use SwiftUI as the “Interface” mode and SwiftUI App as the “Life Cycle” mode. Also, make sure that you check the “Use Core Data” box. This will automatically set up the initial Core Data implementation for our app!

Make sure you “reset” the ContentView by removing the generated code from it since we won’t need it for our app.

import SwiftUI
import CoreData

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

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

In the project navigator, you can spot the “.xcdatamodeld” file Xcode created for us. In this file, we set up and manage the Entities of our Core Data data model for our SwiftUI app. If you are not familiar with the concept of Entities: You can think of an entity as a class, and an attribute, as a property of that class. The only Entity we need for our app is for holding the different orders. Delete the default “Item” Entity and create a new one by clicking on the large plus button at the bottom and then double-click on the created Entity to rename it to “Order”.

We need to know the following information about each order: The type of pizza the customer ordered, how many slices he wants to eat, and the number of the table the customer is sitting at. Each order should also have a unique “id” and a “status” attribute for keeping track of whether the order is already completed. For the id, we use the UUID type (this automatically creates a unique id for us). For the numberOfSclices we select Integer16 and for the rest String.

How Core Data works in SwiftUI and Xcode 12

That’s it! We just finished setting up a basic Core Data model for holding the orders for our pizza restaurant app. Wasn’t that easy? Let’s take a look at how CoreData was implemented into our SwiftUI project by checking the “Use CoreData” box earlier. 

To look behind the scenes, open the PizzaRestaurantApp.swift file. You already know that the App struct primarily handles booting up the initial view, which is the ContentView by default. Because we checked “Use CoreData” when creating our project earlier, Xcode created a property called persistenceController and applied an important modifier to the launched ContentView.

Let’s take a look at persistenceController property first.

let persistenceController = PersistenceController.shared

This property is assigned to a PersistenceController. We can find this PersistenceController in the Persistence.swift file. The PersistenceController struct contained in this file includes various properties.

Let us briefly review the most important ones. The preview property allows us to use the CoreData functionality inside preview simulators. 

Note: Since we have deleted the default “Item” Entity and created a new one called “Order”, we have to make a quick adjustment here. Delete the existing for-in loop and insert the following loops instead.

static var preview: PersistenceController = {
        //...
        for _ in 0..<10 {
            let newItem = Order(context: viewContext)
            newItem.status = "pending"
            newItem.id = UUID()
            newItem.tableNumber = "12"
            newItem.pizzaType = "Margherita"
            newItem.numberOfSlices = 4
        }
        //...
    }()

The container property is the heart of the PersistenceController, which performs many different operations for us in the background when we store and call data. Most importantly, the container allows us to access the so-called viewContext, which serves as in an in-memory scratchpad where objects are created, fetched, updated, deleted, and saved back to the persistent store of the device where the app runs on.

The container gets initialized within the PersistenceController’s init function. In this, the container property gets assigned to an NSPersistentContainer instance. We need to use the name of our “.xcdatamodeld” file, which is “Shared” (or *YourAppName* when you created a mere iOS App project), as the “name” argument.

init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "PizzaRestaurant")
        //...
    }

Now, let’s take a look at the .environment modifier applied to the ContentView in our PizzaRestaurantApp struct.

ContentView()
    .environment(\.managedObjectContext, persistenceController.container.viewContext)

What does this .environment modifier do? Before our ContentView gets launched as the root view, it feeds the environment’s managedObjectContext key with the viewContext we just talked about.

The “environment” is where system-wide settings are saved, for instance, Calendar, Locale, ColorScheme, and now, also the viewContext contained in the persistenceController’s container property. Each of these settings has its own key; in our case, it’s the .managedObjectContext key.

Now, every view in our app can use the viewContext as a “scratchpad” to retrieve, update, and store objects. We simply need to use the managedObjectContext environment key for accessing it, as you will see later on.

Don’t worry if you are not familiar with this. The only thing you need to remember is that we can use the managedObjectContext for fetching and saving our orders. You’ll see how easy this is in a moment.

But first, we make a small adjustment to our CoreData data model.

Customizing our data model 🛠

Let’s hold on a second and reconsider choosing String as the status attribute’s type. Each order’s status should only be “Pending”, “Preparing” and “Completed”. Wouldn’t be using an enum the better choice for this? Unfortunately, we can’t create and use an enum inside the .xcdatamodeld file itself. But as said, by creating and designing the Order entity, Core Data created a corresponding class under the hood. We can access and modify this class by clicking on the Order entity, going to the Xcode toolbar, and selecting Editor-“Create NSObjectManagedSubclass”. 

After creating the subclass, Xcode generated two files for us. The Order+CoreDataClass.swift file holds the class itself, and the Order+CoreDataProperties.swift contains its properties inside an extension. 

After we created our data model’s subclass, we need to tell Xcode that the data model is no longer defined by the visual builder in our “.xcdatamodeld” file only, but manually defined by the corresponding subclass we just created. To do this, open the “.xcdatamodeld” file, click on the Order entity and open the data model inspector. Then choose “Manual/None” as the Codegen mode.

At this point, we can remove the question marks from the String-type properties since we don’t want them to be Optionals. Xcode should also create another extension adopting the Identifiable protocol (this will make it easier for us to use Order instances inside the ContentView’s List later). Since we declared an id property, we already conform to this protocol.

extension Order: Identifiable {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Order> {
        return NSFetchRequest<Order>(entityName: "Order")
    }

    @NSManaged public var pizzaType: String
    @NSManaged public var numberOfSlices: Int16
    @NSManaged public var id: UUID?
    @NSManaged public var tableNumber: String
    @NSManaged public var status: String

}

extension Order : Identifiable {

}

Below the Order extension we can declare our Status enum with the three different cases.

enum Status: String {
    case pending = "Pending"
    case preparing = "Preparing"
    case completed = "Completed"
}

If we now try to use the Status enum as the status’ data type, we will get an error.

You see that @NSManagedObject properties can’t be used with enums directly. But how else can we save the status of an order in Core Data? Here’s a workaround: We go ahead with using our NSManaged status property but not of our Status type. Instead, it should be a String again. Next, we add another regular variable called “orderStatus”. Because it’s not an NSManaged property, it can be of the type Status. We assign a setter and getter to our orderStatus. When this property is set, it will also set the NSManaged property accordingly. Using a getter, we try to convert the status string to a Status case when retrieving it.

extension Order {

    //...
    @NSManaged public var status: String
    
    var orderStatus: Status {
        set {
            status = newValue.rawValue
        }
        get {
            Status(rawValue: status) ?? .pending
        }
    }

}

Awesome, we finalized the Core Data model for our SwiftUI app!

Composing our UI 🎨

Important: Before moving on with composing our ContentView, we need to make sure that its preview can access the view Context as well. Otherwise the SwiftUI preview will fail when why try to implement CoreData functionality inside it. To do this, we use the viewContext of our PersistenceController and assign it to the environment’s managedObjectContext key just as we did in our App struct.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

Now our ContentView preview is able to manage CoreData requests!

The ContentView of our pizza restaurant app should contain a list of all orders already taken which the corresponding waiter can manage. Since we can’t store any data yet, we are using only a test list for now.

struct ContentView: View {
    var body: some View {
        List {
            Text("Sample order")
        }
    }
}

We also want to add a navigation bar to our app. To do so, we wrap our List into a NavigationView and use the .navigationBarTitle modifier.

NavigationView {
    List {
        Text("Sample order")
    }
        .navigationTitle("My Orders")
}

The navigation bar should contain a button the waiter can use to add a new order.

List {
    Text("Sample order")
}
    .navigationTitle("My Orders")
    .navigationBarItems(trailing: Button(action: {
        print("Open order sheet")
    }, label: {
        Image(systemName: "plus.circle")
            .imageScale(.large)
    }))

The preview canvas should look like this so far:

When we tap on the Button, we want to open a second view. For this, we create a new SwiftUI file and name it “OrderSheet. We want to display the OrderSheet as a modal view. To do this, we add a State to our ContentView to control when the OrderSheet should be displayed.

struct ContentView: View {
@State var showOrderSheet = false
    var body: some View {
       //...
    }
}

To display the OrderSheet as a modal view, we use the .sheet modifier.

List {
    Text("Sample order")
}
    //...
    .sheet(isPresented: $showOrderSheet) {
        OrderSheet()
    }

Whenever the showOrderSheet State is true the OrderSheet overlays the ContentView. Now we can toggle the showOrderSheet State from our navigation bar button.

.navigationBarItems(trailing: Button(action: {
    showOrderSheet = true
}, label: {
    Image(systemName: "plus.circle")
        .imageScale(.large)
}))

For our OrderSheet view’s body, we’ll be using the Form view to embed the user controls in, for example, a Picker with the different pizza options available. To represent the number of slices that the customer wishes to order, we use a Stepper. Finally, we use a TextField where the user can select the table number for the order.

Finally, we want to save the data after the user taps on the “Add Order” button.

For the OrderSheet’s UI, you can use copy & paste the following code:

struct OrderSheet: View {
       
    let pizzaTypes = ["Pizza Margherita", "Greek Pizza", "Pizza Supreme", "Pizza California", "New York Pizza"]
    
    @State var selectedPizzaIndex = 1
    @State var numberOfSlices = 1
    @State var tableNumber = ""
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Pizza Details")) {
                    Picker(selection: $selectedPizzaIndex, label: Text("Pizza Type")) {
                        ForEach(0 ..< pizzaTypes.count) {
                                Text(self.pizzaTypes[$0]).tag($0)
                        }
                    }
                    
                    Stepper("\(numberOfSlices) Slices", value: $numberOfSlices, in: 1...12)
                }
                
                Section(header: Text("Table")) {
                    TextField("Table Number", text: $tableNumber)
                        .keyboardType(.numberPad)
                    
                }
                
                Button(action: {
                    print("Save the order!")
                }) {
                    Text("Add Order")
                }
            }
                .navigationTitle("Add Order")
        }
    }
}

The OrderSheet’s preview should now look like this:

Saving data using Core Data and SwiftUI 🆕

Great, we’re done composing our PizzaRestaurant app’s interface, but nothing gets saved and persisted yet. To change this, we need to access to the viewContext first to persistently save a created order. Since, as we saw in the beginning, the managed object context is injected in our environment, we can simply access it by using the @Environment property wrapper inside our OrderSheet above its States.

@Environment(\.managedObjectContext) private var viewContext

Now that our OrderSheet has access to the device’s “scratchpad” we are ready to create an Order instance when tapping on the “Add Order” Button. But first, we want to make sure that the tableNumber String is not empty by using a guard statement.

Button(action: {
    guard self.tableNumber != "" else {return}
    let newOrder = Order(context: viewContext)
    newOrder.pizzaType = self.pizzaTypes[self.selectedPizzaIndex]
    newOrder.orderStatus = .pending
    newOrder.tableNumber = self.tableNumber
    newOrder.numberOfSlices = Int16(self.numberOfSlices)
    newOrder.id = UUID()
}) {
    Text("Add Order")
}

Then we’re trying to save the created order. If that fails, we print the corresponding error.

Button(action: {
    //...
    newOrder.id = UUID()
    do {
        try viewContext.save()
        print("Order saved.")
    } catch {
        print(error.localizedDescription)
    }
}) {
    Text("Add Order")
}

After the new order got saved, we want to close the OrderSheet modal view. We can do this by adding the following @Environment property to our OrderSheet.

@Environment (\.presentationMode) var presentationMode

By referring to this property we can manually close the modal view:

do {
    try viewContext.save()
    print("Order saved.")
    presentationMode.wrappedValue.dismiss()
} catch {
    print(error.localizedDescription)
}

Okay, let’s run our app to see if that works. Note that the preview canvas isn’t able to simulate CoreData’s functionality. Therefore, we need to run the app in the regular simulator. Click on the navigation bar button and fill out the OrderSheet form. Then click on “Add Order”. We saved the created order and dismissed the OrderSheet. However, our ContentView’s List is still displaying its sample row.

Fetching and displaying stored orders 📖

To change this, our ContentView needs to read out the saved orders. Achieving this functionality is quite simple by using a @FetchRequest property. But first, our ContentView itself requires access to the viewContext. We do this by using the @Environment property again. Below the ContentView’s @Environment property, insert the following properties:

@Environment(\.managedObjectContext) private var viewContext

@FetchRequest(entity: Order.entity(), sortDescriptors: [], predicate: NSPredicate(format: "status != %@", Status.completed.rawValue))

var orders: FetchedResults<Order>

The @FetchRequest permanently reads out the persistent storage for fetching stored orders from it. With the “predicate” argument, we filter out all orders already completed since we don’t want them to display in our ContentView’s List. The @FetchRequest then passes the retrieved orders to the orders property. Whenever we save a new order, the @FetchRequest will notice and add it to the orders data set. Similar to the State functionality, this causes the ContentView to renew its body.

Now we’re ready to display the fetched data inside our List, like this:

List {
    ForEach(orders) { order in
        HStack {
            VStack(alignment: .leading) {
                Text("\(order.pizzaType) - \(order.numberOfSlices) slices")
                    .font(.headline)
                Text("Table \(order.tableNumber)")
                    .font(.subheadline)
            }
            Spacer()
            Button(action: {print("Update order")}) {
                Text(order.orderStatus == .pending ? "Prepare" : "Complete")
                    .foregroundColor(.blue)
            }
        }
        .frame(height: 50)
    }
}
    .listStyle(PlainListStyle())
    //...

Hint: The reason we use a ForEach loop inside the List instead of inserting the orders data set in the List itself will become clear when deleting orders.

When we run our app again, we see that our @FetchRequest successfully retrieves the just saved order from the persistent storage.

Hint: Also, our ContentView preview shows us different orders. However, these are not the ones that are located in the device’s persistent storage, e.g., the simulator. Rather, they are generated for test purposes by the preview property of our PersistenceController. Can you remember when we adapted the corresponding code at the beginning? The resulting sample Order instances are now used by the preview simulator, which itself has no persistent storage.

Updating Core Data entries 🔄

The Button on the right side of each row can be used to update the particular Order’s status. When we add a new Order, its status is .pending. Therefore the Button reads “Prepare”. When the user taps on the Button we want to update the status to .preparing, and the Button should read “Complete”. When the user taps again, we want the Order’s status to be .completed, which causes the @FetchRequest to filter the Order out.

To implement this functionality, we add the following function below our ContentView’s body.

func updateOrder(order: Order) {
        let newStatus = order.orderStatus == .pending ? Status.preparing : .completed
        viewContext.performAndWait {
            order.orderStatus = newStatus
            try? viewContext.save()
        }
    }

We can call the updateOrder function from our row’s button with passing the particular order instance:

Button(action: {
    updateOrder(order: order)
}) {
    Text(order.orderStatus == .pending ? "Prepare" : "Complete")
        .foregroundColor(.blue)
}

Now we can run the app and tap on the “Prepare” button to mark the currently pending order as prepared. If we click on “Complete”, the Order will be filtered out and eventually removed from our List.

Deleting orders from the persistent storage 🗑

Deleting stored data is almost as simple as updating it. All we have to do is to delete the specific Order from the viewContext. Then, since the @FetchRequest will automatically detect that the Order was deleted, it will update our ContentView accordingly and remove the row from the table with a nice default animation.

To let the user delete rows, we add the .onDelete modifier to the ForEach loop. We can’t apply this modifier to Lists. This is why we inserted a ForEach loop inside the List.

List {
    ForEach(orders) { order in
        //...
    }
        .onDelete { indexSet in
            for index in indexSet {
                viewContext.delete(orders[index])
            }
            do {
                try viewContext.save()
            } catch {
                print(error.localizedDescription)
            }
        }
}

The .onDelete modifier detects the row(s) the user wants to delete by swiping and uses there index/indices to remove the corresponding Order entries from the viewContext.

If we run the application now, we can see that we can easily delete the order by swiping a row.

Conclusion 🎊

That’s it. We finished our small pizza restaurant app! You learned how to use Core Data in SwiftUI to store data persistently. We talked through all basic CRUD operations: Creating, reading, updating, and deleting data. We also understood what a managedObjectContext is and how we can fetch stored data by using SwiftUI’s @FetchRequest.

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


If 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

SwiftUI – How to create a Tab Bar

Updated for Xcode 12.0 and SwiftUI 2.0 ✅

Tab bars allow the user to switch between different views of your app quickly. You see them in almost every iOS app. Fortunately, creating a tab bar with SwiftUI is very simple, using a TabView instance 🚀

And this is how it works:

Step 1: Prepare the SwiftUI views your tab bar will contain later. Create a SwiftUI view file for each tab view of the tab bar. To keep your project organized, you can put them in a separate group.


Step 2: Next, we create a new SwiftUI view file called “HostingTabBar”. This HostingTabBar should be the entrance/root view when our app gets launched. For this purpose, we change the SwiftUI App Lifecycle in our *YourAppName*App struct.

@main
struct TabBarApp: App {
    var body: some Scene {
        WindowGroup {
            HostingTabBar()
        }
    }
}

Step 3: Next, we add an enum to our HostingTabBar, which we use to represent our tab bar’s different views. We also declare a State property that we can use to monitor and programmatically control, which Tab is currently shown.

struct HostingTabBar: View {
    
    private enum Tab: Hashable {
        case home
        case explore
        case user
        case settings
    }
    
    @State private var selectedTab: Tab = .home
    
    var body: some View {
        //...
    }
}

Step 4: Now we can add a TabView instance to our HostingTabBar. For this, we bind the selectedTab State to the initialized TabView. 

var body: some View {
    TabView(selection: $selectedTab) {
        
    }
}

Step 5: Next, initialize the different tab views. Wrap them into the TabView closure and assign a unique .tag to each tab view. With the help of each tab view’s .tag, we can control which tab view should be open and move between them programmatically.

TabView(selection: $selectedTab) {
    HomeView()
        .tag(0)
    ExploreView()
        .tag(1)
    UserView()
        .tag(2)
    SettingsView()
        .tag(3)
}

Step 6: Using the .tabItem modifier, you can now design the individual tab items. We can construct a tab item using a Text view, an Image view, or a combination of both.

TabView(selection: $selectedTab) {
    HomeView()
        .tag(0)
        .tabItem {
            Text("Home")
            Image(systemName: "house.fill")
        }
    ExploreView()
        .tag(1)
        .tabItem {
            Text("Explore")
            Image(systemName: "magnifyingglass")
        }
    UserView()
        .tag(2)
        .tabItem {
            Text("User")
            Image(systemName: "person.crop.circle")
        }
    SettingsView()
        .tag(3)
        .tabItem {
            Text("Settings")
            Image(systemName: "gear")
        }
}

If you now run the app and tap on a tab item, you can move between the tab views.

You can programmatically navigate between the tab views by changing the Tab assigned to the selectedTab State (for example, by using a Button).

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

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!