我正在使用 macOS 14 和 Xcode 15,并且是 Swift/SwiftUI 的新手。
过去几天我一直在尝试实现一个选择器,它允许以与 Finder 相同的方式选择多个项目,即用户可以取消、选择一个项目,或者使用 Shift 键选择两个或多个连续的项目。
在我尝试使用“onChange”之前,它有点有效,文本显示的数据有效,但没有更新,并且选择器不允许我选择多个项目。
我不知道如何实现 onChange,因为它最近更新了。我的网络搜索仅返回 IOS 的代码或旧代码。
我在尝试“onChange”修改时遇到了几个我不明白的错误。无论我如何表达我的问题,zzzcode.ai 都不会生成可编译的代码。
请注意,当显示 URL 时,我仅显示最后一个组件。
我想首先解决“onChange”问题,因为它可能会修复一些错误。
import SwiftUI
extension URL: Identifiable {
public var id: Self { self }
}
struct PickTransectView: View {
@Binding var datList: [URL] // local file URLs w/.dat
@Binding var selectedURLs: [URL]? // subset of datList
// only display last URL component in this view
var body: some View {
VStack {
Text("Selected Transects:")
if let selectedURLs = selectedURLs { // if nil take from datList
if(selectedURLs.count > 1) {
Text("\(selectedURLs.first!.lastPathComponent) - \(selectedURLs.last!.lastPathComponent)")
} else {
Text("\(selectedURLs.last!.lastPathComponent)")
}
} else if let firstURL = datList.first, let lastURL = datList.last {
Text("\(firstURL.lastPathComponent) - \(lastURL.lastPathComponent)")
}
// expected return 1 or several contiguous URLs
Picker(selection: $selectedURLs, label: Text("Detailed List")) {
ForEach(datList, id: \.self) { url in
Text(url.lastPathComponent)
}
}
.pickerStyle(MenuPickerStyle())
.onChange(of: selectedURLs) { _ in // it fails here !
// Update Text on change of selectedURLs
if selectedURLs.count > 1 { // first & last URLs in selection
Text("\(selectedURLs.first!.lastPathComponent) - \(selectedURLs.last!.lastPathComponent)")
} else if let firstURL = selectedURLs.first {
Text("\(firstURL.lastPathComponent)")
}
}
}
}
}
当用户点击 TransectPickerView 中的按钮时,会出现一个弹出表来完成这项工作。
结构 TransectPickerView: 视图 { @Binding var selectedURLs:[URL] @Binding var datList: [URL] // 带有 .dat 的本地文件 URL
@State private var isPresentingSelection: Bool = false
var body: some View {
VStack {
Button(action: {
isPresentingSelection = true
}) {
Text("Select Transects")
}
.buttonStyle(ButtonStyleControls())
.padding()
} // POPUP
.sheet(isPresented: $isPresentingSelection) {
FileURLSelectionSheetView(
selectedURLs: $selectedURLs,
datList: $datList)
}
}
}
下面的代码显示本地文件 URL 数组的最后一个组成部分,并允许用户选择(和取消选择)文件。如果选择了第二个文件,则会构建两个选定文件之间(包括这两个文件)的文件数组。按钮还允许用户:选择所有文件、接受关闭视图的选择或关闭视图而不保存。
结构 FileURLSelectionSheetView: 查看 { @Binding var selectedURLs: [URL] // 待填充 @Binding var datList: [URL] // 带.dat 的本地文件 URL @Environment(.presentationMode) var 演示模式 // 当地的 @State var urlIndexSet = Set() // 消失 @State var firstURL:URL? @State var showSelectAll = true
var body: some View {
VStack {
Text(" Select Transects ")
.font(.title)
.padding()
ScrollView {
ForEach(datList.indices, id: \.self) { index in // present all URLs
let url = datList[index] // user selection
Text("\(url.lastPathComponent)")
.background(urlIndexSet.contains(index) ? Color.yellow.opacity(0.4) : Color.clear)
.onTapGesture { // tap logic tree starts here ...
if urlIndexSet.isEmpty {
urlIndexSet.insert(index) // first endpoint
} else if urlIndexSet.count == 1 { // endpoint
if urlIndexSet.contains(index) { // edit
urlIndexSet.removeAll()
} else { // 2nd end point, create list
if index > urlIndexSet.first! { // ascending
for item in urlIndexSet.first!...index {
urlIndexSet.insert(item)
}
} else { // descending
for item in index...urlIndexSet.first!{
urlIndexSet.insert(item)
}
}
}
} // onTapGesture
} // 1st ForEach
} // ScrollView
.frame(maxHeight: .infinity)
Button(action: {
if showSelectAll {
for item in 0..<datList.count {
urlIndexSet.insert(item)
}
showSelectAll = false
} else {
urlIndexSet.removeAll()
showSelectAll = true
}
}) {
if showSelectAll {
Text("Select All")
.padding(.top, 10)
} else {
Text("Reset All")
.padding(.top, 10)
}
}
.buttonStyle(ButtonStyleControls())
HStack() {
Button(action: { // This appears to present correctly
selectedURLs.removeAll() //
presentationMode.wrappedValue.dismiss()
}) {
Text("Cancel")
}
.buttonStyle(ButtonStyleControls())
Spacer()
// ============= populaate selectedURLs ===========
Button(action: {
selectedURLs.removeAll()
let indexList = urlIndexSet.sorted()
for item in indexList
{
selectedURLs.append(datList[item])
}
presentationMode.wrappedValue.dismiss()
}) {
Text("Accept")
}
.buttonStyle(ButtonStyleControls())
.disabled(urlIndexSet.isEmpty)
.opacity(urlIndexSet.isEmpty ? 0.5 : 1.0) // Mute if disabled
} // HStack
.padding()
Divider()
Text("\(urlIndexSet.count) selected")
.font(.title2)
} // VStack
.frame(maxWidth: .infinity, maxHeight: .infinity) // Adjust the height of the VStack
}
} // end popup view struct
}
我已成功测试了最多 28 个 URL。如果它超出显示器的高度,我希望它能够滚动。