SwiftUI 官方教程 (三)
由于 API 变动,此文章部分内容已失效,最新完整中文教程及代码请查看 https://github.com/WillieWangWei/SwiftUI-Tutorials
微信技术群
SwiftUI
代表未来构建 App 的方向,欢迎加群一起交流技术,解决问题。
加群现在需要申请了,可以先加我微信,备注 “SwiftUI”,我会拉你进群。
构建列表与导航
完成了基础的地标详情 view 后,我们需要为用户提供查看完整地标列表,以及查看每个地标详情的方法。
在本文中,我们将会创建可显示任何地标信息的 view ,并动态生成滚动列表,用户可以点按该列表以查看地标的详细视图。另外,我们还将使用 Xcode 的
canvas
来显示不同设备的大小,以此来微调 UI。下载项目文件并按照以下步骤操作。
- 预计完成时间:35 分钟
- 初始项目文件:下载
1. 了解样本数据
在 上一个教程 中,我们把数据硬编码到了所有自定义 view 中。在本文中,我们来学习如何将数据传递到自定义 view 中并显示。
下载初始项目并熟悉一下样本数据。
1.1 在 Project navigator
中,选择 Models
> Landmark.swift
。
Landmark.swift
声明了一个 Landmark
结构体,用来存储 app 需要显示的所有地标数据,并从 landmarkData.json
导入一组地标数据。
Landmark.swift
import SwiftUI
import CoreLocationstruct Landmark: Hashable, Codable {var id: Intvar name: Stringfileprivate var imageName: Stringfileprivate var coordinates: Coordinatesvar state: Stringvar park: Stringvar category: Categoryvar locationCoordinate: CLLocationCoordinate2D {CLLocationCoordinate2D(latitude: coordinates.latitude,longitude: coordinates.longitude)}func image(forSize size: Int) -> Image {ImageStore.shared.image(name: imageName, size: size)}enum Category: String, CaseIterable, Codable, Hashable {case featured = "Featured"case lakes = "Lakes"case rivers = "Rivers"}
}struct Coordinates: Hashable, Codable {var latitude: Doublevar longitude: Double
}
1.2 在 Project navigator
中,选择 Resources
> landmarkData.json
。
我们会在本教程的剩余部分以及随后的所有内容中使用此样本数据。
landmarkData.json
[{"name": "Turtle Rock","category": "Featured","city": "Twentynine Palms","state": "California","id": 1001,"park": "Joshua Tree National Park","coordinates": {"longitude": -116.166868,"latitude": 34.011286},"imageName": "turtlerock"},{"name": "Silver Salmon Creek","category": "Lakes","city": "Port Alsworth","state": "Alaska","id": 1002,"park": "Lake Clark National Park and Preserve","coordinates": {"longitude": -152.665167,"latitude": 59.980167},"imageName": "silversalmoncreek"},...
]
1.3 需要注意的是, 上一个教程 中的 ContentView
类型现在更名为 LandmarkDetail
。
接下来我们还会创建多个 view 类型。
LandmarkDetail.swift
import SwiftUIstruct LandmarkDetail: View {var body: some View {VStack {MapView().frame(height: 300)CircleImage().offset(y: -130).padding(.bottom, -130)VStack(alignment: .leading) {Text("Turtle Rock").font(.title)HStack(alignment: .top) {Text("Joshua Tree National Park").font(.subheadline)Spacer()Text("California").font(.subheadline)}}.padding()Spacer()}}
}struct LandmarkDetail_Preview: PreviewProvider {static var previews: some View {LandmarkDetail()}
}
2. 创建 Row View
我们在本文中构建的第一个 view 是用于显示每个地标详情的 row
。 row
将地标数据存储在 landmark
属性中,这样一个 row
就可以显示任何地标。稍后我们会把多个 row
组合成一个地标列表。
2.1 创建一个新的 SwiftUI
view,命名为 LandmarkRow.swift
。
2.2 如果预览没有显示,请选择 Editor
> Editor and Canvas
, 然后单击 Get Started
。
2.3 给 LandmarkRow
添加一个存储属性 landmark
。
当你添加 landmark
属性时,预览会停止工作,因为 LandmarkRow
类型在初始化时需要一个 landmark
实例。
LandmarkRow.swift
import SwiftUIstruct LandmarkRow: View {var landmark: Landmarkvar body: some View {Text("Hello World")}
}struct LandmarkRow_Previews: PreviewProvider {static var previews: some View {LandmarkRow()}
}
为了恢复预览,我们需要修改 PreviewProvider
。
2.4 在 LandmarkRow_Previews
的静态属性 previews
中,给 LandmarkRow
的初始化方法添加 landmark
参数,并将 landmarkData
数组的第一个元素赋值给 landmark
参数。
这时预览就会显示 Hello World
的文字。
LandmarkRow.swift
import SwiftUIstruct LandmarkRow: View {var landmark: Landmarkvar body: some View {Text("Hello World")}
}struct LandmarkRow_Previews: PreviewProvider {static var previews: some View {LandmarkRow(landmark: landmarkData[0])}
}
恢复预览后,我们就可以构建 row
的布局了。
2.5 把现有的 text view 嵌套到一个 HStack
中。
LandmarkRow.swift
import SwiftUIstruct LandmarkRow: View {var landmark: Landmarkvar body: some View {HStack {Text("Hello World")}}
}struct LandmarkRow_Previews: PreviewProvider {static var previews: some View {LandmarkRow(landmark: landmarkData[0])}
}
2.6 将 text view 的内容修改成 landmark.name
。
LandmarkRow.swift
import SwiftUIstruct LandmarkRow: View {var landmark: Landmarkvar body: some View {HStack {Text(landmark.name)}}
}struct LandmarkRow_Previews: PreviewProvider {static var previews: some View {LandmarkRow(landmark: landmarkData[0])}
}
2.7 在 text view 前添加一个图片来完成 row
。
LandmarkRow.swift
import SwiftUIstruct LandmarkRow: View {var landmark: Landmarkvar body: some View {HStack {landmark.image(forSize: 50)Text(landmark.name)}}
}struct LandmarkRow_Previews: PreviewProvider {static var previews: some View {LandmarkRow(landmark: landmarkData[0])}
}
3. 自定义 Row 的预览
Xcode的 canvas
会自动识别并显示当前编辑器中符合 PreviewProvider
协议的任何类型。 preview provider
返回一个或多个 view ,其中包含了用来配置大小和设备的选项。
通过自定义 preview provider
的返回值,我们可以让预览来显示需要的内容。
3.1 在 LandmarkRow_Previews
中,把 landmark
的参数改成 landmarkData
数组的第二个元素。
预览会立即从第一个元素切换到第二个元素的显示。
LandmarkRow.swift
import SwiftUIstruct LandmarkRow: View {var landmark: Landmarkvar body: some View {HStack {landmark.image(forSize: 50)Text(landmark.name)}}
}struct LandmarkRow_Previews: PreviewProvider {static var previews: some View {LandmarkRow(landmark: landmarkData[1])}
}
3.2 用 previewLayout(_:)
方法设置 row
在列表中的大概大小。
LandmarkRow.swift
import SwiftUIstruct LandmarkRow: View {var landmark: Landmarkvar body: some View {HStack {landmark.image(forSize: 50)Text(landmark.name)}}
}struct LandmarkRow_Previews: PreviewProvider {static var previews: some View {LandmarkRow(landmark: landmarkData[1]).previewLayout(.fixed(width: 300, height: 70))}
}
我们可以在 preview provider
中使用 Group
来返回多个预览。
3.3 把返回的 row
包装到一个 Group
中,并且把第一个 row
添加回来。
Group
是一个组合 view 的容器。 Xcode 会在 canvas
中把 Group
的子 view 作为分开的预览渲染出来。
LandmarkRow.swift
import SwiftUIstruct LandmarkRow: View {var landmark: Landmarkvar body: some View {HStack {landmark.image(forSize: 50)Text(landmark.name)}}
}struct LandmarkRow_Previews: PreviewProvider {static var previews: some View {Group {LandmarkRow(landmark: landmarkData[0]).previewLayout(.fixed(width: 300, height: 70))LandmarkRow(landmark: landmarkData[1]).previewLayout(.fixed(width: 300, height: 70))}}
}
把 previewLayout(_:)
的调用移到 group
声明的外面来精简代码。
一个 view 的子项会继承 view 的上下文设置,比如这里的预览设置。
LandmarkRow.swift
import SwiftUIstruct LandmarkRow: View {var landmark: Landmarkvar body: some View {HStack {landmark.image(forSize: 50)Text(landmark.name)}}
}struct LandmarkRow_Previews: PreviewProvider {static var previews: some View {Group {LandmarkRow(landmark: landmarkData[0])LandmarkRow(landmark: landmarkData[1])}.previewLayout(.fixed(width: 300, height: 70))}
}
在 preview provider
中编写的代码只会改变 Xcode 在 canvas
中的显示。由于 #if DEBUG
指令的存在,当 app 发布时,编译器会删除这些代码。
4. 创建地标列表
使用 SwiftUI
的 List
类型可以显示平台特有的列表 view 。列表的元素可以是静态的,就像我们创建的 stacks
的子 view 一样;也可以是动态生成的。甚至可以把静态和动态生成的 view 混合在一起。
4.1 创建一个新的 SwiftUI
view,命名为 LandmarkList.swift
。
4.2 把默认的 Text
view 换成 List
,然后传入两个包含头两个地标数据的 LandmarkRow
对象,作为 List
的子项。
预览会以适合 iOS 样式的列表来显示这两个地标。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {List {LandmarkRow(landmark: landmarkData[0])LandmarkRow(landmark: landmarkData[1])}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {LandmarkList()}
}
5. 动态化列表
相比于给 list
指定单个元素,我们还可以直接从集合中生成 row
。
通过传递一个数据集合和一个给每个元素提供 view 的闭包来让 list
显示集合的元素。 list
通过传递的闭包来把每个集合中的元素转换成子 view 。
5.1 移除现有的两个静态地标 row
,然后给 List
的初始化方法传递 landmarkData
。
list
使用 identifiable
的数据,我们可以使用以下两个方法之一来让数据变成 identifiable
:调用 identified(by:)
方法,使用 key path
属性来唯一标识每个元素,或者让数据类型遵循 Identifiable
协议。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {List(landmarkData.identified(by: \.id)) { landmark in}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {LandmarkList()}
}
5.2 在闭包中返回 LandmarkRow
,我们就完成了自动生成内容的 list
。
这会给 landmarkData
数组中的每一个元素创建一个 LandmarkRow
。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {List(landmarkData.identified(by: \.id)) { landmark inLandmarkRow(landmark: landmark)}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {LandmarkList()}
}
接下来,我们通过给 Landmark
类型添加遵循 Identifiable
的声明来简化代码。
5.3 切换到 Landmark.swift
,声明遵循 Identifiable
协议。
当 Landmark
类型声明了 Identifiable
协议需要的 id
属性后,我们就完成了对 Landmark
的修改。
Landmark.swift
import SwiftUI
import CoreLocationstruct Landmark: Hashable, Codable, Identifiable {var id: Intvar name: Stringfileprivate var imageName: Stringfileprivate var coordinates: Coordinatesvar state: Stringvar park: Stringvar category: Categoryvar locationCoordinate: CLLocationCoordinate2D {CLLocationCoordinate2D(latitude: coordinates.latitude,longitude: coordinates.longitude)}func image(forSize size: Int) -> Image {ImageStore.shared.image(name: imageName, size: size)}enum Category: String, CaseIterable, Codable, Hashable {case featured = "Featured"case lakes = "Lakes"case rivers = "Rivers"}
}struct Coordinates: Hashable, Codable {var latitude: Doublevar longitude: Double
}
5.4 切回 LandmarkList
,删除 identified(by:)
的调用。
从现在开始,我们可以直接使用 Landmark
元素的集合。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {List(landmarkData) { landmark inLandmarkRow(landmark: landmark)}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {LandmarkList()}
}
6. 在列表和详情之间设置导航
虽然列表已经能显示了,但是我们还不能通过点击单个地标来查看地标详情页面。
把 list
嵌入一个 NavigationView
中,并把每个 row
嵌套在一个 NavigationButton
中来设置到目标 view 的转场,这样 list
就具有了导航功能。
6.1 把自动创建地标的 list
嵌入到一个 NavigationView
中。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {NavigationView {List(landmarkData) { landmark inLandmarkRow(landmark: landmark)}}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {LandmarkList()}
}
调用 navigationBarTitle(_:)
方法来设置 list
显示时导航栏的标题。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {NavigationView {List(landmarkData) { landmark inLandmarkRow(landmark: landmark)}.navigationBarTitle(Text("Landmarks"))}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {LandmarkList()}
}
6.3 在 list
的闭包中,把返回的 row
包装在一个 NavigationButton
中,并把 LandmarkDetail
view 作为目标。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {NavigationView {List(landmarkData) { landmark inNavigationButton(destination: LandmarkDetail()) {LandmarkRow(landmark: landmark)}}.navigationBarTitle(Text("Landmarks"))}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {LandmarkList()}
}
6.4 切换到实时模式后可以直接在预览中尝试导航功能。单击 Live Preview
按钮,然后点击地标来访问详情页面。
7. 给子 View 传递数据
LandmarkDetail
现在依然使用硬编码的数据来显示地标。像 LandmarkRow
一样,LandmarkDetail
类型和它组合的其他 view 都需要一个 landmark
属性作为它们的数据源。
在开始子 view 的内容时,我们会把 CircleImage
、 MapView
和 LandmarkDetail
的显示从硬编码改为传入的数据。
7.1 在 CircleImage.swif
中,添加存储属性 image
。
这是使用 SwiftUI
构建 view 时的常见模式。我们的自定义 view 通常会为特定视图包装和封装一些 modifiers
。
CircleImage.swift
import SwiftUIstruct CircleImage: View {var image: Imagevar body: some View {image.clipShape(Circle()).overlay(Circle().stroke(Color.white, lineWidth: 4)).shadow(radius: 10)}
}struct CircleImage_Preview: PreviewProvider {static var previews: some View {CircleImage()}
}
7.2 更新 preview provider
,传递一个 Turtle Rock
的图片。
CircleImage.swift
import SwiftUIstruct CircleImage: View {var image: Imagevar body: some View {image.clipShape(Circle()).overlay(Circle().stroke(Color.white, lineWidth: 4)).shadow(radius: 10)}
}struct CircleImage_Preview: PreviewProvider {static var previews: some View {CircleImage(image: Image("turtlerock"))}
}
7.3 在 MapView.swift
中,给 MapView
添加一个 coordinate
属性,然后把经纬度的硬编码换成使用这个属性。
MapView.swift
import SwiftUI
import MapKitstruct MapView: UIViewRepresentable {var coordinate: CLLocationCoordinate2Dfunc makeUIView(context: Context) -> MKMapView {MKMapView(frame: .zero)}func updateUIView(_ view: MKMapView, context: Context) {let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)let region = MKCoordinateRegion(center: coordinate, span: span)view.setRegion(region, animated: true)}
}struct MapView_Preview: PreviewProvider {static var previews: some View {MapView()}
}
7.4 更新 preview provider
,传递数据数组中第一个地标的坐标。
MapView.swift
import SwiftUI
import MapKitstruct MapView: UIViewRepresentable {var coordinate: CLLocationCoordinate2Dfunc makeUIView(context: Context) -> MKMapView {MKMapView(frame: .zero)}func updateUIView(_ view: MKMapView, context: Context) {let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)let region = MKCoordinateRegion(center: coordinate, span: span)view.setRegion(region, animated: true)}
}struct MapView_Preview: PreviewProvider {static var previews: some View {MapView(coordinate: landmarkData[0].locationCoordinate)}
}
7.5 在 LandmarkDetail.swift
中,给 LandmarkDetail
类型添加 landmark
属性。
LandmarkDetail.swift
import SwiftUIstruct LandmarkDetail: View {var landmark: Landmarkvar body: some View {VStack {MapView().frame(height: 300)CircleImage().offset(y: -130).padding(.bottom, -130)VStack(alignment: .leading) {Text("Turtle Rock").font(.title)HStack(alignment: .top) {Text("Joshua Tree National Park").font(.subheadline)Spacer()Text("California").font(.subheadline)}}.padding()Spacer()}}
}struct LandmarkDetail_Preview: PreviewProvider {static var previews: some View {LandmarkDetail()}
}
7.6 更新 preview provider
,使用 landmarkData
中的第一个地标。
LandmarkDetail.swift
import SwiftUIstruct LandmarkDetail: View {var landmark: Landmarkvar body: some View {VStack {MapView().frame(height: 300)CircleImage().offset(y: -130).padding(.bottom, -130)VStack(alignment: .leading) {Text("Turtle Rock").font(.title)HStack(alignment: .top) {Text("Joshua Tree National Park").font(.subheadline)Spacer()Text("California").font(.subheadline)}}.padding()Spacer()}}
}struct LandmarkDetail_Preview: PreviewProvider {static var previews: some View {LandmarkDetail(landmark: landmarkData[0])}
}
7.7 将所需数据传递给我们的自定义类型。
LandmarkDetail.swift
import SwiftUIstruct LandmarkDetail: View {var landmark: Landmarkvar body: some View {VStack {MapView(coordinate: landmark.locationCoordinate).frame(height: 300)CircleImage(image: landmark.image(forSize: 250)).offset(y: -130).padding(.bottom, -130)VStack(alignment: .leading) {Text(landmark.name).font(.title)HStack(alignment: .top) {Text(landmark.park).font(.subheadline)Spacer()Text(landmark.state).font(.subheadline)}}.padding()Spacer()}}
}struct LandmarkDetail_Preview: PreviewProvider {static var previews: some View {LandmarkDetail(landmark: landmarkData[0])}
}
7.8 最后,调用 navigationBarTitle(_:displayMode:)
方法,给导航栏添加显示详情 view 时的标题。
LandmarkDetail.swift
import SwiftUIstruct LandmarkDetail: View {var landmark: Landmarkvar body: some View {VStack {MapView(coordinate: landmark.locationCoordinate).frame(height: 300)CircleImage(image: landmark.image(forSize: 250)).offset(y: -130).padding(.bottom, -130)VStack(alignment: .leading) {Text(landmark.name).font(.title)HStack(alignment: .top) {Text(landmark.park).font(.subheadline)Spacer()Text(landmark.state).font(.subheadline)}}.padding()Spacer()}.navigationBarTitle(Text(landmark.name), displayMode: .inline)}
}struct LandmarkDetail_Preview: PreviewProvider {static var previews: some View {LandmarkDetail(landmark: landmarkData[0])}
}
7.9 在 SceneDelegate.swift
中,把 app 的 rootView
改成 LandmarkList
。
当我们不使用预览而是在模拟器中独立运行 app 时,app 会以 SceneDelegate
中定义的 rootView
开始显示。
SceneDelegate.swift
import UIKit
import SwiftUIclass SceneDelegate: UIResponder, UIWindowSceneDelegate {var window: UIWindow?func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).// Use a UIHostingController as window root view controllerlet window = UIWindow(frame: UIScreen.main.bounds)window.rootViewController = UIHostingController(rootView: LandmarkList())self.window = windowwindow.makeKeyAndVisible()}// ...
}
7.10 在 LandmarkList.swift
中,给目标 LandmarkDetail
传递当前的地标。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {NavigationView {List(landmarkData) { landmark inNavigationButton(destination: LandmarkDetail(landmark: landmark)) {LandmarkRow(landmark: landmark)}}.navigationBarTitle(Text("Landmarks"))}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {LandmarkList()}
}
7.11 切换到实时预览,可以查看从列表导航到正确的地标详情 view 了。
8. 动态生成预览
接下来,我们会在 LandmarkList_Previews
中添加代码以在不同的设备尺寸上渲染列表。默认情况下,预览会以当前的 scheme
中设备的大小进行渲染。我们可以通过调用 previewDevice(_:)
方法来改变预览设备。
8.1 首先,改变当前 list
的预览来显示 iPhone SE 的尺寸。
我们可以输入任何 Xcode scheme
菜单中显示的设备名称。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {NavigationView {List(landmarkData) { landmark inNavigationButton(destination: LandmarkDetail(landmark: landmark)) {LandmarkRow(landmark: landmark)}}.navigationBarTitle(Text("Landmarks"))}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {LandmarkList().previewDevice(PreviewDevice(rawValue: "iPhone SE"))}
}
8.2 在 list
预览中用设备名称数组作为数据,将 LandmarkList
嵌入到 ForEach
实例中。
ForEach
以与 list
相同的方式对集合进行操作,这样我们就可以在任何可以使用子视图的地方使用它,比如 stacks
, lists
,groups
等。当数据元素像这里使用的字符串一样是简单的值类型时,我们可以使用 \.self
作为标识符的 key path
。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {NavigationView {List(landmarkData) { landmark inNavigationButton(destination: LandmarkDetail(landmark: landmark)) {LandmarkRow(landmark: landmark)}}.navigationBarTitle(Text("Landmarks"))}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {ForEach(["iPhone SE", "iPhone XS Max"].identified(by: \.self)) { deviceName inLandmarkList().previewDevice(PreviewDevice(rawValue: deviceName))}}
}
8.3 使用 previewDisplayName(_:)
方法把设备名称作为 labels
添加到预览中。
LandmarkList.swift
import SwiftUIstruct LandmarkList: View {var body: some View {NavigationView {List(landmarkData) { landmark inNavigationButton(destination: LandmarkDetail(landmark: landmark)) {LandmarkRow(landmark: landmark)}}.navigationBarTitle(Text("Landmarks"))}}
}struct LandmarkList_Previews: PreviewProvider {static var previews: some View {ForEach(["iPhone SE", "iPhone XS Max"].identified(by: \.self)) { deviceName inLandmarkList().previewDevice(PreviewDevice(rawValue: deviceName)).previewDisplayName(deviceName)}}
}
8.4 我们可以在 canvas
中体验不同的设备,对比它们在渲染 view
时的差异。
SwiftUI 官方教程 (三)相关推荐
- SwiftUI官方教程解读
原文链接:https://www.jianshu.com/p/ecfdbea7a0ed SwiftUI简介 SwiftUI是wwdc2019发布的一个新的UI框架,通过声明和修改视图来布局UI和创建流 ...
- SwiftUI 官方教程(五)
SwiftUI官方教程(五) 5. 同时使用 UIKit 和 SwiftUI 至此,我们已准备好创建 map view 了,接下来使用 MapKit 中的 MKMapView 类来渲染地图. 在 Sw ...
- SwiftUI 官方教程 (十)
由于 API 变动,此文章部分内容已失效,最新完整中文教程及代码请查看 https://github.com/WillieWangWei/SwiftUI-Tutorials 微信技术群 SwiftUI ...
- SwiftUI 官方教程 (九)
由于 API 变动,此文章部分内容已失效,最新完整中文教程及代码请查看 https://github.com/WillieWangWei/SwiftUI-Tutorials 微信技术群 SwiftUI ...
- SwiftUI 官方教程 (一)
由于 API 变动,此文章部分内容已失效,最新完整中文教程及代码请查看 https://github.com/WillieWangWei/SwiftUI-Tutorials 微信技术群 SwiftUI ...
- SwiftUI 官方教程 (五)
由于 API 变动,此文章部分内容已失效,最新完整中文教程及代码请查看 https://github.com/WillieWangWei/SwiftUI-Tutorials 微信技术群 SwiftUI ...
- SwiftUI 官方教程 (四)
由于 API 变动,此文章部分内容已失效,最新完整中文教程及代码请查看 https://github.com/WillieWangWei/SwiftUI-Tutorials 微信技术群 SwiftUI ...
- [译] SwiftUI 官方教程 (五)
完整中文教程及代码请查看 github.com/WillieWangW- 绘制 Path 和 Shape 用户访问列表中的地标时应当获得徽章,为此,我们需要创建徽章.在本文中,我们将通过组合 path ...
- SCAPY官方教程三
一.scapy属性配置 Scapy 的交互式 shell 在终端会话中运行.发送数据包需要root权限,所以我们在sudo这里使用: $ sudo scapy -H Welcome to Scapy ...
最新文章
- servlet对mysql数据库的数据增删改
- C++中的c_str()函数用法
- 玩的起也要输的起 。。没什么,照样支持你。。
- linux安装sz rz_超级好用的文件传输命令rz与sz
- PCL中点云可视化:坐标字段、随机、单一颜色、法向量
- foreach 页面超时php,解决php运行超时的方法
- WINDOWS游戏编程大师技巧-常见编绎连接错误FAQ
- 人脸识别系统落地酒店 刷脸入住更安全
- 迅雷如何添加html文件夹,迅雷7上我的收藏怎么找
- caj转换成word的方法,每天免费使用
- 武汉Java程序员工资是否还会增长?工资为什么那么高?
- 不要再被误导了,64位X86 CPU是没有64位寻址能力的!
- mac 解决每次打开终端都要source的问题
- 云创青少年人工智能学院城市合伙人大会圆满收官!
- windows下、Linux下最好用markdown查看器:Typora(查看.md文件)
- gff3转mysql_PASA的使用
- PBOC规范研究之七 ----应用选择(转)
- html贪吃蛇怎么加蛇尾,贪吃蛇大作战蛇身的秘密 巧用蛇头蛇尾蛇身
- uniapp之sl-filter下拉筛选
- 软件测试-web端测试方法