I created an OTP field using textfield but I want to disable highlighting the text when you double tap or longpress on a TextField.
I tried adjusting the font size to 0. This seems to shrink the grey highlight but it did not hide it totally.
I can't use textSelection(.disabled) because I can only use Xcode 12.3 and this API seems to be available in higher Xcode versions.
I also cannot adjust the frame width of textField to 0 when editing is true because the paste functionality would be disable. Paste to text is a needed requirement.
Here is my code:
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)
}
}
In the frame of the TextField.
.frame(width: textFieldOriginalWidth, height: textBoxHeight)
Change the width to be reactive to the isEditing
state. If true make the width 0
otherwise make it textFieldOriginalWidth
like this:
.frame(width: isEditing ? 0 : textFieldOriginalWidth, height: textBoxHeight)
Of course this doesn't disable it. But it will not highlight and allow the user to past, copy, etc...
It will have the desired outcome.
To get the OTP to "Autofill" or show up in the keyboard.
Set the Textfield
's .textContentType
as .oneTimeCode
.
The OS should handle the rest read the Apple docs.
Which you have done for textfield.
This action should paste your copied text:
Button(action: paste, label: {
Text("Paste")
})
.
.
.
func paste() {
let pasteboard = UIPasteboard.general
guard let pastedString = pasteboard.string else {
return
}
viewModel.otpField = pastedString
}