Skip to content

Instantly share code, notes, and snippets.

@zntfdr
Created July 23, 2020 14:32
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zntfdr/f2cb9f8f5edff371784288f27756cef3 to your computer and use it in GitHub Desktop.
Save zntfdr/f2cb9f8f5edff371784288f27756cef3 to your computer and use it in GitHub Desktop.
// Original article here: https://www.fivestars.blog/code/section-title-index-swiftui.html
import 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)
}
}
struct RowView: View {
let text: String
var body: some View {
Text(text)
}
}
struct ContentView: View {
let devices: [String: [String]] = database
var body: some View {
ScrollViewReader { proxy in
List {
devicesList
}
.listStyle(InsetGroupedListStyle())
.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
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)
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ContentView()
}
}
}
@st-small
Copy link

st-small commented Jul 8, 2022

If we have data in dictionary where a key is a String value all will be works well, but if I will use custom model it doesn't work.

Your example was [String : [String]] but I want use for example [CustomSectionModel : [String]], where CustomSectionModel has 2 properties (id: UUID and title: String). Is it possible to update example from your article to support my model?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment