Integrate, analyze and improve subscriptions in your iOS app

Dev notes

SwiftUI tutorial

Hi! It’s Renat from Apphud – the best tool to analyze iOS subscriptions. As you know Apple at WWDC 2019 has announced their new SwiftUI framework which supposed to replace (or not?) existing UIKit. SwiftUI lets developers to write code in declarative way. That shortens amount of code a lot!

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.

Before you begin

To work with SwiftUI you need to have latest Xcode 11. You have to be a registered Apple Developer as well. Having latest macOS Catalina is not required but recommended (Canvas will not work without it).

Okay, now in Xcode 11 create a new project and make sure “Use SwiftUI” is checked.

Questions and answers

Where has Interface Builder gone?

In SwiftUI you don’t need Interface Builder anymore – there’s new Canvas to replace it, an interactive interface editor which has 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 @IBoutlet connection on some changed property. In this article I will skip Canvas explanations and focus only on code.

Are there any changes in app launch with SwiftUI?

Yes, now top-level object in view hierarchy is not UIWindow, but new UIScene(or it’s child UIWindowScene). 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 AppDelegateSceneDelegateand ContentViewSceneDelegate – is a delegate class of UIScene object which supposed to manage scenes in the app. Methods look very similar to AppDelegate’s methods, aren’t they?

SceneDelegate is set in Info.plist
SceneDelegate is set in Info.plist

In delegate method (scene: willConnectTo: options:) a new window is being created with UIHostingController as it’s root controller. You will see that UIHostingController is being initialized with ContentView as it’s root view.

So ContentView – is our “home” page. And we will code in this file.

What are the differences between View and UIView?

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!")     
     }
}

What is “body”?

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 View protocol.

Opaque return types and what is some keyword?

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 given type. In our case we have to return something that conforms to View protocol. This can be TextImage or our custom class. But again: just one instance of it. Otherwise, compiler will throw an error.

Compiler throws an error when trying to return more than one view
Compiler throws an error when trying to return more than one view

What is the syntax inside {} braces and where is addSubview?

In Swift 5.1 there’s a new feature called Function Builders which allows grouping objects in a declarative way for a certain operations. This looks like a closure that returns an array but without any commas and 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
}

How to add Views and controls to view hierarchy?

Creating views is super easy: you write them inside braces and modify appearance with functions. These functions are 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 other way. Here is a list of views with their analogs:

  • UITableView → List
  • UICollectionView doesn’t have an analog 😱
  • UILabel →Text
  • UITextField → TextField
  • UIImageView → Image
  • UINavigationController → NavigationView
  • UIButton → Button
  • UIStackView → HStack /VStack
  • UISwitch → Toggle
  • UISlider → Slider
  • UITextView doesn’t have an analog 😱
  • UIAlertController → Alert / ActionSheet
  • UISegmentedControl → SegmentedControl
  • UIStepper → Stepper
  • UIDatePicker → DatePicker

How to navigate between screens?

Instead of 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 .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!

How to position views on screen?

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, in 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 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))
          }
     }
}

How to show a navigation bar?

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 navigationBarTitle modifier is added not to NavigationView but to it’s child. DisplayMode is a parameter that controls navigation bar’s style: large or default.

Is there analog to viewDidLoad?

Yes, it is. You can use 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 var statusString property which has @State parameter. What does it mean?

Property wrappers or what is @State?

Swift 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 notification center!

@State attribute lets us to read some view’s value without additional code. In an 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 $ 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).

Can I add views at a runtime?

Not exactly. You can’t add a subview at anytime because SwiftUI is 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 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 return keyword. It’s a new feature in Swift 5.1. You can now omit the return keyword in a single expression functions.

Conclusion

I have covered just a small piece of questions about SwiftUI. Hope this article will help a beginners 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.

So better to learn both UIKit and SwiftUI – thus you will be a more valuable developer.

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.

Apphud is iOS subscriptions analytics tool. One of its main features is sending subscriptions-based notifications to your favorite analytics tool. Apphud fills the gap in sending events from Apple, practicing a hybrid approach: we use both Subscriptions Status Polling and Apple Subscriptions Notifications to collect the most accurate information. All you need if to integrate our lightweight SDK and setup integrations. Apphud will do the rest.

Subscribe to our newsletter!