#ios #animation #swiftui #textfield #one-time-password
Вопрос:
Я создал поле OTP, которое будет анимировать строки каждый раз, когда вы вводите цифру. Это хорошо работает. Тем не менее, я хочу, чтобы анимация зеленых линий выполнялась по одной(слева направо) при вставке, как при вводе числа.
В настоящее время, когда вы вставляете текст, все строки анимируются одновременно, как показано на видео.
Ожидается, что при вставке строка должна становиться зеленой по одной слева направо.
Пожалуйста, обратите внимание, что моя кнопка «Вставить» — это пользовательская кнопка, которую я создал в качестве обходного пути для какой-либо другой функции.
Я также могу использовать только Xcode 12.3.
Вот мой код:
Некоторые шестнадцатеричные цвета и шрифт являются пользовательскими. Вы можете заменить его при воспроизведении.
@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 {
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)
.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 decimalInputOnly = pastedString
.components(separatedBy:CharacterSet.decimalDigits.inverted)
.joined()
let otpField = decimalInputOnly.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)
}
}
@available(iOS 13.0.0, *)
struct CXLRPOTPView_Previews: PreviewProvider {
static var previews: some View {
CXLRPOTPView(viewModel: OTPViewModel())
.previewLayout(.sizeThatFits)
}
}
Ответ №1:
похоже, у вас были трудные времена с otp. 🙂
Я обновил OTPViewModel
его, добавив две переменные.
OperationQueue
С maxConcurrentOperationCount
набором как один, чтобы цифры можно было добавлять по otpField
одной. (Чтобы исправить анимацию)
Строка userPastedText
для didSet
наблюдателя свойств, в которой я добавляю по otpField
одному.
Я также обновил paste
функцию с CXLRPOTPView
«вставить» в userPastedText
. Это и есть код.
class OTPViewModel: ObservableObject {
let operationQueue: OperationQueue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
return operationQueue
}()
.
.
.
var userPastedText = "" {
didSet {
for char in userPastedText {
operationQueue.addOperation {
Thread.sleep(forTimeInterval: 0.2)
DispatchQueue.main.async {
self.otpField = String(char)
}
}
}
}
}
.
.
.
}
struct CXLRPOTPView: View {
.
.
.
func pasteText() {
let pasteboard = UIPasteboard.general
guard let pastedString = pasteboard.string else {
return
}
let decimalInputOnly = pastedString
.components(separatedBy:CharacterSet.decimalDigits.inverted)
.joined()
let otpField = decimalInputOnly.prefix(viewModel.numberOfFields)
viewModel.userPastedText = String(otpField)
}
.
.
.
}
Комментарии:
1. хахахаха, ты прав, и я уже должен тебе заплатить. Это потому, что мы только что переехали в SwiftUI, и я гораздо лучше знаком с UIKit, и мне нужно больше учиться. Мое решение похоже на это, но я не выполнил его должным образом, я использовал только очередь отправки и повторил вставленный цикл, но ваш способ более плавный. Спасибо @Alhomaidhi, ты слишком много раз спасал мою задницу здесь.