SwiftUI, cannot dismiss sheet after the keyboard - ios

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)
}

Related

TextField is not moved back down with keyboard avoidance on

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)

SwiftUI Modal Inherits SearchBar during Sheet Presentation

Consider the following example with a list and a button wrapped in a HStack that opens up a sheet:
struct ContentView: View {
#State var text: String = ""
#State var showSheet = false
var body: some View {
NavigationView {
List {
HStack {
button
}
Text("Hello World")
}
.searchable(text: $text)
}
}
var button: some View {
Button("Press", action: { showSheet = true })
.sheet(isPresented: $showSheet) {
modalView
}
}
var modalView: some View {
NavigationView {
List {
Text("Test")
}
}
}
}
On press of the button, a modal is presented to the user. However, the searchable modifier gets passed to the modal, see this video.
Now if the HStack is removed, everything works fine:
List {
button
Text("Hello World")
}
In addition, everything works also fine if the modal is not a NavigationView:
var modalView: some View {
List {
Text("Test")
}
}
Does somebody know what the problem here might be or is it once again one of those weird SwiftUI bugs?
putting the sheet, outside of the button and the List, works for me. I think .sheet is not meant to be inside a List, especially where searchable is operating.
struct ContentView: View {
#State var text: String = ""
#State var showSheet = false
var body: some View {
NavigationView {
List {
HStack {
button
}
Text("Hello World")
}
.searchable(text: $text)
}
.sheet(isPresented: $showSheet) {
modalView
}
}
var button: some View {
Button("Press", action: { showSheet = true })
}
var modalView: some View {
NavigationView {
List {
Text("Test")
}
}
}
}
Another workaround is to use navigationBarHidden = true, but then you must live without the navigation bar in the sheet view.
var modalView: some View {
NavigationView {
List {
Text("Test")
}
.navigationBarHidden(true)
}
}
Btw, on iPadOS it helps to use .searchable(text: $text, placement: .sidebar)

SwiftUI can't hide navigationBar on pushed view when previous view search bar active

I have a problem when pushing to a new view with a search bar active.
In the pushed view I want the navigation bar to be hidden. It works in all cases except when the search field is active on the pushing view AND the navigation style is StackNavigationViewStyle.
In the example project below if you select a row, the new view is pushed and the navigation bar is hidden as expected. However, if you first select the search bar to make it active and then press a row, the navigation bar is no longer hidden on the pushed view.
Removing the StackNavigationViewStyle, everything will work fine however I need to have StackNavigationViewStyle.
struct ContentView: View {
#State var searchString = ""
var body: some View {
NavigationView {
List {
NavigationLink {
PushedView()
} label: {
Text("Press Me")
}
}
.listStyle(PlainListStyle())
.searchable(text: $searchString)
.navigationTitle("First View")
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct PushedView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
Button {
self.presentationMode.wrappedValue.dismiss()
} label: {
Text("Pop View")
}
.navigationTitle("Second View")
.navigationBarHidden(true)
}
}
This might be a solution for now:
import SwiftUI
struct ContentView: View {
#State var searchString = ""
#FocusState private var focusedField: Bool
#State private var isHidden: Bool = false
var body: some View {
NavigationView {
List {
NavigationLink { PushedView(isHidden: $isHidden).onAppear { isHidden = true}.onDisappear {
isHidden = false
} } label: { Text("Press Me") }
}
.navigationTitle("First View")
.searchable(text: $searchString)
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct PushedView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#Binding var isHidden: Bool
var body: some View {
Button {
self.presentationMode.wrappedValue.dismiss()
} label: {
Text("Pop View")
}
.navigationTitle("Second View")
.navigationBarHidden(isHidden)
}
}

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 transition from modal sheet to regular view with Navigation Link

I'm working with SwiftUI and I have a starting page. When a user presses a button on this page, a modal sheet pops up.
In side the modal sheet, I have some code like this:
NavigationLink(destination: NextView(), tag: 2, selection: $tag) {
EmptyView()
}
and my modal sheet view is wrapped inside of a Navigation View.
When the value of tag becomes 2, the view does indeed go to NextView(), but it's also presented as a modal sheet that the user can swipe down from, and I don't want this.
I'd like to transition from a modal sheet to a regular view.
Is this possible? I've tried hiding the navigation bar, etc. but it doesn't seem to make a difference.
Any help with this matter would be appreciated.
You can do this by creating an environmentObject and bind the navigationLink destination value to the environmentObject's value then change the value of the environmentObject in the modal view.
Here is a code explaining what I mean
import SwiftUI
class NavigationManager: ObservableObject{
#Published private(set) var dest: AnyView? = nil
#Published var isActive: Bool = false
func move(to: AnyView) {
self.dest = to
self.isActive = true
}
}
struct StackOverflow6: View {
#State var showModal: Bool = false
#EnvironmentObject var navigationManager: NavigationManager
var body: some View {
NavigationView {
ZStack {
NavigationLink(destination: self.navigationManager.dest, isActive: self.$navigationManager.isActive) {
EmptyView()
}
Button(action: {
self.showModal.toggle()
}) {
Text("Show Modal")
}
}
}
.sheet(isPresented: self.$showModal) {
secondView(isPresented: self.$showModal).environmentObject(self.navigationManager)
}
}
}
struct StackOverflow6_Previews: PreviewProvider {
static var previews: some View {
StackOverflow6().environmentObject(NavigationManager())
}
}
struct secondView: View {
#EnvironmentObject var navigationManager: NavigationManager
#Binding var isPresented: Bool
#State var dest: AnyView? = nil
var body: some View {
VStack {
Text("Modal view")
Button(action: {
self.isPresented = false
self.dest = AnyView(thirdView())
}) {
Text("Press me to navigate")
}
}
.onDisappear {
// This code can run any where but I placed it in `.onDisappear` so you can see the animation
if let dest = self.dest {
self.navigationManager.move(to: dest)
}
}
}
}
struct thirdView: View {
var body: some View {
Text("3rd")
.navigationBarTitle(Text("3rd View"))
}
}
Hope this helps, if you have any questions regarding this code, please let me know.

Resources