1

As a user types characters in a textfield, I would like to display some animation on each newly typed character (kinda like how Cash App animates numbers but I'd like to implement it for alphabetical characters as well).

enter image description here

Is it possible to do this in SwiftUI? My intuition is that I might have to bridge to UIKit for more nuanced access to a textfield's element but not sure how to actually implement that.

7
  • Do you want to invoke an animation on the textfield itself or another part of the view? I don't think you have to use UIKit for any of these cases, is possible with SwiftUI. Commented Nov 23, 2021 at 15:06
  • On textfield itself as users type in characters onto the field. If this is the case, how can it be animated using SwiftUI alone? I thought the animation scope only wraps the textfield but not necessarily every each character in it? Commented Nov 23, 2021 at 15:15
  • which kind animation? do have an example of what you want? Commented Nov 23, 2021 at 15:53
  • for example, upon typing, the newly typed character slides from left. Commented Nov 23, 2021 at 16:16
  • You cannot do it with standard API from Apple, need a custom code for that. Also you mentioned about UIKit, i do not think that would possible with that either. Commented Nov 23, 2021 at 16:28

1 Answer 1

3

You can just create a "Fake" TextField that appears over the real one. Then show the characters in a ForEach.

It is done with FocusState in iOS 15

@available(iOS 15.0, *) struct AnimatedInputView: View { @FocusState private var isFocused: Int? @State var text: String = "" //If all the fonts match the cursor is better aligned @State var font: Font = .system(size: 48, weight: .bold, design: .default) @State var color: Color = .gray var body: some View { HStack(alignment: .center, spacing: 0){ //To maintain size in between the 2 views Text(text) .font(font) .opacity(0) .overlay( //This textField will be invisible TextField("", text: $text) .font(font) .foregroundColor(.clear) .focused($isFocused, equals: 1) ) .background( ZStack{ HStack(alignment: .center, spacing: 0, content: { //You need an array of unique/identifiable characters let uniqueArray = text.uniqueCharacters() ForEach(uniqueArray, id: \.id, content: { char in CharView(char: char.char, isLast: char == uniqueArray.last, font: font) }) }) }.opacity(1) .minimumScaleFactor(0.1) ) .onAppear(perform: { //Bring focus to the hidden TextField DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { isFocused = 1 }) }) } .padding() .border(color) .font(.title) //Bring focus to the hidden textfield .onTapGesture { isFocused = 1 } } } struct CharView: View{ var char: Character var isLast: Bool var font: Font @State var scale: CGFloat = 0.75 var body: some View{ Text(char.description) .font(font) .minimumScaleFactor(0.1) .scaleEffect(scale) .onAppear(perform: { //Animate only if last character if isLast{ withAnimation(.linear(duration: 0.5)){ scale = 1 } }else{ scale = 1 } }) } } @available(iOS 15.0, *) struct AnimatedInputView_Previews: PreviewProvider { static var previews: some View { AnimatedInputView() } } //Convert String to Unique characers extension String{ func uniqueCharacters() -> [UniqueCharacter]{ let array: [Character] = Array(self) return array.uniqueCharacters() } func numberOnly() -> String { self.trimmingCharacters(in: CharacterSet(charactersIn: "-0123456789.").inverted) } } extension Array where Element == Character { func uniqueCharacters() -> [UniqueCharacter]{ var array: [UniqueCharacter] = [] for char in self{ array.append(UniqueCharacter(char: char)) } return array } } //String/Characters can be repeating so yu have to make them a unique value struct UniqueCharacter: Identifiable, Equatable{ var char: Character var id: UUID = UUID() } 

Here is a sample version that. only takes numbers like the calculator sample

import SwiftUI @available(iOS 15.0, *) struct AnimatedInputView: View { @FocusState private var isFocused: Int? @State var text: String = "" //If all the fonts match the cursor is better aligned @State var font: Font = .system(size: 48, weight: .bold, design: .default) @State var color: Color = .gray var body: some View { HStack(alignment: .center, spacing: 0){ Text("$").font(font) //To maintain size in between the 2 views Text(text) .font(font) .opacity(0) .overlay( //This textField will be invisible TextField("", text: $text) .font(font) .foregroundColor(.clear) .focused($isFocused, equals: 1) .onChange(of: text, perform: { value in if Double(text) == nil{ //Leaves the negative and decimal period text = text.numberOnly() } //This condition can be improved. //Checks for 2 occurences of the decimal period //Possible solution while text.components(separatedBy: ".").count > 2{ color = .red text.removeLast() } //This condition can be improved. //Checks for 2 occurences of the negative //Possible solution while text.components(separatedBy: "-").count > 2{ color = .red text.removeLast() } color = .gray }) ) .background( ZStack{ HStack(alignment: .center, spacing: 0, content: { //You need an array of unique/identifiable characters let uniqueArray = text.uniqueCharacters() ForEach(uniqueArray, id: \.id, content: { char in CharView(char: char.char, isLast: char == uniqueArray.last, font: font) }) }) }.opacity(1) .minimumScaleFactor(0.1) ) .onAppear(perform: { //Bring focus to the hidden TextField DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { isFocused = 1 }) }) } .padding() .border(color) .font(.title) //Bring focus to the hidden textfield .onTapGesture { isFocused = 1 } } } 
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you. Do you think that to enable the editing, we can somehow programmatically change the Z-index of the textfield and the overlay text? Perhaps use a ZStack rather than overlay. And when the user clicks on the text, we can just bring the Textfield to the front for editing, and update the char-array per edit... It's complex but thank you for the solution!
Possible but very complex likely prone to more bugs.
@PipEvangelist Actually, I figured out another way to do it. it looks a tiny bit off but a better version. It allows editing. The cursor is just a bit off
Thank you! This is brilliant

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.