You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NavigationStack跳转后FocusState失效问题求助

解决SwiftUI中NavigationStack跳转返回后TextField无法自动获取FocusState的问题

问题描述

我有一个TitleList视图,用户可通过.swipeAction将NavigationLink替换为TextField来编辑行标题。当前存在的问题是:当正在编辑标题时,若用户点击其他行的NavigationLink并通过根视图的NavigationStack跳转至新视图,返回根视图后再次尝试编辑该行,尽管已将其FocusState属性设为true,TextField仍无法自动激活(需手动点击才弹出键盘)。

原代码

import SwiftUI

struct HomeView: View {
    @State private var titles: [String] = ["Title 1", "Title 2", "Title 3"]

    var body: some View {
        NavigationStack {
            VStack {
                List {
                    TitleList(titles: $titles)
                }
            }
        }
    }
}

struct TitleList: View {
    @Binding var titles: [String]
    
    @State private var renamingTitleIndex: Int?
    @State private var renamingTextFieldUserInput: String = ""
    
    @FocusState private var renamingTextFieldIsFocused: Bool
    
    var body: some View {
        ForEach(Array(titles.enumerated()), id: \.offset) { index, title in
            if renamingTitleIndex != index {
                NavigationLink(destination: Text("Test")) {
                    Text(titles[index])
                }
                .swipeActions(allowsFullSwipe: false) {
                    Button {
                        renamingTextFieldIsFocused = true
                        renamingTitleIndex = index
                        renamingTextFieldUserInput = ""
                    } label: {
                        Label("Rename", systemImage: "pencil")
                    }
                }
            } else {
                VStack {
                    TextField("", text: $renamingTextFieldUserInput)
                        .focused($renamingTextFieldIsFocused)
                        .onSubmit {
                            if !renamingTextFieldUserInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                                titles[index] = renamingTextFieldUserInput
                            }
                        }
                        .onDisappear {
                            renamingTextFieldIsFocused = false
                        }
                        .onChange(of: renamingTextFieldIsFocused) { isFocused in
                            if !isFocused {
                                renamingTitleIndex = nil
                                renamingTextFieldUserInput = ""
                            }
                        }
                }
            }
        }
    }
}

问题根源

跳转返回后焦点设置失效的核心原因是:当从子视图返回时,SwiftUI需要重新构建编辑状态的TextField视图,而原代码中先设置renamingTextFieldIsFocused = true再切换renamingTitleIndex,此时TextField还未完成渲染,焦点设置无法绑定到实际的视图元素上。另外,onDisappear中的无条件焦点重置逻辑,会在页面跳转时干扰后续的焦点恢复。

解决方案

通过调整状态更新顺序、优化焦点重置逻辑即可解决问题,修改后的代码如下:

import SwiftUI

struct HomeView: View {
    @State private var titles: [String] = ["Title 1", "Title 2", "Title 3"]

    var body: some View {
        NavigationStack {
            VStack {
                List {
                    TitleList(titles: $titles)
                }
            }
        }
    }
}

struct TitleList: View {
    @Binding var titles: [String]
    
    @State private var renamingTitleIndex: Int?
    @State private var renamingTextFieldUserInput: String = ""
    
    @FocusState private var renamingTextFieldIsFocused: Bool
    
    var body: some View {
        ForEach(Array(titles.enumerated()), id: \.offset) { index, title in
            if renamingTitleIndex != index {
                NavigationLink(destination: Text("Test")) {
                    Text(titles[index])
                }
                .swipeActions(allowsFullSwipe: false) {
                    Button {
                        // 先切换视图状态,确保TextField开始渲染
                        renamingTitleIndex = index
                        // 预填充原标题,提升编辑体验
                        renamingTextFieldUserInput = titles[index]
                        // 延迟设置焦点,等待TextField完成视图构建
                        DispatchQueue.main.async {
                            renamingTextFieldIsFocused = true
                        }
                    } label: {
                        Label("Rename", systemImage: "pencil")
                    }
                }
            } else {
                VStack {
                    TextField("", text: $renamingTextFieldUserInput)
                        .focused($renamingTextFieldIsFocused)
                        .onSubmit {
                            let trimmedInput = renamingTextFieldUserInput.trimmingCharacters(in: .whitespacesAndNewlines)
                            if !trimmedInput.isEmpty {
                                titles[index] = trimmedInput
                            }
                            // 提交后主动重置所有状态
                            renamingTitleIndex = nil
                            renamingTextFieldUserInput = ""
                            renamingTextFieldIsFocused = false
                        }
                        .onChange(of: renamingTextFieldIsFocused) { isFocused in
                            if !isFocused {
                                // 仅当当前行处于编辑状态时重置,避免跳转时被动触发
                                if renamingTitleIndex == index {
                                    renamingTitleIndex = nil
                                    renamingTextFieldUserInput = ""
                                }
                            }
                        }
                }
            }
        }
    }
}

关键修改点

  1. 调整状态更新顺序:先设置renamingTitleIndex让TextField视图进入渲染流程,再通过DispatchQueue.main.async延迟设置焦点,确保焦点绑定到已渲染完成的TextField上。
  2. 优化预填充逻辑:点击重命名时自动填入原标题,无需用户重新输入。
  3. 移除不必要的onDisappear逻辑:避免页面跳转时被动触发焦点重置,仅在主动失去焦点且当前行处于编辑状态时才重置状态。
  4. 提交逻辑闭环:提交编辑后主动重置所有相关状态,确保视图回到正常状态。

内容的提问来源于stack exchange,提问作者Ser Pounce

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 04:25:56