diff --git a/docs/swiftui.html b/docs/swiftui.html new file mode 100644 index 00000000..a970a64e --- /dev/null +++ b/docs/swiftui.html @@ -0,0 +1,953 @@ +
+ + + +该备忘单提供了使用 SwiftUI 的标签的一些示例等
+SwiftUI 提供用于声明应用程序用户界面的视图、控件和布局结构
+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)
+ }
+ }
+ }
+ }
+}
+
+要在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)")
+}
+
+显示与环境相关的图像的视图。
+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
+将图像用作背景
+Text("Hello World")
+ .font(.largeTitle)
+ .background(
+ Image("hello_world")
+ .resizable()
+ .frame(width: 100, height: 100)
+ )
+
+以垂直线排列其子项的视图
+VStack (alignment: .center, spacing: 20){
+ Text("Hello")
+ Divider()
+ Text("World")
+}
+
+创建静态可滚动列表。文档 - VStack
+将其子级排列在一条水平线上的视图。
+创建静态可滚动列表
+HStack (alignment: .center, spacing: 20){
+ Text("Hello")
+ Divider()
+ Text("World")
+}
+
+文档 - HStack
+iOS 14
一种视图,将其子级排列在垂直增长的线中,仅在需要时创建项。
ScrollView {
+ LazyVStack(alignment: .leading) {
+ ForEach(1...100, id: \.self) {
+ Text("Row \($0)")
+ }
+ }
+}
+
+文档 - LazyVStack
+将子项排列在水平增长的线中的视图,仅在需要时创建项。
+ScrollView(.horizontal) {
+ LazyHStack(alignment: .center, spacing: 20) {
+ ForEach(1...100, id: \.self) {
+ Text("Column \($0)")
+ }
+ }
+}
+
+文档 - LazyHStack
+覆盖其子项的视图,使子项在两个轴上对齐。
+ZStack {
+ Text("Hello")
+ .padding(10)
+ .background(Color.red)
+ .opacity(0.8)
+ Text("World")
+ .padding(20)
+ .background(Color.red)
+ .offset(x: 0, y: 40)
+}
+
+文档 - ZStack
+容器视图,将其子视图排列在垂直增长的网格中,仅在需要时创建项目。
+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
+在打开和关闭状态之间切换的控件。
+@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
+