如何在 Swift UI 的 TextField 中禁用突出显示的文本
How to disable highlighted text in TextField in Swift UI
我使用文本字段创建了一个 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)
}
}
在 TextField 的框架中。
.frame(width: textFieldOriginalWidth, height: textBoxHeight)
更改宽度以响应 isEditing
状态。如果 true 使宽度 0
否则使它 textFieldOriginalWidth
像这样:
.frame(width: isEditing ? 0 : textFieldOriginalWidth, height: textBoxHeight)
当然这不会禁用它。但它不会突出显示并允许用户过去、复制等...
会得到想要的结果。
更新
将 OTP 设置为“自动填充”或显示在键盘中。
将 Textfield
的 .textContentType
设置为 .oneTimeCode
。
OS 应该处理其余的阅读 Apple docs。
您为文本字段所做的。
此操作应粘贴您复制的文本:
Button(action: paste, label: {
Text("Paste")
})
.
.
.
func paste() {
let pasteboard = UIPasteboard.general
guard let pastedString = pasteboard.string else {
return
}
viewModel.otpField = pastedString
}
解决方案是在编辑时将 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)
}
}
我使用文本字段创建了一个 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)
}
}
在 TextField 的框架中。
.frame(width: textFieldOriginalWidth, height: textBoxHeight)
更改宽度以响应 isEditing
状态。如果 true 使宽度 0
否则使它 textFieldOriginalWidth
像这样:
.frame(width: isEditing ? 0 : textFieldOriginalWidth, height: textBoxHeight)
当然这不会禁用它。但它不会突出显示并允许用户过去、复制等...
会得到想要的结果。
更新
将 OTP 设置为“自动填充”或显示在键盘中。
将 Textfield
的 .textContentType
设置为 .oneTimeCode
。
OS 应该处理其余的阅读 Apple docs。
您为文本字段所做的。
此操作应粘贴您复制的文本:
Button(action: paste, label: {
Text("Paste")
})
.
.
.
func paste() {
let pasteboard = UIPasteboard.general
guard let pastedString = pasteboard.string else {
return
}
viewModel.otpField = pastedString
}
解决方案是在编辑时将 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)
}
}