[英]SwiftUI TabView - run code in subview after sequential taps
I am trying to implement the behavior in a TabView
when the user taps the same tab multiple times, such as in the iOS AppStore app.当用户多次点击同一个选项卡时,我试图在TabView
中实现该行为,例如在 iOS AppStore 应用程序中。 First tap: switch to that view, second tap: pop to root, third tap: scroll to the top if needed.第一次点击:切换到该视图,第二次点击:弹出到根,第三次点击:如果需要滚动到顶部。
The code below works fine for switching and didTap()
is called for every tap.下面的代码适用于切换,每次点击都会调用didTap()
。
import SwiftUI
enum Tab: String {
case one
case two
}
struct AppView: View {
@State private var activeTab = Tab.one
var body: some View {
TabView(selection: $activeTab.onChange(didTap)) {
One()
.tabItem {
Label("one", systemImage: "1.lane")
}
.tag(Tab.one)
Two()
.tabItem {
Label("two", systemImage: "2.lane")
}
.tag(Tab.two)
}
}
func didTap(to value: Tab) {
print(value) // this captures every tap
}
}
extension Binding {
func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
Binding(
get: { self.wrappedValue },
set: { newValue in
self.wrappedValue = newValue
handler(newValue)
}
)
}
}
What I am struggling with, is how to tell either One
or Two
that it was tapped for a second or third time?我正在苦苦挣扎的是,如何告诉One
或Two
它已被第二次或第三次点击? (How to pop and scroll is not the issue). (如何弹出和滚动不是问题)。
I have seen this: TabView, tabItem: running code on selection or adding an onTapGesture but it doesn't explain how to run code in one of the views.我看过这个: TabView, tabItem: running code on selection or adding an onTapGesture但它没有解释如何在其中一个视图中运行代码。
Any suggestions?有什么建议么?
You can record additional taps (of same value) in an array.您可以在数组中记录额外的抽头(具有相同的值)。 The array count gives you the number of taps on the same Tab.数组计数为您提供同一选项卡上的点击次数。
EDIT: now with explicit subview struct.编辑:现在有明确的子视图结构。
struct ContentView: View {
@State private var activeTab = Tab.one
@State private var tapState: [Tab] = [Tab.one] // because .one is default
var body: some View {
TabView(selection: $activeTab.onChange(didTap)) {
SubView(title: "One", tapCount: tapState.count)
.tabItem {
Label("one", systemImage: "1.lane")
}
.tag(Tab.one)
SubView(title: "Two", tapCount: tapState.count)
.tabItem {
Label("two", systemImage: "2.lane")
}
.tag(Tab.two)
}
}
func didTap(to value: Tab) {
print(value) // this captures every tap
if tapState.last == value {
tapState.append(value) // apped next tap if same value
print("tapped \(tapState.count) times")
} else {
tapState = [value] // reset tap state to new tab selection
}
}
}
struct SubView: View {
let title: String
let tapCount: Int
var body: some View {
VStack {
Text("Subview \(title)").font(.title)
Text("tapped \(tapCount) times")
}
}
}
Although the answer by @ChrisR below did answer my question, I couldn't figure out the next step, ie the logic when to pop or scroll based on the number of taps in a SubView
.虽然下面@ChrisR 的回答确实回答了我的问题,但我无法弄清楚下一步,即根据SubView
中的点击次数弹出或滚动的逻辑。 After lots of reading and trial and error, I recently came across this article: https://notificare.com/blog/2022/11/25/a-better-tabview-in-swiftui/经过大量阅读和反复试验,我最近看到了这篇文章: https://notificare.com/blog/2022/11/25/a-better-tabview-in-swiftui/
Inspired by this article, but with some modifications, I came up with the following which does exactly what I was looking for.受这篇文章的启发,但经过一些修改,我想出了以下内容,它完全符合我的要求。
The two main changes are:两个主要变化是:
EmptyView
with an id
is added as the first row in the List
to be used by proxy.scrollTo()
.带有id
的EmptyView
添加为List
中的第一行,供proxy.scrollTo()
使用。@StateObject var appState
that stores the navigation paths for the subviews, I added the paths as separate @State
properties.我没有使用存储子视图导航路径的全局@StateObject var appState
,而是将路径添加为单独的@State
属性。 This avoids the Update NavigationAuthority bound path tried to update multiple times per frame.
这避免了Update NavigationAuthority bound path tried to update multiple times per frame.
warning.警告。Hopefully this is helpful for someone.希望这对某人有帮助。
enum Tab: String {
case one
case two
}
struct ContentView: View {
@State var selectedTab = Tab.one
@State var oneNavigationPath = NavigationPath()
@State var twoNavigationPath = NavigationPath()
var body: some View {
ScrollViewReader { proxy in
TabView(selection: tabViewSelectionBinding(proxy: proxy)) {
SubView(title: "One", path: $oneNavigationPath)
.tabItem {
Label("one", systemImage: "1.lane")
}
.tag(Tab.one)
SubView(title: "Two", path: $twoNavigationPath)
.tabItem {
Label("two", systemImage: "2.lane")
}
.tag(Tab.two)
}
}
}
private func tabViewSelectionBinding(proxy: ScrollViewProxy) -> Binding<Tab> {
Binding<Tab>(
get: { selectedTab },
set: { newValue in
if selectedTab == newValue {
switch selectedTab {
case .one:
if oneNavigationPath.isEmpty {
withAnimation {
proxy.scrollTo(Tab.one, anchor: .bottom)
}
} else {
withAnimation {
oneNavigationPath = NavigationPath()
}
}
case .two:
if twoNavigationPath.isEmpty {
withAnimation {
proxy.scrollTo(Tab.two, anchor: .bottom)
}
} else {
withAnimation {
twoNavigationPath = NavigationPath()
}
}
}
}
selectedTab = newValue
}
)
}
}
struct SubView: View {
let title: String
let items = Array(1 ... 100)
@Binding var path: NavigationPath
var body: some View {
NavigationStack(path: $path) {
List {
EmptyView()
.id(Tab(rawValue: title.lowercased()))
ForEach(items, id: \.self) { item in
NavigationLink(value: item) {
Text("Item \(item)")
}
}
}
.navigationTitle(title)
.navigationDestination(for: Int.self) { item in
Text("Item \(item)")
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.