v53hLf
SAP MM 组织结构 PDF

总揽

MM 模块的总揽图如下所示:
Dzdh7D

SAP MM 中的主数据

在 MM 模块中,需要维护和管理的主数据包括:

  • 物料主数据管理:将企业所有的物料数据集成在单一的物料数据库中,消除了数据冗余的问题,并实现各部门对数据的共享
  • 供应商主数据管理:将为企业进行供应的供应商的信息集成在供应商主数据库中予以统一管理和维护,并实现共享
  • 采购主数据管理:包含与采购活动相关的各类主数据:交易信息记录、货源清单、配额协议、框架协议、合同、订供货时间表、价格条件等

SAP MM 中的主要业务流程

MM 模块主要分为两部分业务功能:采购管理库存管理

  • 采购管理:重点关注物料或是服务的采购,货源的确定,采购订单到货与应付款的监控等;

    首先是要有需求,有了需求后,就要去找货源,有了货源后就要去选择供应商,通过一系统列的询、报价后,进行价格等方面的比较,选择适合的供应商,选择好供应商后就可以下单给供应商,然后监控这张订单和仓库的回货情况,等仓库全部收完货后,作发票校验。做完后财务就可以付款给供应商。

rgZLhE

  • 库存管理:重点关注物料的移动、物料数量与金额的管理以及库存盘点等。

SAP MM 最常用的业务流程事务代码

MM01 【创建物料主数据】
VD01 【创建客户主数据】
FD01 【维护客户财务数据】
VD51 【创建客户-物料信息记录】
FD32 【更改客户信贷管理】
MK01 【创建供应商主数据】
FK01 【维护供应商财务数据】
ME11 【创建采购信息记录】
ME01 【创建货源清单】
CS01 【创建物料清单BOM】

4VO1By

背景

最近在推进我的一款mac应用产品,需使用到menuBar,以前都使用swift appDelegate,现在使用新的MenuBarExtras验证下。

实践

在 macOS 13 Ventura 中,Apple 终于提供了一种实现MenuBarExtras SwiftUI 方式的方法。它首次在 WWDC 演讲“为您的 SwiftUI 应用程序带来多个窗口”中引入,并使在 Swift UI 中编写实用程序应用程序变得轻而易举。

现在MenuBarExtras可以直接将您的应用程序主体与您的Windows或WindowGroups.

1
2
3
4
5
6
7
8
9
@main 
struct UtilityApp : App {
var body: some Scene {
MenuBarExtra ( "UtilityApp" , systemImage: "hammer" ) { ... }

WindowGroup { ... }
}
}

MenuBarExtras大多数时候采用三个参数:

TitleKey: 标识它的字符串。很可能是您的应用程序的名称
Image:菜单栏中显示的符号。最好是,一个SFSymbol。通过这种方式,您可以开箱即用地获得浅色和深色主题行为。
Content: 这几乎可以是任何东西。不过,这取决于所选样式的呈现方式。
有两种预定义样式MenuBarExtras

菜单

两者中较容易的是.menu。它是默认样式,将 Menu Bar Extra 的内容呈现为标准菜单。
62CM3J

1
2
3
4
5
6
7
8
9
10
@main 
struct UtilityApp : App {
var body: some Scene {
MenuBarExtra ( "UtilityApp" , systemImage: "hammer" ) {
AppMenu ()
}

WindowGroup { ... }
}
}

当然你也可以显性的使用 .menuBarExtraStyle(.menu)

窗户

窗口样式允许您将任何类型的内容呈现到的MenuBarExtra弹出窗口中,并且可用于需要更多自定义控件(如滑块或开关)的应用程序。
oZjjne

为了启用窗口样式,将.menuBarExtraStyle修饰符添加到MenuBarExtra并将其设置为.window。

1
2
3
4
5
6
7
8
9
10
@main 
struct UtilityApp : App {
var body: some Scene {
MenuBarExtra ( "UtilityApp" , systemImage: "hammer" ) {
AppMenu ()
}.menuBarExtraStyle(.window)

WindowGroup { ... }
}
}

从 Dock 隐藏应用程序

如果您的应用仅包含一个MenuBarExtra并且不需要额外的窗口,您可以WindowGroup完全删除该窗口。在这些情况下,您很可能也不希望您的应用出现在 Dock 中。

这可以通过UIElement在您的应用程序的info.plist. 代理应用程序不会出现在用户的 Dock 中
OOXQjc

实践

我们来实践一下使用MVVM模式,并使用async await进行网络请求。

XfOeyo

Library.swift

1
2
3
4
5
import Foundation
struct Common: Codable,Hashable {
var act: String = ""
var prompt: String = ""
}

LibraryViewModel.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
@MainActor
class LibraryViewModel: ObservableObject {
@Published var commons: [Common] = []
@Published var commonSelected:Common = Common()
func getCommons() async {
guard let url = URL(string: "https://carteclip.com/api/v1/ama/prompts") else {
print("URL无效,请检查输入~")
return
}
do {
var request = URLRequest(url: url)
request.setValue("zh", forHTTPHeaderField: "prefer-language")
let (data, _) = try await URLSession.shared.data(
for: request
)
if let decodedResponse = try? JSONDecoder().decode([Common].self, from: data) {
self.commons = decodedResponse
}
} catch {
print("数据解码失败,请检查~")
}
}
}

LibraryView.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import SwiftUI
struct LibraryRow: View {
var common: Common
var body: some View {
VStack(alignment: .leading) {
Spacer()
Text(common.act)
.listRowSeparator(.hidden)
.font(.system(size: 18))
.frame(maxWidth: .infinity, alignment: .leading)

Spacer()
Text(common.prompt)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.system(size: 15))
.lineLimit(3)
Spacer()
}
}

}
struct LibraryView: View {
@State var presentSheetKey = false
@State var cur_common:Common = Common(act: "", prompt: "")
@ObservedObject var viewModel: LibraryViewModel
var body: some View {
NavigationStack {
List(viewModel.commons,id: \.self) { common in
Section{
Button {
viewModel.commonSelected = common
presentSheetKey = true
} label: {
LibraryRow(common:common).listRowSeparator(.hidden)
}.buttonStyle(.plain)

}
}.task {
await viewModel.getCommons()
}.refreshable {
await viewModel.getCommons()
}
}.sheet(isPresented: $presentSheetKey) {
VStack(alignment: .leading,spacing: 20) {
Text(viewModel.commonSelected.act).font(.system(size: 17)).foregroundColor(.primary)
Text(viewModel.commonSelected.prompt).font(.system(size: 15)).foregroundColor(.secondary).lineSpacing(8)
Spacer()
HStack(alignment: .bottom,spacing: 20){
Spacer()
Button {
presentSheetKey = false
} label: {
Text("取消").foregroundColor(.primary)
}
Button {

} label: {
Text("开始对话").foregroundColor(.primary)
}
}


}.padding()
.presentationDetents([.fraction(0.5),.medium])
.presentationBackground(.thinMaterial)
.presentationCornerRadius(20)
.presentationDragIndicator(.visible)
}

}
}
struct LibraryView_Previews: PreviewProvider {
static var previews: some View {
LibraryView( viewModel: LibraryViewModel())
}
}

LibraryDetailView.swift

1
2
3
4
5
6
7
8
9
10
11

import Foundation
import SwiftUI

struct LibraryDetailView: View {
var common:Common
@Environment(\.dismiss) var dismiss
var body: some View {
Text(common.prompt)
}
}

Pjqg7f

我们以暗黑模式的适配来实践EnvironmentObject

暗黑模式适配

定义AppSetting,设置 @Published var darkModeSettings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class AppSetting: ObservableObject {
@Published var darkModeSettings: Int = UserDefaults.standard.integer(forKey: "darkMode") {
didSet {
UserDefaults.standard.set(self.darkModeSettings, forKey: "darkMode")
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
switch self.darkModeSettings {
case 0:
window?.overrideUserInterfaceStyle = .unspecified
case 1:
window?.overrideUserInterfaceStyle = .light
case 2:
window?.overrideUserInterfaceStyle = .dark
default:
window?.overrideUserInterfaceStyle = .unspecified
}
}
}
}

传入 .environmentObject(AppSetting())

1
2
3
4
5
6
7
8
@main
struct tableViewApp: App {
var body: some Scene {
WindowGroup {
ContentView().environmentObject(AppSetting())
}
}
}

使用@EnvironmentObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

struct SettingsView: View {
@EnvironmentObject var appSettings: AppSetting

var body: some View {
NavigationStack {
List {
Section {
HStack{
Button {
appSettings.darkModeSettings = 0
} label: {
Text("跟随系统").foregroundColor(.primary)
}
Spacer()
if(appSettings.darkModeSettings == 0){
Image(systemName: "checkmark")
}
}
HStack{
Button {
appSettings.darkModeSettings = 1
} label: {
Text("白").foregroundColor(.primary)
}
Spacer()
if(appSettings.darkModeSettings == 1){
Image(systemName: "checkmark")
}
}
HStack{
Button {
appSettings.darkModeSettings = 2
} label: {
Text("暗黑").foregroundColor(.primary)
}
Spacer()
if(appSettings.darkModeSettings == 2){
Image(systemName: "checkmark")
}

}

} header: {
Text("主题设置")
.textCase(nil)
}
}
.listStyle(.insetGrouped)
.navigationTitle("设置")
}
}
}

相关链接阅读

SwiftUI: 全局状态管理

@StateObject 修饰的对象与 @ObservedObject 一样,都需要遵循 Observable 协议,功能也类似。区别在于,@StateObject 修饰的对象只会在所属的 View 中创建一次并在 View 的生命周期内存储相应的状态,而 @ObservedObject 修饰的对象会随着 View 的重绘生成新的对象,不会在 View 的生命周期内存储该对象的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import SwiftUI

class Counter: ObservableObject {
@Published var count: Int = 0
}
struct StateObjectDemo: View {
@State private var buttonTitle = "Tap me"
var body: some View {
ItemList()
}
}
struct ItemList: View {
@State private var items = ["hello", "world"]
var body: some View {
VStack {
Button("Append item to list") {
items.append("test")
}
List(items, id: \.self) { name in
Text(name)
}
CounterView1()
CounterView2()
}
}
}


struct CounterView1: View {
// StateObject示例
@StateObject var counter1 = Counter()
var body: some View {
VStack {
Text("StateObject count: \(counter1.count)")
Button("点击StateObject➕1") {
counter1.count += 1
}
}
}
}

struct CounterView2: View {
// ObservedObject示例
@ObservedObject var counter2 = Counter()
var body: some View {
VStack {
Text("ObservedObject count: \(counter2.count)")
Button("点击ObservedObject➕1") {
counter2.count += 1
}
}
}
}

struct StateObjectDemo_Previews: PreviewProvider {
static var previews: some View {
StateObjectDemo()
}
}


如上代码所示,当我们点击 CounterView1 和 CounterView2 中的按钮时,会给相应的 Counter 实例的 count 属性加 1,一旦我们点击 “Append item to list” 按钮,View 会执行重绘,这时 CounterView2 中的 count 会重置为 0,而 CounterView1 中使用 @StateObject 修饰的对象中的 count 仍然持有当前的数据状态。

那么我们应该在什么场景下分别使用这两个属性包装器呢?

在 View 的生命周期内,需要一直持有并存储对象的状态时,使用 @StateObject 修饰。基本上,绝大多数情况下的 viewModel 都会是这种情况。

只有在少数情况下,View 不需要一直持有该对象,该对象的状态会随着外界的条件改变而刷新自己时,我们才用到 @ObservedObject 修饰,比如上面的例子,如果我们要求点击 “Append item to list” 按钮时,count 就重置,就需要用 @ObservedObject 修饰了。

push && present

qquebh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

import SwiftUI

struct NavigationStackView: View {
@State var presentSheetKey = false
@State var presentFullScreenCoverKey = false
var body: some View {
NavigationStack {
List {
Section {
NavigationLink(destination: PushView(),label: {Text("I am Root. Tap for Push View -1")})

NavigationLink{
PushView()
} label: {
Text("I am Root. Tap for Push View -2")
}
NavigationLink("I am Root. Tap for Push View -3", destination: PushView())

NavigationLink("I am Root. Tap for Push View -4") {
PushView()
}
} header: {
Text("push")
}
Section {
Button {
self.presentSheetKey.toggle()
} label: {
Text("sheet").foregroundColor(.primary)
}

Button {
self.presentFullScreenCoverKey.toggle()
} label: {
Text("fullScreenCover").foregroundColor(.primary)
}
} header: {
Text("present")
}
}.navigationTitle("view跳转")
}
.sheet(isPresented: $presentSheetKey) {
//非全屏模式
PresentView()
}
.fullScreenCover(isPresented: $presentFullScreenCoverKey, content: {
//全屏模式
PresentView()
})
}
}

struct PushView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
VStack {
Image(systemName: "phone")
.resizable()
.frame(width: 80, height: 80)
Text("hello")
.font(.system(.title, design: .rounded))
.fontWeight(.black)
Spacer()
Button("Here is Detail Push View. Tap to go back."){
dismiss()
}
}
}
}

struct PresentView: View {
//用于退出该界面
//@Environment(\.presentationMode) var presentationMode
@Environment(\.dismiss) var dismiss
var body: some View {
VStack(alignment: .center, spacing: nil, content: {
Spacer()
HStack(content: {
Spacer()
})
Button("Here is Detail Present View. Tap to go back.") {
dismiss()
// self.presentationMode.wrappedValue.dismiss()
}
.font(.system(size: 20))
.foregroundColor(.red)
.background(Color.white)
Spacer()
})
.background(Color.gray)
.navigationBarTitle("presentView", displayMode: .inline)
}
}

struct NavigationStackView_Previews: PreviewProvider {
static var previews: some View {
NavigationStackView()
}
}


相关

SwiftUI:页面跳转和导航设置

E9R7p0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//
// ContentView.swift
// tableView
//
// Created by junyao on 2023/4/22.
//

import SwiftUI
import TabBar

struct ContentView: View {
enum TabItems: Int {
case editor, notes, share, settings
}
@State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
EditorView()
.badge(10)
.tabItem {
Label("对话", systemImage: "pencil.circle")
Text("Editor")
}.tag(TabItems.editor.rawValue)
.onTapGesture {
selectedTab = TabItems.editor.rawValue
}

NotesView()
.tabItem {
Label("指令库", systemImage: "note.text")
Text("Notes")
}.tag(TabItems.notes.rawValue)
.onTapGesture {
selectedTab = TabItems.notes.rawValue
}

SettingsView()
.tabItem {
Label("设置", systemImage: "gearshape")
Text("Settings")
}.tag(TabItems.settings.rawValue)
.onTapGesture {
selectedTab = TabItems.settings.rawValue
}
}.accentColor(.black).tint(.black)
.onAppear(perform: {
//修改未选择的项的颜色
UITabBar.appearance().unselectedItemTintColor = UIColor(Color.gray)
})
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

相关

SwiftUI——如何修改TabView的各种属性(包括tabItem的属性),例如颜色、位置、大小等

Stable Diffusion 模型资源

  1. Hugging Face
  2. Civitai

模型安装

Stable Diffusion Web Ui安装过程中会默认下载Stable Diffusion v1.5模型,名称为v1-5-pruned-emaonly。如果想用最新的Stable Diffusion v2.1,可以从Hugging Face上下载官方版本stabilityai/stable-diffusion-2-1。下载后将模型复制到models目录下的Stable-diffusion目录即可。完成后点击页面左上角的刷新按钮,即可在模型下拉列表中看到新加入的模型。

除了标准模型外,Stable Diffusion还有其他几种类型的模型,models目录下每一个子目录就是一种类型的模型,其中用的最多的是LoRA模型。

我们来实践几个模型

基础大模型

  • v1-5-pruned-emaonly.ckpt
    Stable Diffusion基础大模型,原则上所有其他大模型都在这个模型基础上再微调出来的

  • chilloutmix_NiPrunedFp32Fix.safetensors
    AI真人基础模型,一般搭配LoRA小模型

  • Anything、Waifu、novelai、Counterfeit
    二次元漫画型

LoRA示例

LoRA(Low-Rank Adaptation)模型是小型稳定扩散模型,可对标准模型进行微调。它通常比标准模型小10-100倍,这使得LoRA模型在文件大小和训练效果之间取得了很好平衡。LoRA无法单独使用,需要跟标准模型配合使用,这种组合使用方式也为Stable Diffusion带来了强大的灵活性。

下面我们使用 chilloutmix大模型 + LoRA的微调小模型koreanDollLikeness来试试

LoRA模型下载后需要放到Lora目录中,使用时在提示中加入LoRA语法,语法格式如下:

1
<lora:filename:multiplier>

filename是LoRA模型的文件名(不带文件后缀)

multiplier是LoRA 模型的权重,默认值为1,将其设置为 0 将禁用该模型。

Prompt
选择好模型后,我们开始设计prompt。首先我们引入LoRA

1
<lora:koreanDollLikeness:0.66>

然后定义生成图片的风格,我们希望超写实风,可以用如下关键词:

1
best quality, ultra high res, (photorealistic:1.4)

其中photorealistic我们赋予较高的权重1.4。

接着来定义图片的主体内容,这里我将希望图片中出现的元素都做个权重增强:

1
1girl, thighhighs, ((school uniform)),((pleated skirt)), ((black stockings)), (full body), (Kpop idol), (platinum blonde hair:1), ((puffy eyes))

最后,修饰一些表情、姿势的细节:

1
smiling, solo focus, looking at viewer, facing front

这样我们完整的promt是:

1
<lora:koreanDollLikeness:0.66>, best quality, ultra high res, (photorealistic:1.4), 1girl, thighhighs, ((school uniform)),((pleated skirt)), ((black stockings)), (full body), (Kpop idol), (platinum blonde hair:1), ((puffy eyes)), smiling, solo focus, looking at viewer, facing front

Negative prompt
我们还需要提供Negative prompt去除我们不想要的风格和元素:

1
paintings, sketches, (worst quality:2), (low quality:2), (normal quality:2), lowres, normal quality, ((monochrome)), ((grayscale)), skin spots, acnes, skin blemishes, age spot, glan

这里主要剔除了绘画风、简笔画、低质量、灰度图,同时去除雀斑、痤疮等皮肤瑕疵。

参数设置
为了让图片生成得更加真实自然,我们需要对参数做一些调整,需要调整的参数如下:

Sampler: DPM++ SDE Karras
Sample Steps: 28
CFG scale: 8
Size: 512×768

dddd44d

常见问题

Q1:SD:2.1安装报错

1
NansException: A tensor with all NaNs was produced in VAE. This could be because there's not enough precision to represent the picture. Try adding --no-half-vae commandline argument to fix this.

解决

1
Settings > Stable Diffusion > Enable option "Upcast cross attention layer to float32" (at Stable Diffusion localhost server).

简单文字列表

8oQgNf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//
// NotesView.swift
// tableView
//
// Created by junyao on 2023/4/22.
//

import SwiftUI

struct Message: Identifiable {
var id = UUID()
var title: String
var des: String
}

// 定义数组,存放数据
var Messages = [
Message(title: "充当Linux终端", des: "我想让你充当 Linux 终端。我将输入命令,您将回复终端应显示的内容。我希望您只在一个唯一的代码块内回复终端输出,而不是其他任何内容。不要写解释。除非我指示您这样做,否则不要键入命令。当我需要用英语告诉你一些事情时,我会把文字放在中括号内[就像这样]。我的第一个命令是 pwd"),
Message(title: "充当英翻中", des: "下面我让你来充当翻译家,你的目标是把任何语言翻译成中文,请翻译时不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。请翻译下面这句话:“how are you ?"),
Message(title: "担任面试官", des: "我想让你担任Android开发工程师面试官。我将成为候选人,您将向我询问Android开发工程师职位的面试问题。我希望你只作为面试官回答。不要一次写出所有的问题。我希望你只对我进行采访。问我问题,等待我的回答。不要写解释。像面试官一样一个一个问我,等我回答。我的第一句话是“面试官你好"),
Message(title: "充当旅游指南", des: "我想让你做一个旅游指南。我会把我的位置写给你,你会推荐一个靠近我的位置的地方。在某些情况下,我还会告诉您我将访问的地方类型。您还会向我推荐靠近我的第一个位置的类似类型的地方。我的第一个建议请求是“我在上海,我只想参观博物馆。"),
Message(title: "充当讲故事的人", des: "我想让你扮演讲故事的角色。您将想出引人入胜、富有想象力和吸引观众的有趣故事。它可以是童话故事、教育故事或任何其他类型的故事,有可能吸引人们的注意力和想象力。根据目标受众,您可以为讲故事环节选择特定的主题或主题,例如,如果是儿童,则可以谈论动物;如果是成年人,那么基于历史的故事可能会更好地吸引他们等等。我的第一个要求是“我需要一个关于毅力的有趣故事。"),
]

struct NotesView: View {
var body: some View {
List {
ForEach(Messages) { Message in
Section {
Text(Message.title)
.listRowSeparator(.hidden)
.font(.title3)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(1)
Text(Message.des)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(3)
.padding(1)
}.padding()
}.listRowInsets(EdgeInsets())
}
}
}

struct NotesView_Previews: PreviewProvider {
static var previews: some View {
NotesView()
}
}

列表操作 onDelete & onMove & ContextMenu & ActionSheets

完整实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import SwiftUI
struct Message: Identifiable {
var id = UUID()
var title: String
var last: String
}
// 定义数组,存放数据
var Messages = [
Message(title: "充当英翻中", last: "很好,让我们开始为想要减肥的人设计一个锻炼计划。首先,我们需要了解他们的目标和当前的健身水平。您可以问他们关于以下问题的信息:您的身高、体重和BMI(身体质量指数)是多少?您每周进行多少次有氧运动和力量训练?您每次进行运动的时间是多长?您的饮食习惯是什么?您是否有任何特殊的饮食限制?您的工作和日常活动水平是什么?一旦您了解了他"),
Message(title: "担任私人教练", last: ""),
Message(title: "随便聊聊", last: ""),
Message(title: "随便聊聊", last: ""),
Message(title: "随便聊聊", last: "好的,这是一个小学生的笑话:"),
]
struct DialogueView: View {
@State var messagesItems = Messages
@State var showActionSheet = false

var body: some View {
NavigationView{
List {
ForEach(messagesItems) { Message in
HStack {
Image("dialogue")
.resizable()
.renderingMode(.template)
.foregroundColor(.gray)
.frame(width: 40, height: 40)
VStack {
Text(Message.title)
.foregroundColor(.black)
.listRowSeparator(.hidden)
.font(.system(size: 17).weight(.light))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(1)
Text(Message.last)
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.system(size: 15))
.lineLimit(1)
.padding(1)
}
Image("drag")
.resizable()
.frame(width: 20, height: 20)
}.contextMenu {
Button(action: {
// 点击删除
// self.delete(item: Message)
// 点击打开ActionSheet弹窗
self.showActionSheet.toggle()
}) {
HStack {
Text("删除")
Image(systemName: "trash")
}
}
}
// ActionSheet弹窗
.actionSheet(isPresented: self.$showActionSheet) {
ActionSheet(
title: Text("你确定要删除此项吗?"),
message: nil,
buttons: [
.destructive(Text("删除"), action: {
//点击删除
self.delete(item: Message)
}),
.cancel(Text("取消"))
])
}

}
.onDelete(perform: deleteRow)
.onMove(perform: moveItem)
}
.navigationBarItems(leading:
HStack {
Image(systemName: "trash")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(.gray)
},trailing:
HStack {
Button("新对话") {
print("Specials tapped!")
}
}
)
}
}
// 滑动删除方法
func deleteRow(at offsets: IndexSet) {
messagesItems.remove(atOffsets: offsets)
}
// 拖动排序方法
func moveItem(from source: IndexSet, to destination: Int) {
messagesItems.move(fromOffsets: source, toOffset: destination)
}
//contextMenu 删除的方法
func delete(item Message: Message) {
if let index = self.messagesItems.firstIndex(where: { $0.id == Message.id }) {
self.messagesItems.remove(at: index)
}
}
}

struct DialogueView_Previews: PreviewProvider {
static var previews: some View {
DialogueView()
}
}

onDelete & onMove
48yxb4

1
2
3
4
5
6
7
8
9
10
11
.onDelete(perform: deleteRow)
.onMove(perform: moveItem)

// 滑动删除方法
func deleteRow(at offsets: IndexSet) {
messagesItems.remove(atOffsets: offsets)
}
// 拖动排序方法
func moveItem(from source: IndexSet, to destination: Int) {
messagesItems.move(fromOffsets: source, toOffset: destination)
}

ContextMenu
hLrB9B

1
2
3
4
5
6
7
8
9
10
11
.contextMenu {
    Button(action: {
        // 点击删除
self.delete(item: Message)
    }) {
        HStack {
            Text("删除")
            Image(systemName: "trash")
        }
    }
}
1
2
3
4
5
6
    //删除的方法
    func delete(item Message: Message) {
        if let index = self.messagesItems.firstIndex(where: { $0.id == Message.id }) {
            self.messagesItems.remove(at: index)
        }
    }

ActionSheets
vV7cEY

1
2
3
4
5
6
7
8
9
10
11
12
13
.contextMenu {
Button(action: {
// 点击删除
// self.delete(item: Message)
// 点击打开ActionSheet弹窗
self.showActionSheet.toggle()
}) {
HStack {
Text("删除")
Image(systemName: "trash")
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
.actionSheet(isPresented: self.$showActionSheet) {
ActionSheet(
title: Text("你确定要删除此项吗?"),
message: nil,
buttons: [
.destructive(Text("删除"), action: {
//点击删除
self.delete(item: Message)
}),
.cancel(Text("取消"))
])
}

在 SwiftUI 中,如果你要使用列表(List)或者集合视图(CollectionView),你需要使用 Identifiable 协议来确保每个元素都具有唯一的标识符。如果你的数据类型本身已经有了唯一标识符(比如一个唯一的 ID 字段),那么可以通过让该数据类型遵循 Identifiable 协议并实现 id 属性来实现。

1
2
3
4
struct Person: Identifiable {
let id: String
let name: String
}

然后,在你的列表或者集合视图中,你可以像这样使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct ContentView: View {
let people = [
Person(id: "1", name: "Alice"),
Person(id: "2", name: "Bob"),
Person(id: "3", name: "Charlie"),
]

var body: some View {
List(people) { person in
Text(person.name)
}
}
}

这里的 List 会自动使用 id 属性来确定每个元素的唯一性,所以你不需要手动指定标识符。
如果你的数据类型没有唯一标识符,你也可以使用一个自动生成的标识符。在这种情况下,你可以使用 id() 函数来为每个元素生成一个唯一的标识符。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Person {
let name: String
}

struct ContentView: View {
let people = [
Person(name: "Alice"),
Person(name: "Bob"),
Person(name: "Charlie"),
]

var body: some View {
List(people, id: \.self) { person in
Text(person.name)
}
}
}

这里的 id: .self 会告诉 SwiftUI 使用每个元素本身作为标识符。

0%