使用 Swift UI 更改文本字段时动画颜色的变化
Animating the change of colors when text field is changed using Swift UI
我创建了一个 OTP 页面,如果选择了具有相应行的文本字段,它会更改下划线的颜色。颜色变化正常,但我想添加动画,线条的颜色会像动画一样缓慢变化。我附上了一个关于预期和当前行为的示例。
我试图让动画更加轻松,但在消除数字和文本时出现问题。他们都不会同时下降或过渡。在录音中,当我点击完成时,台词稍微落后了。
PS。我只能使用Xcode 12.3版本。
import SwiftUI
@available(iOS 13.0, *)
class OTPViewModel: ObservableObject {
@Published var otpField = "" {
didSet {
isNextTypedArr = Array(repeating: false, count: 6)
guard otpField.count <= 6,
otpField.last?.isNumber ?? true else {
otpField = oldValue
return
}
if otpField.count < 6 {
isNextTypedArr[otpField.count] = true
}
}
}
var otp1: String {
guard otpField.count >= 1 else {
return ""
}
return String(Array(otpField)[0])
}
var otp2: String {
guard otpField.count >= 2 else {
return ""
}
return String(Array(otpField)[1])
}
var otp3: String {
guard otpField.count >= 3 else {
return ""
}
return String(Array(otpField)[2])
}
var otp4: String {
guard otpField.count >= 4 else {
return ""
}
return String(Array(otpField)[3])
}
var otp5: String {
guard otpField.count >= 5 else {
return ""
}
return String(Array(otpField)[4])
}
var otp6: String {
guard otpField.count >= 6 else {
return ""
}
return String(Array(otpField)[5])
}
@Published var isNextTypedArr = Array(repeating: false, count: 6)
@Published var borderColor: Color = .black
@Published var isEditing = false {
didSet {
isNextTypedArr = Array(repeating: false, count: 6)
if isEditing && otpField.count < 6 {
isNextTypedArr[otpField.count] = true
}
}
}
}
@available(iOS 13.0, *)
struct CXLRPOTPView: View {
@ObservedObject var viewModel = OTPViewModel()
let textBoxWidth = UIScreen.main.bounds.width / 8
let textBoxHeight = UIScreen.main.bounds.width / 8
let spaceBetweenLines: CGFloat = 16
let paddingOfBox: CGFloat = 1
var textFieldOriginalWidth: CGFloat {
(textBoxWidth*6)+(spaceBetweenLines*3)+((paddingOfBox*2)*3)
}
var body: some View {
VStack {
ZStack {
HStack (spacing: spaceBetweenLines){
otpText(text: viewModel.otp1, isNextTyped: $viewModel.isNextTypedArr[0])
otpText(text: viewModel.otp2, isNextTyped: $viewModel.isNextTypedArr[1])
otpText(text: viewModel.otp3, isNextTyped: $viewModel.isNextTypedArr[2])
otpText(text: viewModel.otp4, isNextTyped: $viewModel.isNextTypedArr[3])
otpText(text: viewModel.otp5, isNextTyped: $viewModel.isNextTypedArr[4])
otpText(text: viewModel.otp6, isNextTyped: $viewModel.isNextTypedArr[5])
}
TextField("", text: $viewModel.otpField) { isEditing in
viewModel.isEditing = isEditing
}
.frame(width: viewModel.isEditing ? 0 : textFieldOriginalWidth, height: textBoxHeight)
.textContentType(.oneTimeCode)
.foregroundColor(.clear)
.accentColor(.clear)
.background(Color.clear)
.keyboardType(.numberPad)
}
}
}
@available(iOS 13.0, *)
private func otpText(text: String, isNextTyped: Binding<Bool>) -> some View {
return Text(text)
.font(Font.custom("GTWalsheim-Regular", size: 34))
.frame(width: textBoxWidth, height: textBoxHeight)
.background(VStack{
Spacer()
RoundedRectangle(cornerRadius: 1)
.frame(height: 2)
.foregroundColor(isNextTyped.wrappedValue ? Color(hex: "#367878") : Color(hex: "#BCBEC0"))
.animation(.easeIn) //this made the text bounced.
})
.padding(paddingOfBox)
}
}
问题是您实际上并没有偏移颜色,所以默认情况下它只是淡化颜色。我将绿色下划线作为一个单独的部分,根据光标当前所在的位置进行偏移。
要解决动画问题,即使在做一些不相关的事情时它也会动画 - 使用 .animation(_:value:)
代替 - 提供一个值,当该值发生变化时 仅 发生动画。
将otpText
方法更改为:
@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()
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)
}
.clipped()
})
.padding(paddingOfBox)
}
以及你在哪里使用这个函数(虽然,相当混乱,你可以用一个循环和一个数组而不是 otp1
、otp2
等来清理你的代码):
HStack (spacing: spaceBetweenLines){
otpText(text: viewModel.otp1, isEditing: viewModel.isEditing, beforeCursor: 0 < viewModel.otpField.count, afterCursor: false)
otpText(text: viewModel.otp2, isEditing: viewModel.isEditing, beforeCursor: 1 < viewModel.otpField.count, afterCursor: viewModel.otpField.count < 1)
otpText(text: viewModel.otp3, isEditing: viewModel.isEditing, beforeCursor: 2 < viewModel.otpField.count, afterCursor: viewModel.otpField.count < 2)
otpText(text: viewModel.otp4, isEditing: viewModel.isEditing, beforeCursor: 3 < viewModel.otpField.count, afterCursor: viewModel.otpField.count < 3)
otpText(text: viewModel.otp5, isEditing: viewModel.isEditing, beforeCursor: 4 < viewModel.otpField.count, afterCursor: viewModel.otpField.count < 4)
otpText(text: viewModel.otp6, isEditing: viewModel.isEditing, beforeCursor: false, afterCursor: viewModel.otpField.count < 5)
}
结果:
要清理代码,删除所有 otp1
、otp2
等并替换为以下内容:
func otp(digit: Int) -> String {
guard otpField.count >= digit else {
return ""
}
return String(Array(otpField)[digit - 1])
}
那么可以简化查看代码:
HStack (spacing: spaceBetweenLines){
ForEach(1 ... 6, 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
)
}
}
问题是您没有正确偏移颜色,因此它默认为颜色淡化。绿色下划线是一个单独的组件,它会根据指针所在的位置移动。
我创建了一个 OTP 页面,如果选择了具有相应行的文本字段,它会更改下划线的颜色。颜色变化正常,但我想添加动画,线条的颜色会像动画一样缓慢变化。我附上了一个关于预期和当前行为的示例。
我试图让动画更加轻松,但在消除数字和文本时出现问题。他们都不会同时下降或过渡。在录音中,当我点击完成时,台词稍微落后了。
PS。我只能使用Xcode 12.3版本。
import SwiftUI
@available(iOS 13.0, *)
class OTPViewModel: ObservableObject {
@Published var otpField = "" {
didSet {
isNextTypedArr = Array(repeating: false, count: 6)
guard otpField.count <= 6,
otpField.last?.isNumber ?? true else {
otpField = oldValue
return
}
if otpField.count < 6 {
isNextTypedArr[otpField.count] = true
}
}
}
var otp1: String {
guard otpField.count >= 1 else {
return ""
}
return String(Array(otpField)[0])
}
var otp2: String {
guard otpField.count >= 2 else {
return ""
}
return String(Array(otpField)[1])
}
var otp3: String {
guard otpField.count >= 3 else {
return ""
}
return String(Array(otpField)[2])
}
var otp4: String {
guard otpField.count >= 4 else {
return ""
}
return String(Array(otpField)[3])
}
var otp5: String {
guard otpField.count >= 5 else {
return ""
}
return String(Array(otpField)[4])
}
var otp6: String {
guard otpField.count >= 6 else {
return ""
}
return String(Array(otpField)[5])
}
@Published var isNextTypedArr = Array(repeating: false, count: 6)
@Published var borderColor: Color = .black
@Published var isEditing = false {
didSet {
isNextTypedArr = Array(repeating: false, count: 6)
if isEditing && otpField.count < 6 {
isNextTypedArr[otpField.count] = true
}
}
}
}
@available(iOS 13.0, *)
struct CXLRPOTPView: View {
@ObservedObject var viewModel = OTPViewModel()
let textBoxWidth = UIScreen.main.bounds.width / 8
let textBoxHeight = UIScreen.main.bounds.width / 8
let spaceBetweenLines: CGFloat = 16
let paddingOfBox: CGFloat = 1
var textFieldOriginalWidth: CGFloat {
(textBoxWidth*6)+(spaceBetweenLines*3)+((paddingOfBox*2)*3)
}
var body: some View {
VStack {
ZStack {
HStack (spacing: spaceBetweenLines){
otpText(text: viewModel.otp1, isNextTyped: $viewModel.isNextTypedArr[0])
otpText(text: viewModel.otp2, isNextTyped: $viewModel.isNextTypedArr[1])
otpText(text: viewModel.otp3, isNextTyped: $viewModel.isNextTypedArr[2])
otpText(text: viewModel.otp4, isNextTyped: $viewModel.isNextTypedArr[3])
otpText(text: viewModel.otp5, isNextTyped: $viewModel.isNextTypedArr[4])
otpText(text: viewModel.otp6, isNextTyped: $viewModel.isNextTypedArr[5])
}
TextField("", text: $viewModel.otpField) { isEditing in
viewModel.isEditing = isEditing
}
.frame(width: viewModel.isEditing ? 0 : textFieldOriginalWidth, height: textBoxHeight)
.textContentType(.oneTimeCode)
.foregroundColor(.clear)
.accentColor(.clear)
.background(Color.clear)
.keyboardType(.numberPad)
}
}
}
@available(iOS 13.0, *)
private func otpText(text: String, isNextTyped: Binding<Bool>) -> some View {
return Text(text)
.font(Font.custom("GTWalsheim-Regular", size: 34))
.frame(width: textBoxWidth, height: textBoxHeight)
.background(VStack{
Spacer()
RoundedRectangle(cornerRadius: 1)
.frame(height: 2)
.foregroundColor(isNextTyped.wrappedValue ? Color(hex: "#367878") : Color(hex: "#BCBEC0"))
.animation(.easeIn) //this made the text bounced.
})
.padding(paddingOfBox)
}
}
问题是您实际上并没有偏移颜色,所以默认情况下它只是淡化颜色。我将绿色下划线作为一个单独的部分,根据光标当前所在的位置进行偏移。
要解决动画问题,即使在做一些不相关的事情时它也会动画 - 使用 .animation(_:value:)
代替 - 提供一个值,当该值发生变化时 仅 发生动画。
将otpText
方法更改为:
@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()
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)
}
.clipped()
})
.padding(paddingOfBox)
}
以及你在哪里使用这个函数(虽然,相当混乱,你可以用一个循环和一个数组而不是 otp1
、otp2
等来清理你的代码):
HStack (spacing: spaceBetweenLines){
otpText(text: viewModel.otp1, isEditing: viewModel.isEditing, beforeCursor: 0 < viewModel.otpField.count, afterCursor: false)
otpText(text: viewModel.otp2, isEditing: viewModel.isEditing, beforeCursor: 1 < viewModel.otpField.count, afterCursor: viewModel.otpField.count < 1)
otpText(text: viewModel.otp3, isEditing: viewModel.isEditing, beforeCursor: 2 < viewModel.otpField.count, afterCursor: viewModel.otpField.count < 2)
otpText(text: viewModel.otp4, isEditing: viewModel.isEditing, beforeCursor: 3 < viewModel.otpField.count, afterCursor: viewModel.otpField.count < 3)
otpText(text: viewModel.otp5, isEditing: viewModel.isEditing, beforeCursor: 4 < viewModel.otpField.count, afterCursor: viewModel.otpField.count < 4)
otpText(text: viewModel.otp6, isEditing: viewModel.isEditing, beforeCursor: false, afterCursor: viewModel.otpField.count < 5)
}
结果:
要清理代码,删除所有 otp1
、otp2
等并替换为以下内容:
func otp(digit: Int) -> String {
guard otpField.count >= digit else {
return ""
}
return String(Array(otpField)[digit - 1])
}
那么可以简化查看代码:
HStack (spacing: spaceBetweenLines){
ForEach(1 ... 6, 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
)
}
}
问题是您没有正确偏移颜色,因此它默认为颜色淡化。绿色下划线是一个单独的组件,它会根据指针所在的位置移动。