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

macOS下Sheet中SwiftUI TextField光标位置异常问题求助

解决macOS SwiftUI Sheet中TextField光标自动跳转到末尾的问题

问题描述

在macOS系统的SwiftUI框架中,当Sheet弹窗内的TextField绑定父视图的@State变量时,会出现异常:输入文本后将光标移至文本中间,进行输入或删除操作时,光标会自动跳转到文本末尾。仅在此场景下触发该问题,以下是可复现的代码示例:

import SwiftUI

@main
struct MyApp: App {
    @State private var isSheetPresented = false
    @State private var sheetText = ""

    var body: some Scene {
        WindowGroup {
            VStack {
                Button("Open Sheet") {
                    isSheetPresented = true
                }
                .padding()
            }
            .sheet(isPresented: $isSheetPresented) {
                SheetView(sheetText: $sheetText)
            }
        }
    }
}

struct SheetView: View {
    @Binding var sheetText: String

    var body: some View {
        VStack {
            TextField("Enter text", text: $sheetText)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
        }
    }
}

即使不使用子视图,直接在Sheet修饰器内写TextField绑定父视图状态,问题依然存在:

import SwiftUI

@main
struct MyApp: App {
    @State private var isSheetPresented = false
    @State private var sheetText = ""

    var body: some Scene {
        WindowGroup {
            VStack {
                Button("Open Sheet") {
                    isSheetPresented = true
                }
                .padding()
            }
            .sheet(isPresented: $isSheetPresented) {
                VStack {
                    TextField("Enter text", text: $sheetText)
                        .padding()
                }
            }
        }
    }
}

原因分析

问题根源在于:文本状态存储在父视图中,每次文本变化会触发父视图的重计算,导致Sheet弹窗内的TextField被重新初始化,光标位置因此丢失并重置到文本末尾。

可行解决方案

方案1:将状态移至Sheet内部视图

把文本状态从父视图转移到Sheet的子视图中,这样文本变化只会触发子视图的更新,不会影响父视图重渲染,光标位置就能正常保留。

如果需要将Sheet内的文本传回父视图,可以通过闭包回调实现:

import SwiftUI

@main
struct MyApp: App {
    @State private var isSheetPresented = false

    var body: some Scene {
        WindowGroup {
            VStack {
                Button("Open Sheet") {
                    isSheetPresented = true
                }
                .padding()
            }
            .sheet(isPresented: $isSheetPresented) {
                SheetView { inputText in
                    // 在这里处理从Sheet返回的文本
                    print("Sheet输入的文本:\(inputText)")
                }
            }
        }
    }
}

struct SheetView: View {
    @State private var sheetText = ""
    let onDismiss: (String) -> Void

    var body: some View {
        VStack {
            TextField("Enter text", text: $sheetText)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
        }
        .onDisappear {
            onDismiss(sheetText)
        }
    }
}

方案2:使用ObservableObject封装状态

将文本状态封装到ObservableObject类中,父视图通过@StateObject持有该对象(macOS 11及以上版本支持)。这种方式下,文本变化只会通知绑定的TextField更新,不会触发父视图的重渲染,光标位置得以保留,同时父视图还能实时获取文本内容。

import SwiftUI

class SheetTextViewModel: ObservableObject {
    @Published var sheetText = ""
}

@main
struct MyApp: App {
    @State private var isSheetPresented = false
    @StateObject private var viewModel = SheetTextViewModel()

    var body: some Scene {
        WindowGroup {
            VStack {
                Button("Open Sheet") {
                    isSheetPresented = true
                }
                .padding()
                // 父视图可以实时显示Sheet内的文本
                Text("当前输入:\(viewModel.sheetText)")
            }
            .sheet(isPresented: $isSheetPresented) {
                SheetView(viewModel: viewModel)
            }
        }
    }
}

struct SheetView: View {
    @ObservedObject var viewModel: SheetTextViewModel

    var body: some View {
        VStack {
            TextField("Enter text", text: $viewModel.sheetText)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 14:23:23