This article will be the beginning of a series of articles on SwiftUI, and also a way to document my own SwiftUI learning. You are welcome to point out the problems and grow together.

Declarative UI versus traditional imperative UI, as shown below.

Imperative UI: first create a Label, second set its coordinates, width and height, and third add the Label to the superview. The fourth step is to create a Button, the fifth step is to set its coordinates, the sixth step is to add a binding event to the Button, the seventh step is to add the Button to the parent view, the eighth step is to implement the Button binding method, in the method to achieve every click on the Label display number plus one, and then the new number assigned to the Label.

Declarative UI: You need a page, and the page needs a Label, a Button. The contents of a Label are displayed as a number. When a button is clicked, the value is increased by one, and the contents of the Label are automatically refreshed.

Imperative UI requires us to tell the computer what to do step by step, whereas declarative UI requires us to specify a page we want, and the computer does the rest.

For a new layout idea, we should try to avoid using UIKit knowledge to understand SwiftUI components in the learning process, which may make our learning more complicated. A is not very appropriate metaphor, like a driving school coach they like most is the small white, hate the most is that I don’t know where theres learned a little, little white knowledge is the biggest advantage of clean, not all kinds of bad habits and understanding of the new knowledge is more pure, what you say what he is supposed to be. The slacker is the opposite. Confusion in the process of learning new knowledge is often caused by a collision with a previous knowledge, which subverts cognition. Such collision is also a good thing to some extent, which can make us have a deeper understanding of knowledge.

struct ContentView: View {
    var body: some View {   
        Text("hello world")
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Copy the code

SwiftUI view is not a Class but a Struct. The biggest advantage of SwiftUI view is that it is light enough. Controls that inherit FROM UIView, like the ones we used earlier, can be a bit heavy. If you look at UIView’s definition, it defines a lot of properties and methods that we rarely use, but subclasses that inherit from UIView inherit them whether we need them or not, and the resource overhead is much higher than Struct. The View is not UIView, although it looks like it, but it’s not a component, it’s just a protocol, defined as follows.

public protocol View {

    associatedtype Body : View

    /// The content and behavior of the view.
    @ViewBuilder var body: Self.Body { get }
}
Copy the code

Based on the component

Common base components are Text, Image, Button, VStack, HStack, ZStack, List, NavigationView, Spacer, and so on.

The style code for Text is as follows

struct ContentView: View { var body: some View { VStack(alignment: .leading, spacing: 10) { Text("hello world hello world hello world") .font(.title) .fontWeight(.bold) .foregroundColor(.blue) .lineLimit(2)  .padding(.all, 10) .background( Capsule() .fill(LinearGradient(gradient: Gradient(colors: [Color.red, Color.orange]), startPoint: /*@START_MENU_TOKEN@*/.leading/*@END_MENU_TOKEN@*/, endPoint: /* @start_menu_token @*/.trailing/* @end_menu_token @*/)).opacity(.red).opacity(0.5)).frame(width: 200, height: 0) Alignment:.center).linespacing (10.0).overlay(RoundedRectangle(cornerRadius: Opacity (opacity: opacity).opacity(opacity: opacity).opacity(opacity: opacity).opacity(opacity: opacity).opacity(opacity: opacity).opacity(opacity: opacity).opacity(opacity: opacity).opacity(opacity: opacity).opacity(opacity: opacity).opacity(opacity: opacity).opacity(opacity: opacity) 1, Perform: {print(" click ")})}}Copy the code

According to the effect

Explain what each method does

font

The default font is largeTitle, title, title2, title3, headline, subheadline, etc. The advantage of using these fonts is to automatically adjust the system dynamic font size. It can be adjusted automatically without adaptation.

If these fonts do not meet the designer’s requirements, you can also customize the font. Define a font with size 16 and weight medium.

 .font(Font.system(size: 16, weight: .medium, design: .default))
Copy the code

If the font throw provided by the system is not enough, you can also customize the font

public static func custom(_ name: String, size: CGFloat) -> Font
Copy the code

fontWeight

To set Weight, the system provides some default weights such as ultraLight, Thin, Light, Regular, Medium, Semibold, bold, heavy, etc.

foregroundColor

.foregroundColor(Color.blue)
Copy the code

Foreground color, which can be used to set the display color of text content. Blue looks like a Color, but it’s also a View

extension Color : View {

    public typealias Body = Never
}
Copy the code

So sometimes when you want to set the background Color you can just use Color, for example

struct ContentView: View {
    var body: some View {
        Color.blue
    }
}
Copy the code

The system provides many colors that can be used directly, such as. Clear,. Red,. Blue and so on

accentColor

Accent color. The default accent color of the system is blue. Some controls use accent color as the default color, such as Button

Button(action: {}) {
      Text("Accented Button")
 }
Copy the code

At this time, if you want to adjust the color of the text there are two ways, one is to set the foreground color, the other is to set the emphasis color. Either of the following can set the content to red

Button(action: {}) {
            Text("Accented Button")
  }
.foregroundColor(.red)

Button(action: {}) {
   Text("Accented Button")
}
 .accentColor(.red)
Copy the code

lineLimit

Limit the number of rows

padding

Inside margins, the system provides a variety of options

.padding(.top, 10) // top inner margin.padding(.leading, 10) // left inner margin.padding(.bottom, 10) // bottom inner margin.padding(.trailing, 10) Float (.horizontal, 10) // Float (.horizontal, 10) // float (.horizontal, 10) // float (.horizontal, 10) // float (.vertical, 10) 10) // The vertical inner margin is 10 on each sideCopy the code

background

@inlinable public func background<Background>(_ background: Background, 
alignment: Alignment = .center) -> some View where Background : View
Copy the code

This is a generic function, placeholder type Background that follows the View protocol, and the first parameter Background is also Background, which means we just pass a type that follows the View protocol, and the second parameter is alignment. There are many styles you can set, from a single color to a gradient to even a Text(“1234”), as long as you follow the View protocol.

struct TestOne: View {
    var body: some View {
        Text("Hello, World!")
        .background(
          // Color.red  // 红色
          // Text("1234")
           // 渐变色
            LinearGradient(gradient: Gradient(colors: [Color.red, Color.orange]), startPoint: .leading, endPoint: .trailing)
        )
    }
}
Copy the code

frame

Can set width, height, on its way.

lineSpacing

Line spacing

overlay

Overlay, a layer on top of the View. You can use it to put a border around your View.

struct TestOne: View { var body: some View { VStack { Text("Hello, World!" Overlay (// rectangle with a corner set to 0 and rectangle with a corner set to 0 .continuous) .stroke(Color.red, lineWidth: 1) ) .padding(.all, 10) Text("Hello, World!" Overlay (// rectangle with a corner set to 10 and rectangle with a corner set to 10).overlay(// Rectangle with a corner set to 10 and rectangle with a corner set to 10 and rectangle with a corner set to 10 .continuous) .stroke(Color.red, lineWidth: 1) ) .padding(.all, 10) } } }Copy the code

shadow

Used to set the shadows around. Three parameters need to be passed: color, RADIUS, and x and y offsets.

@inlinable public func shadow(color: color = color (.srgblinear, white: 0, opacity: 0.33), radius: CGFloat, x: Opacity = 0, y: 0) -> some view.shadow (color: color.black.opacity (0.7), radius: 20, x: 0, y: 0)Copy the code

onTapGesture

Click gesture,count is the number of consecutive clicks triggered by the gesture, 2 is the number of consecutive clicks triggered by the gesture, perform corresponds to the processing logic of click events.

struct ContentView: View {
    var body: some View {
        
        Text("hello world")
            .onTapGesture(count: 2, perform: {
                print("点击了")
            })
    }
}
Copy the code

PreviewLayout is used to set the size of the preview image

ContentView().previewLayout(.fixed(width: 300, height: 300)) // The size of the preview is 300 * 300Copy the code

How to encapsulate a component

Follow three principles: 1. Separation of style and content, 2. Cascading, 3

struct SXControlButton: View { var text: String var backgroundColor: Color var textColor: Color var action: () -> () var body: some View { ZStack { Circle() .fill(backgroundColor) Text(text) .foregroundColor(textColor) } .onTapGesture(perform: action) .frame(width: 60, height: 60) } } struct Zujian_Previews: PreviewProvider { static var previews: Some View {HStack {SXControlButton(text: "微信", backgroundColor:.red, textColor:.white) {} SXControlButton(text: "QQ", backgroundColor:.green, textColor:.white) {} SXControlButton(text: "微博", backgroundColor:.purple, textColor:. .white) {} } .previewLayout(.fixed(width: 300, height: 100)) } }Copy the code

This is something you’ll see a lot when you’re using UIKit. Define a Button with text, backgroundColor, and textColor. This code looks fine, but does not scale very well. For example, if you want to add a high lightColor, var lightColor: Var lightColor: Color =.red when defining lightColor, the constructor will change everywhere the Button is used unless it is defined with a default value. Ideally, style and content should be separated, like CSS and HTML.

The code above can be optimized by cascading from top to bottom, from outside to inside, and by setting foregroundColor and accentColor on the outermost layer, all views inside will be in effect. This reduces the amount of code, all child views can share the style of the parent view, and can also configure their own style separately. You can set up a set of styles to share.

struct SXControlButton: View { var text: String var action: () -> () var body: some View { ZStack { Circle() Text(text) .foregroundColor(.accentColor) } .onTapGesture(perform: action) .frame(width: 60, height: 60) } } struct Zujian_Previews: PreviewProvider { static var previews: Some View {HStack {SXControlButton(text: "微信") {} SXControlButton(text: "微信") {} "QQ") {} .foregroundColor(.green) .accentColor(.white) SXControlButton(text: "微博") {}.foregroundcolor (.purple).accentcolor (.white)}.foregroundcolor (.red).accentcolor (.white) .previewLayout(.fixed(width: 300, height: 100)) } }Copy the code

Now there is one more thing that is not perfect is the frame size of the Button. Now we are writing it dead, so if you want to be flexible you can put the frame Settings outside

SXControlButton(text: "微信") {}. Frame (width: 60, height: 60)Copy the code

Or, a more elegant way to write it, do it through enumerations. First, let’s introduce a new concept, Environment

Environment

Context, also known as a global context, is automatically created by SwiftUI.

1. Use the Environment to pass system-wide Settings, such as ContentSizeCategory and LayoutDirection.

2. You can also use it to add all observableObjects associated with the current view.

3. You can inject view-specific values, such as isEnabled, editMode, presentationMode.

4. We can also customize environment variables for View for easy use. For example, the following code is the first usage scenario.

struct XXX : View {
    @Environment(\.sizeCategory) var size
    var body: some View {
    }
}
Copy the code

The @environment attribute modifier allows us to read the value of the Environment variable sizeCategory. When the user changes the font size in system Settings, the View will be redrawn according to the latest size.

Next, the fourth custom environment variable is used to adjust the size of the control. The code is as follows. First define the enumeration to contain three cases, small, big, and Large. The corresponding height is 60,70,80, height and width are equal. To implement the EnvironmentKey protocol, you need to implement the static property defaultValue of type ViewSize? This parameter is optional. The default value is. Small. Then extend EnvironmentValues to customize the environment variable viewSize, implementing get and set with key values for viewSize itself. By setting.viewsize (.large) in the parent view, all child views automatically inherit the parent view’s environment, and can internally fetch the values of variables in the environment via @environment (.viewsize) private var viewSize.

enum ViewSize: EnvironmentKey { static var defaultValue: ViewSize? = .small case small case big case large var height: CGFloat { switch self { case .small: return 60 case .big: return 70 case .large: return 80 } } var width: CGFloat { height } } extension EnvironmentValues { var viewSize: ViewSize? { get { self[ViewSize.self] } set { self[ViewSize.self] = newValue } } } extension View { func viewSize(_ size: ViewSize?) -> some View { self.environment(\.viewSize, size) } } struct SXControlButton: View { var text: String var action: () -> () @Environment(\.viewSize) private var viewSize var body: some View { ZStack { Circle() Text(text) .foregroundColor(.accentColor) } .onTapGesture(perform: action) .frame(width: viewSize?.width, height: viewSize?.height) } } struct Zujian_Previews: PreviewProvider { static var previews: Some View {HStack {SXControlButton(text: "微信") {} SXControlButton(text: "微信") {} "QQ") {} .foregroundColor(.green) .accentColor(.white) SXControlButton(text: "微博") {}.foregroundcolor (.red).accentcolor (.white).viewsize (.large) }}Copy the code

If individual child views need to set their own size, use environment to override the environment of the parent view. Such as

SXControlButton(text: "微信") {}
 .environment(\.viewSize, .small)
Copy the code

Now this is fine, but it’s a little bit generic, so we want something that works for all views, so we can pull out the ViewSize.

import SwiftUI

enum Size: EnvironmentKey {
    
    static var defaultValue: Size? = .small
    
    case small
    case big
    case large
}

extension EnvironmentValues {
    var size: Size? {
        get {
            self[Size.self]
        }
        set {
            self[Size.self] = newValue
        }
    }
}

extension View {
    func size(_ size: Size?) -> some View {
        self.environment(\.size, size)
    }
}

Copy the code

Then make code adjustments

fileprivate extension Size { var height: CGFloat { switch self { case .small: return 60 case .big: return 70 case .large: return 80 } } var width: CGFloat { height } } struct CommonButton: View { var text: String var action: () -> () @Environment(\.size) private var size var body: some View { ZStack { Circle() Text(text) .foregroundColor(.accentColor) } .onTapGesture(perform: action) .frame(width: size?.width, height: size?.height) } } struct CommonView: PreviewProvider { static var previews: Some View {HStack {CommonButton(text: "微信") {} CommonButton(text: "QQ") {} .foregroundColor(.green) .accentColor(.white) CommonButton(text: "微博") {}.foregroundcolor (.red).accentcolor (.white).size(.large)}}Copy the code

This makes the code more flexible and easier to customize. If you want to customize your View to support Size, just add @environment (.size) private var Size.

SwiftUI layout will be updated in the next post

The resources

The official document: developer.apple.com/documentati…

ICloudEnd:www.jianshu.com/p/53d9672c7…

Bilibili: Zhang Gong CHmod777, an intrepid indie developer, has a very good short and concise video. www.bilibili.com/video/BV1o5…