在使用可搜索修饰符时,我遇到了 SwiftUI 的 NavigationStack 问题。在视图之间导航时,一切都按预期工作,但如果我使用搜索栏过滤列表,然后点击过滤结果,我可以导航到下一个视图。然而,在随后的视图中,我的“设置并返回根”按钮(本应调用 popToRoot())不起作用。设置如下:
结构: RootView:包含包含项目 1-7 的列表。 ActivityView:包含可以使用可搜索修饰符过滤的活动列表。 设置视图:包含一个标记为“设置并返回根”的按钮,该按钮调用 popToRoot() 导航回根视图。
struct RootView: View {
@EnvironmentObject var navManager: NavigationStateManager
var body: some View {
NavigationStack(path: $navManager.selectionPath) {
List(1...7, id: \.self) { item in
Button("Element \(item)") {
// Navigate to ActivityView with an example string
navManager.selectionPath.append(NavigationTarget.activity)
}
}
.navigationTitle("Root View")
.navigationDestination(for: NavigationTarget.self) { destination in
switch destination {
case .activity:
ActivityView()
case .settings:
SettingsView()
}
}
}
}
}
struct ActivityView: View {
@EnvironmentObject var navManager: NavigationStateManager
let activities = ["Running", "Swimming", "Cycling", "Hiking", "Yoga", "Weightlifting", "Boxing"]
@State private var searchText = ""
var filteredActivities: [String] {
if searchText.isEmpty {
return activities
} else {
return activities.filter { $0.localizedCaseInsensitiveContains(searchText) }
}
}
var body: some View {
List {
ForEach(filteredActivities, id: \.self) { activity in
NavigationLink(
destination: SettingsView(), // Navigiere zur SettingsView
label: {
HStack {
Text(activity)
.padding()
Spacer()
}
}
)
}
}
.navigationTitle("Choose Activity")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search Activities")
}
}
struct SettingsView: View {
@EnvironmentObject var navManager: NavigationStateManager
var body: some View {
VStack {
Text("Settings")
.font(.largeTitle)
.padding()
Button("Set and Return to Root") {
// Pop to the root view when the button is pressed
navManager.popToRoot()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.navigationTitle("Settings")
}
}
// Define enum globally at the top
enum NavigationTarget {
case activity
case settings
}
class NavigationStateManager: ObservableObject {
@Published var selectionPath = NavigationPath()
func popToRoot() {
selectionPath = NavigationPath()
}
func popView() {
selectionPath.removeLast()
}
}
// RootView, ActivityView, SettingsView, and ContentView follow as described earlier...
当我在 ActivityView 中搜索并点击筛选结果时,我成功导航到 SettingsView。然而,在此视图中,按下“Set and Return to Root”按钮不会触发返回 RootView 的导航,即使 popToRoot() 正在被调用。
此问题仅在使用搜索栏和过滤结果时出现。如果我不使用搜索栏进行导航,该按钮将按预期工作。
为什么 popToRoot() 函数在搜索操作后失败,如何确保过滤列表后可以返回到根视图?
任何见解或建议将不胜感激!
问题出在您的 ActivityView 中,您“手动”导航,即不影响路径,该路径需要 NavigationTarget 的值/大小写(根据 RootView 中
.navigationDestination
的配置。
要修复此问题,请使用 NavigationTarget 的
destination
作为 NavigationLink,而不是设置 value
。所以你的 filteredActivities
循环应该是:
ForEach(filteredActivities, id: \.self) { activity in
NavigationLink(
value: NavigationTarget.settings, // <- Here, use value instead of destination
label: {
HStack {
Text(activity)
.padding()
Spacer()
}
}
)
}
这是完整的代码,其中包括必要的预览设置(对于 XCode 16,使用 @Previewable),这是您的原始代码所遗漏的。
import SwiftUI
struct RootView: View {
@EnvironmentObject var navManager: NavigationStateManager
var body: some View {
NavigationStack(path: $navManager.selectionPath) {
List(1...7, id: \.self) { item in
Button("Element \(item)") {
// Navigate to ActivityView with an example string
navManager.selectionPath.append(NavigationTarget.activity)
}
}
.navigationTitle("Root View")
.navigationDestination(for: NavigationTarget.self) { destination in
switch destination {
case .activity:
ActivityView()
case .settings:
ActivitySettingsView()
}
}
}
}
}
struct ActivityView: View {
@EnvironmentObject var navManager: NavigationStateManager
let activities = ["Running", "Swimming", "Cycling", "Hiking", "Yoga", "Weightlifting", "Boxing"]
@State private var searchText = ""
var filteredActivities: [String] {
if searchText.isEmpty {
return activities
} else {
return activities.filter { $0.localizedCaseInsensitiveContains(searchText) }
}
}
var body: some View {
List {
ForEach(filteredActivities, id: \.self) { activity in
NavigationLink(
value: NavigationTarget.settings, // <- Here, use value instead of destination
label: {
HStack {
Text(activity)
.padding()
Spacer()
}
}
)
}
}
.navigationTitle("Choose Activity")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search Activities")
}
}
struct ActivitySettingsView: View {
@EnvironmentObject var navManager: NavigationStateManager
var body: some View {
VStack {
Text("Settings")
.font(.largeTitle)
.padding()
Button("Set and Return to Root") {
// Pop to the root view when the button is pressed
navManager.popToRoot()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
// .cornerRadius(10) // <- Deprecated
.clipShape(RoundedRectangle(cornerRadius: 10)) // <- Use this instead
}
.navigationTitle("Settings")
}
}
// Define enum globally at the top
enum NavigationTarget {
case activity
case settings
}
class NavigationStateManager: ObservableObject {
@Published var selectionPath = NavigationPath()
func popToRoot() {
selectionPath = NavigationPath()
}
func popView() {
selectionPath.removeLast()
}
}
// RootView, ActivityView, SettingsView, and ContentView follow as described earlier...
#Preview {
@Previewable @StateObject var navManager: NavigationStateManager = NavigationStateManager()
RootView()
.environmentObject(navManager)
}
我注意到你仍在使用旧的 Observable 对象协议,包括 ObservableObject、EnvironmentObject 等。我不知道这是否是故意的,但你应该考虑迁移到新的 Observable 宏。
这是上面代码的一个版本,它使用了 Observable 宏:
import SwiftUI
import Observation
struct RootView: View {
@Environment(NavigationStateManager.self) private var navManager: NavigationStateManager
var body: some View {
@Bindable var navManager = navManager //get a binding to the Environment object
NavigationStack(path: $navManager.selectionPath) {
List(1...7, id: \.self) { item in
Button("Element \(item)") {
// Navigate to ActivityView with an example string
navManager.selectionPath.append(NavigationTarget.activity)
}
}
.navigationTitle("Root View")
.navigationDestination(for: NavigationTarget.self) { destination in
switch destination {
case .activity:
ActivityView()
case .settings:
ActivitySettingsView()
}
}
}
}
}
struct ActivityView: View {
@Environment(NavigationStateManager.self) private var navManager: NavigationStateManager
let activities = ["Running", "Swimming", "Cycling", "Hiking", "Yoga", "Weightlifting", "Boxing"]
@State private var searchText = ""
var filteredActivities: [String] {
if searchText.isEmpty {
return activities
} else {
return activities.filter { $0.localizedCaseInsensitiveContains(searchText) }
}
}
var body: some View {
List {
ForEach(filteredActivities, id: \.self) { activity in
NavigationLink(
value: NavigationTarget.settings, // <- Here, use value instead of destination
// destination: SettingsView(), // Navigiere zur SettingsView
label: {
HStack {
Text(activity)
.padding()
Spacer()
}
}
)
}
}
.navigationTitle("Choose Activity")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search Activities")
}
}
struct ActivitySettingsView: View {
@Environment(NavigationStateManager.self) private var navManager: NavigationStateManager
var body: some View {
VStack {
Text("Settings")
.font(.largeTitle)
.padding()
Button("Set and Return to Root") {
// Pop to the root view when the button is pressed
navManager.popToRoot()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
// .cornerRadius(10) // <- Deprecated
.clipShape(RoundedRectangle(cornerRadius: 10)) // <- Use this instead
}
.navigationTitle("Settings")
}
}
// Define enum globally at the top
enum NavigationTarget {
case activity
case settings
}
@Observable
class NavigationStateManager {
var selectionPath = NavigationPath()
func popToRoot() {
selectionPath = NavigationPath()
}
func popView() {
selectionPath.removeLast()
}
}
// RootView, ActivityView, SettingsView, and ContentView follow as described earlier...
#Preview {
@Previewable @State var navManager: NavigationStateManager = NavigationStateManager()
RootView()
.environment(navManager)
}
如果您计划使用 TabView 以及可能的多个导航堆栈(每个导航堆栈都有其路径),我在here提供了一个答案,它展示了如何进行操作,它允许控制特定选项卡/路径的 popToRoot。