I attempted to bind a isEditing variable to under my UIViewRepresentable which is controlled by a close button in my SwiftUI View.
Under the UIViewRepresentable, I create a UITextfield and what I want to accomplish here is to tap the close button which triggers the isEditing variable and reset the UITextfield to make it leave edit mode. I tried to detect this change under the updateUIView
struct SearchBarViewController: UIViewRepresentable {
let searchEngine = SearchEngine()
let textField = LeftPaddedTextField(frame: .zero)
#Binding var text: String
#Binding var searchArray:[String]
#Binding var isEditing: Bool
func makeUIView(context: UIViewRepresentableContext<SearchBarViewController>) -> UITextField {
textField.delegate = context.coordinator
textField.textColor = UIColor.gray
textField.placeholder = "Where are you going?"
textField.layer.cornerRadius = 20
//textField.layer.borderWidth = 1.5
textField.layer.borderColor = UIColor.tertiaryLabel.cgColor
textField.backgroundColor = UIColor.systemGray6
textField.borderStyle = .none
textField.addTarget(context.coordinator, action: #selector(context.coordinator.textFieldDidChange), for: .editingChanged)
textField.clearButtonMode = .whileEditing
searchEngine.delegate = context.coordinator
return textField
}
func updateUIView(_ uiViewController: UITextField, context: UIViewRepresentableContext<SearchBarViewController>) {
if isEditing {
return
}
print("update is called")
if !isEditing {
//textField.resignFirstResponder()
textField.endEditing(true)
}
}
func makeCoordinator() -> SearchBarViewController.Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, UITextFieldDelegate, SearchEngineDelegate {
var control: SearchBarViewController
init(_ control: SearchBarViewController) {
self.control = control
}
func resultsUpdated(searchEngine: SearchEngine) {
self.control.searchArray = []
if !searchEngine.items.isEmpty {
for i in searchEngine.items {
if let description = i.descriptionText {
self.control.searchArray.append(description)
}
}
}
print()
}
func resolvedResult(result: SearchResult) {
print()
}
func searchErrorHappened(searchError: SearchError) {
print("Error during search: \(searchError)")
}
func textFieldDidBeginEditing(_ textField: UITextField) {
self.control.isEditing = true
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.control.textField.resignFirstResponder()
self.control.isEditing = false
return true
}
// Update model.text when textField.text is changed
#objc func textFieldDidChange() {
if let text = self.control.textField.text {
self.control.text = text
}
if self.control.textField.text != "" {
if let text = control.textField.text {
control.searchEngine.query = text
}
} else {
self.control.searchArray = []
}
}
}
}
And here is the code of the outside view:
struct SearchBarView: View {
#State var isEditing = false
...
var body: some View {
Button(action: {
self.isEditing = false
self.text = ""
}) {
Text("Return")
}
SearchBarViewController(text: $text, searchArray: $searchArray, isEditing: $isEditing)
}
}
But the problem is it doesn't work. After I click the close button the UITextField doesn't exit its edit mode and I still can type. So I am asking whether there is a way to accomplish it.
Thanks for your help in advance.
Try to use passed in instance of text field
func updateUIView(_ uiTextField: UITextField, context: UIViewRepresentableContext<SearchBarViewController>) {
if isEditing {
return
}
print("update is called")
if !isEditing {
uiTextField.resignFirstResponder() // << here !!
// uiTextField.endEditing(true)
}
}
Related
I want to use custom buttons to input text into a TextField, but still show and move the cursor. Is there a way to hide the default keyboard while still showing the cursor?
I was hoping for something like this:
TextField("", text: $text)
.keyboardType(.none)
Here is what it currently looks like.
You can use UIViewRepresentable class and pass the input view as an empty view.
struct HideKeyboardTextField: UIViewRepresentable {
var placeholder: String
#Binding var text: String
func makeUIView(context: UIViewRepresentableContext<HideKeyboardTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.placeholder = placeholder
textField.inputView = UIView()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<HideKeyboardTextField>) {
uiView.text = text
}
func makeCoordinator() -> HideKeyboardTextField.Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: HideKeyboardTextField
init(parent: HideKeyboardTextField) {
self.parent = parent
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
parent.text = textField.text ?? ""
}
}
}
}
Usage:
struct ContentView: View {
#State var text: String = ""
var body: some View {
HideKeyboardTextField(placeholder: "Input", text: $text)
}
}
I try to show the text field keyboard as soon as the view appears and dismiss the keyboard when tap on the keyboard "return" key, the first part of the problem is solved by the code example bellow, But that make the keyboard "return" key doesn't work, Did any one can help to achieve my to show and dismiss the textfiled keyboard as I need.
import SwiftUI
struct MyTextField: UIViewRepresentable {
typealias UIViewType = UITextField
#Binding var becomeFirstResponder: Bool
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
return textField
}
func updateUIView(_ textField: UITextField, context: Context) {
if self.becomeFirstResponder {
DispatchQueue.main.async {
textField.becomeFirstResponder()
self.becomeFirstResponder = false
}
}
}
}
struct TextFieldFirstResponder: View {
#State private var becomeFirstResponder = false
var body: some View {
MyTextField(becomeFirstResponder: self.$becomeFirstResponder)
.onAppear {
self.becomeFirstResponder = true
}
}
}
Use textFieldShouldReturn delegate method. For this make Coordinator for UIViewRepresentable.
struct MyTextField: UIViewRepresentable {
typealias UIViewType = UITextField
#Binding var becomeFirstResponder: Bool
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ textField: UITextField, context: Context) {
if self.becomeFirstResponder {
DispatchQueue.main.async {
textField.becomeFirstResponder()
self.becomeFirstResponder = false
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: MyTextField
init(parent: MyTextField) {
self.parent = parent
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
}
}
}
My problem is shown in the photo below:
I have tried using .fixedSize(horizontal:vertical) on the parent view and the textfield and no positive results.
TheTextField:
struct TheTextField: UIViewRepresentable {
#Binding var text : String
#Binding var placeholder : String
func makeCoordinator() -> TheTextField.Coordinator {
return TheTextField.Coordinator(parent1: self)
}
func makeUIView(context: UIViewRepresentableContext<TheTextField>) -> UITextView {
let tview = UITextView()
tview.isEditable = true
tview.isUserInteractionEnabled = true
tview.isScrollEnabled = false
tview.text = placeholder
tview.textColor = .gray
tview.font = .systemFont(ofSize: 20)
tview.delegate = context.coordinator
return tview
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<TheTextField>) {
}
class Coordinator : NSObject, UITextViewDelegate {
var parent : TheTextField
init(parent1 : TheTextField) {
parent = parent1
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
func textViewDidBeginEditing(_ textView: UITextView) {
textView.text = ""
textView.textColor = .label
}
}
}
I want the textview to not expand and move the cursor to the next line, instead of messing up the parent's view.
Example:
Divider()
TheTextField(text: self.$imageToUpload.textCaption, placeholder: self.$placeholder).padding(.horizontal)
Spacer()
import SwiftUI
struct UITextViewWrapper: UIViewRepresentable {
#Binding var text : String
#Binding var calculatedHeight: CGFloat
func makeCoordinator() -> UITextViewWrapper.Coordinator {
return UITextViewWrapper.Coordinator(text: $text, height: $calculatedHeight)
}
func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
let tview = UITextView()
tview.isEditable = true
tview.isUserInteractionEnabled = true
tview.isScrollEnabled = false
tview.textColor = .gray
tview.font = .systemFont(ofSize: 20)
tview.delegate = context.coordinator
tview.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return tview
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
if uiView.text != self.text {
uiView.text = self.text
}
if uiView.window != nil, uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
}
static func recalculateHeight(view: UIView, result: Binding<CGFloat>){
let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
if result.wrappedValue != newSize.height {
DispatchQueue.main.async {
result.wrappedValue = newSize.height // !! must be called asynchronously
}
}
}
class Coordinator : NSObject, UITextViewDelegate {
var text : Binding<String>
var calculatedHeight : Binding<CGFloat>
init(text: Binding<String>, height: Binding<CGFloat>) {
self.text = text
self.calculatedHeight = height
}
func textViewDidChange(_ textView: UITextView) {
text.wrappedValue = textView.text
UITextViewWrapper.recalculateHeight(view: textView, result: calculatedHeight)
}
func textViewDidBeginEditing(_ textView: UITextView) {
textView.text = ""
textView.textColor = .label
}
}
}
I added functionality for changing the height of the dynamically depending on the length of the text per suggestion by Asperi.
You can easily edit label property from .storyboard file of our project.
Change lines to 0 and line break property to your desired type and then you can have as many lines as you want your label to have.Check this image to see what I'm saying
I have been stumped by this issue for a bit so I hoping to get some help. I have a SwiftUI application that is using UITextView but when I put content in it the text does not wrap. This SwiftUI view I am making both displays and edits text but it currently works for neither.
The text in the second box is much longer but the height does not change.
func makeUIView(context: Context) -> UITextView {
view.isScrollEnabled = false
view.isEditable = editable
view.isUserInteractionEnabled = true
view.contentInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
view.text = self.text
// For debugging
view.layer.borderColor = UIColor.black.cgColor
view.layer.borderWidth = 1.0
var fontPref = UIFont.preferredFont(forTextStyle: render.font)
if render.bold && render.italic {
fontPref = fontPref.bolditalic()
} else if render.bold {
fontPref = fontPref.bold()
} else if render.italic {
fontPref = fontPref.italic()
}
view.font = fontPref
view.textColor = render.color
view.textAlignment = render.align
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
view.textContainer.lineBreakMode = .byWordWrapping
view.delegate = context.coordinator
return view
}
I have tried a few solutions but none of them are working for my use case.
Thanks!
here is a working custom textview that wraps the text and adjusts height:
import SwiftUI
struct NoteTextView: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
#Binding var text: String
var onEndEditing: () -> Void
typealias UIViewType = UITextView
var configuration = { (view: UIViewType) in }
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIViewType {
let textField = UIViewType()
textField.delegate = context.coordinator
textField.font = UIFont.preferredFont(forTextStyle: .body)
textField.text = text
textField.isScrollEnabled = true
textField.isEditable = true
textField.isUserInteractionEnabled = true
return textField
}
func updateUIView(_ uiView: UIViewType, context: UIViewRepresentableContext<Self>) {
uiView.text = text
configuration(uiView)
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: NoteTextView
init(_ uiTextView: NoteTextView) {
self.parent = uiTextView
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
}
}
In SwiftUI I have a simple search TextField where the user type something to be searched and a Button search.
I just want to add the option to have the button Search in the lower right corner of the Keyboard (I saw it in some application)
how can I do that?
iOS 15
You can change the return key for each textField with a simple modifier called: .submitLabel that takes the return key type and you should pass .search. Take a look at the following example:
Also, as you can see, you can have a callback to handle the return key press action just like the old textFieldShouldReturn function that is accessible by '.onSubmit' modifier.
If I understand correctly you want to change the UIReturnKeyType.
In that case you have to use UIKit since there isn't yet any option to change the type of return key in SwiftUI.
To do this, you have to make a custom TextField using UIIKit and then modify it the way you like.
Also keep in mind that the UIReturnKeyType enum is under discussion and may replace with a different implementation.
// MARK: Custom TextField
struct TextFieldTyped: UIViewRepresentable {
let keyboardType: UIKeyboardType
let returnVal: UIReturnKeyType
let tag: Int
#Binding var text: String
#Binding var isfocusAble: [Bool]
func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.keyboardType = self.keyboardType
textField.returnKeyType = self.returnVal
textField.tag = self.tag
textField.delegate = context.coordinator
textField.autocorrectionType = .no
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
if isfocusAble[tag] {
uiView.becomeFirstResponder()
} else {
uiView.resignFirstResponder()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: TextFieldTyped
init(_ textField: TextFieldTyped) {
self.parent = textField
}
func updatefocus(textfield: UITextField) {
textfield.becomeFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if parent.tag == 0 {
parent.isfocusAble = [false, true]
parent.text = textField.text ?? ""
} else if parent.tag == 1 {
parent.isfocusAble = [false, false]
parent.text = textField.text ?? ""
}
return true
}
}
}
And you can use it like this:
(Change the returnVal to .search in your case.)
struct CustomeKT: View {
#State var myTextForTX = ""
#State var focused: [Bool] = [false, true]
var body: some View {
TextFieldTyped(keyboardType: .default, returnVal: .search, tag: 0, text: self.$myTextForTX, isfocusAble: self.$focused)
}
}
Simple Use SearchTextField.swift
import SwiftUI
import UIKit
class UIKitTextField: UITextField, UITextFieldDelegate {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
delegate = self
}
required override init(frame: CGRect) {
super.init(frame: frame)
delegate = self
self.setContentHuggingPriority(.defaultHigh, for: .vertical)
}
var action:(() -> Void)? = nil
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.action?()
if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
return true;
}
return false
}
}
struct SearchTextField : UIViewRepresentable {
#Binding var text: String
var action:() -> Void
func makeCoordinator() -> SearchTextField.Coordinator {
return Coordinator(value: self)
}
class Coordinator: NSObject,UITextFieldDelegate {
var parent:SearchTextField
init(value: SearchTextField) {
self.parent = value
}
#objc func textFieldEditingChanged(_ sender: UIKitTextField) {
self.parent.text = sender.text ?? ""
}
}
func makeUIView(context: Context) -> UIKitTextField {
let textfield = UIKitTextField(frame: .zero)
textfield.addTarget(context.coordinator, action: #selector(Coordinator.textFieldEditingChanged(_:)), for: .editingChanged)
textfield.text = self.text
textfield.placeholder = "search"
textfield.borderStyle = .none
textfield.returnKeyType = .search
textfield.action = self.action
return textfield
}
func updateUIView(_ uiView: UIKitTextField,
context: Context) {
uiView.text = self.text
}
}
Use :
SearchTextField(text: self.$searchKey) {
self.search(key: self.searchKey)
// or...
}