【问题标题】:How to disable highlighted text in TextField in Swift UI如何在 Swiftui 的 TextField 中禁用突出显示文本
【发布时间】:2021-11-04 00:57:02
【问题描述】:

我使用文本字段创建了一个 OTP 字段,但我想在您双击或长按文本字段时禁用突出显示文本。

我尝试将字体大小调整为 0。这似乎缩小了灰色高光,但并没有完全隐藏它。

我不能使用 textSelection(.disabled),因为我只能使用 Xcode 12.3,而且这个 API 似乎在更高版本的 Xcode 中可用。

当编辑为真时,我也无法将 textField 的框架宽度调整为 0,因为粘贴功能将被禁用。粘贴到文本是必需的要求。

这是我的代码:

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)
    }
}

@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("", text: $viewModel.otpField) { isEditing in
                    viewModel.isEditing = isEditing
                }
                .font(Font.system(size: 90, design: .default))
                .offset(x: 12, y: 10)
                .frame(width: textFieldOriginalWidth, height: textBoxHeight)
                .textContentType(.oneTimeCode)
                .foregroundColor(.clear)
                .background(Color.clear)
                .keyboardType(.decimalPad)
                .accentColor(.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)
    }
}

【问题讨论】:

  • 您可能会发现这个 SO 使用答案(接近答案集的末尾)。 stackoverflow.com/questions/58005434/…
  • 嗨@workingdog,你的意思是哪个答案?我看到了这篇文章,但这是为了允许选择。我想禁用我的。
  • “jbaraga”的答案,它说“....我本质上想选择文本而不允许编辑”。我想倒数第二个答案。

标签: ios swiftui textfield highlight one-time-password


【解决方案1】:

解决方案是在编辑时使 TextField 的框架宽度等于 0,但这样做会禁用粘贴功能,因此@Alhomaidhi 的解决方案是添加粘贴按钮。

我这样做并让它在 TextField 双击或长按以模仿 iOS 剪贴板时出现。

完整代码如下:

import SwiftUI

@available(iOS 13.0, *)
class OTPViewModel: ObservableObject {
    
    var numberOfFields: Int
    
    init(numberOfFields: Int = 6) {
        self.numberOfFields = numberOfFields
    }
    
    @Published var otpField = "" {
        didSet {
            showPasteButton = false
            guard otpField.last?.isNumber ?? true else {
                otpField = oldValue
                return
            }
            if otpField.count == numberOfFields {
                hideKeyboard()
                showPasteButton = false
            }
        }
    }
    
    @Published var isEditing = false {
        didSet {
            if !isEditing { showPasteButton = isEditing }
        }
    }
    
    @Published var showPasteButton = 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)
    }
}

@available(iOS 13.0, *)
struct CXLRPOTPView: View {
    @ObservedObject var viewModel = OTPViewModel()
    @Environment(\.colorScheme) var colorScheme
    
    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 {
                // DOUBLE TAP AND LONG PRESS LISTENER
                Text("123456")
                    .onTapGesture(count: 2) {
                        viewModel.showPasteButton = true
                    }
                    .frame(width: textFieldOriginalWidth, height: textBoxHeight)
                    .background(Color.clear)
                    .font(Font.system(size: 90, design: .default))
                    .foregroundColor(Color.clear)
                    .onLongPressGesture(minimumDuration: 0.5) {
                        self.viewModel.showPasteButton = true
                    }
                
                // OTP TEXT
                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 FOR EDITING
                TextField("", text: $viewModel.otpField) { isEditing in
                    viewModel.isEditing = isEditing
                }
                .font(Font.system(size: 90, design: .default))
                .offset(x: 12, y: 10)
                .frame(width: viewModel.isEditing ? 0 : textFieldOriginalWidth, height: textBoxHeight) // SOLUTION THAT PREVENTED TEXT HIGHLIGHT
                .textContentType(.oneTimeCode)
                .foregroundColor(.clear)
                .background(Color.clear)
                .keyboardType(.numberPad)
                .accentColor(.clear)
                
                // PASTE BUTTON
                Button(action: pasteText, label: {
                    Text("Paste")
                })
                .padding(.top, 9)
                .padding(.bottom, 9)
                .padding(.trailing, 16)
                .padding(.leading, 16)
                .font(Font.system(size: 14, design: .default))
                .accentColor(Color(.white))
                .background(Color(colorScheme == .light ? UIColor.black : UIColor.systemGray6))
                .cornerRadius(7.0)
                .overlay(
                    RoundedRectangle(cornerRadius: 7).stroke(Color(.black), lineWidth: 2)
                )
                .opacity(viewModel.showPasteButton ? 1 : 0)
                .offset(x: viewModel.numberOfFields >= 6 ? -150 : -100, y: -40)
            } //: ZSTACK
        } //: VSTACK
    }
    
    func pasteText() {
        let pasteboard = UIPasteboard.general
        guard let pastedString = pasteboard.string else {
            return
        }
        let otpField = pastedString.prefix(viewModel.numberOfFields)
        viewModel.otpField = String(otpField)
    }
    
    @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)
            .foregroundColor(Color.black)
    }
}

@available(iOS 13.0.0, *)
struct CXLRPOTPView_Previews: PreviewProvider {
    static var previews: some View {
        CXLRPOTPView(viewModel: OTPViewModel())
            .previewLayout(.sizeThatFits)
    }
}

【讨论】:

    【解决方案2】:

    在 TextField 的框架中。

    .frame(width: textFieldOriginalWidth, height: textBoxHeight)
    

    更改宽度以响应isEditing 状态。如果为真,则设置宽度0,否则设置为textFieldOriginalWidth

    像这样:

    .frame(width: isEditing ? 0 : textFieldOriginalWidth, height: textBoxHeight)
    

    当然这不会禁用它。但它不会突出显示并允许用户过去、复制等...

    它会有预期的结果。

    更新

    让 OTP “自动填充”或显示在键盘中。

    Textfield.textContentType 设置为.oneTimeCode。 操作系统应该处理其余的读取Apple docs

    你为文本字段做了什么。

    此操作应粘贴您复制的文本:

    Button(action: paste, label: {
        Text("Paste")
    })
    
    .
    .
    .
    func paste() {
        let pasteboard = UIPasteboard.general
        guard let pastedString = pasteboard.string else {
            return
        }
        viewModel.otpField = pastedString
    }
    

    【讨论】:

    • 您好@Alhomaidhi,如果您还记得的话,这是原始代码,不幸的是,我不得不更改它,因为如果我添加此框架设置,粘贴功能将不起作用。我需要能够使用剪贴板在文本字段中粘贴数字。这样做 .frame(width: isEditing ? 0 : textFieldOriginalWidth, height: textBoxHeight) 我相信会将“粘贴”框移出视图。
    • 您好,我知道这不是最好的解决方案,但是键盘应该具有过去的功能 + 或许可以添加一个模仿动作的按钮?
    • 我明白了。你能帮我吗?我仍在尝试在 SwiftUI 中找到相关指南
    • 嗨@Alhomaidhi,我更新了我的问题。 .oneTimeCode 完美地工作,但是,它也是能够在 textField 上粘贴不同文本的要求。希望你能再次帮助我。谢谢。
    • 在TextField上长按/双击时有没有办法显示/隐藏按钮?这样,当您双击文本字段或长按时,您可以只显示按钮。
    猜你喜欢
    • 2010-10-24
    • 2019-08-10
    • 1970-01-01
    • 2021-03-02
    • 2020-09-18
    • 2017-10-14
    相关资源
    最近更新 更多