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…