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

如何在SwiftUI中实现不可编辑TextField的滚动功能?

实现跨平台SwiftUI可滚动、可选择但不可编辑的多行文本框

在SwiftUI中,原生TextField(包括多行模式)没有单独的「可编辑」属性——直接使用.disabled(true)会同时禁用滚动和文本选择功能,在macOS上还会让文本变灰,可读性下降。而UIKit/AppKit里的文本控件可以轻松实现「可滚动、可选择但不可编辑」的效果,下面提供两种跨平台的解决方案:

方案一:包装UIKit/AppKit原生文本控件(推荐)

通过UIViewRepresentable(iOS)和NSViewRepresentable(macOS)包装原生的UITextView和NSTextView,可以完美实现需求,且行为和原生控件一致:

import SwiftUI

// 跨平台可选择、可滚动、不可编辑的文本视图
struct SelectableScrollableTextView: View {
    let text: String
    
    var body: some View {
        Group {
#if os(iOS)
            UITextViewWrapper(text: text)
#elseif os(macOS)
            NSTextViewWrapper(text: text)
#endif
        }
        .frame(minWidth: 200, minHeight: 30)
        .border(.gray)
        .padding()
    }
}

// iOS平台的UITextView包装
struct UITextViewWrapper: UIViewRepresentable {
    let text: String
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.text = text
        textView.isEditable = false
        textView.isSelectable = true
        textView.isScrollEnabled = true
        textView.autocorrectionType = .no
        textView.textAlignment = .left
        textView.textContainerInset = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        if uiView.text != text {
            uiView.text = text
        }
    }
}

// macOS平台的NSTextView包装
struct NSTextViewWrapper: NSViewRepresentable {
    let text: String
    
    func makeNSView(context: Context) -> NSTextView {
        let textView = NSTextView()
        textView.string = text
        textView.isEditable = false
        textView.isSelectable = true
        textView.allowsUndo = false
        textView.automaticQuoteSubstitutionEnabled = false
        textView.textContainer?.lineFragmentPadding = 2
        textView.textContainerInset = NSSize(width: 2, height: 2)
        // 避免macOS上文本变灰
        textView.textColor = .labelColor
        return textView
    }
    
    func updateNSView(_ nsView: NSTextView, context: Context) {
        if nsView.string != text {
            nsView.string = text
        }
    }
}

// 使用示例
struct ContentView: View {
    let text: String = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Odio eu feugiat pretium nibh ipsum. Nunc consequat interdum varius sit. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Risus viverra adipiscing at in tellus. Tellus integer feugiat scelerisque varius morbi enim nunc faucibus a. At quis risus sed vulputate. Nulla facilisi etiam dignissim diam quis enim lobortis. Facilisi cras fermentum odio eu feugiat pretium nibh ipsum. Sed velit dignissim sodales ut eu sem integer vitae. Nam at lectus urna duis convallis convallis tellus id. Blandit turpis cursus in hac. Et sollicitudin ac orci phasellus egestas tellus rutrum tellus. Feugiat in ante metus dictum at tempor commodo ullamcorper a.

Sapien eget mi proin sed libero enim sed faucibus turpis. Eu sem integer vitae justo eget magna fermentum iaculis. Ut lectus arcu bibendum at varius vel pharetra vel. Nisl rhoncus mattis rhoncus urna. Sem integer vitae justo eget magna fermentum iaculis. Tortor consequat id porta nibh. Arcu bibendum at varius vel pharetra vel. Varius duis at consectetur lorem donec massa. Mi proin sed libero enim sed faucibus turpis in eu. Laoreet suspendisse interdum consectetur libero id faucibus. Aliquam ut porttitor leo a diam sollicitudin tempor. Morbi enim nunc faucibus a. Posuere sollicitudin aliquam ultrices sagittis orci a scelerisque purus. Feugiat vivamus at augue eget. Feugiat in ante metus dictum at tempor commodo. Eu nisl nunc mi ipsum. Habitasse platea dictumst quisque sagittis. Mattis pellentesque id nibh tortor id. Eu feugiat pretium nibh ipsum consequat nisl vel pretium. Libero enim sed faucibus turpis.
"""
    
    var body: some View {
        VStack {
            SelectableScrollableTextView(text: text)
        }
    }
}

#Preview {
    ContentView()
}

方案说明

  • 分别包装iOS的UITextView和macOS的NSTextView,设置isEditable = false、isSelectable = true,同时启用滚动
  • macOS平台手动设置文本颜色为.labelColor,避免禁用编辑后文本变灰
  • 保持了原生控件的所有滚动、选择行为,同时完全禁止编辑

方案二:SwiftUI原生TextField的折中方案

如果不想包装原生控件,可以通过拦截文本修改事件来模拟不可编辑,但需要注意:iOS上仍会弹出键盘(可以额外隐藏),macOS上行为也不够完美,仅作为备选:

struct ContentView: View {
    @State private var originalText: String = """
    你的长文本内容...
    """
    @State private var displayText: String = ""
    
    var body: some View {
        VStack {
            TextField("", text: $displayText, axis: .vertical)
                .disableAutocorrection(true)
                .lineLimit(20)
                .multilineTextAlignment(.leading)
                .padding(.all, 2)
                .frame(minWidth: 200, minHeight: 30)
                .padding()
                .border(.gray)
                .onAppear {
                    displayText = originalText
                }
                .onChange(of: displayText) { newValue in
                    // 强制恢复原文本,阻止编辑
                    if newValue != originalText {
                        displayText = originalText
                    }
                }
#if os(iOS)
                .onTapGesture {
                    // 隐藏键盘
                    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                }
#endif
        }
    }
}

方案说明

  • 通过onChange拦截文本修改,强制恢复原内容,实现不可编辑
  • iOS上需要手动隐藏键盘,否则点击后会弹出
  • 该方案存在局限性,比如用户仍能触发编辑状态,体验不如原生控件包装方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 04:17:33