Read #Binding Value - swiftui

I try to make custom textfield so I give it #Binding to response to the text value as showing below, The problem is when I try to detect the change of text its just response in the "Preview", But when run the app on the "Simulator" it doesn't response, I tried many different ways to solve this problem but nothing is work.
import SwiftUI
struct MyTextField: UIViewRepresentable {
typealias UIViewType = UITextField
#Binding var becomeFirstResponder: Bool
#Binding var text: String
var placeholder = ""
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.widthAnchor.constraint(equalToConstant: 320).isActive = true
textField.textColor = UIColor.systemBlue
textField.font = UIFont.boldSystemFont(ofSize: 22)
textField.textAlignment = .left
textField.keyboardType = .default
textField.minimumFontSize = 13
textField.adjustsFontSizeToFitWidth = true
textField.text = self._text.wrappedValue
textField.placeholder = self.placeholder
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()
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text ?? ""
guard let stringRange = Range(range, in: currentText) else {
return false
}
let updateText = currentText.replacingCharacters(in: stringRange, with: string)
return updateText.count < 20
}
}
}
struct TextFieldFirstResponder: View {
#State private var becomeFirstResponder = false
#State private var text = "LLL"
private var placeholder = "Untitled"
var body: some View {
VStack {
ZStack(alignment: .trailing) {
MyTextField(becomeFirstResponder: self.$becomeFirstResponder, text: self.$text, placeholder: self.placeholder)
.frame(width: 343, height: 56, alignment: .leading)
.padding(EdgeInsets(top: 27, leading: 13, bottom: 0, trailing: 0))
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color(UIColor.secondarySystemBackground))
.frame(width: 342, height: 56, alignment: .center)
)
.onAppear {
self.becomeFirstResponder = true
}
}
Text("\(self.$text.wrappedValue)") // <------ Do not read the "text"
}
}
}
struct TextFieldFirstResponder_Previews: PreviewProvider {
static var previews: some View {
TextFieldFirstResponder()
}
}

You don't need to use $ here, read property directly
Text(self.text) // <------ Do not use $
and I assume you wanted to update it via binding
let updateText = currentText.replacingCharacters(in: stringRange, with: string)
self.parent.text = updateText // << here !!
return updateText.count < 20
Tested & worked with Xcode 12.1 / iOS 14.1

Related

How to add a first responder to a multi line text field

I have a multi line text field, and need it to become a first responder when it appears. Also, it needs to resign as a first responder when the onCommit parameter of MultiLineTextField is fired, or one of 2 buttons are tapped.
As it stands, the keyboard dismisses when it should but immediately reappears when it shouldn't.
I know I could just use the new iOS 16 TextField .axis parameter, but I need to stick with iOS 15.5, hence the long drawn out code below.
import SwiftUI
struct MultilineTextField: View {
private var placeholder: String
private var onCommit: (() -> Void)?
#State private var viewHeight: CGFloat = 40 //start with one line
#State private var shouldShowPlaceholder = false
#Binding private var text: String
private var internalText: Binding<String> {
Binding<String>(get: { self.text } ) {
self.text = $0
self.shouldShowPlaceholder = $0.isEmpty
}
}
var body: some View {
UITextViewWrapper(text: self.internalText, calculatedHeight: $viewHeight, onDone: onCommit)
.frame(minHeight: viewHeight, maxHeight: viewHeight)
.background(placeholderView, alignment: .topLeading)
}
var placeholderView: some View {
Group {
if shouldShowPlaceholder {
Text(placeholder).foregroundColor(.gray)
.padding(.leading, 4)
.padding(.top, 8)
}
}
}
init (_ placeholder: String = "", text: Binding<String>, onCommit: (() -> Void)? = nil) {
self.placeholder = placeholder
self.onCommit = onCommit
self._text = text
self._shouldShowPlaceholder = State<Bool>(initialValue: self.text.isEmpty)
}
}
private struct UITextViewWrapper: UIViewRepresentable {
typealias UIViewType = UITextView
#Binding var text: String
#Binding var calculatedHeight: CGFloat
var onDone: (() -> Void)?
func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
let textField = UITextView()
textField.delegate = context.coordinator
textField.isEditable = true
textField.font = UIFont.preferredFont(forTextStyle: .body)
textField.isSelectable = true
textField.isUserInteractionEnabled = true
textField.autocorrectionType = .no
textField.isScrollEnabled = false
textField.backgroundColor = UIColor.clear
textField.keyboardType = .asciiCapable
textField.textColor = .systemBlue
textField.textAlignment = .center
if nil != onDone {
textField.returnKeyType = .done
}
textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textField
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
if uiView.text != self.text {
uiView.text = self.text
}
if uiView.window != nil, !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
}
private 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 // call in next render cycle.
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text, height: $calculatedHeight, onDone: onDone)
}
final class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
var calculatedHeight: Binding<CGFloat>
var onDone: (() -> Void)?
init(text: Binding<String>, height: Binding<CGFloat>, onDone: (() -> Void)? = nil) {
self.text = text
self.calculatedHeight = height
self.onDone = onDone
}
func textViewDidChange(_ uiView: UITextView) {
text.wrappedValue = uiView.text
UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if let onDone = self.onDone, text == "\n" {
textView.resignFirstResponder()
onDone()
return false
}
return true
}
}
}
MultilineTextField Usage:
MultilineTextField("", text: Binding<String>(
get: { userAnswer },
set: {
self.userAnswer = $0.allowedCharacters(string: $0)
self.enableHint()
}), onCommit: {
if self.userAnswer.isEmpty {
nativeForeignPlaceholder = NSMutableAttributedString(string: "Tap here to answer...")
} else {
answerDisabled = true
checkAnswer()
}
})
.overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.systemBlue, lineWidth: overlayLineWidth))
.modifier(TextFieldClearButton(text: $userAnswer))
.placeholder(when: userAnswer.isEmpty) {
TextWithAttributedString(attributedString: nativeForeignPlaceholder ?? NSMutableAttributedString())
}
.font(.system(size: fontSize, weight: .regular, design: .rounded))
.lineLimit(2)
.disabled(answerDisabled)
.onAppear {
// key part: delay setting isFocused until after some-internal-iOS setup
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
answerIsFocused = true
}
}
.onTapGesture(perform: tapOnAnswerField)
.frame(dynamicWidth: 675, dynamicHeight: 100, alignment: .center)
.position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY + geometry.size.height / 8.5)
func tapOnAnswerField() {
answerIsFocused = true
}

How do I change the height of this UIViewRepresentable?

I found this code on the web :
struct CustomTextField: UIViewRepresentable {
#Binding var text: String
#State var placeholder: String
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.borderStyle = .roundedRect
textField.placeholder = placeholder
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.keyboardType = .URL
textField.frame = CGRect(x: 0, y: 0, width: 20.00, height: 10)
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ view: UITextField, context: Context) {
view.text = text
}
}
extension CustomTextField {
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
self.text = textField.text ?? ""
}
}
}
}
The code works absolutely fine. The problem with this is that I am not able to find a suitable way to increase the height of this. As you can see, I tried to use a CGRect as the frame, to no effect. How can I change the size (particularly height in my specific scenario) of this custom UIViewRepresentable?
Just the same you would do with any other SwiftUI view:
CustomTextField(text: $text, placeholder: "")
// constant
.frame(height: 100)
// fill available height
.frame(maxHeight: .infinity)
If you wanna make it respect intrinsicContentSize, check out this answer

Toolbar accessory added to UITextView as a UIViewRepresentable only displays after first launch of iMessage extension application

Would like to have the toolbar show all the time.
Any help is greatly appreciated as this is a real drag for the user experience.
I've added a toolbar to the keyboard for the TextView as shown below.
However the toolbar only shows after the app has run once. Meaning the toolbar does not show the first time the app is run. the app works every time after the initial load.
This is on IOS 14.3, Xcode 12.3, Swift 5, iMessage extension app. Fails on simulator or real device.
struct CustomTextEditor: UIViewRepresentable {
#Binding var text: String
private var returnType: UIReturnKeyType
private var keyType: UIKeyboardType
private var displayDoneBar: Bool
private var commitHandler: (()->Void)?
init(text: Binding<String>,
returnType: UIReturnKeyType = .done,
keyboardType: UIKeyboardType,
displayDoneBar: Bool,
onCommit: (()->Void)?) {
self._text = text
self.returnType = returnType
self.keyType = keyboardType
self.displayDoneBar = displayDoneBar
self.commitHandler = onCommit
}
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.keyboardType = keyType
textView.returnKeyType = returnType
textView.backgroundColor = .clear
textView.font = UIFont.systemFont(ofSize: 20, weight: .regular)
textView.isEditable = true
textView.delegate = context.coordinator
if self.displayDoneBar {
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace,
target: self,
action: nil)
let doneButton = UIBarButtonItem(title: "Close Keyboard",
style: .done,
target: self,
action: #selector(textView.doneButtonPressed(button:)))
let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 300, height: 50))
toolBar.items = [flexibleSpace, doneButton, flexibleSpace]
toolBar.setItems([flexibleSpace, doneButton, flexibleSpace], animated: true)
toolBar.sizeToFit()
textView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
textView.translatesAutoresizingMaskIntoConstraints = true
textView.inputAccessoryView = toolBar
}
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
textView.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: CustomTextEditor
init(_ textView: CustomTextEditor) {
self.parent = textView
}
func textViewDidChange(_ textView: UITextView) {
self.parent.$text.wrappedValue = textView.text
}
func textViewDidEndEditing(_ textView: UITextView) {
self.parent.$text.wrappedValue = textView.text
parent.commitHandler?()
}
}
}
extension UITextView {
#objc func doneButtonPressed(button:UIBarButtonItem) -> Void {
self.resignFirstResponder()
}
}
This is how it's called...
import SwiftUI
final class ContentViewHostController: UIHostingController<ContentView> {
weak var myWindow: UIWindow?
init() {
super.init(rootView: ContentView())
}
required init?(coder: NSCoder) {
super.init(coder: coder, rootView: ContentView())
}
}
let kTextColor = Color(hex: "3E484F")
let kOverlayRadius: CGFloat = 10
let kOverlayWidth: CGFloat = 2
let kOverlayColor = kTextColor
struct ContentView: View {
#State var text = ""
var body: some View {
VStack {
Spacer()
CustomTextEditor(text: $text, returnType: .default, keyboardType: .default, displayDoneBar: true, onCommit: nil)
.foregroundColor(kTextColor)
.overlay(
RoundedRectangle(cornerRadius: kOverlayRadius)
.stroke(kOverlayColor, lineWidth: kOverlayWidth)
)
.frame(width: 200, height: 100, alignment: .center)
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
From MessagesViewController...
override func willBecomeActive(with conversation: MSConversation) {
let childViewCtrl = ContentViewHostController()
childViewCtrl.view.layoutIfNeeded() // avoids snapshot warning?
if let window = self.view.window {
childViewCtrl.myWindow = window
window.rootViewController = childViewCtrl
}
}

View automatically jumps to Multiline Text Field

My SwiftUI View is kinda acting weird since i added a MultilineTextField. When pressing a item on the List, the view kind jumps back and forth and then it jumps automatically to the last text field in the view as seen in this video. This just happened after i added a MultilineTextField at the end.
MultilineTextField definition:
import Foundation
import SwiftUI
import UIKit
fileprivate struct UITextViewWrapper: UIViewRepresentable {
typealias UIViewType = UITextView
#Binding var text: String
#Binding var calculatedHeight: CGFloat
var onDone: (() -> Void)?
func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
let textField = UITextView()
textField.delegate = context.coordinator
textField.isEditable = true
textField.font = UIFont.preferredFont(forTextStyle: .body)
textField.isSelectable = true
textField.isUserInteractionEnabled = true
textField.isScrollEnabled = false
textField.backgroundColor = UIColor.clear
if nil != onDone {
textField.returnKeyType = .done
}
textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textField
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
if uiView.text != self.text {
uiView.text = self.text
}
if uiView.window != nil, !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
}
fileprivate 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
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text, height: $calculatedHeight, onDone: onDone)
}
final class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
var calculatedHeight: Binding<CGFloat>
var onDone: (() -> Void)?
init(text: Binding<String>, height: Binding<CGFloat>, onDone: (() -> Void)? = nil) {
self.text = text
self.calculatedHeight = height
self.onDone = onDone
}
func textViewDidChange(_ uiView: UITextView) {
text.wrappedValue = uiView.text
UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if let onDone = self.onDone, text == "\n" {
textView.resignFirstResponder()
onDone()
return false
}
return true
}
}
}
struct MultilineTextField: View {
private var placeholder: String
private var onCommit: (() -> Void)?
#Binding private var text: String
private var internalText: Binding<String> {
Binding<String>(get: { self.text } ) {
self.text = $0
self.showingPlaceholder = $0.isEmpty
}
}
#State private var dynamicHeight: CGFloat = 100
#State private var showingPlaceholder = false
init (_ placeholder: String = "", text: Binding<String>, onCommit: (() -> Void)? = nil) {
self.placeholder = placeholder
self.onCommit = onCommit
self._text = text
self._showingPlaceholder = State<Bool>(initialValue: self.text.isEmpty)
}
var body: some View {
UITextViewWrapper(text: self.internalText, calculatedHeight: $dynamicHeight, onDone: onCommit)
.frame(minHeight: dynamicHeight, maxHeight: dynamicHeight)
.background(placeholderView, alignment: .topLeading)
}
var placeholderView: some View {
Group {
if showingPlaceholder {
Text(placeholder).foregroundColor(.gray)
.padding(.leading, 4)
.padding(.top, 8)
}
}
}
}
#if DEBUG
struct MultilineTextField_Previews: PreviewProvider {
static var test:String = ""//some very very very long description string to be initially wider than screen"
static var testBinding = Binding<String>(get: { test }, set: {
// print("New value: \($0)")
test = $0 } )
static var previews: some View {
VStack(alignment: .leading) {
Text("Description:")
MultilineTextField("Enter some text here", text: testBinding, onCommit: {
print("Final text: \(test)")
})
.overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.black))
Text("Something static here...")
Spacer()
}
.padding()
}
}
#endif
Code:
struct DetailZwei : View {
#State var data : dataTypeZwei
#State var viewModel = GerätEditieren()
#Environment(\.presentationMode) var presentationMode
#State private var showingAlert = false
var body : some View {
NavigationView {
ScrollView {
VStack {
Group {
Section(header: Text("")) {
Text("Seriennummer")
TextField("Seriennummer", text: $data.sn).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Objekt")
TextField("Objekt", text: $data.objekt).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Gerätetyp")
TextField("Gerätetyp", text: $data.typ).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Geräteposition")
TextField("Geräteposition", text: $data.pos).textFieldStyle(RoundedBorderTextFieldStyle())
}
Group {
Text("Installationsdatum")
TextField("Installationsdatum", text: $data.ida).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Leasing oder Gekauft?")
TextField("Leasing oder Gekauft?", text: $data.lg).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Leasing")
TextField("Ablaufdatum Leasing", text: $data.la).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Garantie")
TextField("Ablaufdatum Garantie", text: $data.ga).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("")) {
Text("Strasse")
TextField("Strasse", text: $data.str).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Hausnummer")
TextField("Hausnummer", text: $data.nr).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Postleitzahl")
TextField("Postleitzahl", text: $data.plz).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ort")
TextField("Ort", text: $data.ort).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("")) {
Text("Ansprechperson")
TextField("Ansprechperson", text: $data.vp).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Telefonnummer")
TextField("Telefonnummer", text: $data.tel).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("VDS").bold()) {
Text("Eingetragen durch")
TextField("Eingetragen durch", text: $data.ed).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Lieferdatum VDS")
TextField("Lieferdatum VDS", text: $data.ldvds).textFieldStyle(RoundedBorderTextFieldStyle())
}
// This is the Text Field
Section(header: Text("")) {
Text("Zusätzliche Informationen")
MultilineTextField("Zusätzliche Informationen", text: $data.zusatz).overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.black))
}
}.padding()
.navigationBarTitle("Gerät bearbeiten", displayMode: .inline)
.navigationBarItems(leading: Button(action: { self.handleCancelTapped() }, label: {
Text("Abbrechen")
}),
trailing: Button(action: { self.handleDoneTapped() }, label: {
Text("Speichern")
})
// .disabled(!viewModel.modified)
)
}.alert(isPresented: $showingAlert) {
Alert(title: Text("Änderungen gespeichert"), message: Text("Die Änderungen vom Gerät \(data.sn) wurden erfolgreich gespeichert!"), dismissButton: .default(Text("Zurück").bold()){
self.handleCancelTapped()
})
}
}
}
}
When you opening the view, your Custom TextField calls firstResponders. Just remove calling firstResponder on load and your view will start at the beginning.
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
if uiView.text != self.text {
uiView.text = self.text
}
if uiView.window != nil, !uiView.isFirstResponder {
//uiView.becomeFirstResponder() << Here calling firstResponder
}
UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
}
SwiftUI TextFields do not support firstResponder yet, however with Representable and using UIKit it is possible, like in your solution, Grüezi

App freezes when return button is tapped on keyboard with UIViewRepresentable

When I click the return button on the keyboard, the application freezes and never becomes responsive again. I am using a UIViewRepresentable that returns a UITextField because I am unable to create an outline on the text using SwiftUI. I'm running this on Xcode beta 5 on and iPhone XR simulator.
import SwiftUI
import UIKit
struct CustomTextField: View {
#State var text: String
#State var pos: CGPoint
var body: some View {
StrokeTextLabel(text: text)
// .frame(width: UIScreen.main.bounds.width - 16, height: 40, alignment: .center)
.position(pos)
.gesture(dragGesture)
}
var dragGesture : some Gesture {
DragGesture()
.onChanged { value in
self.pos = value.location
print(self.pos)
}
}
}
struct StrokeTextLabel: UIViewRepresentable {
var text: String
func makeUIView(context: Context) -> UITextField {
let attributedStringParagraphStyle = NSMutableParagraphStyle()
attributedStringParagraphStyle.alignment = NSTextAlignment.center
let attributedString = NSAttributedString(
string: text,
attributes:[
NSAttributedString.Key.strokeColor : UIColor.white,
NSAttributedString.Key.foregroundColor : UIColor.black,
NSAttributedString.Key.strokeWidth : -4.0,
NSAttributedString.Key.font: UIFont(name: "impact", size: 50.0)!
]
)
let strokeLabel = UITextField(frame: CGRect.zero)
strokeLabel.attributedText = attributedString
strokeLabel.backgroundColor = UIColor.clear
strokeLabel.sizeToFit()
strokeLabel.center = CGPoint.init(x: 0.0, y: 0.0)
strokeLabel.sizeToFit()
return strokeLabel
}
func updateUIView(_ uiView: UITextField, context: Context) {}
}
#if DEBUG
struct CustomTextField_Previews: PreviewProvider {
static var previews: some View {
CustomTextField(text: "test text", pos: CGPoint(x: 300, y: 500))
}
}
#endif
I expect the application to be close the keyboard when the return button is clicked but instead the app freezes.
Credits to this answer goes to https://github.com/valvoline/SATextField/blob/master/SATextField/ContentView.swift. This answer wouldn't be here without the above source.
Nothing major changed.
struct CustomTextField: View {
#State var text: String
#State var pos: CGPoint
var body: some View {
StrokeTextLabel(text: $text, changeHandler: { newString in
self.text = newString
}, onCommitHandler: {
print("commitHandler")
})
.position(pos)
.gesture(dragGesture)
}
var dragGesture : some Gesture {
DragGesture()
.onChanged { value in
self.pos = value.location
print(self.pos)
}
}
}
Added UITextField and UITextFieldDelegate to use their functions
class WrappableTextField: UITextField, UITextFieldDelegate {
var textFieldChangedHandler: ((String)->Void)?
var onCommitHandler: (()->Void)?
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let currentValue = textField.text as NSString? {
let proposedValue = currentValue.replacingCharacters(in: range, with: string)
textFieldChangedHandler?(proposedValue as String)
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
onCommitHandler?()
}
}
Use the delegate as an object and add the attributes. Also made the string a Binding
struct StrokeTextLabel: UIViewRepresentable {
private let tmpView = WrappableTextField()
#Binding var text: String
//var exposed to SwiftUI object init
var changeHandler:((String)->Void)?
var onCommitHandler:(()->Void)?
func makeUIView(context: Context) -> UITextField {
tmpView.delegate = tmpView
tmpView.onCommitHandler = onCommitHandler
tmpView.textFieldChangedHandler = changeHandler
let attributedStringParagraphStyle = NSMutableParagraphStyle()
attributedStringParagraphStyle.alignment = NSTextAlignment.center
let attributedString = NSAttributedString(
string: text,
attributes:[
NSAttributedString.Key.strokeColor : UIColor.white,
NSAttributedString.Key.foregroundColor : UIColor.black,
NSAttributedString.Key.strokeWidth : -4.0,
NSAttributedString.Key.font: UIFont(name: "impact", size: 50.0)!
]
)
tmpView.attributedText = attributedString
tmpView.autocorrectionType = .no
tmpView.keyboardType = .default
return tmpView
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
}