SwiftUI Show/Dismiss Keyboard - swiftui

This code below show and hide TextField keyboard perfectly except this warning message keep showing to me when run the code, did anyone can help to avoid this warning please ???
import UIKit
import SwiftUI
struct FirstResponderTextFiels: UIViewRepresentable {
#Binding var text: String
let placeholder: String
#Binding var showKeyboard: Bool
// Create the coordinator
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
#Binding var showKeyboard: Bool
var becameFirstResponder = false
init(text: Binding<String>, showKeyboard: Binding<Bool>) {
self._text = text
self._showKeyboard = showKeyboard
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text, showKeyboard: $showKeyboard)
}
// Create the textfield
func makeUIView(context: Context) -> some UIView {
let textField = UITextField()
textField.delegate = context.coordinator
textField.placeholder = placeholder
return textField
}
func updateUIView(_ uiView: UIViewType, context: Context) {
if context.coordinator.showKeyboard {
uiView.becomeFirstResponder()
context.coordinator.showKeyboard = false
}
}
}
The warning message

After review the code I found if I remove this part of code it has no effect and it just work fine
func updateUIView(_ uiView: UIViewType, context: Context) {
if context.coordinator.showKeyboard {
uiView.becomeFirstResponder()
context.coordinator.showKeyboard = false // <--- Remove it
}
}

Related

Setting up .onCommit{} in UITextFieldViewRepresentable SwiftUI

I bridge UIKit with SwiftUI as follows:
struct UITextFieldViewRepresentable: UIViewRepresentable {
#Binding var language: String
#Binding var text: String
init(language: Binding<String>, text: Binding<String>) {
self._language = language
self._text = text
}
func makeUIView(context: Context) -> UITextField {
let textField = getTextField()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
// Change the language in the wordTextField here.
if let wordTextField = uiView as? WordTextField {
wordTextField.language = self.language
}
}
private func getTextField() -> UITextField {
let textField = WordTextField(frame: .zero)
textField.language = self.language
textField.textAlignment = .center
textField.font = UIFont.systemFont(ofSize: 15, weight: .regular)
return textField
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text)
}
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
init(text: Binding<String>) {
self._text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
}
class WordTextField: UITextField {
var language: String? {
didSet {
if self.isFirstResponder{
self.resignFirstResponder()
self.becomeFirstResponder()
}
}
}
override var textInputMode: UITextInputMode? {
if let language = self.language {
print("text input mode: \(language)")
for inputMode in UITextInputMode.activeInputModes {
if let inputModeLanguage = inputMode.primaryLanguage, inputModeLanguage == language {
return inputMode
}
}
}
return super.textInputMode
}
}
}
And call it as follows:
UITextFieldViewRepresentable(language: $keyboardLanguage, text: $foreignThing)
This works fine in some parts of my app. In other parts, I need a text field which calls a method when the user taps the enter key after entering text. It's written like this:
TextField("", text: Binding<String>(
get: { self.userAnswer },
set: {
self.userAnswer = $0
self.enableHint()
}), onCommit: {
if self.userAnswer.isEmpty {
answerPlaceholder = NSMutableAttributedString(string: "Tap here to answer...")
} else {
answerDisabled = true
checkAnswer()
}
})
I tried implementing the above with UITextFieldViewRepresenatable as follows:
UITextFieldViewRepresentable(language: $keyboardLanguage, text: Binding<String>(
get: { self.userAnswer },
set: {
self.userAnswer = $0
self.enableHint()
}), onCommit: {
if self.userAnswer.isEmpty {
answerPlaceholder = NSMutableAttributedString(string: "Tap here to answer...")
} else {
answerDisabled = true
checkAnswer()
}
})
I'm getting 'compiler couldn't type check this expression in reasonable time' error. I think I've narrowed it down to not implementing .onCommit:{} in my UITextFieldViewRepresentable()
If this is the problem, then I'd like to know how .onCommit:{} can be implemented in UITextFieldViewRepresentable().
There are a few mistakes in the UIViewRepresentable implementation:
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text)
}
This text binding will be out of date when the View is recreated, so change it to:
func makeCoordinator() -> Coordinator {
return Coordinator()
}
You can store the textField inside the coordinator, make it a lazy, set delegate self, then return it from the make func, eg..
func makeUIView(context: Context) -> UITextField {
context.coordinator.textField // lazy property that sets delegate self.
}
In update, you need to make use of the new value of the binding, e.g.
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
// Change the language in the wordTextField here.
if let wordTextField = uiView as? WordTextField {
wordTextField.language = self.language
}
// use the new binding
context.coordinator.textDidChange = { newText in
text = newText
}
}
class Coordinator: NSObject, UITextFieldDelegate {
lazy var textField: UITextField = {
let textField = UITextField()
textField.delegate = self
return textField
}()
var textDidChange: ((String) -> Void)?
func textFieldDidChangeSelection(_ textField: UITextField) {
textDidChange?(textField.text)
}
}
You'll see something similar in PaymentButton.swift in Apple's Fruta sample.
Perhaps a simpler way would be this (but I've not tested it yet):
// update
context.coordinator.textBinding = _text
// Coordinator
var textBinding: Binding<String>?
// textViewDidChange
textBinding?.wrappedValue = textField.text

How to integrate Buttons and Links into a Text in SwiftUI

I wondered how to add Buttons and Links to a SwiftUI Text. Some Example: In a long Text, some special Words are Buttons, or Links, like in a Wikipedia Article:
There are some of the Words blue marked as links, how can I reach that in SwiftUI?
Thanks, Boothosh
I know the pain! I have spend a lot of time on internet reading articles on how to do the same and came across the easiest solution.
Reference for the solution here
1. Add TextView(...) to your project
/// Text view with click able links
struct TextView: UIViewRepresentable {
#Binding var text: String
#Binding var textStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = UIFont.preferredFont(forTextStyle: textStyle)
textView.autocapitalizationType = .sentences
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.isEditable = false
textView.dataDetectorTypes = .link
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
}
func makeCoordinator() -> Coordinator {
Coordinator($text)
}
class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
init(_ text: Binding<String>) {
self.text = text
}
func textViewDidChange(_ textView: UITextView) {
self.text.wrappedValue = textView.text
}
}
}
Full Usage:
struct DetailsView: View {
#State var text : String = "Yo.. try https://google.com"
#State private var textStyle = UIFont.TextStyle.body
var body: some View {
TextView(text: $text, textStyle: $textStyle)
}
}

Swiftui - Access UIKit methods/properties from UIViewRepresentable

I have created a SwiftUI TextView based on a UITextView using UIViewRepresentable (s. code below). Displaying text in Swiftui works OK.
But now I need to access internal functions of UITextView from my model. How do I call e.g. UITextView.scrollRangeToVisible(_:) or access properties like UITextView.isEditable ?
My model needs to do these modifications based on internal model states.
Any ideas ? Thanks
(p.s. I am aware of TextEditor in SwiftUI, but I need support for iOS 13!)
struct TextView: UIViewRepresentable {
#ObservedObject var config: ConfigModel = .shared
#Binding var text: String
#State var isEditable: Bool
var borderColor: UIColor
var borderWidth: CGFloat
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let myTextView = UITextView()
myTextView.delegate = context.coordinator
myTextView.isScrollEnabled = true
myTextView.isEditable = isEditable
myTextView.isUserInteractionEnabled = true
myTextView.layer.borderColor = borderColor.cgColor
myTextView.layer.borderWidth = borderWidth
myTextView.layer.cornerRadius = 8
return myTextView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.font = uiView.font?.withSize(CGFloat(config.textsize))
uiView.text = text
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: TextView
init(_ uiTextView: TextView) {
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
}
}
}
You can use something like configurator callback pattern, like
struct TextView: UIViewRepresentable {
#ObservedObject var config: ConfigModel = .shared
#Binding var text: String
#State var isEditable: Bool
var borderColor: UIColor
var borderWidth: CGFloat
var configurator: ((UITextView) -> ())? // << here !!
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let myTextView = UITextView()
myTextView.delegate = context.coordinator
myTextView.isScrollEnabled = true
myTextView.isEditable = isEditable
myTextView.isUserInteractionEnabled = true
myTextView.layer.borderColor = borderColor.cgColor
myTextView.layer.borderWidth = borderWidth
myTextView.layer.cornerRadius = 8
return myTextView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.font = uiView.font?.withSize(CGFloat(config.textsize))
uiView.text = text
// alternat is to call this function in makeUIView, which is called once,
// and the store externally to send methods directly.
configurator?(myTextView) // << here !!
}
// ... other code
}
and use it in your SwiftUI view like
TextView(...) { uiText in
uiText.isEditing = some
}
Note: depending on your scenarios it might be additional conditions need to avoid update cycling, not sure.

UITextField not updating to change in ObservableObject (SwiftUI)

I am trying to make a UITextView that edits a value currentDisplayedAddress. The value is also changed by other views, and I want the UITextView to update its text when that occurs.
The view initializes correctly, and I can edit currentDisplayedAddress from AddressTextField with no problem and trigger relevant view updates. However, when the value is changed by other views, the textField's text does not change, even though textField.text prints the correct updated value inside updateUIView and other views update accordingly.
I have no idea what may have caused this. Any help is extremely appreciated.
struct AddressTextField: UIViewRepresentable {
private let textField = UITextField(frame: .zero)
var commit: () -> Void
#EnvironmentObject var userData: UserDataModel
func makeUIView(context: UIViewRepresentableContext<AddressTextField>) -> UITextField {
textField.text = self.userData.currentDisplayedAddress
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<AddressTextField>) {
if self.textField.text != self.userData.currentDisplayedAddress {
DispatchQueue.main.async {
self.textField.text = self.userData.currentDisplayedAddress
}
}
}
(...)
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject, UITextFieldDelegate {
var addressTextField: AddressTextField
func textFieldShouldReturn(_ textField: UITextField) -> Bool { //delegate method
textField.resignFirstResponder()
addressTextField.userData.currentDisplayedAddress = textField.text ?? String()
addressTextField.commit()
return true
}
}
}
You should not create UITextField as property, because AddressTextField struct can be recreated during parent update. The makeUIView is exactly the place to create UI-instances, the representable is handling its reference on your behalf.
So here is fixed variant. Tested with Xcode 11.4 / iOS 13.4
struct AddressTextField: UIViewRepresentable {
var commit: () -> Void = {}
#EnvironmentObject var userData: UserDataModel
func makeUIView(context: UIViewRepresentableContext<AddressTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.text = self.userData.currentDisplayedAddress
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ textField: UITextField, context: UIViewRepresentableContext<AddressTextField>) {
if textField.text != self.userData.currentDisplayedAddress {
textField.text = self.userData.currentDisplayedAddress
}
}
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject, UITextFieldDelegate {
var addressTextField: AddressTextField
init(_ addressTextField: AddressTextField) {
self.addressTextField = addressTextField
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool { //delegate method
textField.resignFirstResponder()
addressTextField.userData.currentDisplayedAddress = textField.text ?? String()
addressTextField.commit()
return true
}
}
}

Cannot assign to property: '$text' is immutable

I wanted to make a custom textfield in SwiftUI to can handle first responder but I had this error in the code and struct is immutable I don't know what should I do?
struct CustomTextField: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
var didBecomeFirstResponder = false
init(txt: Binding<String>) {
self.$text = txt
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
}
#Binding var text: String
var isFirstResponder: Bool = false
func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
return textField
}
func makeCoordinator() -> CustomTextField.Coordinator {
return Coordinator(txt: $text)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.text = text
if isFirstResponder && !context.coordinator.didBecomeFirstResponder {
uiView.becomeFirstResponder()
context.coordinator.didBecomeFirstResponder = true
}
}
}
In beta 4, the implementation of property wrappers changed.
Until beta 3, this was valid:
self.$text = txt
In beta 4, it changed to:
self._text = txt
Check for the difference in implementation, in this other question I posted:
https://stackoverflow.com/a/57088052/7786555
And for more details:
https://stackoverflow.com/a/56975728/7786555