SwiftUI select contents of TextField / introspect - swiftui

I'm using introspect to select the text in a field using the below method :
import Introspect
private class TextFieldObserver: NSObject {
#objc
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.selectAll(nil)
}
}
private let textFieldObserver = TextFieldObserver()
var body: some View {
TextField(name, text: field )
.introspectTextField { textField in
textField.addTarget(
self.textFieldObserver,
action: #selector(TextFieldObserver.textFieldDidBeginEditing),
for: .editingDidBegin
)
}
}
}
This works as expected when selecting the field. Could this be modified to select the field without having to enter / tap into it ? So the field appears with any text selected (no focus required) ?
Thankyou

Related

Document is not being marked dirty when element is being changed programmatically

What is wrong with this SwiftUI code?
When the button "Reset Text" is pressed the document data is changed (as seen in the debugger), but the view is not updated.
If the document is then saved, the modified data is not saved.
The method "func fileWrapper" is not called because it does not think that the field "text" has changed.
If characters are typed in the TextEditor so the field is dirty and then saved, the data is saved.
If characters are types in the TextEditor so the field is dirty, then the "Reset Text" button pressed, the TextEditor view is not updated, but the "Reset Text" is saved.
If I wait a while 1 to 3 minutes, the TextEditor will sometimes update to the new text.
import SwiftUI
import UniformTypeIdentifiers
#main
struct DocumentAppApp: App {
var body: some Scene {
DocumentGroup(newDocument: DocumentAppDocument()) { file in
ContentView(document: file.$document)
}
}
}
extension UTType {
static var exampleText: UTType {
UTType(importedAs: "com.example.plain-text")
}
}
class DocumentAppDocument: ObservableObject, FileDocument {
#Published var text: String
init(text: String = "This is some text") {
self.text = text
}
static var readableContentTypes: [UTType] { [.exampleText] }
required init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
text = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = text.data(using: .utf8)!
return .init(regularFileWithContents: data)
}
}
struct ContentView: View {
#Binding var document: DocumentAppDocument
var body: some View {
VStack {
TextEditor(text: $document.text)
Button(action: {
// document.objectWillChange.send()
document.text = "Reset Text"
}, label: { Text("Reset Text") })
.frame(width: 200, height: 50)
.border(Color.black)
.padding()
}
}
}
I tried added a call to document.objectWillChange.send() and it made no difference.
I tried as both a struct and a class
I've tried various combinations of #State, #StateObject and #Binding.
It looks like I could call .save on the DocumentGroup, but I couldn't figure out how to get ahold of it.
Test case
Create a new document.
Type in some text -> test is there
Save document.
Open document -> text is there.
Press "Reset Text" Button. Nothing happens.
Save.
Open -> previous text is there.
Type some text, press button -> nothing happens.
Save.
Open -> "Reset Text: text is present.
I found the problem.
class DocumentAppDocument: ObservableObject, FileDocument {
Should not be a class and ObservableObject.
The Binding of the class and DocumentAppDocument and the #Published don't work with each other.
A workaround I used when I had a similar issue was to add a Boolean property to the document struct that stores whether the document has been edited. Changing the value from false to true should mark the document as changed. In your case you would set the property to true when clicking the Reset Text button.

SwiftUI view get views in the body or define view as property

I am trying to get the view inside the body for observing purpose, but looking for different ways the view inside the body can be accessed.
struct ContentView: View {
#State var userNameText: String
var body: some View {
startObservingInput()
return TextField("hello", text: $userNameText)
}
func startObservingInput() {
// How do we get TextField instance here.
// Option 1 - Pass as parmeter here.
// Option 2 - Is there a way to get view from body ex: self.body.textFieldView
// Option 3 - can create as a property in CotentView but the text binding refers to self which will not be allowed before its initalized so that will fail to compile
//var textField = TextField("hello", text: $userNameText)
}
}
Option 1 is simple, where we pass the TextField view.
Option 2 is something I am looking for, if we can get any view inside the hierarchy. In this case Text Field.
Option 3, Tried to create a property but I get the following error.
ex:
struct ContentView: View {
#State var userNameText: String
var textField = TextField("hello", text: $userNameText)
......
}
Cannot use instance member '$userNameText' within property initializer; property initializers run before 'self' is available
SwiftUI is different from what you're probably used to. Unlike UIKit, you don't "store" views in properties. There's no delegates either.
Instead, you directly pass in a property — userNameText — that will be linked to the text field's text. Since this updates itself automatically, you can use the onChange modifier to observe changes.
struct ContentView: View {
#State var userNameText: String
var body: some View {
TextField("hello", text: $userNameText)
.onChange(of: userNameText) { newValue in
print("Text changed to: \(newValue)")
}
}
}
Here is what I did, look at it:
//
// ViewProp.swift
// SwiftDemo1
//
// Created by CreoleMacbookPro on 12/19/22.
//
import SwiftUI
struct ViewProp: View {
#State var userNameText: String = " "
var body: some View {
let textField: TextField<Text> = TextField("hello", text: $userNameText)
let simpleText: Text = Text("Hello, World!")
let _ = print(type(of: textField))
startObservingInput(textField: textField)
Button {
userNameText = "Jatin Bhuva"
} label: {
Text("Press Me..")
}
// textField
}
func startObservingInput(textField: TextField<Text>) -> some View {
textField
// How do we get TextField instance here.
// Option 1 - Pass as parmeter here.
// Option 2 - Is there a way to get view from body ex: self.body.textFieldView
// Option 3 - can create as a property in CotentView but the text binding refers to self which will not be allowed before its initalized so that will fail to compile
//var textField = TextField("hello", text: $userNameText)
}
}
struct ViewProp_Previews: PreviewProvider {
static var previews: some View {
ViewProp()
}
}

SwiftUI changing text title of navigationBar Button at the current view to "Back" instead of inheriting the text from previous view title

I have a swiftUI view with navigation links that when i click will navigate to another view . The issue is the second view navigationBa button title still has the title of the previous view instead of a logical back title . How i can have the title as Back with changing the title as "Back" in the first view .
First view navigationBar code: The second view just shows the news website in a WebView.
.navigationBarTitle("Breaking News")
The way i tried is changing the title to this:
.navigationBarTitle("Back")
This will work but the title of the first view changes to "Back" Instead of "Breaking News"
Is there any way i can fix this
An alternative approach is to hide the back button and create your own back button like this:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination : SomeView()) {
Text("Open")
}
.navigationBarTitle("Breaking News")
}
}
}
// Use navigationBarItems for creating your own bar item.
struct SomeView : View {
#Environment(\.presentationMode) var mode
var body : some View {
Text("Hello, World!")
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading:
Button(action : {
self.mode.wrappedValue.dismiss()
}){
Text("\(Image(systemName: "chevron.left"))Back")
})
}
}
The accepted answer looks glitchy, it removes a lot of standard behaviour and animations, including long press gesture.
Consider using custom backBarButtonTitle modifier:
struct FirstView: View {
var body: some View {
Text("First view")
.backBarButtonTitle("Back")
}
}
It sets the backButtonTitle property to the topmost UINavigationItem in stack. Be sure to use this modifier on the view, whose title you want to change, see documentation.
Here is the implementation of the backBarButtonTitle:
import SwiftUI
import UIKit
extension View {
func backBarButtonTitle(_ title: String) -> some View {
modifier(BackButtonModifier(title: title))
}
}
// MARK: - BackButtonModifier
struct BackButtonModifier: ViewModifier {
let title: String
func body(content: Content) -> some View {
content.background(BackButtonTitleView(title: title))
}
}
// MARK: - BackButtonTitleView
private struct BackButtonTitleView: UIViewRepresentable {
let title: String
func makeUIView(context _: Context) -> BackButtonTitleUIView {
BackButtonTitleUIView(title: title)
}
func updateUIView(_: BackButtonTitleUIView, context _: Context) {}
}
// MARK: - BackButtonTitleUIView
private final class BackButtonTitleUIView: UIView {
// MARK: Lifecycle
init(title: String) {
self.title = title
super.init(frame: .zero)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Internal
override func layoutSubviews() {
super.layoutSubviews()
if didConfigureTitle {
return
}
let topNavigationItem = searchNavigationController(currentResponder: self)?
.topViewController?
.navigationItem
if let topNavigationItem {
topNavigationItem.backButtonTitle = title
didConfigureTitle = true
}
}
// MARK: Private
private let title: String
private var didConfigureTitle = false
private func searchNavigationController(currentResponder: UIResponder) -> UINavigationController? {
if let navigationController = currentResponder as? UINavigationController {
return navigationController
} else if let nextResponder = currentResponder.next {
return searchNavigationController(currentResponder: nextResponder)
} else {
return nil
}
}
}

Binding<String?> on the SwiftUI View TextField

I have the following view model:
struct RegistrationViewModel {
var firstname: String?
}
I want to bind the firstname property in the TextField as shown below:
TextField("First name", text: $registrationVM.firstname)
.textFieldStyle(RoundedBorderTextFieldStyle())
I keep getting an error that Binding is not allowed.
To bind objects your variable needs to conform to one of the new wrappers #State, #Binding, #ObservableObject, etc.
Because your RegistrationViewModel doesn't conform to View the only way to do it is to have your RegistrationViewModel conform to ObservableObject.
class RegistrationViewModel: ObservableObject {
#Published var firstname: String?
}
Once that is done you can call it View using
#ObservedObject var resgistrationVM: RegistrationViewModel = RegistrationViewModel()
or as an #EnvironmentObject
https://developer.apple.com/tutorials/swiftui/handling-user-input
Also, SwiftUI does not work well with optionals but an extension can handle that very easily.
SwiftUI Optional TextField
extension Optional where Wrapped == String {
var _bound: String? {
get {
return self
}
set {
self = newValue
}
}
public var bound: String {
get {
return _bound ?? ""
}
set {
_bound = newValue.isEmpty ? nil : newValue
}
}
}

How to detect changes to the BindableObjects from the View?

In UIKit, I would have code like this:
#IBOutlet weak var itemNameField: UITextField!
#IBAction func itemNameFieldDone(_ sender: UITextField) {
thisItem.myName = sender.text ?? thisItem.myName
thisItem.modified()
}
In the model object:
func modified() {
dateModified = Date()
let cds = FoodyDataStack.thisDataStack
uuidUser = cds.uuidUser
uuidFamily = cds.uuidFamily
}
In SwiftUI:
TextField($thisItem.myName)
Declarative, nice and short. SwiftUI takes care of updating the myName property as the user types in the TextField, but how do I get the dateModified property to update at the same time?
Use the TextField initializer that includes onEditingChanged and include whatever updating code you need in the closure.
TextField($thisCategory.myName, placeholder: nil, onEditingChanged: { (changed) in
self.thisCategory.modified()
self.dataStack.didChange.send(self.dataStack)
}).textFieldStyle(.roundedBorder)
iOS 14
There is a new modifier called onChange to detect changes of any state:
struct ContentView: View {
#State var text: String = ""
var body: some View {
TextField("Title", text: $text)
.onChange(of: text, perform: { value in
print(text)
})
}
}
You can add didSet observer to myName property in your item type declaration and then call modified from there:
var myName: String = "" {
didSet {
self.modified()
}
}