On SwiftUI's TextField you can set an action for the return/submit button of the keyboard using the .onSubmit() modifier. How do you achieve the same with TextEditor? (.onSubmit() doesn't seem to work.)
You can use on change for the bound variable for the TextEditor like this:
TextEditor(text: $text)
.onChange(of: text) { _ in
if !text.filter({ $0.isNewline }).isEmpty {
print("Found new line character")
}
}
Realize that TextEditor does not have a submit button, per se. It is meant to accept an unlimited amount of all kinds of text.
Another way to know when the user created a new line:
TextEditor(text: $text)
.onChange(of: text) { string in
for char in string
{
if char() == "\n"
{
print("Found new line character")
}
}
}
Yrb answer and this one works well but once there's a new line found and every time a new character is added to the TextEditor you're doing the same action: print("Found new line character")
If you want to know every time user presses enter or creates a new line this one is a better solution for you:
TextEditor(text: $text)
.onChange(of: text) { string in
if string.last == "\n"
{
print("Found new line character")
}
}
Here is your solution:
onCommit: TextField calls onCommit closure when the user presses the Return key.
Related
I am using AttributedString in SwiftUI (Mac application) to customize the appearance of portions of a long string. I'm displaying the text formatted successfully and it appears correct.
My code looks like this:
struct TextView: View {
var body: some View {
ScrollView {
Text(tag())
}.padding()
}
func tag() -> AttributedString {
// code which creates the attributed string and applies formatting to various locations
}
}
At this point I want to add "touch points" ("interactive points") to the text (imagine hyperlinks) which will provide additional information when particular locations (pieces of text) are interacted with.
Ive seen some similar questions describing usage (or combinations) of NSTextAttachment , NSAttributedStringKey.link , UITextViewDelegate
see:
NSAttributedString click event in UILabel using Swift
but this isn't (or at least not obvious) the idiomatic "SwiftUI" way and seems cumbersome.
I would want to tag the string with the formatting while adding the "Attachment" which can be recognized in the view event handler:
func tag() -> AttributedString {
// loose for this example
var attributedString = AttributedString("My string which is very long")
for range in getRangesOfAttributes {
attributedString[range].foregroundColor = getRandomColor()
attributedString[range].attachment = Attachment() <<<<<<< this is missing, how do I tag this portion and recognize when it got interacted with in the View
}
}
func getRangesOfAttributes() -> ClosedRange<AttributedString.Index> {
... returns a bunch of ranges which need to be tagged
}
// the view can now do something once the attachment is clicked
var body: View {
Text(tag())
.onClickOfAttachment(...) // <<<< This is contrived, how can I do this?
}
I am trying to create a simple text field that allows inputs with newline sequences and outputs the text with line breaks.
struct Foo: View {
#State private var inputText: String
var body: some View {
VStack {
TextField("foo", text: $inputText)
Text(inputText)
}
}
}
Newline sequences work fine in Text("hello\nworld") but will simply display the "\n" instead of a newline if used with TextField. I have also tried accessing the wrappedValue of the bound string to no avail. What am I missing here?
Edit: I'm not interested in a multiline input. I'm interested in a single line input like "hello\nworld". However, when I capture "hello\nworld" in a bound variable through a TextField, I am not seeing the output I expect, which is hello then a newline then world. Instead, I am seeing the string exactly as it was input: hello\nworld.
It sounds like you want to take a user-entered String of "Hello\nworld" (which would read as an escaped string as "Hello\nworld") and replace a literal (non-escaped) "\n" with the a newline character.
This would do that:
struct ContentView: View {
#State private var inputText: String = ""
var body: some View {
VStack {
TextField("foo", text: $inputText)
Text(inputText.replacingOccurrences(of: "\\n", with: "\n"))
}
}
}
As far as I read about conditional Views this code should work. But it doesn't.
struct Consts {
static let myCondition = false //no difference if true or false
}
struct ContentView: View {
#State var toggle: Bool = false
var body: some View {
List() {
Text("Short Text is in first line")
Text("Second Line Text is a little longer but not much")
if Consts.myCondition {
Text("This is a conditional text. As in: when the user hasn't purchased the option he / she don't need a hint how to use this feature.")
// } else {
// Text("else doesn't help either.")
}
Toggle("I also have a toggle but it has nothing to do with this.", isOn: $toggle)
Text("Here we have a longer Text. Dont know what to type any more.")
Text("More text which is longer than a few lines.")
Text("Not so long Text")
}
.navigationTitle("Hints & Settings")
}
}
It compiles without warnings or errors. It loads up and displays fine, on simulator and on device. But every time I scroll the List upwards from the end, as soon as this if condition { Text() } should become visible the app crashes with
Fatal error: file SwiftUI, line 0
2021-03-07 06:36:26.548126+0100 WTFif WatchKit Extension[23163:641812] Fatal error: file SwiftUI, line 0
This is not limited to watchOS. It reproduces the same way in iOS, just the Texts have to be longer so that the if condition { Text() } becomes invisible when scrolling.
I have worked around the error with an array, conditional ranges and two ForEach() blocks.
struct ContentView: View {
let myHints = ["Short Text is in first line",
"Second Line Text is a little longer but not much",
"This is a conditional text. As in: when the user hasn't purchased the option he / she don't need to hint how to use this feature.",
"Here we have a longer Text. Dont know what to type any more.",
"More text which is longer than a few lines.",
"Not so long Text"]
var myUpperRange: ClosedRange<Int> {
if Consts.myCondition {
return 0...1
} else {
return 0...2
}
}
var myLowerRange: ClosedRange<Int> {
return 3...5
}
#State var toggle: Bool = false
var body: some View {
List() {
ForEach (myUpperRange, id: \.self) { i in
Text(myHints[i])
}
Toggle("I also have a toggle but it has nothing to do with this.", isOn: $toggle)
ForEach (myLowerRange, id: \.self) { i in
Text(myHints[i])
}
}
.navigationTitle("Hints & Settings")
}
}
My question basically is: am I not getting it or did I find a bug in Xcode / SwiftUI? Should my code above work? What could I have done different to make it work with the simple list of Texts?
Background: I also have a TabView with an if condition { MyTab() } which works without crashing. Do I have to worry that this might crash in the same way? Should I work around this as well before shipping?
PS: I am using Xcode 12.4 (12D4e)
Apparently this has been fixed by iOS 15 Beta 4: on my test device I had the error prior to the Beta 4 update, using a test app compiled with Xcode 13 Beta 3. After the update to iOS 15 Beta 4 the error is gone.
So I think it’s reasonable to say that the update to iOS 15 Beta 4 did fix the error.
i have a form that works correctly but would like to add a toggle to indicated if a garment is used or new. if it is new toggle on, if its used a condtion box will show where the user can input the garments condition.
when then toggle is on, i would like to set the value to static string value of "New"
code i have tried:
Form{
VStack {
Toggle(isOn: self.$itemNew) {
Text("is the item new?")
self.$Cond == "New"
}
if !itemNew {
TextField("Item Condition:", text: self.$Cond) {}
}
}
}
that code didnt seem to work, any pointers on where i should be looking to solve this one?
Thanks
You don't have to use a $, you can create a Binding manually like this:
Toggle(isOn: Binding(get: {self.cond == "New" ? true : false},
set: {
if newValue == true{
self.cond == "New"
}else{
self.cond == "Not really new"
}
})) {
Text("is the item new?")
}
And the self.cond is still must be a #State variable to trigger View update its visual presentation.
But your case isn't look very ordinary for that instrument. Is there other possible values of the string?
[RESOLVED]
I am using a codable struct which stores the object values retrieved from an API call so I have amended my TextField using Cenk Belgin's example, I've also removed extra bits I've added in so if anyone else is trying to do the same thing then they won't have pieces of code from my app that aren't required.
TextField("Product Code", text: $item.ProdCode)
.onReceive(item.ProdCode.publisher.collect()) {
self.item.ProdCode = String($0.prefix(5))
}
Here is one way, not sure if it was mentioned in the other examples you gave:
#State var text = ""
var body: some View {
TextField("text", text: $text)
.onReceive(text.publisher.collect()) {
self.text = String($0.prefix(5))
}
}
The text.publisher will publish each character as it is typed. Collect them into an array and then just take the prefix.
From iOS 14 you can add onChange modifier to the TextField and use it like so :
TextField("Some Placeholder", text: self.$someValue)
.onChange(of: self.someValue, perform: { value in
if value.count > 10 {
self.someValue = String(value.prefix(10))
}
})
Works fine for me.
You can also do it in the Textfield binding directly:
TextField("Text", text: Binding(get: {item.ProCode}, set: {item.ProCode = $0.prefix(5).trimmingCharacters(in: .whitespacesAndNewlines)}))