该备忘单提供了使用 SwiftUI 的标签的一些示例等
SwiftUI 提供用于声明应用程序用户界面的视图、控件和布局结构
- SwiftUI Document (apple.com)
- SwiftUI 2.0 备忘清单 (swiftui-example)
- Swift 备忘清单 (jaywcjlove.github.io)
import SwiftUI
struct AlbumDetail: View {
var album: Album
var body: some View {
List(album.songs) { song in
HStack {
Image(album.cover)
VStack(alignment: .leading) {
Text(song.title)
}
}
}
}
}
- Text & Label (UILabel)
- Image (UIImageView)
- TextField / SecureField (UITextField)
- TextEditor (UITextView)
- Toggle (UISwitch)
- Slider (UISlider)
- Button/ Link (UIButton)
- List (UITableView)
- LazyVGrid / LazyHGrid (UICollectionView)
- NavigationView (UINavigationController)
- TabView (UITabBarController)
- Alert (UIAlertController 带有样式 .alert)
- ActionSheet (UIAlertController 带有样式 .actionSheet)
- HStack / LazyHStack (UIStackView 带水平轴)
- VStack / LazyVStack (UIStackView 带垂直轴)
- Picker (UISegmentedControl)
- Stepper (UIStepper)
- DatePicker (UIDatePicker)
- Text (NSAttributedString)无等效项)
- Map (MapKit)
- ProgressView (UIProgressView)
- Shape / Rectangle / Circle
要在UI中显示文本,只需编写:
Text("Hello World")
添加样式
Text("Hello World")
.font(.largeTitle)
.foregroundColor(Color.green)
.lineSpacing(50)
.lineLimit(nil)
.padding()
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
var now = Date()
var body: some View {
Text("Task due date: \(now, formatter: Self.dateFormatter)")
}
可以使用以下代码行在文本旁边设置图标。
Label("SwiftUI CheatSheet", systemImage: "up.icloud")
文档 - Label
可以设置URL,单击后将重定向到浏览器。
Link("Click me", destination: URL(string: "your_url")!)
文档 - Label
显示与环境相关的图像的视图。
Image("foo") // 图像名称是foo
我们可以使用新的 SF Symbols
Image(systemName: "clock.fill")
您可以向系统图标集添加样式以匹配您使用的字体
Image(systemName: "cloud.heavyrain.fill")
.foregroundColor(.red)
.font(.title)
Image(systemName: "clock")
.foregroundColor(.red)
.font(Font.system(.largeTitle).bold())
为图像添加样式
Image("foo")
.resizable() // 调整大小以便填充所有可用空间
.aspectRatio(contentMode: .fit)
文档 - Image
创建矩形的步骤
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 200)
创建圆的步骤
Circle()
.fill(Color.blue)
.frame(width: 50, height: 50)
文档 - Image
显示任务完成进度的视图。
@State private var progress = 0.5
VStack {
ProgressView(value: progress)
Button("More", action: { progress += 0.05 })
}
通过应用 CircularProgressViewStyle
,可以将其用作 UIActivityIndicatorView
。
ProgressView(value: progress)
.progressViewStyle(CircularProgressViewStyle())
文档 - ProgressView
显示指定区域的地图
import MapKit
@State var region = MKCoordinateRegion(center: .init(latitude: 37.334722, longitude: -122.008889), latitudinalMeters: 300, longitudinalMeters: 300)
Map(coordinateRegion: $region)
您可以通过指定 interactionModes
(使用[]
禁用所有交互)来控制地图的交互。
struct PinItem: Identifiable {
let id = UUID()
let coordinate: CLLocationCoordinate2D
}
Map(coordinateRegion: $region,
interactionModes: [],
showsUserLocation: true,
userTrackingMode: nil,
annotationItems: [PinItem(coordinate: .init(latitude: 37.334722, longitude: -122.008889))]) { item in
MapMarker(coordinate: item.coordinate)
}
文档 - Map
VStack
是 垂直
堆栈布局,用于将子视图垂直排列。默认将子视图从上到下排列
VStack (alignment: .center, spacing: 20){
Text("Hello")
Divider()
Text("World")
}
文档 - VStack
HStack
是 水平
堆栈布局,用于将子视图水平排列。默认将子视图从左到右排列
HStack (alignment: .center, spacing: 20){
Text("Hello")
Divider()
Text("World")
}
文档 - HStack
ZStack
是 层叠
堆栈布局,用于将子视图重叠在一起。按照添加的顺序从下到上排列子视图,即先添加的视图会在下面,后添加的视图会覆盖在上面
ZStack {
Text("Hello")
Text("World")
}
文档 - ZStack
iOS 14.0
之后新增的视图,仅在需要时才会创建和渲染
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(1...100, id: \.self) {
Text("Row \($0)")
}
}
}
- 懒加载:只有当子视图进入可视区域时,才会被创建和渲染
- 自适应:子视图的宽高可以自适应
- 性能优化:适用于大量子视图或动态内容的场景
- 文档 - LazyVStack
- 文档 - LazyHStack
容器视图,将其子视图排列在垂直
增长的网格中,仅在需要时创建项目
var columns: [GridItem] =
Array(
repeating: .init(.fixed(20)), count: 5
)
ScrollView {
LazyVGrid(columns: columns) {
ForEach((0...100), id: \.self) {
Text("\($0)").background(Color.pink)
}
}
}
文档 - LazyVGrid
容器视图,将其子视图排列在水平
增长的网格中,仅在需要时创建项目
var rows: [GridItem] =
Array(
repeating: .init(.fixed(20)), count: 2
)
ScrollView(.horizontal) {
LazyHGrid(rows: rows, alignment: .top) {
ForEach((0...100), id: \.self) {
Text("\($0)").background(Color.pink)
}
}
}
文档 - LazyHGrid
沿其包含的堆栈布局的主轴或如果不包含在堆栈中的两个轴上扩展的灵活空间。
HStack {
Image(systemName: "clock")
Spacer()
Text("Time")
}
文档 - Spacer
可用于分隔其他内容的视觉元素。
HStack {
Image(systemName: "clock")
Divider()
Text("Time")
}.fixedSize()
文档 - Divider
将图像用作背景
Text("Hello World")
.font(.largeTitle)
.background(
Image("hello_world")
.resizable()
.frame(width: 100, height: 100)
)
在打开和关闭状态之间切换的控件。
@State var isShowing = true // toggle state
Toggle(isOn: $isShowing) {
Text("Hello World")
}
如果您的 Toggle
的标签只有 Text
,则可以使用此更简单的签名进行初始化。
Toggle("Hello World", isOn: $isShowing)
文档 - Toggle
在触发时执行操作的控件。
Button(
action: {
print("did tap")
},
label: { Text("Click Me") }
)
如果 Button
的标签仅为 Text
,则可以使用此更简单的签名进行初始化。
Button("Click Me") {
print("did tap")
}
您可以通过此按钮了解一下
Button(action: {
// 退出应用
NSApplication.shared.terminate(self)
}, label: {
Image(systemName: "clock")
Text("Click Me")
Text("Subtitle")
})
.foregroundColor(Color.white)
.padding()
.background(Color.blue)
.cornerRadius(5)
文档 - Button
显示可编辑文本界面的控件。
@State var name: String = "John"
var body: some View {
TextField("Name's placeholder", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
取消编辑框焦点样式。
extension NSTextField { // << workaround !!!
open override var focusRingType: NSFocusRingType {
get { .none }
set { }
}
}
如何居中放置 TextField
的文本
struct ContentView: View {
@State var text: String = "TextField Text"
var body: some View {
TextField("Placeholder Text", text: $text)
.padding(.all, 20)
.multilineTextAlignment(.center)
}
}
文档 - TextField
用户安全地输入私人文本的控件。
@State var password: String = "1234"
var body: some View {
SecureField($password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
文档 - SecureField
可以显示和编辑长格式文本的视图。
@State private var fullText: String = "这是一些可编辑的文本..."
var body: some View {
TextEditor(text: $fullText)
}
设置 TextEditor
背景颜色
extension NSTextView {
open override var frame: CGRect {
didSet {
backgroundColor = .clear
// drawsBackground = true
}
}
}
struct DetailContent: View {
@State private var profileText: String = "输入您的简历"
var body: some View {
VSplitView(){
TextEditor(text: $profileText)
.background(Color.red)
}
}
}
文档 - TextEditor
日期选择器(DatePicker)的样式也会根据其祖先而改变。 在 Form
或 List
下,它显示为单个列表行,您可以点击以展开到日期选择器(就像日历应用程序一样)。
@State var selectedDate = Date()
var dateClosedRange: ClosedRange<Date> {
let min = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
let max = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
return min...max
}
NavigationView {
Form {
Section {
DatePicker(
selection: $selectedDate,
in: dateClosedRange,
displayedComponents: .date,
label: { Text("Due Date") }
)
}
}
}
在表格和列表的外部,它显示为普通的轮式拾取器
@State var selectedDate = Date()
var dateClosedRange: ClosedRange<Date> {
let min = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
let max = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
return min...max
}
DatePicker(selection: $selectedDate, in: dateClosedRange,
displayedComponents: [.hourAndMinute, .date],
label: { Text("Due Date") }
)
如果 DatePicker
的标签仅是纯文本,则可以使用此更简单的签名进行初始化。
DatePicker("Due Date", selection: $selectedDate, in: dateClosedRange,
displayedComponents: [.hourAndMinute, .date])
可以使用 ClosedRange
,PartialRangeThrough
和 PartialRangeFrom
来设置 minimumDate
和 maximumDate
。
DatePicker("Minimum Date", selection: $selectedDate,
in: Date()...,
displayedComponents: [.date])
DatePicker("Maximum Date", selection: $selectedDate,
in: ...Date(),
displayedComponents: [.date])
文档 - DatePicker
用于从值的有界线性范围中选择一个值的控件。
@State var progress: Float = 0
Slider(value: $progress,
from: 0.0,
through: 100.0,
by: 5.0)
滑块缺少 minimumValueImage
和 maximumValueImage
,但是我们可以通过 HStack
轻松地复制它
@State var progress: Float = 0
HStack {
Image(systemName: "sun.min")
Slider(value: $progress,
from: 0.0,
through: 100.0,
by: 5.0)
Image(systemName: "sun.max.fill")
}.padding()
文档 - Slider
用于从一组互斥值中进行选择的控件。
选择器样式的更改基于其祖先,在 Form
或 List
下,它显示为单个列表行,您可以点击以进入一个显示所有可能选项的新屏幕。
NavigationView {
Form {
Section {
Picker(selection: $selection,
label: Text("Picker Name"),
content: {
Text("Value 1").tag(0)
Text("Value 2").tag(1)
Text("Value 3").tag(2)
Text("Value 4").tag(3)
})
}
}
}
您可以使用 .pickerStyle(WheelPickerStyle())
覆盖样式。
@State var mapChoioce = 0
var settings = ["Map", "Transit", "Satellite"]
Picker("Options", selection: $mapChoioce) {
ForEach(0 ..< settings.count) { index in
Text(self.settings[index])
.tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
在 SwiftUI
中,UISegmentedControl
只是 Picker
的另一种样式。分段控制(SegmentedControl)在 iOS 13
中也焕然一新。文档 - Picker
用于执行语义递增和递减操作的控件。
@State var quantity: Int = 0
Stepper(
value: $quantity,
in: 0...10,
label: { Text("Quantity \(quantity)")}
)
如果 Stepper
的标签只有 Text
,则可以使用此更简单的签名进行初始化。
Stepper(
"Quantity \(quantity)",
value: $quantity,
in: 0...10
)
如果要完全控制,他们可以提供裸机步进器,您可以在其中管理自己的数据源。
@State var quantity: Int = 0
Stepper(onIncrement: {
self.quantity += 1
}, onDecrement: {
self.quantity -= 1
}, label: { Text("Quantity \(quantity)") })
如果您还为带有 step
的初始化程序的每个步骤指定了一个值的数量。
Stepper(
value: $quantity, in: 0...10, step: 2
) {
Text("Quantity \(quantity)")
}
文档 - Stepper
对于单次敲击
Text("Tap me!").onTapGesture {
print("Tapped!")
}
用于双击
Text("Tap me!").onTapGesture(count: 2) {
print("Tapped!")
}
手势如轻敲手势、长按手势、拖拉手势
Text("Tap")
.gesture(
TapGesture()
.onEnded { _ in
// do something
}
)
Text("Drag Me")
.gesture(
DragGesture(minimumDistance: 50)
.onEnded { _ in
// do something
}
)
Text("Long Press")
.gesture(
LongPressGesture(minimumDuration: 2)
.onEnded { _ in
// do something
}
)
onChange 是一个新的视图修改器,可用于所有 SwiftUI 视图。它允许您侦听状态更改并相应地对视图执行操作
TextEditor(text: $currentText)
.onChange(of: clearText) { value in
if clearText{
currentText = ""
}
}
一个容器,用于显示排列在单列中的数据行。创建静态可滚动列表
List {
Text("Hello world")
Text("Hello world")
Text("Hello world")
}
let names = ["John", "Apple", "Seed"]
List(names) { name in
Text(name)
}
添加 Section
List {
Section(header: Text("UIKit"), footer: Text("We will miss you")) {
Text("UITableView")
}
Section(header: Text("SwiftUI"), footer: Text("A lot to learn")) {
Text("List")
}
}
List {
Text("Hello world")
Image(systemName: "clock")
}
添加 .listStyle(GroupedListStyle())
List {
Section(header: Text("UIKit"),
footer: Text("我们会想念你的")) {
Text("UITableView")
}
Section(header: Text("SwiftUI"),
footer: Text("要学的东西很多")) {
Text("List")
}
}.listStyle(GroupedListStyle())
要使其插入分组(.insetGrouped
),请添加 .listStyle(GroupedListStyle())
并强制使用常规水平尺寸类 .environment(\.horizontalSizeClass, .regular)
。
List {
Section(header: Text("UIKit"), footer: Text("We will miss you")) {
Text("UITableView")
}
Section(header: Text("SwiftUI"), footer: Text("A lot to learn")) {
Text("List")
}
}.listStyle(GroupedListStyle())
.environment(\.horizontalSizeClass, .regular)
插图分组已添加到
iOS 13.2
中的SwiftUI
在 iOS 14
中,我们为此设置了专用样式。
.listStyle(InsetGroupedListStyle())
文档 - List
滚动视图。
ScrollView(alwaysBounceVertical: true) {
Image("foo")
Text("Hello World")
}
文档 - ScrollView
NavigationView
或多或少类似于 UINavigationController
,它处理视图之间的导航,显示标题,将导航栏放在顶部。
NavigationView {
Text("Hello")
.navigationBarTitle(Text("World"), displayMode: .inline)
}
大标题使用 .large
将条形图项添加到导航视图
NavigationView {
Text("Hello")
.navigationBarTitle(Text("World"), displayMode: .inline)
.navigationBarItems(
trailing:
Button(
action: { print("Going to Setting") },
label: { Text("Setting") }
)
)
}
按下时触发导航演示的按钮。这是 pushViewController
的替代品
NavigationView {
NavigationLink(destination:
Text("Detail")
.navigationBarTitle(Text("Detail"))
) {
Text("Push")
}.navigationBarTitle(Text("Master"))
}
或者通过将组目标添加到自己的视图 DetailView
中,使其更具可读性
NavigationView {
NavigationLink(destination: DetailView()) {
Text("Push")
}.navigationBarTitle(Text("Master"))
}
Group 创建多个视图作为一个视图,同时也避免了 Stack 的10视图最大限制
VStack {
Group {
Text("Hello")
Text("Hello")
Text("Hello")
}
Group {
Text("Hello")
Text("Hello")
}
}
一个视图,允许使用可交互的用户界面元素在多个子视图之间进行切换。
TabView {
Text("First View")
.font(.title)
.tabItem({ Text("First") })
.tag(0)
Text("Second View")
.font(.title)
.tabItem({ Text("Second") })
.tag(1)
}
图像和文本在一起。 您可以在此处使用 SF Symbol
。
TabView {
Text("First View")
.font(.title)
.tabItem({
Image(systemName: "circle")
Text("First")
})
.tag(0)
Text("Second View")
.font(.title)
.tabItem(VStack {
Image("second")
Text("Second")
})
.tag(1)
}
或者您可以省略 VStack
TabView {
Text("First View")
.font(.title)
.tabItem({
Image(systemName: "circle")
Text("First")
})
.tag(0)
Text("Second View")
.font(.title)
.tabItem({
Image("second")
Text("Second")
})
.tag(1)
}
用于对用于数据输入的控件(例如在设置或检查器中)进行分组的容器。
NavigationView {
Form {
Section {
Text("Plain Text")
Stepper(value: $quantity, in: 0...10, label: { Text("Quantity") })
}
Section {
DatePicker($date, label: { Text("Due Date") })
Picker(selection: $selection, label:
Text("Picker Name")
, content: {
Text("Value 1").tag(0)
Text("Value 2").tag(1)
Text("Value 3").tag(2)
Text("Value 4").tag(3)
})
}
}
}
您几乎可以在此表单中放入任何内容,它将为表单呈现适当的样式。文档 - Form
Modal 过渡。我们可以显示基于布尔的 Modal。
@State var isModal: Bool = false
var modal: some View {
Text("Modal")
}
Button("Modal") {
self.isModal = true
}.sheet(isPresented: $isModal, content: {
self.modal
})
文档 - Sheet
警报演示的容器。我们可以根据布尔值显示Alert。
@State var isError: Bool = false
Button("Alert") {
self.isError = true
}.alert(isPresented: $isError, content: {
Alert(title: Text("Error"),
message: Text("Error Reason"),
dismissButton: .default(Text("OK"))
)
})
@State var error: AlertError?
var body: some View {
Button("Alert Error") {
self.error = AlertError(reason: "Reason")
}.alert(item: $error, content: { error in
alert(reason: error.reason)
})
}
func alert(reason: String) -> Alert {
Alert(title: Text("Error"),
message: Text(reason),
dismissButton: .default(Text("OK"))
)
}
struct AlertError: Identifiable {
var id: String {
return reason
}
let reason: String
}
文档 - Alert
操作表演示文稿的存储类型。我们可以显示基于布尔值的 ActionSheet
@State var isSheet: Bool = false
var actionSheet: ActionSheet {
ActionSheet(title: Text("Action"),
message: Text("Description"),
buttons: [
.default(Text("OK"), action: {
}),
.destructive(Text("Delete"), action: {
})
]
)
}
Button("Action Sheet") {
self.isSheet = true
}.actionSheet(isPresented: $isSheet,
content: {
self.actionSheet
})
@State var sheetDetail: SheetDetail?
var body: some View {
Button("Action Sheet") {
self.sheetDetail = ModSheetDetail(body: "Detail")
}.actionSheet(item: $sheetDetail, content: { detail in
self.sheet(detail: detail.body)
})
}
func sheet(detail: String) -> ActionSheet {
ActionSheet(title: Text("Action"),
message: Text(detail),
buttons: [
.default(Text("OK"), action: {
}),
.destructive(Text("Delete"), action: {
})
]
)
}
struct SheetDetail: Identifiable {
var id: String {
return body
}
let body: String
}
文档 - ActionSheet
import SwiftData
// 通过@Model宏来定义模型schema
// 支持基础值类型String、Int、CGFloat等
// 支持复杂类型Struct、Enum、Codable、集合等
@Model
class Person {
var id: String
var name: String
var age: Int
init(name: String, age: Int) {
self.id = UUID().uuidString
self.name = name
self.age = age
}
}
@Model
class Person {
// @Attribute(.unique)为id添加唯一约束
@Attribute(.unique) var id: String
var name: String
var age: Int
init(name: String, age: Int) {
self.id = UUID().uuidString
self.name = name
self.age = age
}
}
@Model
class Person {
@Attribute(.unique)
var id: String
var name: String
var age: Int
// @Relationship(deleteRule: .cascade)
// 使得Person在数据库里被删除时
// 删除掉所有关联的students
@Relationship(deleteRule: .cascade)
var students: [Student]? = []
init(name: String, age: Int) {
self.id = UUID().uuidString
self.name = name
self.age = age
}
}
@Model
class Person {
@Attribute(.unique)
var id: String
var name: String
// @Transient表示不要持久化这个属性
// 需要提供一个默认值
@Transient
var age: Int = 0
init(name: String) {
self.id = UUID().uuidString
self.name = name
}
}
struct ContentView: View {
// Query 可以高效地查询大型数据集,并自定义返回内容的方式,如排序、过滤
@Query(sort: \.age, order: .reverse) var persons: [Person]
@Environment(\.modelContext) var modelContext
var body: some View {
NavigationStack() {
List {
ForEach(trips) { trip in
// ...
}
}
}
}
}
// 用 Schema 进行初始化
let container = try ModelContainer(for: Person.self)
// 用配置(ModelConfiguration)初始化
let container = try ModelContainer(
for: Person.self,
configurations: ModelConfiguration(url: URL("path"))
)
// 通过View 和 Scene 的修饰器来快速关联一个 ModelContainer
struct SwiftDataDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Person.self)
}
}
// 在配置Model Container完成后
// 通过Environment 来访问到 modelContext
struct ContextView : View {
@Environment(\.modelContext)
private var context
}
// 或者直接获取共享的主Actor context
let context = container.mainContext
// 或者直接初始化一个新的Context
let context = ModelContext(container)
let person = Person(name: "Lily", age: 10)
// Insert a new person
context.insert(person)
// Delete an existing person
context.delete(person)
// Manually save changes to the context
try context.save()
let personPredicate = #Predicate<Person> {
$0.name == "Lily" &&
$0.age == 10
}
let descriptor = FetchDescriptor<Person>(predicate: personPredicate)
let persons = try? context.fetch(descriptor)
- SwiftUI 2.0 Cheat Sheet (github.com)
- SwiftUI 2.0 备忘清单 (swiftui-example)
- Swift 备忘清单 (jaywcjlove.github.io)