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!