SwiftUI is a powerful and promising framework that may well surpass UIkit in the future in terms of its robustness and reliability. When Apple announced SwiftUI in 2019, it rocked the development world, with some people considering it Apple's most exciting piece of news since the announcement of Swift in 2014.
The SwiftUI framework plays into Apple's goal of getting everybody coding: it simplifies the basics and saves you time — which you can then invest into developing custom features.
If you want to learn how to use SwiftUI and determine whether it's a good fit for your project, you've come to the right place.
Getting started with SwiftUI may seem intimidating, but it doesn't have to be. In this iOS SwiftUI tutorial, we'll break down the framework, examine its key features and benefits, list its challenges, and then give examples to show you how to navigate the latest version. We'll also examine how SwiftUI differs from other UI frameworks like Flutter and UIkit.
Get a handle on how to use this modern framework, and you'll be able to build dynamic user interfaces for apps that run on iOS platforms:
Author's Note: Hi! It's Renat from Apphud – the best tool for analyzing iOS subscriptions. As you know, at WWDC 2019, Apple announced their new SwiftUI framework, which is supposed to replace the existing UIKit. SwiftUI lets developers write code in a declarative way. That shortens the amount of code a lot!
There are already plenty of Apple SwiftUI tutorials with code and examples. I, instead, will try to make this article in Question-Answer format. Alright, let's go.
First things first, let's start with the SwiftUI basics. SwiftUI offers a completely new approach to building UIs for iOS, macOS, and other Apple platforms.
With the release of iOS 13, developers gained access to the SwiftUI framework, which allows you to build a UI entirely with Swift code.
So, why is SwiftUI preferable to other user interface toolkits? Well, its main draw is that it allows you to design apps declaratively. In other words, you tell the framework how you want your UI to look and work — and then it figures out how to accomplish your vision.
Contrast that with imperative UI (which is what developers had to deal with before iOS 13), in which you would have to specify implementation details and control every tiny detail.
Let's say you wanted to build a form with UIKit. You'd need to write code for all details, such as: creating a vertical UIStackView element, adding it to the View, adding rules to center it on the X- and Y-axis of the root view, creating a UITextField element, and so on.
With SwiftUI's declarative UI, you'd just have to tell the framework that you want to group a button and two text fields in a vertical stack on-screen. SwiftUI then does it for you, making assumptions about some details. The framework makes logical assumptions, but if it isn't what you wanted, you can make modifications.
So far, we can see that because of SwiftUI's declarative UI, it saves developers a lot of work. But that's not its only strength. SwiftUI works across all Apple platforms, so you just have to learn one layout framework and language. There's a lot of time-saving there to benefit from.
We've already mentioned how SwiftUI uses declarative programming, whereas UIkit uses imperative, but let's examine the differences between the two frameworks in even more detail.
UIKit was the backbone of iOS UI development for 10+ years; it's a mature platform that has been used for pretty much every iOS app out there since iPhone OS 2 was released. As a result, this powerful, versatile framework has had plenty of time to develop development resources.If you get stuck on something, it's pretty easy to find the answers you need. This means that developers can use the wealth of existing know-how to create high-performing, elegant apps.
SwiftUI, however, is bold and ambitious. It could totally transform the way iOS apps are developed in the future. However, it is still brand-new, and it lacks the knowledge base that UIKit has. You have to weigh the pros and cons of both frameworks. SwiftUI makes cumbersome UI development much more intuitive, but if you get stuck, it could be harder to work through it.
SwiftUI is far more approachable to developers who haven't worked with it before. When you start a new SwiftUI project, the interface is minimal and intuitive, whereas a new UIkit project looks pretty overwhelming.
Apple already has published a great series of articles about SwiftUI with code and examples. I, instead, will try to make this article in Question-Answer format. Alright, let’s go.
There are two aspects to consider here: developer performance and app performance. There isn't a noticeable difference in the final product, regardless of which framework it was built with. SwiftUI uses AppKit and UIkit behind the scenes, so rendering isn't actually any faster.
But when you look at development build time, the scale tips in SwiftUI's favor. The hierarchy of View is located within value-type structures that are stored on the stack. Therefore, there isn't any costly memory allocation, and, in some situations, there is greater performance.
When compared to UIKit, SwiftUI has a noticeable lack of documentation on user behavior simulation and testing. While developers can find their own solutions, it's important to keep in mind that this aspect has been the slowest-maturing for the platform.
While UIKit has long been the predominant method of developing iOS apps, there is another framework - Flutter. It is a multi-platform, open-source mobile SDK from Google and can be used to build Android and iOS apps from the same source code. Like SwiftUI, Flutter is a declarative UI framework.
Flutter and SwiftUI both allow you to create composable components. The former framework calls them "widgets," and the latter calls them "views."
Everything is a widget in Flutter. If you're creating a Text widget and want to add some styles to the text, you'll need to make another widget, TextStyle, and assign it to the Text widget's parameter.
In SwiftUI, things are a little different - not everything is a "view." Instead, you work with views and modifiers – so you would make a Text view and then apply fonts and colors via modifiers.
Another difference is that Flutter has two types of widgets, stateless and stateful. Stateful widgets must be used to store a local state in Flutter. But with SwiftUI, the local state can be added to any view.
To work with SwiftUI you need to have the latest Xcode. You have to be a registered Apple Developer as well. Having macOS Catalina+ is not required but recommended (Canvas will not work without it).
Okay, now in Xcode create a new project and make sure "Use SwiftUI” is checked.
Are you ready to see SwiftUI in action? In the SwiftUI example below, we'll use it to create an app that shows a list of frameworks/technologies for developing mobile apps.
Step 1: Launch Xcode and create a new project.
Step 2: Select App, then click Next.
Step 3: Type "demo" in the Product Name field, and click Next. Xcode will launch the project.
Step 4: Find ContentView.swift, which is the file you'll be writing Swift code in. Clear out the code so only these SwiftUI components remain.
Step 5: On Xcode's navigator panel, choose an iPhone simulator. Then, click the play symbol to run the program. The demo app will show you the list of technologies and platforms that can be used to build a mobile app.
In SwiftUI you don’t need Interface Builder anymore – there’s a new Canvas to replace it, an interactive interface editor which has a very close connection with code. While you write the code Canvas automatically generates a visual interpretation and vice versa. Very useful and safe. For example, your app won’t crash if you forgot to remove or update the @IBOutlet
connection on some changed property. In this article, I will skip Canvas explanations and focus only on code.
Yes, now the top-level object in view hierarchy is not UIWindow
, but new UIScene
(or its child UIWindowScene
). A window is being added to the scene now and these changes are related not to SwiftUI but to iOS 13 itself.
After creating the project you will notice files AppDelegate
, SceneDelegate
and ContentView
. SceneDelegate
– is a delegate class of UIScene
object which is supposed to manage scenes in the app. Methods look very similar to AppDelegate’s
methods, aren’t they?
In the delegate method scene: willConnectTo: options:
a new window is being created with UIHostingController
as its root controller. You will see that UIHostingController
is being initialized with ContentView
as its root view.
So ContentView
– is our “home” page. And we will code in this file.
Let’s explain ContentView
contents word by word. First, ContentView
is a struct
. Very good beginning! This expands developing opportunities. Next, View
is a protocol now! And a very simple protocol. The only method that needs to be implemented – is a body
property. All your subviews must be inside body
and they must have their own body
as well.
struct ContentView: View { var body: some View { Text("Hello, world!") } }
Body
– is our primary container which includes all our subviews. You can think it’s like a body
in an html page where ContentView
is a page itself. However in this case our body
must have just one some View
– any class that conforms to the View
protocol.
some TypeName
– is a new thing in Swift 5.1 which is called opaque return type. Opaque return types are used in cases where you need to return an object with no matter which class but conformable to a given type. In our case, we have to return something that conforms to the View
protocol. This can be Text
, Image
, or our custom class. But again: just one instance of it. Otherwise, the compiler will throw an error.
In Swift 5.1 there’s a new feature called Function Builders which allows grouping objects in a declarative way for certain operations. This looks like a closure that returns an array but without any commas and a return
keyword.
This mechanism has become a primary feature in SwiftUI. A special builder which creates and adds subviews in a declarative way has been called ViewBuilder
. All the hard thing is being processed behind the scenes – you just write your views inside a closure block with a new line. SwiftUI automatically adds them to a view hierarchy and makes optimization for you.
//Announcing ViewBuilder in SwiftUI header file @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, \*) @\_functionBuilder public struct ViewBuilder { /// Builds an empty view from an block containing no statements, \`{ }\`. public static func buildBlock() -> EmptyView /// Passes a single view written as a child view (e..g, \`{ Text("Hello") }\`) through /// unmodified. public static func buildBlock<Content>(\_ content: Content) -> Content where Content : View }
Creating views is super easy: you write them inside braces and modify appearance with functions. These functions always return view
so you can make a chain of functions separated with dots. The hard thing is to learn new syntax and the list of available modifiers and controls. And of course, Xcode 11 Beta is buggy (as usual) so syntax highlighting might not work sometimes.
var body: some View { VStack{ Text("World Time").font(.system(size: 30)) Text("Yet another subtitle").font(.system(size: 20)) } }
Not all the views have their analogs in SwiftUI. Some of them must be implemented in another way. Here is a list of views with their analogs:
UITableView
→ List
UICollectionView
doesn’t have an analogUILabel
→ Text
UITextField
→ TextField
UIImageView
→ Image
UINavigationController
→ NavigationView
UIButton
→ Button
UIStackView
→ HStack
/ VStack
UISwitch
→ Toggle
UISlider
→ Slider
UITextView
→ doesn’t have an analogUIAlertController
→ Alert
/ ActionSheet
UISegmentedControl
→ SegmentedControl
UIStepper
→ Stepper
UIDatePicker
→ DatePicker
Instead of a navigation controller, you should use NavigationView
. Wrap your code inside the braces and add an action to push the new view. This may be a tap on the list’s row (ex UITableView
) or a button tap.
An example of pushing a DetailView
var body: some View { NavigationView { Text("World Time").font(.system(size: 30)) NavigationLink(destination: DetailView() { Text("Go Detail") } } }
To present a screen modally you should use the .sheet
construction like this:
Button(action: { print("Button Pushed") self.show\_modal = true }) { Text("Present Modal") }.sheet(isPresented: self.$show\_modal) { ModalView() }
As being said, your body
must return some view, which can be any class. It means that you can even push a Text
or an Image
!
Generally, you should use stacks. All the views depend on each other now. You can align your views horizontally (HStack
), vertically (VStack
), or above each other (ZStack
). You can also use ScrollView
and ListView
, add padding
, and even set frame
. However, the SwiftUI frame
works in a different manner. This will be explained in the next article.
By combining all these containers you can make quite a big tree of views. So you may ask about performance in this case. Don’t worry: SwiftUI is optimized in such a way that the use of stacks doesn’t hit performance. It is said in this video from WWDC (starting at 15:32).
var body: some View { NavigationView { VStack { NavigationLink(destination: LargeView(timeString: subtitle)) { Text("See Fullscreen") } Text("World Time").font(.system(size: 30)) } } }
Wrapping your view hierarchy in a NavigationView{}
won’t be enough. To show a navigation bar you should add a navigation bar title.
NavigationView { VStack{}.navigationBarTitle(Text("World Time"), displayMode: .inline) }
Keep in mind that the navigationBarTitle
modifier is added not to NavigationView
but to its child. DisplayMode is a parameter that controls the navigation bar’s style: large or default.
Yes, it is. You can use the onAppear{}
modifier and add your code inside the closure. This is similar to Javascript. This modifier can be added to any view. In the example below an http-request is being sent:
struct ContentView : View { @State var statusString : String = "World Time" var body: some View { NavigationView { VStack { NavigationLink(destination:DetailView()) { Text("Go Detail") } Text(statusString).font(.system(size: 30)) }.onAppear { self.loadTime() } } } func loadTime(){ NetworkService().getTime { (time) in if let aTime = time { self.statusString = "\\(aTime.date())" } } } }
We have written a loadTime
function which sends an http-request to get the time from a server. We won’t focus on NetworkService
class, you can download the source code from the link at the end of this article.
You will notice the var statusString
property which has the @State
parameter. What does it mean?
Swift UI update 5.1 has introduced a new feature called property wrappers (or property delegates). This is a set of special property attributes which add some amazing functionality to our properties. In SwiftUI property wrappers are used to update or bind one of your view’s state with your property. For example, a Toggle
’s value. By using property wrappers you will be able to get changed values of your views without having to write protocols, functions, or even the notification center!
@State
attribute lets us read some view’s values without additional code. In the example above we are updating a text on the screen automatically when statusString
changes.
In a new example below we bind properties between `Toggle`’s state and our own property by using the $
sign.
//When Toggle’s value changes it also changes our property value. struct DetailsView: View { @State var changeToggle: Bool var body: some View { Toggle(isOn: $changeToggle) { Text("Change Toggle") } } }
Property wrappers are very important in SwiftUI and in Swift 5.1 itself. It is quite a big theme so it will need a separate article. You can watch these nice videos from WWDC: this one (starting at 37th minute), this (starting at 12th minute), and this (starting at 19th minute).
Not exactly. You can’t add a subview at any time because SwiftUI is a declarative framework. And it renders a whole view. But you may add conditions inside a body and reload the view when these conditions change. In this example, we use @State — if
paired with the isTimeLoaded
property.
struct ContentView : View { @State var statusString : String = "World Time" @State var isTimeLoaded : Bool = false var body: some View { NavigationView { VStack { if isTimeLoaded { addNavigationLink() } Text(statusString).font(.system(size: 30)).lineLimit(nil) }.navigationBarTitle(Text("World Time"), displayMode: .inline) }.onAppear { self.loadTime() } } func addNavigationLink() -> some View { NavigationLink(destination: Text("124!!!")) { Text("Go Detail") } } func loadTime(){ NetworkService().getTime { (time) in if let aTime = time { self.statusString = "\\(aTime.date().description(with: Locale.current))" self.isTimeLoaded = true } } } } struct DetailView : View { var timeString : String var body : some View { Text(timeString).font(.system(size: 40)).lineLimit(nil) } }
BTW in addNavigationLink
you may notice there is no the return
keyword. It’s a new feature in Swift 5.1. You can now omit the return
keyword in a single expression function.
I have covered just a small piece of questions about SwiftUI. Hope this article will help a beginner to understand a new framework. And one last question: should I learn UIKit? Of course, yes. UIKit is still a primary framework for iOS and it continues to improve. Furthermore, many SwiftUI classes are just wrappers around UIKit. And there are no libraries, pods for SwiftUI yet. All has to be done by yourself.
In this SwiftUI guide, I have covered just a small piece of questions about the topic. I hope this article will help beginners understand the new framework and the SwiftUI library. And one last question: should I learn UIKit? Of course, yes. UIKit is still a primary framework for iOS, and it continues to improve. Furthermore, many SwiftUI classes are just wrappers around UIKit. And there are no libraries or pods for SwiftUI yet. It all has to be done by yourself.
Project source code can be downloaded here.
After approval
What to do after successful approval? Add Apphud SDK to your app and find out how much you earn in real-time with many more features.