SwiftUI TabView -连续点击后在子视图中运行代码



当用户多次点击相同的标签时,我试图在TabView中实现这种行为,例如在iOS AppStore应用程序中。第一次点击:切换到该视图,第二次点击:弹出到根,第三次点击:如果需要,滚动到顶部。

下面的代码可以很好地切换,并且每次分接都调用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)
}
)
}
}

我正在努力,是如何告诉OneTwo,它是第二次或第三次?(如何弹出和滚动不是问题)。

我看到过这个:TabView, tabItem:在选择或添加onTapGesture时运行代码,但它没有解释如何在其中一个视图中运行代码。

有什么建议吗?

您可以在数组中记录额外的点击(相同值)。数组计数为您提供了在同一个选项卡上点击的次数。

编辑:现在显示子视图结构

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")
}
}
}

虽然@ChrisR的答案确实回答了我的问题,但我无法弄清楚下一步,即根据SubView的点击次数弹出到根或滚动到顶部的逻辑。经过大量的阅读和反复试验,我最近看到了这篇文章:https://notificare.com/blog/2022/11/25/a-better-tabview-in-swiftui/

受到这篇文章的启发,但经过一些修改,我想出了下面的方法,这正是我想要的。

两个主要的变化是:

  1. idEmptyView作为List的第一行(但不可见)被proxy.scrollTo()用作锚。
  2. 我没有使用存储子视图导航路径的全局@StateObject var appState,而是将路径添加为单独的@State属性。这避免了Update NavigationAuthority bound path tried to update multiple times per frame.警告。

希望对大家有所帮助。

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)")
}
}
}
}

最新更新