有没有可能在右手边有一个索引的列表,就像下面SwiftUI中的例子一样?
cngwdvgl1#
我在SwiftUI中完成了此操作
// // Contacts.swift // TestCalendar // // Created by Christopher Riner on 9/11/20. // import SwiftUI struct Contact: Identifiable, Comparable { static func < (lhs: Contact, rhs: Contact) -> Bool { return (lhs.lastName, lhs.firstName) < (rhs.lastName, rhs.firstName) } var id = UUID() let firstName: String let lastName: String } let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] struct Contacts: View { @State private var searchText = "" var contacts = [Contact]() var body: some View { VStack { ScrollViewReader { scrollProxy in ZStack { List { SearchBar(searchText: $searchText) .padding(EdgeInsets(top: 0, leading: -20, bottom: 0, trailing: -20)) ForEach(alphabet, id: \.self) { letter in Section(header: Text(letter).id(letter)) { ForEach(contacts.filter({ (contact) -> Bool in contact.lastName.prefix(1) == letter })) { contact in HStack { Image(systemName: "person.circle.fill").font(.largeTitle).padding(.trailing, 5) Text(contact.firstName) Text(contact.lastName) } } } } } .navigationTitle("Contacts") .listStyle(PlainListStyle()) .resignKeyboardOnDragGesture() VStack { ForEach(alphabet, id: \.self) { letter in HStack { Spacer() Button(action: { print("letter = \(letter)") //need to figure out if there is a name in this section before I allow scrollto or it will crash if contacts.first(where: { $0.lastName.prefix(1) == letter }) != nil { withAnimation { scrollProxy.scrollTo(letter) } } }, label: { Text(letter) .font(.system(size: 12)) .padding(.trailing, 7) }) } } } } } } } init() { contacts.append(Contact(firstName: "Chris", lastName: "Ryan")) contacts.append(Contact(firstName: "Allyson", lastName: "Ryan")) contacts.append(Contact(firstName: "Jonathan", lastName: "Ryan")) contacts.append(Contact(firstName: "Brendan", lastName: "Ryaan")) contacts.append(Contact(firstName: "Jaxon", lastName: "Riner")) contacts.append(Contact(firstName: "Leif", lastName: "Adams")) contacts.append(Contact(firstName: "Frank", lastName: "Conors")) contacts.append(Contact(firstName: "Allyssa", lastName: "Bishop")) contacts.append(Contact(firstName: "Justin", lastName: "Bishop")) contacts.append(Contact(firstName: "Johnny", lastName: "Appleseed")) contacts.append(Contact(firstName: "George", lastName: "Washingotn")) contacts.append(Contact(firstName: "Abraham", lastName: "Lincoln")) contacts.append(Contact(firstName: "Steve", lastName: "Jobs")) contacts.append(Contact(firstName: "Steve", lastName: "Woz")) contacts.append(Contact(firstName: "Bill", lastName: "Gates")) contacts.append(Contact(firstName: "Donald", lastName: "Trump")) contacts.append(Contact(firstName: "Darth", lastName: "Vader")) contacts.append(Contact(firstName: "Clark", lastName: "Kent")) contacts.append(Contact(firstName: "Bruce", lastName: "Wayne")) contacts.append(Contact(firstName: "John", lastName: "Doe")) contacts.append(Contact(firstName: "Jane", lastName: "Doe")) contacts.sort() } } struct Contacts_Previews: PreviewProvider { static var previews: some View { Contacts() } }
y3bcpkx12#
看看这个tutorial by Federico Zanetello,它是一个100%的SwiftUI解决方案。
最终结果:
完整代码(作者:费德里科·萨内泰罗):
let database: [String: [String]] = [ "iPhone": [ "iPhone", "iPhone 3G", "iPhone 3GS", "iPhone 4", "iPhone 4S", "iPhone 5", "iPhone 5C", "iPhone 5S", "iPhone 6", "iPhone 6 Plus", "iPhone 6S", "iPhone 6S Plus", "iPhone SE", "iPhone 7", "iPhone 7 Plus", "iPhone 8", "iPhone 8 Plus", "iPhone X", "iPhone Xs", "iPhone Xs Max", "iPhone Xʀ", "iPhone 11", "iPhone 11 Pro", "iPhone 11 Pro Max", "iPhone SE 2" ], "iPad": [ "iPad", "iPad 2", "iPad 3", "iPad 4", "iPad 5", "iPad 6", "iPad 7", "iPad Air", "iPad Air 2", "iPad Air 3", "iPad Mini", "iPad Mini 2", "iPad Mini 3", "iPad Mini 4", "iPad Mini 5", "iPad Pro 9.7-inch", "iPad Pro 10.5-inch", "iPad Pro 11-inch", "iPad Pro 11-inch 2", "iPad Pro 12.9-inch", "iPad Pro 12.9-inch 2", "iPad Pro 12.9-inch 3", "iPad Pro 12.9-inch 4" ], "iPod": [ "iPod Touch", "iPod Touch 2", "iPod Touch 3", "iPod Touch 4", "iPod Touch 5", "iPod Touch 6" ], "Apple TV": [ "Apple TV 2", "Apple TV 3", "Apple TV 4", "Apple TV 4K" ], "Apple Watch": [ "Apple Watch", "Apple Watch Series 1", "Apple Watch Series 2", "Apple Watch Series 3", "Apple Watch Series 4", "Apple Watch Series 5" ], "HomePod": [ "HomePod" ] ] struct HeaderView: View { let title: String var body: some View { Text(title) .font(.title) .fontWeight(.bold) .padding() .frame(maxWidth: .infinity, alignment: .leading) } } struct RowView: View { let text: String var body: some View { Text(text) .padding() .frame(maxWidth: .infinity, alignment: .leading) } } struct ContentView: View { let devices: [String: [String]] = database var body: some View { ScrollViewReader { proxy in ScrollView { LazyVStack { devicesList } } .overlay(sectionIndexTitles(proxy: proxy)) } .navigationBarTitle("Apple Devices") } var devicesList: some View { ForEach(devices.sorted(by: { (lhs, rhs) -> Bool in lhs.key < rhs.key }), id: \.key) { categoryName, devicesArray in Section( header: HeaderView(title: categoryName) ) { ForEach(devicesArray, id: \.self) { name in RowView(text: name) } } } } func sectionIndexTitles(proxy: ScrollViewProxy) -> some View { SectionIndexTitles(proxy: proxy, titles: devices.keys.sorted()) .frame(maxWidth: .infinity, alignment: .trailing) .padding() } } struct SectionIndexTitles: View { let proxy: ScrollViewProxy let titles: [String] @GestureState private var dragLocation: CGPoint = .zero var body: some View { VStack { ForEach(titles, id: \.self) { title in SectionIndexTitle(image: sfSymbol(for: title)) .background(dragObserver(title: title)) } } .gesture( DragGesture(minimumDistance: 0, coordinateSpace: .global) .updating($dragLocation) { value, state, _ in state = value.location } ) } func dragObserver(title: String) -> some View { GeometryReader { geometry in dragObserver(geometry: geometry, title: title) } } func dragObserver(geometry: GeometryProxy, title: String) -> some View { if geometry.frame(in: .global).contains(dragLocation) { DispatchQueue.main.async { proxy.scrollTo(title, anchor: .center) } } return Rectangle().fill(Color.clear) } func sfSymbol(for deviceCategory: String) -> Image { let systemName: String switch deviceCategory { case "iPhone": systemName = "iphone" case "iPad": systemName = "ipad" case "iPod": systemName = "ipod" case "Apple TV": systemName = "appletv" case "Apple Watch": systemName = "applewatch" case "HomePod": systemName = "homepod" default: systemName = "xmark" } return Image(systemName: systemName) } } struct SectionIndexTitle: View { let image: Image var body: some View { RoundedRectangle(cornerRadius: 8, style: .continuous) .foregroundColor(Color.gray.opacity(0.1)) .frame(width: 40, height: 40) .overlay( image .foregroundColor(.blue) ) } }
roqulrg33#
我一直在寻找同一问题的解决方案,但目前我们唯一的选择是使用UITableView作为视图。
UITableView
import SwiftUI import UIKit struct TableView: UIViewRepresentable { func makeUIView(context: Context) -> UITableView { let tableView = UITableView(frame: .zero, style: .plain) tableView.delegate = context.coordinator tableView.dataSource = context.coordinator return tableView } func updateUIView(_ uiView: UITableView, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator() } final class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 2 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cellId = "cellIdentifier" let cell = tableView.dequeueReusableCell(withIdentifier: cellId) ?? UITableViewCell(style: .default, reuseIdentifier: cellId) cell.textLabel?.text = "\(indexPath)" return cell } func sectionIndexTitles(for tableView: UITableView) -> [String]? { ["a", "b"] } }
vulvrdjw4#
请参阅DirectX在本页提供的解决方案,并考虑投赞成票。这是正确答案。我采用了他的视图并创建了一个ViewModifier,您可以将其用于任何包含SwiftUI List with Sections(表视图)的视图。只需确保提供一个标题(节)列表,该列表与要添加索引的视图中的标题相对应。单击字母滚动到列表的该节。注意,我只提供了在调用视图修改器时实际上可以滚动到的索引。像任何视图修改器一样使用:
SimpleDemoView().modifier(VerticalIndex(indexableList: contacts))
下面是修改器的代码:
struct VerticalIndex: ViewModifier { let indexableList: [String] func body(content: Content) -> some View { var body: some View { ScrollViewReader { scrollProxy in ZStack { content VStack { ForEach(indexableList, id: \.self) { letter in HStack { Spacer() Button(action: { withAnimation { scrollProxy.scrollTo(letter) } }, label: { Text(letter) .font(.system(size: 12)) .padding(.trailing, 7) }) } } } } } } return body } }
下面是使用DirectX提供的示例时的外观:
为了完整起见,下面是重现显示的代码:
struct SimpleDemo_Previews: PreviewProvider { static var previews: some View { SimpleDemoView().modifier(VerticalIndex(indexableList: contacts)) } } struct SimpleDemoView: View { var body: some View { List { ForEach(alphabet, id: \.self) { letter in Section(header: Text(letter).id(letter)) { ForEach(contacts.filter({ (contact) -> Bool in contact.lastName.prefix(1) == letter })) { contact in HStack { Image(systemName: "person.circle.fill").font(.largeTitle).padding(.trailing, 5) Text(contact.firstName) Text(contact.lastName) } } } } } .navigationTitle("Contacts") .listStyle(PlainListStyle()) } }
以下是用于提供演示的示例数据(根据DirectX的解决方案修改):
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] //swiftlint:disable comma let contacts: [Contact] = { var contacts = [Contact]() contacts.append(Contact(firstName: "Chris", lastName: "Ryan")) contacts.append(Contact(firstName: "Allyson", lastName: "Ryan")) contacts.append(Contact(firstName: "Jonathan", lastName: "Ryan")) contacts.append(Contact(firstName: "Brendan", lastName: "Ryaan")) contacts.append(Contact(firstName: "Jaxon", lastName: "Riner")) contacts.append(Contact(firstName: "Leif", lastName: "Adams")) contacts.append(Contact(firstName: "Frank", lastName: "Conors")) contacts.append(Contact(firstName: "Allyssa", lastName: "Bishop")) contacts.append(Contact(firstName: "Justin", lastName: "Bishop")) contacts.append(Contact(firstName: "Johnny", lastName: "Appleseed")) contacts.append(Contact(firstName: "George", lastName: "Washingotn")) contacts.append(Contact(firstName: "Abraham", lastName: "Lincoln")) contacts.append(Contact(firstName: "Steve", lastName: "Jobs")) contacts.append(Contact(firstName: "Steve", lastName: "Woz")) contacts.append(Contact(firstName: "Bill", lastName: "Gates")) contacts.append(Contact(firstName: "Donald", lastName: "Trump")) contacts.append(Contact(firstName: "Darth", lastName: "Vader")) contacts.append(Contact(firstName: "Clark", lastName: "Kent")) contacts.append(Contact(firstName: "Bruce", lastName: "Wayne")) contacts.append(Contact(firstName: "John", lastName: "Doe")) contacts.append(Contact(firstName: "Jane", lastName: "Doe")) return contacts.sorted() }() let indexes = Array(Set(contacts.compactMap({ String($0.lastName.prefix(1)) }))).sorted()
ebdffaop5#
我对@Mozahler和@DirectX的代码做了一些修改,以优化结果。1.我不希望主列表包含没有内容的头,所以在实现中List {下面的行变为:
ForEach(indexes, id: \.self) { letter in
而不是
ForEach(alphabet, id: \.self) { letter in
1.为索引列设置背景和统一宽度可使其与任何背景相分离并统一结果:
Text(letter) .frame(width: 16) .foregroundColor(Constants.color.textColor) .background(Color.secondary.opacity(0.5)) .font(Constants.font.customFootnoteFont) .padding(.trailing, 7)
niknxzdl6#
我喜欢这个答案:所以如果你赞成这个,也给予他/她一个赞成票。)
import SwiftUI struct AlphabetSidebarView: View { var listView: AnyView var lookup: (String) -> (any Hashable)? let alphabet: [String] = { (65...90).map { String(UnicodeScalar($0)!) } }() var body: some View { ScrollViewReader { scrollProxy in ZStack { listView HStack(alignment: .center) { Spacer() VStack(alignment: .center) { ForEach(alphabet, id: \.self) { letter in Button(action: { if let found = lookup(letter) { withAnimation { scrollProxy.scrollTo(found, anchor: .top) } } }, label: { Text(letter) .foregroundColor(.label) .minimumScaleFactor(0.5) .font(.subheadline) .padding(.trailing, 4) }) } } } } } } }
像这样使用它:
AlphabetSidebarView(listView: AnyView(contactsListView)) { letter in contacts.first { $0.name.prefix(1) == letter } }
8ljdwjyq7#
如果需要符合UITableViewDataSource, UITableViewDelegate协议的类,则:
UITableViewDataSource, UITableViewDelegate
import SwiftUI struct SelectRegionView: View { var body: some View { TableWithIndexView(sectionItems: [["Alex", "Anna"], ["John"]], sectionTitles: ["A", "J"]) } } #if DEBUG struct SelectRegionView_Previews: PreviewProvider { static var previews: some View { SelectRegionView() } } #endif struct TableWithIndexView<T: CustomStringConvertible>: UIViewRepresentable { /// the items to show public var sectionItems = [[T]]() /// the section titles public var sectionTitles = [String]() func makeUIView(context: Context) -> UITableView { let tableView = UITableView(frame: .zero, style: .plain) let coordinator = context.coordinator coordinator.sectionTitles = sectionTitles coordinator.sectionItemCounts = sectionItems.map({$0.count}) // Create cell for given `indexPath` coordinator.createCell = { tableView, indexPath -> UITableViewCell in let cellId = "cellIdentifier" let cell = tableView.dequeueReusableCell(withIdentifier: cellId) ?? UITableViewCell(style: .default, reuseIdentifier: cellId) cell.textLabel?.text = "\(sectionItems[indexPath.section][indexPath.row])" return cell } tableView.delegate = coordinator tableView.dataSource = coordinator return tableView } func updateUIView(_ uiView: UITableView, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator() } final class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate { /// the items to show fileprivate var createCell: ((UITableView, IndexPath)->(UITableViewCell))? fileprivate var sectionTitles = [String]() fileprivate var sectionItemCounts = [Int]() /// Section titles func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionTitles[section] } /// Number of sections func numberOfSections(in tableView: UITableView) -> Int { return sectionTitles.count } /// Number of rows in a section func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { sectionItemCounts[section] } /// Cell for indexPath func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cellId = "cellIdentifier" return createCell?(tableView, indexPath) ?? UITableViewCell(style: .default, reuseIdentifier: cellId) } /// Section index title func sectionIndexTitles(for tableView: UITableView) -> [String]? { /// Get first letters return sectionTitles.map({ String($0.first!).lowercased() }) } } }
7条答案
按热度按时间cngwdvgl1#
我在SwiftUI中完成了此操作
y3bcpkx12#
看看这个tutorial by Federico Zanetello,它是一个100%的SwiftUI解决方案。
最终结果:
完整代码(作者:费德里科·萨内泰罗):
roqulrg33#
我一直在寻找同一问题的解决方案,但目前我们唯一的选择是使用
UITableView
作为视图。vulvrdjw4#
请参阅DirectX在本页提供的解决方案,并考虑投赞成票。这是正确答案。
我采用了他的视图并创建了一个ViewModifier,您可以将其用于任何包含SwiftUI List with Sections(表视图)的视图。
只需确保提供一个标题(节)列表,该列表与要添加索引的视图中的标题相对应。单击字母滚动到列表的该节。注意,我只提供了在调用视图修改器时实际上可以滚动到的索引。
像任何视图修改器一样使用:
下面是修改器的代码:
下面是使用DirectX提供的示例时的外观:
为了完整起见,下面是重现显示的代码:
以下是用于提供演示的示例数据(根据DirectX的解决方案修改):
ebdffaop5#
我对@Mozahler和@DirectX的代码做了一些修改,以优化结果。
1.我不希望主列表包含没有内容的头,所以在实现中List {下面的行变为:
而不是
1.为索引列设置背景和统一宽度可使其与任何背景相分离并统一结果:
niknxzdl6#
我喜欢这个答案:所以如果你赞成这个,也给予他/她一个赞成票。)
像这样使用它:
8ljdwjyq7#
如果需要符合
UITableViewDataSource, UITableViewDelegate
协议的类,则: