TextField is not moved back down with keyboard avoidance on - ios

Does any know what I've done wrong here?
Problem
When I tap on the text field and the keyboard appears. Tap on the navgation link to get to the second screen. Then go back, the text field has not returned to the "non-focused" position.
iOS: 16
Sim: iPhone 14 Pro
Expectation
I am expecting to see the text field back at its original starting place. That is, when I tap the field, the keyboard avoidance causes that field to move up. Then I tap return on the keyboard to dismiss the keyboard, the text field returns to its starting position. When I navigation between view, I expect the same behaviour because the keyboard has been dismissed.
Steps
Tap the textField (assuming software keyboard is on)
Tap the navigationLink
Tap Back
import SwiftUI
struct ContentView: View {
#State private var text: String = "Hello, world!"
var body: some View {
NavigationView {
VStack {
NavigationLink("Hello", destination: { Text("World") })
TextField("Whoops", text: $text)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Try hide keyboard first and then navigate to the destination as following : -
#State private var text: String = "Hello, world!"
#State var action : Int? = 0
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("World"), tag: 1, selection: $action) {
EmptyView()
}
Button {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
action = 1
} label: {
Text("Hello")
}
TextField("Whoops", text: $text)
}
}
}

You should change the focus of your textField before pushing another View. Since iOS 15 we can user #FocusState which does exactly that:
#State private var text: String = "Hello, world!"
#State private var pushView:Bool = false
#FocusState private var isFocused: Bool
var body: some View {
NavigationView {
VStack {
Button {
isFocused = false
pushView = true
} label: {
Text("Hello")
}
NavigationLink(isActive: $pushView) {
Text("World")
} label: {
EmptyView()
}
TextField("Whoops", text: $text).onSubmit {
pushView = true
}.focused($isFocused)
}
}
}
As for the Still view you can try implementing .ignoresSafeArea(.keyboard)

Related

SwiftUI - How to focus a TextField within a sheet as it is appearing?

I have a search TextField within a View that is triggered to appear within a sheet on top of the ContentView.
I'm able to automatically focus this TextField when the sheet appears using #FocusState and onAppear, however, I'm finding that the sheet needs to fully appear before the TextField is focused and the on screen keyboard appears.
This feels quite slow and I've noticed in many other apps that they are able to trigger the on screen keyboard and the sheet appearing simultaneously.
Here is my code:
struct ContentView: View {
#State var showSearch = false
var body: some View {
Button {
showSearch = true
} label: {
Text("Search")
}
.sheet(isPresented: $showSearch) {
SearchView()
}
}
}
struct SearchView: View {
#State var searchTerm = ""
#FocusState private var searchFocus: Bool
var body: some View {
TextField("Search", text: $searchTerm)
.focused($searchFocus)
.onAppear() {
searchFocus = true
}
}
}
Is there a different way to do this that will make the keyboard appear as the sheet is appearing, making the overall experience feel more seamless?
Here is an approach with a custom sheet that brings in the keyboard somewhat earlier. Not sure if its worth the effort though:
struct ContentView: View {
#State var showSearch = false
var body: some View {
ZStack {
Button {
withAnimation {
showSearch = true
}
} label: {
Text("Search")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
if showSearch {
SearchView(isPresented: $showSearch)
.transition(.move(edge: .bottom))
}
}
// .sheet(isPresented: $showSearch) {
// SearchView()
// }
}
}
struct SearchView: View {
#Binding var isPresented: Bool
#State var searchTerm = ""
#FocusState private var searchFocus: Bool
var body: some View {
Form {
TextField("Search", text: $searchTerm)
.focused($searchFocus)
Button("Close") {
searchFocus = false
withAnimation {
isPresented = false
}
}
}
.onAppear() {
searchFocus = true
}
}
}

SwiftUI Bizarre Picker Behavior with Modal Sheets

I'm experiencing a truly bizarre behavior with an app that has pickers in a view that
calls a map as a sheet modal. The pickers are not directly involved in calling the map -
but set conditions for the annotations that will be displayed. And in the simple example
I have included below, it is clear the issue has nothing to do with the map - the
problem behavior is present with a simple text view in the modal.
The issue: if the user swipes to dismiss the modal it appears to always work as expected.
If the user taps a button to dismiss the modal with either the environment dismiss or
through a binding to the #State that calls the view, then after the second showing of the
modal, you can no longer raise the picker - the displayed value just turns color - as if
a color toggle on tap. I've also tried showing the modal as full screen, as an animation
and as a transition. I get the same result every time. I'm at the point where I think this
must be an Xcode bug, but I hope someone can show me a solution. The same behavior exists
in Preview, Simulator and a real Device.
Here is a very stripped down example which demonstrates the issue:
enum StorageKeys: String {
case appChosenFuel, appChosenState
}//enum
struct ContentView: View {
#State private var showGroupMapView: Bool = false
#State private var showTooBigAlert: Bool = false
#State private var justANumber: Int = 500
var body: some View {
NavigationView {
VStack {
Text("Picker plus Sheet Test")
.padding()
FuelPickerView()
}
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
if justANumber > 1000 {
showTooBigAlert = true
} else {
withAnimation {
showGroupMapView.toggle()
}
}
} label: {
Image(systemName: "map")
.font(.system(size: 20))
.frame(width: 40, height: 40)
}
.sheet(isPresented: $showGroupMapView, onDismiss: {
print("you dismissed me")
}) {
GroupMapView()
}
}//toolbar group
}//toolbar
}//nav
}//body
}//sruct
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct GroupMapView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
ZStack {
Text("This is the map view")
VStack {
HStack {
Spacer()
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: "gear")
.padding(.trailing, 20)
.padding(.top, 20)
}
}
Spacer()
}
}//z
}
}//struct
class FuelPickerViewModel: ObservableObject {
struct FuelItem: Identifiable, Hashable {
let id = UUID()
let name: String
let initials: String
}
#Published var fuelItems = [
FuelItem(name: "Biodiesel (B20 and above)", initials: "BD"),
FuelItem(name: "Compressed Natural Gas", initials: "CNG"),
FuelItem(name: "Ethanol (E85)", initials: "E85"),
FuelItem(name: "Electric", initials: "ELEC"),
FuelItem(name: "Hydrogen", initials: "HY"),
FuelItem(name: "Liquified Natural Gas", initials: "LNG"),
FuelItem(name: "Liquified Petroleum Gas (Propane)", initials: "LPG")
]
}//class
struct FuelPickerView: View {
#AppStorage(StorageKeys.appChosenFuel.rawValue) var appChosenFuel = "ELEC"
#AppStorage(StorageKeys.appChosenState.rawValue) var appChosenState = "CO"
#StateObject var fuelPickerVM = FuelPickerViewModel()
var body: some View {
return VStack {
Picker("Fuel", selection: $appChosenFuel) {
ForEach(fuelPickerVM.fuelItems, id: \.self) {
Text($0.initials)
}
}
}
}//body
}//struct
And after the second modal display/dismiss with the button, tapping the picker does
nothing except change the background color:
Any guidance would be appreciated: Xcode 13.2.1 iOS 15.2

SwiftUI Keyboard Toolbar Scope

Say we have the following view of two text fields:
struct ContentView: View {
#State private var first = ""
#State private var second = ""
var body: some View {
VStack {
TextField("First", text: $first)
.toolbar {
ToolbarItem(placement: .keyboard) {
Button("Test") { }
}
}
TextField("Second", text: $second)
}
}
}
The toolbar modifier is applied only to the "first" text field. My expectation is therefore that it only shows up on the keyboard, when the "first" text field is in focus.
What happens in practice though, it that it also shows up when the "second" text field is in focus.
Is this intended behaviour? And if so, how can I have different keyboard toolbars for different text fields?
The only thing that I've found so far that solves this problem works, but doesn't feel right. It also generates some layout constraint warnings in the console.
If you wrap each TextField in a NavigationView each `TextField will have its own context and thus its own toolbar.
Something like this:
struct ContentView: View {
#State private var first = ""
#State private var second = ""
var body: some View {
VStack {
NavigationView {
TextField("First", text: $first)
.toolbar {
ToolbarItem(placement: .keyboard) {
Button("Test") { }
}
}
}
NavigationView {
TextField("Second", text: $second)
}
}
}
}

SwiftUI, cannot dismiss sheet after the keyboard

In SwiftUI, I open a CommentsView sheet like this:
#State private var selectedCategory: Category?
Button(category.name) {
selectedCategory = category
}
.sheet(item: $selectedCategory) { category in
CommentsView(category: category)
}
CommentsView:
struct CommentsView: View {
#Environment(\.presentationMode) private var presentationMode
#State private var enteredComment: String = ""
let category: Category
var body: some View {
VStack {
TextField("Add a comment", text: $enteredComment)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button("Close") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
The problem is: I cannot dismiss the CommentsView after I focus on the text field and see the keyboard. Before focusing the "Close" button works as expected.
Change the position of .sheet to be at the top VStack/HStack or ZStack, NOT at the Button View
.sheet(item: $selectedCategory) { category in
CommentsView(category: category)
}

SwiftUI, weird NavigationLink behavior when working with actionSheet

I want to detect if the user meets the prerequisite first before I let him/her in. If the prerequisite is not met, the app will pop an actionSheet and show the user some ways to unlock the feature.
It works perfectly fine when I tap on the text. But when I tap on the blank place on the list. It just skip the Binding. And the weird thing is that in my actually project, the Binding becomes "true" even if I only set it to false.
Here's the question. Am I using the correct approach or did I miss anything? Or is this a bug?
Thank you.
struct ContentView: View {
#State var linkOne = false
#State var linkTwo = false
#State var linkThree = false
#State var actionOne = false
#State var actionTwo = false
#State var actionThree = false
var body: some View {
NavigationView{
List{
NavigationLink("Destination View One", destination: DestOneView(), isActive: self.$linkOne)
.actionSheet(isPresented: self.$actionOne) { () -> ActionSheet in
ActionSheet(title: Text("Hello"), message:Text("This is weird"), buttons: [ActionSheet.Button.cancel()])
}.onTapGesture {
self.actionOne = true
// self.linkOne = true
}
NavigationLink("Destination View Two", destination: DestTwoView(), isActive: self.$linkTwo)
.actionSheet(isPresented: self.$actionTwo) { () -> ActionSheet in
ActionSheet(title: Text("Hello"), message:Text("This is weird"), buttons: [ActionSheet.Button.cancel()])
}
.onTapGesture {
self.actionTwo = true
// self.linkTwo = true
}
NavigationLink("Destination View Three", destination: DestThreeView(), isActive: self.$linkThree)
.actionSheet(isPresented: self.$actionThree) { () -> ActionSheet in
ActionSheet(title: Text("Hello"), message:Text("This is weird"), buttons: [ActionSheet.Button.cancel()])
}
.onTapGesture {
self.actionThree = true
// self.linkThree = true
}
}
}
}
}
Three other views.
struct DestOneView: View {
var body: some View {
Text("First View")
}
}
struct DestTwoView: View {
var body: some View {
Text("Second View")
}
}
struct DestThreeView: View {
var body: some View {
Text("Third View")
}
}
Generally overriding gestures does not work well within the List. One of the solutions can be to use a Button to present a NavigationLink:
import SwiftUI
struct ContentView: View {
#State private var linkOne = false
...
var body: some View {
NavigationView {
List {
NavigationLink(destination: SomeView(), isActive: $linkOne) {
EmptyView()
}
Button(action: {
// here you can perform actions
self.linkOne = true
}, label: {
Text("Some text!")
})
Spacer()
}
}
}
}
When I came back and tested my the code. The solution didn't really work. May be because of the List's bug. People on another post said that the List in the new view only show once if the sheet is inside the List. So I only got an empty List in the new view when I tap the button in Xcode Version 11.5. For some reasion, if I use NavigationView, all contents are shrunk into the middle of the view instead of aligning to the top.
My work around is to set the Binding in .onAppear. It pops the actionSheet when the view loads. And then use the presentationMode method to return to the previous view.
#Environment(\.presentationMode) var presentationMode
.
.
.
ActionSheet.Button.default(Text("Dismiss"), action: {
self.presentationMode.wrappedValue.dismiss()}

Resources