Disable keyboard avoidance in a bottom sheet - ios

TLDR: The view modifier .ignoresSafeArea(.keyboard) does not appear to work when used inside a bottom sheet. Is there a workaround?
In a SwiftUI View, tapping a TextField invokes the keyboard and the Textfield then moves upwards to avoid the keyboard.
struct ContentView: View {
#State var mytext: String = "Some text"
var body: some View {
VStack {
Spacer()
TextField("abc", text: $mytext)
Spacer()
}
}
}
This keyboard avoidance behaviour can be disabled by adding the .ignoresSafeArea modifier
struct ContentView: View {
#State var mytext: String = "Some text"
var body: some View {
VStack {
Spacer()
TextField("abc", text: $mytext)
Spacer()
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
and the TextField no longer moves upwards.
If this technique is applied inside to a view in a bottom sheet it no longer works and the entire sheet is pushed up by the keyboard.
struct ContentView: View {
#State var mytext: String = "Some text"
#State var isPresented: Bool = true
var body: some View {
Color.mint
.sheet(isPresented: $isPresented) {
VStack {
Spacer()
TextField("abc", text: $mytext)
Spacer()
}
.presentationDetents( [.fraction(0.33)] )
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
}
I've tried applying .ignoresSafeArea(.keyboard, edges: .bottom) to every view thats exposed in the code with no success.
I suspect that the bug is due to the bottom sheet implementation using a UIHostingController internally. This can been seen using Xcode's Debug View Hierarchy tool.
Others have described how UIHostingController does not respect the .ignoresSafeArea(.keyboard, edges: .bottom) modifier and have developed workarounds but these are not applicable here because the UIHostingController is created internally, not explicitly in my code.
Is there any way to get the view inside the sheet to ignore the keyboard and stay put?
I'm open to any and all suggestions. Thanks!

Related

How do I vertically center my content on my swiftui app?

I'm a beginner here, and just trying to put together a simple form and button (right now they don't do anything). For some reason I can't seem to move the form -- which is just going to be one text input by the way, so open to using a different method other than form -- to be centered vertically. Can anyone help me diagnose the problem? Here is my code:
import SwiftUI
struct CreateTopic: View {
var body: some View {
VStack {
Text("Create Topic")
.font(.largeTitle)
Spacer() // <-- Add a Spacer view above the Form view
Form {
TextField("Name", text: $name)
// TextField("Email", text: $email)
// TextField("Phone", text: $phone)
}
.frame(height: 100)
Spacer() // <-- Add a Spacer view below the Form view
NavigationView {
StandardButton(text: "Test", action: {})
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
#State private var name = ""
// #State private var email = ""
// #State private var phone = ""
}
struct CreateTopic_Previews: PreviewProvider {
static var previews: some View {
CreateTopic()
}
}
I tried adding that Spacer() above the Form, which didn't really do anything. It did however move the button.
Should I not be using a Form if I am only looking to use one simple text input?
There are multiple misunderstandings here. We can quickly solve them.
Form is a scrollable form. It is typically meant to encompass the entire screen. And if you use a form, you will not be able to center its content. Forms always lay out their subviews top-to-bottom. You could center a form within a larger view, but I wouldn't. I ran your code and you can scroll the form within the 100pt-tall rectangle. It's just weird.
NavigationView is meant to wrap the entire screen, not just a single button. It does things like add a navigation bar, display a title, and support buttons in the corners of the screen. It also supports the standard in-and-out screen transitions across all of iOS. Think of the Messages app and what happens when you tap into a conversation. NavigationView does that stuff.
With these two clarifications in mind, I would rewrite your view one of two ways.
Option 1, using a Form
struct CreateTopic: View {
var body: some View {
NavigationView {
Form {
Section {
TextField("Name", text: $name)
.textContentType(.name)
TextField("Email", text: $email)
.textContentType(.emailAddress)
TextField("Phone", text: $phone)
.textContentType(.telephoneNumber)
.keyboardType(.phonePad)
}
Section {
Button("Create") {
// Create the topic
}
}
}
.navigationTitle("Create Topic")
}
}
#State private var name = ""
#State private var email = ""
#State private var phone = ""
}
Option 2, without a Form, centering your text fields like you wanted
struct CreateTopic: View {
var body: some View {
NavigationView {
VStack {
TextField("Name", text: $name)
.textContentType(.name)
TextField("Email", text: $email)
.textContentType(.emailAddress)
TextField("Phone", text: $phone)
.textContentType(.telephoneNumber)
.keyboardType(.phonePad)
Button("Create") {
// Create the topic
}
}
.textFieldStyle(.roundedBorder)
.padding()
.frame(maxHeight: .infinity)
.background(Color(.systemGroupedBackground))
.navigationTitle("Create Topic")
}
}
#State private var name = ""
#State private var email = ""
#State private var phone = ""
}
Form fits the style of iOS more, but the second option looks fine and could be what you wanted.

SwiftUI dragging ScrollView flickers as the keyboard is dismissing

When I develop the feature that allows users drag ScrollView to dismiss keyboard in SwiftUI, I find that if you drag ScrollView as the keyboard is dismissing, the ScrollView will flicker. That will destroy the experience of the feature.
Here's the video and minimal code example:
📺 Video
struct ContentView: View {
#State var text:String = ""
var body: some View {
ScrollView {
Rectangle()
.frame(height: 300)
TextField("test", text: $text)
.padding()
.background(Color.gray)
.padding()
}
}
}
It's caused because the keyboard forces the view to resize.
Add
.ignoresSafeArea(.keyboard, edges: .bottom)
to the bottom of your view to solve this issue

SwiftUI, how to change View (don't use button) color when pressing?

As the title, it's a typical use case. And it's easy to achieve if using SwiftUI buttonStyle.
But for some reason I cannot use Button. So, how to do it with View?
You can use onTapGesture modifier for your view
struct ContentView: View {
#State var color = Color.green
var body: some View {
Text("Hello")
.padding()
.background(color)
.onTapGesture {
color = Color.blue
}
}
}

Pulling Down on ScrollView in SwiftUI Popover Closes It

I know that there is a built-in behavior in modals in iOS that when you swipe them down, the modal pulls down and closes. But I'm running into an issue where the slightest downward scroll on a ScrollView in a .popover closes it instantly.
I'm using a .popover in this case because it's a popover in the Mac version of my app, but in iOS it defaults to a modal sheet.
Here's what is happening when I scroll down:
The instant I scroll down, the modal jitters and closes. Here's the sample project that illustrates that:
import SwiftUI
struct ContentView: View {
#State var showModal = false
var body: some View {
ZStack{
Button("Open Sheet"){
showModal = true
}
}
.popover(isPresented: $showModal, arrowEdge: .bottom){
ModalView(showModal: $showModal)
}
}
}
struct ModalView: View{
#Binding var showModal: Bool
var body: some View{
ScrollView{
VStack{
Text("One")
Text("Two")
Text("Three")
}
}
.frame(maxWidth: .infinity)
.padding(20)
.background(Color.gray)
}
}
Is there something I can do to prevent the sheet/modal from closing when I scroll my ScrollView down?
This is a SwiftUI bug. It has been fixed in Xcode 12.5 beta.

SwiftUI: SearchBar moving out of bound when the List is too long

I've implemented a search bar in my app inside a custom header. Beneath the search bar I have added a false List with 100 rows that is intended to show search results.
The problem that I'm facing is:
when the list appears, the search bar moves out of bounds. When I add a top padding of 400px, the search bar comes back to bounds. Link to video 1
The next two are a bit out of topic.
When the keyboard is on screen, the last few rows of the list are not visible. How to fix it? Link to video 2
How to set a background color for a List? I haven't been able to figure that out. (listRowBackground modifier isn't working as suggested by an article I read.)
I'm using Xcode 12.0 beta 6.
let screen = UIScreen.main.bounds
struct SearchBarView: View {
#Binding var search: String
#Binding var searchSelected: Bool
var body: some View {
VStack {
CustomTextField(text: $search, isFirstResponder: true)
.modifier(SearchBarTextFieldStyle(search: $search))
if !search.isEmpty {
List(1..<100) { i in
Text("Hello \(i)")
}.frame(width: screen.width)
}
}
.frame(width: screen.width, height: !search.isEmpty ? screen.height : 40)
.background(Color("ThemeColor"))
}
}
when the list appears, the search bar moves out of bounds. When I add
a top padding of 400px, the search bar comes back to bounds.
The issue is that everything is placed in one VStack. So when search is not empty anymore the TextField shares the space provided to it with the List.
Place the TextField in a separate Stack like this:
struct ContentView: View {
#State var search: String = ""
var body: some View {
// Everything wrapped in one Stack
VStack() {
// Separate Stack for the TextField
HStack() {
TextField("Title", text: self.$search)
}.padding()
// One Stack for the content
VStack {
if !search.isEmpty {
List(1..<100) { i in
Text("Hello \(i)")
}.frame(width: UIScreen.main.bounds.width)
}
}.frame(width: UIScreen.main.bounds.width)
.background(Color.red)
Spacer() // So that the TextField is also on top when no content is displayed
}
}
}
When the keyboard is on screen, the last few rows of the list are not
visible. How to fix it?
Add a padding to the bottom of the list but I'd recommend implementing the solution of this: Move TextField up when the keyboard has appeared in SwiftUI
E.g. with padding:
import SwiftUI
struct ContentView: View {
#State var search: String = ""
var body: some View {
VStack() {
HStack() {
TextField("Title", text: self.$search)
}.padding()
VStack {
if !search.isEmpty {
List(1..<100) { i in
Text("Hello \(i)")
}.frame(width: UIScreen.main.bounds.width)
.padding(.bottom, 300) // here padding
}
}.frame(width: UIScreen.main.bounds.width)
Spacer()
}
}
}
How to set a background color for a List? I haven't been able to
figure that out. (listRowBackground modifier isn't working as
suggested by an article I read.)
This question has also already been answered here:
SwiftUI List color background

Resources