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

Xcode12.3环境下SwiftUI如何禁用TextField文字高亮且保留粘贴功能

兼容低版本的OTP输入框禁用文本高亮方案

要实现禁用双击/长按文本高亮、保留粘贴能力、适配Xcode12.3和iOS13+的要求,可通过UIKit封装自定义TextField实现,完整修改后代码如下:

import SwiftUI

@available(iOS 13.0, *)
class OTPViewModel: ObservableObject {
    
    var numberOfFields: Int
    
    init(numberOfFields: Int = 6) {
        self.numberOfFields = numberOfFields
    }
    
    @Published var otpField = "" {
        didSet {
            guard otpField.last?.isNumber ?? true else {
                otpField = oldValue
                return
            }
            if otpField.count == numberOfFields {
                hideKeyboard()
            }
        }
    }
    
    @Published var isEditing = false
    
    func otp(digit: Int) -> String {
        guard otpField.count >= digit else {
            return ""
        }
        return String(Array(otpField)[digit - 1])
    }
    
    private func hideKeyboard() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

// 自定义无选中高亮UITextField
@available(iOS 13.0, *)
class NoSelectionUITextField: UITextField {
    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        // 仅保留粘贴功能,禁用所有会触发选中的操作
        if action == #selector(UIResponderStandardEditActions.paste(_:)) {
            return super.canPerformAction(action, withSender: sender)
        }
        return false
    }
    
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        // 禁用长按、双击手势,避免触发选中
        if gestureRecognizer is UILongPressGestureRecognizer ||
            (gestureRecognizer is UITapGestureRecognizer && (gestureRecognizer as? UITapGestureRecognizer)?.numberOfTapsRequired == 2) {
            return false
        }
        return super.gestureRecognizerShouldBegin(gestureRecognizer)
    }
}

// 封装为SwiftUI可用组件
@available(iOS 13.0, *)
struct NoSelectionTextField: UIViewRepresentable {
    @Binding var text: String
    let onEditingChanged: (Bool) -> Void
    
    func makeUIView(context: Context) -> NoSelectionUITextField {
        let textField = NoSelectionUITextField()
        textField.delegate = context.coordinator
        textField.text = text
        textField.textContentType = .oneTimeCode
        textField.keyboardType = .decimalPad
        textField.tintColor = .clear
        textField.textColor = .clear
        return textField
    }
    
    func updateUIView(_ uiView: NoSelectionUITextField, context: Context) {
        uiView.text = text
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, UITextFieldDelegate {
        var parent: NoSelectionTextField
        
        init(_ parent: NoSelectionTextField) {
            self.parent = parent
        }
        
        func textFieldDidChangeSelection(_ textField: UITextField) {
            parent.text = textField.text ?? ""
        }
        
        func textFieldDidBeginEditing(_ textField: UITextField) {
            parent.onEditingChanged(true)
        }
        
        func textFieldDidEndEditing(_ textField: UITextField) {
            parent.onEditingChanged(false)
        }
    }
}

@available(iOS 13.0, *)
struct OTPView: View {
    @ObservedObject var viewModel = OTPViewModel()
    
    private let textBoxWidth: CGFloat = 41
    private let textBoxHeight = UIScreen.main.bounds.width / 8
    private let spaceBetweenLines: CGFloat = 16
    private let paddingOfBox: CGFloat = 1
    private var textFieldOriginalWidth: CGFloat {
        (textBoxWidth + CGFloat(18)) * CGFloat(viewModel.numberOfFields)
    }
    
    var body: some View {
        VStack {
            ZStack {
                HStack (spacing: spaceBetweenLines) {
                    ForEach(1 ... viewModel.numberOfFields, id: \.self) { digit in
                        otpText(
                            text: viewModel.otp(digit: digit),
                            isEditing: viewModel.isEditing,
                            beforeCursor: digit - 1 < viewModel.otpField.count,
                            afterCursor: viewModel.otpField.count < digit - 1
                        )
                    }
                } //: HSTACK
                // 替换为自定义的无选中TextField
                NoSelectionTextField(text: $viewModel.otpField, onEditingChanged: { isEditing in
                    viewModel.isEditing = isEditing
                })
                .font(Font.system(size: 90, design: .default))
                .offset(x: 12, y: 10)
                .frame(width: textFieldOriginalWidth, height: textBoxHeight)
                .background(Color.clear)
                
            } //: ZSTACK
        } //: VSTACK
    }
    
    @available(iOS 13.0, *)
    private func otpText(
        text: String,
        isEditing: Bool,
        beforeCursor: Bool,
        afterCursor: Bool
    ) -> some View {
        return Text(text)
            .font(Font.custom("GTWalsheim-Regular", size: 34))
            .frame(width: textBoxWidth, height: textBoxHeight)
            .background(VStack{
                Spacer()
                    .frame(height: 65)
                ZStack {
                    Capsule()
                        .frame(width: textBoxWidth, height: 2)
                        .foregroundColor(Color(hex: "#BCBEC0"))
                    
                    Capsule()
                        .frame(width: textBoxWidth, height: 2)
                        .foregroundColor(Color(hex: "#367878"))
                        .offset(x: (beforeCursor ? textBoxWidth : 0) + (afterCursor ? -textBoxWidth : 0))
                        .animation(.easeInOut, value: [beforeCursor, afterCursor])
                        .opacity(isEditing ? 1 : 0)
                } //: ZSTACK
                .clipped()
            })
            .padding(paddingOfBox)
            .accentColor(.clear)
    }
}

修改后效果:

  • 完全禁用双击、长按触发的文本高亮效果
  • 保留粘贴、系统验证码自动填充能力
  • 适配iOS13及以上版本,可在Xcode12.3下正常编译运行

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 18:39:01