Last active
October 7, 2024 19:04
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public extension Collection { | |
subscript(safe index: Index) -> Element? { | |
indices.contains(index) ? self[index] : nil | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public struct SelectionTabsModel: Identifiable, Hashable { | |
public let id: String | |
public let title: String | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Description
Convenient and customizable way to navigate between sections (tabs) of information with matchedGeometryEffect.
Usage
How to use the
SmoothSelectionTabsView
component in a SwiftUI application: