├── Assets ├── .DS_Store └── Example.gif ├── LICENSE ├── README.md ├── PredictingTextField.swift └── Example └── Example_TextFieldPrediction.swift /Assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonloewe/TextFieldInputPrediction/HEAD/Assets/.DS_Store -------------------------------------------------------------------------------- /Assets/Example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonloewe/TextFieldInputPrediction/HEAD/Assets/Example.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 simonloewe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Live TextField Input Prediction 2 | 3 | ## Simple Swift ```TextField``` Live Input Prediction 4 | This is an approach to get live input predictions/ auto completion of a SwiftUI ```TextField``` 5 | 6 | ### How to use: 7 | Copy the file ```PredictingTextField.swift``` into your Project and use it by calling a 8 | ```Swift 9 | struct MyView: View { 10 | 11 | @State var predictableValues: Array = [] 12 | @State var myPredictedValues: Array = [] 13 | 14 | /// Empty to start with. Will populate once user starts typing 15 | @State var textFieldInput: String = "" 16 | 17 | var body: some View { 18 | PredictingTextField(predictableValues: self.$predictableValues, 19 | predictedValues: self.$myPredictedValues, 20 | textFieldInput: self.$textFieldInput 21 | ) 22 | } 23 | } 24 | ``` 25 | You can and need to pass it the following variables: 26 | 27 | #### Mandatory 28 | 1) ```predictableValues``` -> ```@Binding Array``` Needs to be an Array of Strings as we compare the input of the ```TextField``` which also is a String to these Strings. It can be only one Item in the Array or an empty array. In the later case the ```TextField``` will not make any predictions and the use of a normal ```TextField``` is recommended. 29 | 2) ```predictedValues``` -> ```@Binding Array``` This also needs to be an Array of Strings. Should be empty when initialized. In here will be the prediction/ -s of the input of the ```TextField``` based on the *predictableValues*. It is given as attribute so the predictions can be accessed on the parent view. 30 | 3) ```textFieldInput``` -> ```@Binding String``` This is the text that is currently in the ```TextField``` (This is a parameter that is also used in the Swift implementation of ```TextField```() ). It is provided as Binding Object so it can be reset from the parent view. If e.g. one wants to reset the input of the ```TextField``` once a prediction was made/ selected/ used. 31 | 32 | #### Optional 33 | 4) ```textFieldTitle``` -> ```String?``` Use this to set a Title in an untouched ```TextField``` 34 | 5) ```predictionInterval``` -> ```@State Double?``` This can be modified to accelerate or slower the prediction. Default is a predicition made every 0.1 seconds. 35 | 36 | Note: If the ```TextField``` gets multiple inputs at once. E.g. in form of a String separated by *spaces* it will make predictions on every SubString of that input and append these predictions to the *predictedValues* Array. **Altough every prediction will only occure once in the ```predictedValues```**. 37 | 38 | ### E.g. of use 39 | ![Example of TextFieldPrediction](Assets/Example.gif) 40 | 41 | -------------------------------------------------------------------------------- /PredictingTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // StackOverflow 4 | // 5 | // Created by Simon Löwe on 04.04.20. 6 | // Copyright © 2020 Simon Löwe. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | /// TextField capable of making predictions based on provided predictable values 12 | struct PredictingTextField: View { 13 | 14 | /// All possible predictable values. Can be only one. 15 | @Binding var predictableValues: Array 16 | 17 | /// This returns the values that are being predicted based on the predictable values 18 | @Binding var predictedValues: Array 19 | 20 | /// Current input of the user in the TextField. This is Binded as perhaps there is the urge to alter this during live time. E.g. when a predicted value was selected and the input should be cleared 21 | @Binding var textFieldInput: String 22 | 23 | /// The time interval between predictions based on current input. Default is 0.1 second. I would not recommend setting this to low as it can be CPU heavy. 24 | @State var predictionInterval: Double? 25 | 26 | /// Placeholder in empty TextField 27 | var textFieldTitle: String? 28 | 29 | @State private var isBeingEdited: Bool = false 30 | 31 | init(predictableValues: Binding>, predictedValues: Binding>, textFieldInput: Binding, textFieldTitle: String? = "", predictionInterval: Double? = 0.1){ 32 | 33 | self._predictableValues = predictableValues 34 | self._predictedValues = predictedValues 35 | self._textFieldInput = textFieldInput 36 | 37 | self.textFieldTitle = textFieldTitle 38 | self.predictionInterval = predictionInterval 39 | } 40 | 41 | var body: some View { 42 | TextField(self.textFieldTitle ?? "", text: self.$textFieldInput, onEditingChanged: { editing in self.realTimePrediction(status: editing)}, onCommit: { self.makePrediction()}) 43 | } 44 | 45 | /// Schedules prediction based on interval and only a if input is being made 46 | private func realTimePrediction(status: Bool) { 47 | self.isBeingEdited = status 48 | if status == true { 49 | Timer.scheduledTimer(withTimeInterval: self.predictionInterval ?? 1, repeats: true) { timer in 50 | self.makePrediction() 51 | 52 | if self.isBeingEdited == false { 53 | timer.invalidate() 54 | } 55 | } 56 | } 57 | } 58 | 59 | /// Capitalizes the first letter of a String 60 | private func capitalizeFirstLetter(smallString: String) -> String { 61 | return smallString.prefix(1).capitalized + smallString.dropFirst() 62 | } 63 | 64 | /// Makes prediciton based on current input 65 | private func makePrediction() { 66 | self.predictedValues = [] 67 | if !self.textFieldInput.isEmpty{ 68 | for value in self.predictableValues { 69 | if self.textFieldInput.split(separator: " ").count > 1 { 70 | self.makeMultiPrediction(value: value) 71 | }else { 72 | if value.contains(self.textFieldInput) || value.contains(self.capitalizeFirstLetter(smallString: self.textFieldInput)){ 73 | if !self.predictedValues.contains(String(value)) { 74 | self.predictedValues.append(String(value)) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | /// Makes predictions if the input String is splittable 83 | private func makeMultiPrediction(value: String) { 84 | for subString in self.textFieldInput.split(separator: " ") { 85 | if value.contains(String(subString)) || value.contains(self.capitalizeFirstLetter(smallString: String(subString))){ 86 | if !self.predictedValues.contains(value) { 87 | self.predictedValues.append(value) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Example/Example_TextFieldPrediction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // StackOverflow 4 | // 5 | // Created by Simon Löwe on 04.04.20. 6 | // Copyright © 2020 Simon Löwe. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | 13 | @State var textFieldInput: String = "" 14 | @State var predictableValues: Array = ["First", "Second", "Third", "Fourth"] 15 | @State var predictedValue: Array = [] 16 | 17 | var body: some View { 18 | VStack(alignment: .leading){ 19 | Text("Predictable Values: ").bold() 20 | 21 | HStack{ 22 | ForEach(self.predictableValues, id: \.self){ value in 23 | Text(value) 24 | } 25 | } 26 | 27 | PredictingTextField(predictableValues: self.$predictableValues, predictedValues: self.$predictedValue, textFieldInput: self.$textFieldInput) 28 | .textFieldStyle(RoundedBorderTextFieldStyle()) 29 | 30 | ForEach(self.predictedValue, id: \.self){ value in 31 | Text("Predicted Value: \(value)") 32 | } 33 | }.padding() 34 | } 35 | } 36 | 37 | 38 | /// TextField capable of making predictions based on provided predictable values 39 | struct PredictingTextField: View { 40 | 41 | /// All possible predictable values. Can be only one. 42 | @Binding var predictableValues: Array 43 | 44 | /// This returns the values that are being predicted based on the predictable values 45 | @Binding var predictedValues: Array 46 | 47 | /// Current input of the user in the TextField. This is Binded as perhaps there is the urge to alter this during live time. E.g. when a predicted value was selected and the input should be cleared 48 | @Binding var textFieldInput: String 49 | 50 | /// The time interval between predictions based on current input. Default is 0.1 second. I would not recommend setting this to low as it can be CPU heavy. 51 | @State var predictionInterval: Double? 52 | 53 | /// Placeholder in empty TextField 54 | var textFieldTitle: String? 55 | 56 | @State private var isBeingEdited: Bool = false 57 | 58 | init(predictableValues: Binding>, predictedValues: Binding>, textFieldInput: Binding, textFieldTitle: String? = "", predictionInterval: Double? = 0.1){ 59 | 60 | self._predictableValues = predictableValues 61 | self._predictedValues = predictedValues 62 | self._textFieldInput = textFieldInput 63 | 64 | self.textFieldTitle = textFieldTitle 65 | self.predictionInterval = predictionInterval 66 | } 67 | 68 | var body: some View { 69 | TextField(self.textFieldTitle ?? "", text: self.$textFieldInput, onEditingChanged: { editing in self.realTimePrediction(status: editing)}, onCommit: { self.makePrediction()}) 70 | } 71 | 72 | /// Schedules prediction based on interval and only a if input is being made 73 | private func realTimePrediction(status: Bool) { 74 | self.isBeingEdited = status 75 | if status == true { 76 | Timer.scheduledTimer(withTimeInterval: self.predictionInterval ?? 1, repeats: true) { timer in 77 | self.makePrediction() 78 | 79 | if self.isBeingEdited == false { 80 | timer.invalidate() 81 | } 82 | } 83 | } 84 | } 85 | 86 | /// Capitalizes the first letter of a String 87 | private func capitalizeFirstLetter(smallString: String) -> String { 88 | return smallString.prefix(1).capitalized + smallString.dropFirst() 89 | } 90 | 91 | /// Makes prediciton based on current input 92 | private func makePrediction() { 93 | self.predictedValues = [] 94 | if !self.textFieldInput.isEmpty{ 95 | for value in self.predictableValues { 96 | if self.textFieldInput.split(separator: " ").count > 1 { 97 | self.makeMultiPrediction(value: value) 98 | }else { 99 | if value.contains(self.textFieldInput) || value.contains(self.capitalizeFirstLetter(smallString: self.textFieldInput)){ 100 | if !self.predictedValues.contains(String(value)) { 101 | self.predictedValues.append(String(value)) 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | /// Makes predictions if the input String is splittable 110 | private func makeMultiPrediction(value: String) { 111 | for subString in self.textFieldInput.split(separator: " ") { 112 | if value.contains(String(subString)) || value.contains(self.capitalizeFirstLetter(smallString: String(subString))){ 113 | if !self.predictedValues.contains(value) { 114 | self.predictedValues.append(value) 115 | } 116 | } 117 | } 118 | } 119 | } 120 | --------------------------------------------------------------------------------