For swiftui, this is enough

Time:2020-7-11

Swiftui is a new way to build UI and a new coding style. In this paper, we share the new features of swift 5.1 syntax and the advantages of swiftui in an easy-to-understand language. We hope that it will be helpful to the students who love the mobile terminal and let us understand swiftui as quickly, comprehensively and thoroughly as possible.

1、 Background

Apple released a declarative framework based on swift at the WWDC world wide Developers Conference in 2019, which can be used for the application development of Apple’s products such as watchos, tvos, MacOS, etc., which unifies the UI framework of Apple platform.

As the official website said, better apps. Less code: build better applications with less code. At present, if you want to experience swiftui, you need the following preparations: Xcode 11 beta and MacOS Mojave or higher. If you want to experience real-time preview and complete Xcode 11 functions, you need MacOS 10.15 beta.

This paper describes the features of swiftui from the following three aspects:

  • Understand the underlying implementation of swift 5.1 from the code level;
  • The black magic of swiftui is described from the aspect of data flow;
  • The advantages of swiftui component are described from the layout principle level;

2、 Features of swiftui

This section explains the three new syntax features of opaque result type, propertydelegate and functionbuilder. Combined with some pseudo code and data flow analysis, we can understand their functions in swiftui from simple to deep.

2.1 Opaque Result Type

Create a new swiftui project, and the following code will appear: a text is displayed in the body.

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

You may find the appearance of some view quite unexpected. Generally, the type returned in the closure should be the type used to specify the body. As shown in the following code, if there is only one text in the closure, the type of body should be text.

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

However, in many cases, the specific types of closures can not be determined in UI layout, such as text, button, list, etc. to solve this problem, opaque result type is generated.

In fact, view is a core protocol of swiftui, which represents the element description in the closure. As shown in the following code, it is decorated with an associated type. The protocol with this decoration cannot be used as a type, but can only be used as a type constraint.

Through the modification of some view, it guarantees to the compiler that every closure must return a certain type, which complies with the view protocol, and does not care about which type it is. This design provides a flexible development mode for developers, obliterates the specific types, does not need to modify the public API to determine the return type of each closure, and also reduces the difficulty of code writing.

public protocol View : _View { 
       associatedtype Body : View 
       var body: Self.Body { get } 
}

This is my IOS development exchange group: 519832104, no matter you are Xiaobai or Daniel, welcome to settle in. You can share experience, discuss technology, learn and grow together!
Also attached is a large factory interview questions collected by friends. You need IOS development learning materials and interview real questions. You can enter
Group can download by themselves!

Click here to communicate with IOS Daniel immediately

2.2 PropertyDelegate

Complex UI structure has always been the pain point of front-end layout. Every time user interaction or data changes, the UI needs to be updated in time, otherwise some display problems will be caused. However, in swiftui, any state, content and layout declared in the view will be automatically updated once the source changes. Therefore, the layout is only needed once. Add the @ state keyword in front of the attribute to achieve the effect of dynamic UI update with each data change.

@propertyDelegate public struct State : 
DynamicViewProperty, BindingConvertible

In the above code, a @ state keyword inherits dynamicviewproperty and bindingconvertible. Bindingconvertible is the binding of property values, and dynamicviewproperty is the dynamic binding of view and property.

In other words, when a property is declared, swiftui will bind the state of the current property with the corresponding view. When the state of the property changes, the current view will destroy the previous state and update it in time. Here’s a detailed analysis of this process. In general, the initialization of a string property is implemented. The code is as follows:

public struct MyValue { 
    var myValueStorage: String? = nil


    public var myValue: String { 
        get { 
            myValue = myValueStorage 
            return myValueStorage 
        } 
        set { 
           myValueStorage = newValue 
        }
    } 
}

If there are many such attributes in the code, and some of them are handled specifically, the above method will undoubtedly produce a lot of redundancy. The emergence of property delegate is to solve this problem. Property proxy is a generic type, and different types of properties can be handled through this property agent

@propertyDelegate public struct LateInitialized {
  private var storage: Value?
  
  public init() {
    storage = nil
  }
  
  public var value: Value {
    get{
      guard let value = storage 
      Create dependency (view, value) // establish the relationship between view and data dependency
      return value
    }
    set {
      if(storage != newValue){
        storage = newValue
        Notify (to: swiftui) // inform swiftui of data changes
      }
    }
  }
}

The functions of the above codes are shown in the figure above. Through the modification of @ propertydelegate, different types of values can be handled specifically. The above wrapping method can establish the relationship between view and data, and will judge that when the property value changes, swiftui will be informed to refresh the view. The compiler can generate the following code for myvalue of string type. The modified code looks very concise 。

public struct MyValue {
  var $myValue: LateInitialized = LateInitialized()

  public var myValue: String {
      get { $myValue }
      set { $myValue.value = newValue}
  }
}

Next, let’s take a look at the source code of @ state:

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyDelegate public struct State : DynamicViewProperty, BindingConvertible {

    /// Initialize with the provided initial value.
    public init(initialValue value: Value)

    /// The current state value.
    public var value: Value { get nonmutating set }

    /// Returns a binding referencing the state value.
    public var binding: Binding { get }

    /// Produces the binding referencing this state value
    public var delegateValue: Binding { get }

    /// Produces the binding referencing this state value
    /// TODO: old name for storageValue, to be removed
    public var storageValue: Binding { get }
}

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension State where Value : ExpressibleByNilLiteral {

    /// Initialize with a nil initial value.
    @inlinable public init()
}

The new feature of swift 5.1 is property wrappers (a kind of property decoration syntax sugar) to modify state. The internal implementation is to wrap part of reusable code when the properties get and set. The above “property proxy is a generic type” can effectively realize this part of function.

@In the state, the relationship between the data source and the view is established during get, and the current data reference is returned to enable the view to be obtained. In the set method, the data changes will be monitored, the swiftui will be informed to retrieve the view body again, and then the function will be used to retrieve the view body The builder method reconstructs the UI and draws the interface. During the drawing process, it will automatically compare whether the properties in the view have changed. If there is a change, the corresponding view will be updated to avoid global rendering and resource waste.

Through this programming mode, swiftui helps developers establish connections between various views and data, and handle the relationship between them. Developers only need to pay attention to business logic. The official data structure diagram is as follows:

During user interaction, a user action will be generated. As can be seen from the above figure, the data flow process in swiftui is as follows:

  • This behavior triggers data change and is wrapped by @ state data source;
  • @State detects data changes and triggers view redrawing;
  • According to the logic mentioned above, swiftui judges whether the corresponding view needs to update the UI, and finally presents it to the user again for interaction;

The above is the interaction process of swiftui. The data flow between each node is unidirectional and independent. No matter how complex the logic of the application becomes, this mode is similar to the data mode of flux and Redux architecture.

It is composed of innumerable unidirectional data streams, and each data flow follows the corresponding specifications. In this way, developers do not need to find all interfaces related to the data for troubleshooting, but only need to find the corresponding logical data flow and analyze whether the data works normally in the process.

In different scenarios, swiftui provides different keywords. Its implementation principle is as follows:

  • @State – view and data are dependent, and data changes should be synchronized to the view;
  • @Binding – the parent-child view is directly dependent on data, and data changes should be synchronized to the parent-child view;
  • @Bindableobject – external data structure depends on swiftui;
  • @Environmentobject – quick access to global data sources across components;

The implementation of the above features is based on Swift’s combine framework. Here is a brief introduction. The framework has two very important concepts, observer pattern and responsive programming.

When other objects are described, the corresponding pattern will be automatically notified to the observer. These two types of objects are respectively called the observed target and the observer. An observation target can correspond to multiple observers, and the observer can subscribe to the content they are interested in. This is the source of the keyword @ state in this paper. The attribute is taken as the observation target, and the observer is multiple views with the attribute.

The core of responsive programming is oriented to asynchronous data streams and changes. Responsive programming transforms all events into asynchronous data streams, which makes it more convenient to combine and transform these data streams. Finally, it only needs to listen to the changes of data streams and deal with them. Therefore, it is very simple to handle user interaction and response in swiftui.

2.3 FunctionBuilder

Before you know functionbuilder, you must first understand viewbuilder, which is used [email protected]_ Function builder, the compiler will use. And it has certain requirements for the methods it contains, which are hidden in the last closure parameter of each container type. The so-called “requirements” are described in detail below.

In the combination view, a large number of UI components will be processed in the closure. The function builder establishes the style through the closure, passing the UI description in the closure to the special constructor, providing a development mode similar to DSL. A simple view is implemented as follows:

struct RowCell : View {
    let image : UIImage
    let title : String
    let tip : String
    
    var body: some View {
        HStack{
            Image(uiImage: image)
            Text(title)
            Text(tip)
        }
    }
}

View the initialization code of hstack as follows: the final content is decorated with viewbuilder, that is, the closure expression is specially processed by functionbuilder, and the view is finally constructed.

init(alignment: VerticalAlignment = .center, spacing: 
Length? = nil, @ViewBuilder content: () -> Content)

Without the new feature of function builder, developers must manage container views, such as hstack (shown in the following code). If there are a large number of expressions, developers will undoubtedly feel headache, and the code will be very messy, the structure is not clear enough.

struct RowCell : View {
    let image : UIImage
    let title : String
    let tip : String
    
    var body: some View {
        var builder = HStackBuilder()
        builder.add(Image(uiImage: image))
        builder.add(Text(title))
        builder.add(Text(tip))
        return builder.build()
    }
}

[email protected]_ The contents modified by function builder will implement a constructor, and the functions of the constructor are shown in the above code. The builder declares several buildblock methods to construct the view, which can satisfy various closure expressions. Here are several methods of swiftui’s viewbuilder:

Building Blocks
static func buildBlock() -> EmptyView
//Builds an empty view from a block containing no statements.

static func buildBlock(Content) -> Content
//Passes a single view written as a child view through unmodified.

static func buildBlock(C0, C1) -> TupleView
static func buildBlock(C0, C1, C2) -> TupleView
static func buildBlock(C0, C1, C2, C3) -> TupleView
...

For the content modified by viewbuilder, when the content is called, the view will be built according to the above appropriate buildblock, and the text or other components in the closure will be built into a tupleview and returned.

[email protected]_ Function builder also has some limitations. The buildblock of viewbuilder can pass in at most ten parameters, that is, there can be at most ten views in the layout. If there are more than ten views, we can consider using tupleview to merge views in multiple ways.

As one of the new features of swiftui, functionbuilder tends to the current popular programming method. Developers can use DSL based architectures, such as swiftui, without considering the specific implementation details, because the builder implements a DSL itself.

3、 Components

Through the analysis of DSL view, this section analyzes the layout characteristics of swfitui and the advantages of using this feature in the process of componentization.

At present, component-based programming is the mainstream development method, swfitui brings a new function – it can build reusable components and adopts the idea of declarative programming. The single and simple response view is combined into the complicated and complicated view, and the component can be used on any platform of apple, which achieves the effect of cross platform (only for Apple Devices). It can be divided into basic components, layout components and functional components according to their purposes.

See example link for more components.

Let’s take a button as an example:

struct ContentView : View {
    var body: some View {
        Button(action: {
            // did tap
        },label: {Text("Click me")}
        )
        .foregroundColor(Color.white)
        .cornerRadius(5)
        .padding(20)
        .background(Color.blue)
    }
}

It contains a button whose parent view is a contenview. In fact, contenview will also be included by a rootview, which is created on the window by swiftui. Through a few simple lines of code, set the button click event, style and copy.

Its view DSL structure is shown in the figure below. Swiftui will directly read the internal description information of DSL and collect it, then convert it into basic graphics unit, and finally submit it to the underlying metal or opengl for rendering.

Through this structure, it is found that it is quite different from the layout structure of UIKit. For example, some attributes of buttons, such as background, padding, corner radius, should not appear in the main structure of the view, but in the structure of the button view.

Because, in swiftui, the settings of these properties are carried by a view internally, and then the layout will be calculated and arranged layer by layer according to the layout process of the example above. The advantage of this is that it is convenient for the bottom layer to do monomorphic call when designing rendering functions, eliminating useless branch judgment and improving efficiency.

At the same time, swiftui also supports frame setting, but it will not act on the current element as in UIKit. It also forms a virtual view internally to carry the frame setting, and calculates the frame during the layout process, and finally displays the desired result.

In a word, setting properties for a view in swiftui is no longer a constraint for the current element, but a series of containers are used to contain the current element to prepare for subsequent layout calculation.

The interface of swiftui is no longer like UIKit, which uses viewcontroller to carry all kinds of uivew controls. Instead, everything is view. Therefore, the view can be divided into various detailed components, and then assembled into the final interface through combination. This view assembly method improves the flexibility and reusability of interface development. Therefore, view componentization is a great highlight of swiftui.

4、 See it live in Xcode

Swiftui preview is a major breakthrough of apple, similar to the hot reloading of Rn and fluent. Apple chose to render directly on MacOS, but it needs to be equipped with SwiftUI.framework The Xcode previews interface can only be seen in MacOS 10.15.

Xcode will statically analyze the code (thanks to the swiftsyntax framework) and find all types that comply with the previewprovider protocol for preview rendering. Xcode 11 provides two functions: real-time preview and static preview. Real time preview: code modification can be displayed in Xcode’s preview window in real time; in addition, xcdoe also provides shortcut function, which can quickly and conveniently add components and set component properties by clicking components with Command + mouse.

5、 Imagination

  • Swiftui not only brings a new way to build UI for Apple platform, but also a new style of swift coding;
  • It can be inferred that there will be many component libraries in swiftui to facilitate front-end development;
  • Support hot update, which may make more developers embrace swiftui;
  • Although swiftui has many advantages, it can only be used in systems above IOS 13. Many companies and developers are deterred from using swiftui. At present, most mainstream applications support IOS 9 at least. In at least three years, swiftui can only serve as a theoretical knowledge reserve, so it has a long way to go;
  • Swiftui, a platform independent and purely descriptive UI framework, is exactly the right direction for cross platform solutions. Can it unify the whole front end in the future? This is worth looking forward to;

Introduction to the author

Liang Qijian, development engineer of Ctrip financial payment center, is mainly responsible for the development and optimization of payment IOS side. He likes to study big front end and cross platform technology.

This article is reproduced from the official account Ctrip Technology Center (ID:ctriptech).Link to original text
Click here to communicate with IOS Daniel immediately