SwiftUI TextField re-renders the view after each character typed. How do I make it to re-render the view only after hitting the Return key? - textfield

The same issue exists in Xcode beta 5 . Example code to illustrate the problem listed below
:
struct ContentView: View {
#State var someText = "Change me!"
#State var someNumber = 123.0
var body: some View {
Form {
// The entire View is re-rendered after each character is typed
TextField("Text", text: $someText,
onEditingChanged: { edit in
print("onEditingChanged executed!")
},
onCommit: {
print("onCommit executed!")
}
)
// When you enter a Double value, the View is not re-rendered until you hit Return
TextField("Number", value: $someNumber, formatter: NumberFormatter())
Spacer()
Text("text: \(self.someText), number: \(self.someNumber)")
}
}
}

SwiftUI Views will be recreated each time any of their #ObservedObjects change or any time their #State changes.
You are passing a two-way binding of the View's #State to the TextField. The TextField updates the value of this two-way binding each time its input value changes. This will update the #State of the View and thus the View will be recreated.
I don't think you need to avoid recreating the View to get the effect you want. Recreating Views is the backbone of SwiftUI.
You can add an additional #State property, maybe presentedText, which you update to the value of someText inside of onEditingChanged.
For example (only changed for "text"):
struct ContentView: View {
#State var someText = "Change me!"
#State var someNumber = 123.0
#State var presentedText = someText
var body: some View {
Form {
// The entire View is re-rendered after each character is typed
TextField("Text", text: $someText,
onEditingChanged: { edit in
print("onEditingChanged executed!")
self.presentedText = someText
},
onCommit: {
print("onCommit executed!")
}
)
// When you enter a Double value, the View is not re-rendered until you hit Return
TextField("Number", value: $someNumber, formatter: NumberFormatter())
Spacer()
Text("text: \(self.presentedText), number: \(self.someNumber)")
}
}
}

Related

Better way to raise number pad SwiftUI

Upon navigating to a view, I want the number pad to already be raised. Right now I have a solution that works the first time (albeit with a delay) but fails to raise the number pad if the user navigates back a second time. Is there a better way to raise the number pad in SwiftUI (or to have it always up)?
Example Code:
struct ParentView: View {
#FocusState var numberPadFocused: Bool
#State var isActive: Bool = false
var body: some View {
NavigationView {
VStack {
Button {
numberPadFocused = true
isActive = true
print("Called")
} label: {
Text("Navigate")
}
NavigationLink(destination: ChildView(focusState: $numberPadFocused), isActive: $isActive) { Color.white }
}
}
}
}
struct ChildView: View {
#State var text: String = ""
#FocusState.Binding var focusState: Bool
var body: some View {
TextField("Enter Number...", text: $text)
.keyboardType(.numberPad)
.focused($focusState)
}
}

SwiftUI NavigationLink not displaying view until physical rotation then all is ok?

The opening view of App is blank with a Back Button. However, the intended view will appear after complete rotation of physical device (before touching anything) or by tapping the Back Button on the displayed empty view.
This is a weird bug. After that rotation (to landscape and back to protrait) everything is peachy-keen-o.
I am aware that adding: .navigationViewStyle(StackNavigationViewStyle()) to the NavigationView will sorta resolve the problem. But this solution is a poor fix for my App's needs.
Is there a command to intentionally rotate device in code to resolve this? Or some other fix to get that first view to appear without counting on the user to rotate the device?
I've tried various solutions with no luck.
Below is a quick setup of the problem.
struct FirstView: View {
#Binding var viewNum: Int?
var body: some View {
VStack {
Text("View One")
.padding(20)
Button {
viewNum = 2
} label: {
Text("Go to 2nd view")
}
}
.onAppear {
viewNum = 1
}
}
}
struct MainView: View {
#State var selection: Int? = 1
var body: some View {
// VStack {
NavigationView {
List {
NavigationLink("First View", tag: 1, selection: $selection){ FirstView(viewNum: $selection)}
NavigationLink("Second View", tag: 2, selection: $selection){
Text("View 2").padding(20)
Button {
selection = 1
} label: {
Text("Go to first view")
}
}
}
}
// .navigationViewStyle(StackNavigationViewStyle())
//}
}
}

How do I change the variable value in a different file in Swiftui

I set a variable in the contentview #State var shouldShowModal = false, i want to change it once i press a button shouldShowModal = false. I keep getting Cannot find 'shouldShowModal' in scope.
Here is a working example, passing the value through #Bindings. Read more about #Binding here, or the official documentation.
This means that you can now do shouldShowModal = false with the binding, which will also update the body of the original view containing #State.
struct ContentView: View {
#State private var shouldShowModal = false
var body: some View {
VStack {
Text("Hello world!")
.sheet(isPresented: $shouldShowModal) {
Text("Modal")
}
OtherView(shouldShowModal: $shouldShowModal)
}
}
}
struct OtherView: View {
#Binding var shouldShowModal: Bool
var body: some View {
VStack {
Text("Should show modal: \(shouldShowModal ? "yes" : "no")")
Toggle("Toggle modal", isOn: $shouldShowModal)
}
}
}

Can I disable Navigationlink for a specified set of cells in my SwiftUI list?

I am working on a contacts app in SwiftUI for work which will display hundreds of phone numbers. Some contacts have a detail view with address and alternate numbers, while some contacts do not.
For example, Harry Smith will have basic phone extension listed in row while his cell number and address are listed in detail view.
But the number for the Second Floor Bathroom only needs the basic phone extension in the row, a detail view containing cell number and address for the bathroom is unnecessary.
How do I disable navigation link for those contacts who do not have pertinent detail info?
I prefer to add NavigationLink on condition.
I will have a "Cell" with some contents. (image/file detail)
A - build a "content" View:
struct MediumFileContentRow: View {
var file: MediumFile
var body: some View {
VStack (alignment: .leading) {
let (name, attribs) = file.getInfo() // get from model..
Text(name)
Spacer()
Text(attribs)
}// VStack
}
}
B - Your cell (MediumFileRow) will conditionally add link (NavigationLink) "around" our content view, and will give back different Views using AnyView:
struct MediumFileRow: View {
var file: MediumFile
var body: some View {
let enabled = NavigationLink(destination: Detail(t: file.name!)) {
MediumFileContentRow(file: file)
}
let disabled = MediumFileContentRow(file: file)
return file.isEmpty ? AnyView(disabled) : AnyView(enabled)
}
All (almoast..) The code:
struct MediumFileContentRow: View {
var file: MediumFile
var body: some View {
VStack (alignment: .leading) {
let (s,sd) = file.displayed()
Text(s)
Spacer()
Text(sd).font(.system(size: 9.0))
}// VStack
}
}
struct MediumFileRow: View {
var file: MediumFile
var body: some View {
let enabled = NavigationLink(destination: Detail(t: file.name!)) {
MediumFileContentRow(file: file)
}
let disabled = MediumFileContentRow(file: file)
return file.isEmpty ? AnyView(disabled) : AnyView(enabled)
}
...
..
In list You will have:
var body: some View {
NavigationView{
....
List {
ForEach(mediumFiles, id: \.self) { file in
MediumFileRow(file: file)
}
}
......
advantage: You can easily customise "disabled" cells.

How can I present an overlay above the keyboard in SwiftUI?

I have a screen in my app that automatically displays a keyboard when the screen is presented. The screen allows users to enter their mobile number. In order to select a dialling code, the user needs to tap a button which will then trigger the presentation of an overlay.
Problem
The overlay is being presented, but it's showing up behind the currently present keyboard.
Question
How can I make this overlay be the very top view?
There is no way for me to use the zIndex modifier on the keyboard for obvious reasons. I'm wondering if there is a way to make the overlay the top view when it's about to be presented, or if the overlay can be added to the window.
Thanks in advance
You should probably only have one source of input at any given time — either the keyboard should be presented to enter a number, or the overlay should be presented to pick the dialing code, not both. Here's an example which hides the keyboard when overlay appears, and vice versa. (Keyboard dismissal code from this answer.)
struct ContentView: View {
#State private var number = ""
#State private var showingOverlay = false
var body: some View {
GeometryReader { proxy in
ZStack(alignment: .top) {
// Just here to force ZStack to use the whole screen
Rectangle()
.fill(Color.clear)
VStack(alignment: .leading) {
Button("Select Dialing Code") {
UIApplication.shared.endEditing()
self.showingOverlay = true
}
TextField("Enter your number", text: self.$number, onEditingChanged: {
if $0 { self.showingOverlay = false }
})
.keyboardType(.phonePad)
}
Overlay(showing: self.$showingOverlay)
.frame(height: 400)
.offset(x: 0, y: proxy.size.height + (self.showingOverlay ? -300 : 100))
.animation(.easeInOut)
}
}
}
}
struct Overlay: View {
#Binding var showing: Bool
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 15)
.fill(Color(.systemGray4))
Button("Dismiss") {
self.showing = false
}
}
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}