Skip to content

Instantly share code, notes, and snippets.

@eldaroid
Last active October 7, 2024 19:04
Show Gist options
  • Save eldaroid/c526663811ff67794f729ed20ceb69ae to your computer and use it in GitHub Desktop.
Save eldaroid/c526663811ff67794f729ed20ceb69ae to your computer and use it in GitHub Desktop.
RU: Удобный и настраиваемый способ навигации между разделами (вкладками) информации с помощью эффекта matchedGeometryEffect. EN: Convenient and customizable way to navigate between sections (tabs) of information with matchedGeometryEffect
public extension Collection {
subscript(safe index: Index) -> Element? {
indices.contains(index) ? self[index] : nil
}
}
public struct SelectionTabsModel: Identifiable, Hashable {
public let id: String
public let title: String
}
public struct SmoothSelectionTabsView: View {
private let models: [SelectionTabsModel]
@Binding private var currentTab: Int
@Namespace private var namespace
public init(
models: [SelectionTabsModel],
selectedIndex: Binding<Int>
) {
self.models = models
self._currentTab = selectedIndex
}
public var body: some View {
ScrollViewReader { reader in
VStack(spacing: 0) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(models.indices, id: \.self) { index in
createUnderlineButton(currentTab: $currentTab, namespace: namespace, for: index)
.id(index)
}
}
}
.onChange(of: currentTab) { _, newTabIndex in
reader.scrollTo(newTabIndex, anchor: isLastTabIndex(newTabIndex) ? .trailing : .center)
}
Color.gray
.opacity(0.2)
.frame(height: 1)
}
.frame(maxWidth: .infinity)
}
}
private func createUnderlineButton(
currentTab: Binding<Int>,
namespace: Namespace.ID,
for index: Int
) -> some View {
VStack(spacing: 0) {
Text(models[safe: index]?.title ?? "")
.foregroundColor(currentTab.wrappedValue == index ? .primary : .secondary)
.padding(12)
if currentTab.wrappedValue == index {
Color.gray
.frame(height: 2)
.cornerRadius(10)
.matchedGeometryEffect(
id: "underline",
in: namespace,
properties: .frame
)
}
}
.animation(.spring(response: 0.8), value: currentTab.wrappedValue)
.onTapGesture {
self.currentTab = index
}
}
private func isLastTabIndex(_ index: Int) -> Bool {
return models.count - 1 == index
}
}
@eldaroid
Copy link
Author

eldaroid commented May 13, 2024

Description

Convenient and customizable way to navigate between sections (tabs) of information with matchedGeometryEffect.

SmoothSelectionTabsView

Usage

How to use the SmoothSelectionTabsView component in a SwiftUI application:

struct ContentView: View {
    let models = [
        SelectionTabsModel(id: UUID().uuidString, title: "I want first content"),
        SelectionTabsModel(id: UUID().uuidString, title: "Tab number 2"),
        SelectionTabsModel(id: UUID().uuidString, title: "Last but not least")
    ]
    @State var indexSelect = 0
    
    var body: some View {
        SmoothSelectionTabsView(
            models: models,
            selectedIndex: $indexSelect
        )
    }
}

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