内容简介:原文为Anyway, we’ll get onto exactly how SwiftUI works soon. For now, the least you need to know is that SwiftUI fixes many problems people had with the old Swift + Interface Builder approach:So, I hope you’ll agree there are lots of benefits to be had from m
原文为 @twostraws 的 SwiftUI By Example ,就是简单记录一些主要内容。
Introduction
A brief explanation of the basics of SwiftUI
What is SwiftUI?
- 声明式对比命令式,能够更好地处理不同状态的 UI
- 跨平台:支持 iOS, macOS, tvOS, watchOS
SwiftUI vs Interface Builder and storyboards
- IB 难以阅读和修改
- IB 难以看出修改了内容
- IB 和 Swift 交互不够友好,充满了 Objective-C 设计
- SwiftUI 是个近支持 Swift 的库,这样才可以充分利用 Swift 的特性
Anyway, we’ll get onto exactly how SwiftUI works soon. For now, the least you need to know is that SwiftUI fixes many problems people had with the old Swift + Interface Builder approach:
- We no longer have to argue about programmatic or storyboard-based design, because SwiftUI gives us both at the same time.
- We no longer have to worry about creating source control problems when committing user interface work, because code is much easier to read and manage than storyboard XML.
- We no longer need to worry so much about stringly typed APIs – there are still some, but significantly fewer.
- We no longer need to worry about calling functions that don’t exist, because our user interface gets checked by the Swift compiler.
So, I hope you’ll agree there are lots of benefits to be had from moving to SwiftUI!
Frequently asked questions about SwiftUI
Does SwiftUI replace UIKit?
No. Many parts of SwiftUI directly build on top of existing UIKit components, such as UITableView. Of course, many other parts don’t – they are new controls rendered by SwiftUI and not UIKit.
But the point isn’t to what extent UIKit is involved. Instead, the point is that we don’t care . SwiftUI more or less completely masks UIKit’s behavior, so if you write your app for SwiftUI and Apple replaces UIKit with a singing elephant in two years you don’t have to care – as long as Apple makes the elephant compatible with the same methods and properties that UIKit exposed to SwiftUI, your code doesn’t change.
Is SwiftUI fast?
SwiftUI is screamingly fast – in all my tests so far it seems to outpace UIKit. Having spoken to the team who made it I’m starting to get an idea why: first, they aggressively flatten their layer hierarchy so the system has to do less drawing, but second many operations bypass Core Animation entirely and go straight to Metal for extra speed.
So, yes: SwiftUI is incredibly fast, and all without us having to do any extra work.
How to follow this quick start guide
最好顺序阅读该教程
Migrating from UIKit to SwiftUI
如果你用过 UIKit,不难发现 SwiftUI 就是把 UI 前缀去掉既是对应的组件。
Here’s a list to get you started, with UIKit class names followed by SwiftUI names:
-
UITableView
:List
-
UICollectionView
: No SwiftUI equivalent -
UILabel
:Text
-
UITextField
:TextField
-
UITextField
withisSecureTextEntry
set to true:SecureField
-
UITextView
: No SwiftUI equivalent -
UISwitch
:Toggle
-
UISlider
:Slider
-
UIButton
:Button
-
UINavigationController
:NavigationView
-
UIAlertController
with style.alert
:Alert
-
UIAlertController
with style.actionSheet
:ActionSheet
-
UIStackView
with horizontal axis:HStack
-
UIStackView
with vertical axis:VStack
-
UIImageView
:Image
-
UISegmentedControl
:SegmentedControl
-
UIStepper
:Stepper
-
UIDatePicker
:DatePicker
-
NSAttributedString
: Incompatible with SwiftUI; useText
instead.
Text and images
Getting started with basic controls
What’s in the basic template?
SceneDelegate.swift is responsible for managing the way your app is shown.
let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIHostingController(rootView: ContentView()) self.window = window window.makeKeyAndVisible()
Open ContentView.swift and let’s look at some actual SwiftUI code.
import SwiftUI struct ContentView : View { var body: some View { Text("Hello World") } } #if DEBUG struct ContentView_Previews : PreviewProvider { static var previews: some View { ContentView() } } #endif
First, notice how ContentView
is a struct.
Second, ContentView
conforms to the View
protocol.
第三, body
的返回类型是 some view
。 some
关键字是 Swift 5.1 中的新关键字,是名为 opaque return types 的功能的一部分,在这种情况下,就是字面意思:”这将返回某种 View
,但 SwiftUI 不需要知道(或关心)什么。”
Finally, below ContentView
is a similar-but-different struct called ContentView_Previews
.
How to create static labels with a Text view
Text("Hello World") .lineLimit(3)
Text("This is an extremely long string that will never fit even the widest of Phones") .truncationMode(.middle)
How to style text views with fonts, colors, line spacing, and more
Text("This is an extremely long string that will never fit even the widest of Phones") .truncationMode(.middle) .font(Font.body) .foregroundColor(Color.green) .background(Color.gray) .lineLimit(nil) .lineSpacing(30)
How to format text inside text views
struct ContentView: View { static let taskDateFormat: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .long return formatter }() var dueDate = Date() var body: some View { Text("Task due date: \(dueDate, formatter: Self.taskDateFormat)") } }
How to draw images using Image views
var body: some View { Image("example-image") }
Image(systemName: "cloud.heavyrain.fill") .foregroundColor(.red) .font(.largeTitle)
How to adjust the way an image is fitted to its space
Image("example-image") .resizable() .aspectRatio(contentMode: .fill)
How to render a gradient
Text("Hello World") .padding() .foregroundColor(.white) .background(LinearGradient(gradient: Gradient(colors: [.white, .black]), startPoint: .top, endPoint: .bottom), cornerRadius: 0)
支持更多颜色的渐变
Text("Hello World") .padding() .foregroundColor(.white) .background(LinearGradient(gradient: Gradient(colors: [.white, .red, .black]), startPoint: .leading, endPoint: .trailing), cornerRadius: 0)
How to display solid shapes
Rectangle() .fill(Color.red) .frame(width: 200, height: 200)
How to use images and other views as a backgrounds
Text("Hacking with Swift") .font(.largeTitle) .background( Image("example-image") .resizable() .frame(width: 100, height: 100))
View layout
Position views in a grid structure and more
How to create stacks using VStack and HStack
VStack { Text("SwiftUI") Text("rocks") }
How to customize stack layouts with alignment and spacing
VStack(alignment: .leading, spacing: 20) { Text("SwiftUI") Text("rocks") }
How to control spacing around individual views using padding
Text("SwiftUI") .padding(.bottom, 100)
How to layer views on top of each other using ZStack
ZStack { Rectangle() .fill(Color.red) Text("Hacking with Swift") }
How to return different view types
第一种方案
var body: some View { Group { if Bool.random() { Image("example-image") } else { Text("Better luck next time") } } }
第二种方案
If you haven’t heard of this concept, it effectively forces Swift to forget about what specific type is inside the AnyView
, allowing them to look like they are the same thing. This has a performance cost, though, so don’t use it often.
var body: AnyView { if Bool.random() { return AnyView(Image("example-image")) } else { return AnyView(Text("Better luck next time")) } }
How to create views in a loop using ForEach
VStack(alignment: .leading) { ForEach((1...10).reversed()) { Text("\($0)…") } Text("Ready or not, here I come!") }
struct ContentView : View { let colors: [Color] = [.red, .green, .blue] var body: some View { VStack { ForEach(colors.identified(by: \.self)) { color in Text(color.description.capitalized) .padding() .background(color) } } } }
How to create different layouts using size classes
struct ContentView : View { @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass? var body: some View { if horizontalSizeClass == .compact { return Text("Compact") } else { return Text("Regular") } } }
How to place content outside the safe area
Text("Hello World") .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) .background(Color.red) .edgesIgnoringSafeArea(.all)
Reading input
Respond to interaction and control your program state
Working with state
SwiftUI solves this problem by removing state from our control. When we add properties to our views they are effectively inert – they have values, sure, but changing them doesn’t do anything. But if we added the special @State
attribute before them, SwiftUI will automatically watch for changes and update any parts of our views that use that state.
How to create a toggle switch
相反,我们应该定义一个 @State
布尔属性,用于存储切换的当前值。
struct ContentView : View { @State var showGreeting = true var body: some View { VStack { Toggle(isOn: $showGreeting) { Text("Show welcome message") }.padding() if showGreeting { Text("Hello World!") } } } }
How to create a tappable button
struct ContentView : View { @State var showDetails = false var body: some View { VStack { Button(action: { self.showDetails.toggle() }) { Text("Show details") } if showDetails { Text("You should follow me on Twitter: @twostraws") .font(.largeTitle) .lineLimit(nil) } } } }
How to read text from a TextField
struct ContentView : View { @State var name: String = "Tim" var body: some View { VStack { TextField($name) Text("Hello, \(name)!") } } }
How to add a border to a TextField
TextField($yourBindingHere) .textFieldStyle(.roundedBorder)
How to create secure text fields using SecureField
struct ContentView : View { @State var password: String = "" var body: some View { VStack { SecureField($password) Text("You entered: \(password)") } } }
How to create a Slider and read values from it
struct ContentView : View { @State var celsius: Double = 0 var body: some View { VStack { Slider(value: $celsius, from: -100, through: 100, by: 0.1) Text("\(celsius) Celsius is \(celsius * 9 / 5 + 32) Fahrenheit") } } }
How to create a date picker and read values from it
struct ContentView : View { var colors = ["Red", "Green", "Blue", "Tartan"] @State var selectedColor = 0 var body: some View { VStack { Picker(selection: $selectedColor, label: Text("Please choose a color")) { ForEach(0 ..< colors.count) { Text(self.colors[$0]).tag($0) } } Text("You selected: \(colors[selectedColor])") } } }
How to create a segmented control and read values from it
struct ContentView : View { @State var favoriteColor = 0 var colors = ["Red", "Green", "Blue"] var body: some View { VStack { SegmentedControl(selection: $favoriteColor) { ForEach(0..<colors.count) { index in Text(self.colors[index]).tag(index) } } Text("Value: \(colors[favoriteColor])") } } }
How to read tap and double-tap gestures
Image("example-image") .tapAction(count: 2) { print("Double tapped!") }
How to add a gesture recognizer to a view
struct ContentView : View { @State var scale: Length = 1.0 var body: some View { Image("example-image") .scaleEffect(scale) .gesture( TapGesture() .onEnded { _ in self.scale += 0.1 } ) } }
Lists
Create scrolling tables of data
Working with lists
SwiftUI’s List
view is similar to UITableView
in that it can show static or dynamic table view cells based on your needs.
How to create a list of static items
struct RestaurantRow: View { var name: String var body: some View { Text("Restaurant: \(name)") } } struct ContentView: View { var body: some View { List { RestaurantRow(name: "Joe's Original") RestaurantRow(name: "The Real Joe's Original") RestaurantRow(name: "Original Joe's") } } }
How to create a list of dynamic items
In order to handle dynamic items, you must first tell SwiftUI how it can identify which item is which. This is done using the Identifiable
protocol, which has only one requirement: some sort of id
value that SwiftUI can use to see which item is which.
和 ForEach
一样,可以使用符合 Identifiable
协议的 Model
struct Restaurant: Identifiable { var id = UUID() var name: String } struct RestaurantRow: View { var restaurant: Restaurant var body: some View { Text("Come and eat at \(restaurant.name)") } } struct ContentView: View { var body: some View { let first = Restaurant(name: "Joe's Original") let second = Restaurant(name: "The Real Joe's Original") let third = Restaurant(name: "Original Joe's") let restaurants = [first, second, third] return List(restaurants) { restaurant in RestaurantRow(restaurant: restaurant) } // return List(restaurants, rowContent: RestaurantRow.init) } }
How to add sections to a list
Section(header: Text("Other tasks"), footer: Text("End")) { TaskRow() TaskRow() TaskRow() }
How to make a grouped list
struct ExampleRow: View { var body: some View { Text("Example Row") } } struct ContentView : View { var body: some View { List { Section(header: Text("Examples")) { ExampleRow() ExampleRow() ExampleRow() } }.listStyle(.grouped) } }
Working with implicit stacking
What happens if you create a dynamic list and put more than one thing in each row? SwiftUI’s solution is simple, flexible, and gives us great behavior by default: it creates an implicit HStack
to hold your items, so they automatically get laid out horizontally.
List 会隐式的创建一个 HStack
封装所有元素到 Row 中。
struct ExampleRow: View { var body: some View { Text("Example Row") } } struct ContentView : View { var body: some View { List { Section(header: Text("Examples")) { ExampleRow() ExampleRow() ExampleRow() } }.listStyle(.grouped) } }
Containers
Place your views inside a navigation controller
Working with containers
SwiftUI is designed to be composed right out of the box, which means you can place one view inside another as much as you need.
常见的容器有: NavigationView
, TabbedView
, Group
NavigationView { Text("SwiftUI") .navigationBarTitle(Text("Welcome")) }
var body: some View { NavigationView { Text("SwiftUI") .navigationBarTitle(Text("Welcome")) .navigationBarItems(trailing: Button(action: { print("Help tapped!") }) { Text("Help") }) } }
How to group views together
Stack
不能超过 10 个元素
var body: some View { VStack { Group { Text("Line") Text("Line") Text("Line") Text("Line") Text("Line") Text("Line") } Group { Text("Line") Text("Line") Text("Line") Text("Line") Text("Line") } } }
Alerts and action sheets
Show modal notifications when something happens
Working with presentations
SwiftUI’s declarative approach to programming means that we don’t create and present alert and action sheets in the same way as we did in UIKit. Instead, we define the conditions in which they should be shown, tell it what they should look like, then leave it to figure the rest out for itself.
How to show an alert
struct ContentView : View { @State var showingAlert = false var body: some View { Button(action: { self.showingAlert = true }) { Text("Show Alert") } .presentation($showingAlert) { Alert(title: Text("Important message"), message: Text("Wear sunscreen"), dismissButton: .default(Text("Got it!"))) } } }
How to add actions to alert buttons
struct ContentView : View { @State var showingAlert = false var body: some View { Button(action: { self.showingAlert = true }) { Text("Show Alert") } .presentation($showingAlert) { Alert(title: Text("Are you sure you want to delete this?"), message: Text("There is no undo"), primaryButton: .destructive(Text("Delete")) { print("Deleting...") }, secondaryButton: .cancel()) } } }
How to show an action sheet
struct ContentView : View { @State var showingSheet = false var sheet: ActionSheet { ActionSheet(title: Text("Action"), message: Text("Quote mark"), buttons: [.default(Text("Woo"), onTrigger: { self.showingSheet = false })]) } var body: some View { Button(action: { self.showingSheet = true }) { Text("Woo") } .presentation(showingSheet ? sheet : nil) } }
Presenting views
Move your user from one view to another
struct DetailView: View { var body: some View { Text("Detail") } } struct ContentView : View { var body: some View { NavigationView { NavigationButton(destination: DetailView()) { Text("Click") }.navigationBarTitle(Text("Navigation")) } } }
How to push a new view when a list row is tapped
SwiftUI doesn’t have a direct equivalent of the didSelectRowAt
method of UITableView
, but it doesn’t need one because we can combine NavigationButton
with a list row and get the behavior for free.
struct Restaurant: Identifiable { var id = UUID() var name: String } struct RestaurantRow: View { var restaurant: Restaurant var body: some View { Text(restaurant.name) } } struct RestaurantView: View { var restaurant: Restaurant var body: some View { Text("Come and eat at \(restaurant.name)") .font(.largeTitle) } } struct ContentView: View { var body: some View { let first = Restaurant(name: "Joe's Original") let restaurants = [first] return NavigationView { List(restaurants) { restaurant in NavigationButton(destination: RestaurantView(restaurant: restaurant)) { RestaurantRow(restaurant: restaurant) } }.navigationBarTitle(Text("Select a restaurant")) } } }
How to present a new view using PresentationButton
struct DetailView: View { var body: some View { Text("Detail") } } struct ContentView : View { var body: some View { PresentationButton(Text("Click to show"), destination: DetailView()) } }
Transforming views
Clip, size, scale, spin, and more
How to give a view a custom frame
Button(action: { print("Button tapped") }) { Text("Welcome") .frame(minWidth: 0, maxWidth: 200, minHeight: 0, maxHeight: 200) .font(.largeTitle) } Text("Please log in") .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) .font(.largeTitle) .foregroundColor(.white) .background(Color.red)
How to adjust the position of a view
VStack { Text("Home") Text("Options") .offset(y: 15) .padding(.bottom, 15) Text("Help") }
How to color the padding around a view
Padding 的先后顺序影响结果,因为代码是顺序执行的。
Text("Hacking with Swift") .background(Color.black) .foregroundColor(.white) .padding() Text("Hacking with Swift") .padding() .background(Color.black) .foregroundColor(.white)
How to stack modifiers to create more advanced effects
Text("Forecast: Sun") .font(.largeTitle) .foregroundColor(.white) .padding() .background(Color.red) .padding() .background(Color.orange) .padding() .background(Color.yellow)
How to draw a border around a view
Text("Hacking with Swift") .padding() .border(Color.red, width: 4, cornerRadius: 16)
How to draw a shadow around a view
Text("Hacking with Swift") .padding() .border(Color.red, width: 4) .shadow(color: .red, radius: 5, x: 20, y: 20)
How to clip a view so only part is visible
Button(action: { print("Button tapped") }) { Image(systemName: "bolt.fill") .foregroundColor(.white) .padding() .background(Color.green) .clipShape(Circle()) }
How to rotate a view
struct ContentView: View { @State var rotation: Double = 0 var body: some View { VStack { Slider(value: $rotation, from: 0.0, through: 360.0, by: 1.0) Text("Up we go") .rotationEffect(.degrees(rotation)) } } }
How to rotate a view in 3D
SwiftUI’s rotation3DEffect()
modifier lets us rotate views in 3D space to create beautiful effects in almost no code.
Text("EPISODE LLVM") .font(.largeTitle) .foregroundColor(.yellow) .rotation3DEffect(.degrees(45), axis: (x: 1, y: 0, z: 0))
How to scale a view up or down
Text("Up we go") .scaleEffect(5)
How to round the corners of a view
Text("Round Me") .padding() .background(Color.red) .cornerRadius(25)
How to adjust the opacity of a view
Text("Now you see me") .padding() .background(Color.red) .opacity(0.3)
How to adjust the accent color of a view
iOS uses tint colors to give apps a coordinated theme, and the same functionality is available in SwiftUI under the name accent colors .
How to mask one view with another
Image("stripes") .resizable() .frame(width: 300, height: 300) .mask(Text("SWIFT!") .font(Font.system(size: 72).weight(.black)))
How to blur a view
Text("Welcome to my SwiftUI app") .blur(radius: 2)
How to blend views together
ZStack { Image("paul-hudson") Image("example-image") .blendMode(.multiply) }
How to adjust views by tinting, and desaturating, and more
Image("paul-hudson") .contrast(0.5)
Animation
Bring your views to life with movement
How to create a basic animation
struct ContentView: View { @State var angle: Double = 0 @State var borderThickness: Length = 1 var body: some View { Button(action: { self.angle += 45 self.borderThickness += 1 }) { Text("Tap here") .padding() .border(Color.red, width: borderThickness) .rotationEffect(.degrees(angle)) .animation(.basic()) } } }
How to create a spring animation
struct ContentView: View { @State var angle: Double = 0 var body: some View { Button(action: { self.angle += 45 }) { Text("Tap here") .padding() .rotationEffect(.degrees(angle)) .animation(.spring()) } } }
How to create an explicit animation
Explicit animations are often helpful because they cause every affected view to animation, not just those that have implicit animations attached. For example, if view A has to make room for view B as part of the animation, but only view B has an animation attached, then view A will jump to its new position without animating unless you use explicit animations.
struct ContentView: View { @State var opacity: Double = 1 var body: some View { Button(action: { withAnimation { self.opacity -= 0.2 } }) { Text("Tap here") .padding() .opacity(opacity) } } }
How to add and remove views with a transition
struct ContentView: View { @State var showDetails = false var body: some View { VStack { Button(action: { withAnimation { self.showDetails.toggle() } }) { Text("Tap to show details") } if showDetails { Text("Details go here.") } } } }
By default, SwiftUI uses a fade animation to insert or remove views, but you can change that if you want by attaching a transition()
modifier to a view.
Text("Details go here.") .transition(.move(edge: .bottom))
How to combine transitions
Text("Details go here.").transition(AnyTransition.opacity.combined(with: .slide))
或者使用拓展来封装常用的过渡效果
extension AnyTransition { static var moveAndScale: AnyTransition { AnyTransition.move(edge: .bottom).combined(with: .scale()) } } // Usage Text("Details go here.").transition(.moveAndScale)
How to create asymmetric transitions
SwiftUI lets us specify one transition when adding a view and another when removing it, all done using the asymmetric()
transition type.
Text("Details go here.").transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .bottom)))
Composing views
Make your UI structure easier to understand
How to create and compose custom views
都是基于数据驱动 UI,所以每一级封装都是传 Model: User
struct User { var name: String var jobTitle: String var emailAddress: String var profilePicture: String } struct ProfilePicture: View { var imageName: String var body: some View { Image(imageName) .resizable() .frame(width: 100, height: 100) .clipShape(Circle()) } } struct EmailAddress: View { var address: String var body: some View { HStack { Image(systemName: "envelope") Text(address) } } } struct UserDetails: View { var user: User var body: some View { VStack(alignment: .leading) { Text(user.name) .font(.largeTitle) .foregroundColor(.primary) Text(user.jobTitle) .foregroundColor(.secondary) EmailAddress(address: user.emailAddress) } } } struct UserView: View { var user: User var body: some View { HStack { ProfilePicture(imageName: user.profilePicture) UserDetails(user: user) } } } struct ContentView: View { let user = User(name: "Paul Hudson", jobTitle: "Editor, Hacking with Swift", emailAddress: "paul@hackingwithswift.com", profilePicture: "paul-hudson") var body: some View { UserView(user: user) } }
How to combine text views together
这样子拼接文本很方便
var body: some View { Text("SwiftUI") .font(.largeTitle) + Text("is") .font(.headline) + Text("awesome") .font(.footnote) }
但是有个修改器不适合拼接如: foregroundColor
这时候就需要换成能够使用 +
的修改器,如 color
Text("SwiftUI") .color(.red) + Text("is") .color(.orange) + Text("awesome") .color(.blue)
How to store views as properties
struct ContentView : View { let title = Text("Paul Hudson") .font(.largeTitle) let subtitle = Text("Author") .foregroundColor(.secondary) var body: some View { VStack { title .color(.red) subtitle } } }
How to create custom modifiers
struct PrimaryLabel: ViewModifier { func body(content: Content) -> some View { content .padding() .background(Color.red) .foregroundColor(Color.white) .font(.largeTitle) } } struct ContentView: View { var body: some View { Text("Hello, SwiftUI") .modifier(PrimaryLabel()) } }
Tooling
Build better apps with help from Xcode
How to preview your layout at different Dynamic Type sizes
#if DEBUG struct ContentView_Previews : PreviewProvider { static var previews: some View { Group { ContentView() .environment(\.colorScheme, .dark) } } } #endif
How to preview your layout in light and dark mode
If you want to see both light and dark mode side by side, place multiple previews in a group, like this:
#if DEBUG struct ContentView_Previews : PreviewProvider { static var previews: some View { Group { ContentView() .environment(\.colorScheme, .light) ContentView() .environment(\.colorScheme, .dark) } } } #endif
How to preview your layout in different devices
ContentView() .previewDevice(PreviewDevice(rawValue: "iPhone SE"))
struct ContentView : View { var body: some View { Text("Hello World") .navigationBarTitle(Text("Welcome")) } } #if DEBUG struct ContentView_Previews : PreviewProvider { static var previews: some View { NavigationView { ContentView() } } } #endif
How to use Instruments to profile your SwiftUI code and identify slow layouts
示例代码
import Combine import SwiftUI class FrequentUpdater: BindableObject { var didChange = PassthroughSubject<Void, Never>() var timer: Timer? init() { timer = Timer.scheduledTimer( withTimeInterval: 0.01, repeats: true ) { _ in self.didChange.send(()) } } } struct ContentView : View { @ObjectBinding var updater = FrequentUpdater() @State var tapCount = 0 var body: some View { VStack { Text("\(UUID().uuidString)") Button(action: { self.tapCount += 1 }) { Text("Tap count: \(tapCount)") } } } }
检测我们的代码
默认情况下,SwiftUI 工具告诉我们各种各样的事情:
1. 在此期间创建了多少视图以及创建它们需要多长时间(“View Body”)
2. 视图的属性是什么以及它们如何随时间变化(“View Properties”)
3. 发生了多少次 Core Animation 提交(“Core Animation Commits”)
4. 每个函数调用的确切时间(“Time Profiler”)
监视 body
调用
如果您选择 View Body
轨道 - 这是 instrument 列表中的第一行 - 您应该能够看到乐器将结果分解为 SwiftUI 和您的项目,前者是原始类型,如文本视图和按钮,以及后者包含您的自定义视图类型。在我们的例子中,这意味着“ContentView”应该出现在自定义视图中,因为这是我们视图的名称。
现在,您在这里看不到的是您的代码与 SwiftUI 视图的完美一对一映射,因为 SwiftUI 积极地折叠其视图层次结构以尽可能少地完成工作。所以,不要指望在代码中看到任何 VStack 创建 - 这个应用程序实际上是免费的。
在这个屏幕上,重要的数字是计数和平均持续时间 - 每件事创建的次数,以及花费的时间。因为这是一个压力测试你应该看到非常高的数字,但我们的布局是微不足道的,所以平均持续时间可能是几十微秒。
跟踪状态(state)变化
接下来,选择“View Properties”轨道,这是仪器列表中的第二行。这将显示所有视图的所有属性,包括其当前值和所有先前值。
我们的示例应用程序有一个按钮,通过在数字中添加一个来更改其标签,并且在此 工具 中可见 - 请查看视图类型 ContentView 和属性类型 State
。
可悲的是,Instruments 还没有(还能)向我们展示那里的确切属性名称,如果你跟踪了几个整数状态,这可能会更加令人困惑。然而,它确实有一个不同的技巧:在记录窗口的顶部是一个标记当前视图位置的箭头,如果你拖动它,你会看到应用程序状态随时间的变化 - 每次你点击按钮,你会看到状态整数上升一个,你可以前进和后退来看它发生。
这可以释放巨大的能力,因为它可以让我们直接看到状态变化导致慢速重绘或其他工作 - 这几乎就像是在时间机器中,您可以在运行期间的每个点检查应用程序的确切状态。
识别慢速绘图
虽然 SwiftUI 能够直接调用 Metal 以提高性能,但大多数情况下它更喜欢使用 Core Animation 进行渲染。这意味着我们会自动从 Instruments 获取内置的 Core Animation 分析工具,包括检测昂贵提交(expensive commits)的能力。
当多个更改放在一个组中时,Core Animation 的效果最佳,称为 transaction
。我们在一个事务中有效地堆叠了一系列工作,然后要求 CA 继续渲染工作 - 称为 提交 事务。
因此,当 Instruments 向我们展示昂贵的 Core Animation 提交时,它真正向我们展示的是 SwiftUI 因为更新而被迫重绘屏幕上的像素的次数。理论上,这应该只在我们的应用程序的实际状态导致不同的视图层次结构时发生,因为 SwiftUI 应该能够将我们的 body
属性的新输出与先前的输出进行比较。
寻找缓慢的函数调用
Time Profiler,它向我们展示了在代码的每个部分花费了多少时间。这与乐器中的常规时间分析器完全相同,但如果您之前没有尝试过,那么您至少需要知道:
-
右侧的扩展详细信息窗口默认显示最重的堆栈跟踪,这是运行时间最长的代码段。明亮的代码(白色或黑色,取决于您的 macOS 配色方案)是您编写的代码; 昏暗代码(灰色)是系统库代码。
-
在左侧,您可以看到创建的所有线程,以及公开指示器,让您深入了解它们调用的函数以及这些函数调用的函数等。大多数工作将在“start”内部进行。
-
为避免混乱,您可能需要单击底部的“调用树”按钮,然后选择“隐藏系统库”。这只会显示您编写的代码,但是如果您的问题是您使用的系统库很糟糕,这可能没有帮助。
-
要直接了解具体细节,您还可以单击“调用树”并选择“反转调用树”以翻转事物,以便叶子功能(树末端的功能)显示在顶部,现在可以向下钻取公开指示器(向上钻取?)到调用它们的函数。
最后一些提示
在您收取配置自己的代码之前,有一些事情需要注意:
- 在检查应用程序性能的一小部分时,您应该单击并拖动相关范围,以便仅查看该应用程序部分的统计信息。这使您可以专注于特定操作的性能,例如响应按下按钮。
- 即使你在仪器中看到纯色条,它们只是从远处看起来那样 - 你可以通过按住 Cmd 并按 - 和 + 来查看更多细节
- 要获得最准确的数字,请始终在真实设备上进行配置。
- 如果要通过分析代码进行更改,请始终一次进行一次更改。如果你进行两次更改,可能会使你的性能提高 20%而另一种会降低 10%,但是将它们合在一起意味着你可能会认为它们整体性能提高了 10%。
- Instruments 在 release 模式下运行您的代码,从而实现 Swift 的所有优化。这也会影响您添加到代码中的任何调试标志,因此请小心。
What now?
How to continue learning SwiftUI after the basics
SwiftUI tips and tricks
SwiftUI 拥有强大的标题功能,但也有许多较小的提示和技巧可以帮助您编写更好的应用程序。
@State 设为私有
@State private var score = 0
具有常量绑定的原型
TextField(.constant("Hello")) .textFieldStyle(.roundedBorder)
使用语义颜色
Color.red
依靠自适应填充
Text("Row 1") .padding(10)
合并文本视图
struct ContentView : View { var body: some View { Text("Colored") .color(.red) + Text("SwifUI") .color(.green) + Text("Text") .color(.blue) } }
如何使 print()
工作
右键单击预览画布(preview canvas)中的播放按钮,然后选择“调试预览(Debug Preview)”。
依靠隐式 HStack
struct ContentView : View { let imageNames = ["paul-hudson", "swiftui"] var body: some View { List(imageNames.identified(by: \.self)) { image in Image(image).resizable().frame(width: 40) Text(image) } } }
分割大视图
struct ContentView : View { let users = ["Paul Hudson", "Taylor Swift"] var body: some View { NavigationView { List(users.identified(by: \.self)) { user in NavigationButton(destination: Text("Detail View")) { Image("example-image").resizable().frame(width: 50, height: 50) VStack(alignment: .leading) { Text("Johnny Appleseed").font(.headline) Text("Occupation: Programmer") } } }.navigationBarTitle(Text("Users")) } } }
更好的预览
#if DEBUG struct ContentView_Previews : PreviewProvider { static var previews: some View { Group { ContentView() .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge) ContentView() .environment(\.colorScheme, .dark) NavigationView { ContentView() } } } } #endif
创建自定义修改器
struct PrimaryLabel: ViewModifier { func body(content: Content) -> some View { content .padding() .background(Color.black) .foregroundColor(Color.white) .font(.largeTitle) .cornerRadius(10) } } struct ContentView : View { var body: some View { Text("Hello World") .modifier(PrimaryLabel()) } }
动画变化很容易
struct ContentView : View { @State var showingWelcome = false var body: some View { VStack { Toggle(isOn: $showingWelcome.animation(.spring())) { Text("Toggle label") } if showingWelcome { Text("Hello World") } } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【每日笔记】【Go学习笔记】2019-01-04 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-02 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-07 Codis笔记
- vue笔记3,计算笔记
- Mysql Java 驱动代码阅读笔记及 JDBC 规范笔记
- 【每日笔记】【Go学习笔记】2019-01-16 go网络编程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。