SwiftUI Keyboard dismiss not interactive - swift

Thanks for taking your time to help others :)
Problem description:
App must support iOS 14 (there's no keyboard toolbar), and cannot use Introspect library, sorry.
When using a TextField, I want to dismiss it interactively. It does dismiss the keyboard, but does not take TextField along. And it should.
Simple demo code to replicate what happens:
import SwiftUI
struct ContentView: View {
#State var text: String = ""
init() {
UIScrollView.appearance().keyboardDismissMode = .interactive // To interactively dismiss
}
var body: some View {
VStack {
ScrollView {
LazyVStack {
ForEach(1...200, id: \.self) { msg in
Text("Message \(msg)")
.padding()
.background(Color.red.cornerRadius(8))
}
}
}
TextField("Hello", text: $text)
.textFieldStyle(.roundedBorder)
.padding()
}
.navigationTitle("Example")
.navigationBarTitleDisplayMode(.inline)
}
}
GIF resources to see behaviour:
Good result:
Bad result:
What I have checked?
Registering the keyboard height through Notifications events, like in: this post and adding that height as offset, bottom padding... nothing works.
Any idea?

Related

SwiftUI ScrollView does not respond to keyboard

Thanks for taking your time to help others :)
Problem description:
I want to bring up the ScrollView content as keyboard shows up. And I can't.
Despite, if ScrollView is turned upside down... it works!!! But I can't do that because I have to implement .contextMenu(...) and this produces an even worse bug (detailed in this post).
App must support iOS 14.
Simple code demo to show what happens.
import SwiftUI
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
ScrollView() {
LazyVStack {
ForEach(1..<201, id: \.self) { num in
Text("Message \(num)")
}
// .upsideDown() // With these modifiers, will prompt up the scrollView content
}
}
// .upsideDown() // With these modifiers, will prompt up the scrollView content
TextField("Your text here", text: $text)
.textFieldStyle(.roundedBorder)
.padding()
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Example app")
}
}
extension View {
func upsideDown() -> some View {
self.rotationEffect(.degrees(180))
}
}
GIF resources to see behaviour:
Good result (when upside down):
Bad result (on normal scrollView):
What we have checked?
Already tried this solution, but does not help.
Tried setting an offset to ScrollView of keyboards height when it does come up but... nothing happens.
Questions
Why does this happen when it is upside down? (and not at normal scrollview)
Why on both cases it brings up the TextField but does NOT bring up the content of Scrollview if is not upside down???

Make all View contained TextField be visible above keyboard

It is not question about how to keep TextField above keyboard (put it inside ScrollView), I wonder how to keep TextField containing View fully be visible above Keyboard. For example behind TextField some info text, which should be visible while user print the text.
VStack { // This all should be above keyboard
TextField(...)
Text("Some hints about entered text")
}
In screens:
Now I have this (you can see TextField just above keyboard, but we don't see content under TextField)
But I want to get this (With "Some additional info" label, which should also pop above keyboard when TextField become first responder):
I was try you code and all warks fine right now but you can try like this:
VStack(alignment: .leading) {
TextField(...)
Text("Some hints about entered text")
}
I don't know what I want because there's not much information I can get from the question, but is this right? I understand that you want to include a view that can be a hint from the view behind the TextField.
import SwiftUI
struct ContentView: View {
#State private var text = ""
var body: some View {
VStack { // This all should be above keyboard
Spacer()
TextField("", text: $text)
.background {
Text("Some hints about entered text")
.foregroundColor(.gray)
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

NavigationView frame glitch with safearea

I try to create a custom ViewModifier similar to SwiftUI's .sheet modifier.
When I try to make a NavigationView spring from bottom, the frame of the view just glitched over safearea. The frame looks as if adjust to the safearea when the view moves from bottom to top.
Anyone knows maybe how to constrain the view frame inside the navigation view to avoid this?
Here is what happened. When click the plus button, the SwiftUI .sheet modifier shows up. Custom popup shows up when pressing the gear button.
Problem gif recording here
Here is code of the custom popup view.
struct SettingsView: View {
#Binding var showingSelf: Bool
#Binding var retryWrongCards: Bool
var body: some View {
GeometryReader { geometry in
NavigationView {
List {
Section {
Toggle(isOn: $retryWrongCards) {
Text("Retry Wrong Cards")
}
}
}
.animation(nil)
.navigationViewStyle(StackNavigationViewStyle())
.listStyle(GroupedListStyle())
.navigationBarTitle("Settings")
.navigationBarItems(trailing: Button("Done") {
self.showingSelf = false
})
}
}
}
}
Here's the code of custom modifier
struct Popup<T: View>: ViewModifier {
let popup: T
let isPresented: Bool
init(isPresented: Bool, #ViewBuilder content: () -> T) {
self.isPresented = isPresented
popup = content()
}
func body(content: Content) -> some View {
content
.overlay(popupContent())
}
#ViewBuilder private func popupContent() -> some View {
GeometryReader { geometry in
if isPresented {
popup
.animation(.spring())
.transition(.offset(x: 0, y: geometry.belowScreenEdge))
.frame(width: geometry.size.width, height: geometry.size.height)
}
}
}
}
private extension GeometryProxy {
var belowScreenEdge: CGFloat {
UIScreen.main.bounds.height - frame(in: .global).minY
}
}
After debugging and trying many changes, I was already preparing a mini-project to file a SwiftUI bug for Apple. In the end, it was not necessary and we have a simple solution 🥳!!
TLDR; your SettingsView does not correctly initialise the NavigationView.
The modifier .navigationViewStyle(StackNavigationViewStyle()) has to be applied onto the NavigationView and not inside it (in contrast to navigationBarTitle or navigationBarItems which only work when put inside the NavigationView):
NavigationView {
List {
Section {
Toggle(isOn: $retryWrongCards) {
Text("Retry Wrong Cards")
}
}
}
.animation(nil)
.listStyle(GroupedListStyle())
.navigationBarTitle("Settings")
.navigationBarItems(trailing: Button("Done") {
self.showingSelf = false
})
}
.navigationViewStyle(StackNavigationViewStyle())
After this change your custom sheet modifier behaves for me identically as the regular .sheet modifier! Great work!
Philipp
It seems your animation does exactly what a sheet does. I would just replace it with that. You can easily present a sheet on top of a sheet.
Edit
After you posted your ViewModifier code, it seems the only difference is the animation type and the size of the popup.

SwiftUI Animation bug?

I'm having this odd issue with macOS Montery 3 & Xcode 13 Beta 3, where I get this error with List's and animations:
[General] Row index 0 out of row range (numberOfRows: 0) for <SwiftUIListCoreOutlineView: 0x133885000>
Its kinda hard to explain, but heres a simple reproduction:
Minimal Reproducible Example
Create a new SwiftUI macOS application
Paste this code:
struct ContentView: View {
#State var items = ["Item"]
#ViewBuilder var mainView: some View {
if items.isEmpty {
Text("Im empty")
}
else {
List(items, id: \.self) {s in
Text(s)
}
}
}
var body: some View {
NavigationView {
mainView
.toolbar {
Button(action: {
withAnimation {
items.removeAll()
}
}) {
Image(systemName: "minus")
}
}
Text("Second")
}
}
}
Run the app, and try resizing the sidebar. (You should be able to)
Then press the minus button on the toolbar. This simply removes all the items.
Then, the resizing of the sidebar should be broken. You might also get a bunch of errors in the console.
Is anybody able to reproduce this issue, and is it a bug in SwiftUI?
got the same behavior. The code works well if I remove the "withAnimation". Or put "items.removeAll()" outside the "withAnimation"

BackButton without text SwiftUI

Actually, this is the question.
I have a screen like this, I would like to change the BackButton.
My View screen:
enter image description here
I want instead of blue backbutton with standart array + text BACK, just to be black array WITHOUT text.
In other words, essentially remove the text from the backbutton and change the color to black. How not to achieve this?
You can use the appropriate modifiers inside the View you wish to be black and without text. You need to specify no button and supply your own image as well as the return functionality.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: SecondView()) {
Text("Hello, World!")
}
}
}
}
struct SecondView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
Text("Hello")
Text("Second view")
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
self.presentation.wrappedValue.dismiss()
}) {
Image(systemName: "chevron.left")
}
.foregroundColor(.black))
}
}
Edited: Inserted the appropriate image.