Text jumping (I don't know how else to name that weird behavior) to the top(?) every time user enters a new character. I can reproduce the issue only on iPad OS 16. It affects the user who attempts to write long (several paragraphs) text in the TextEditor.
Reproduced with following code (just past five or more paragraphs of Lorem Ipsum and start typing)
class EditorViewModel: ObservableObject {
#Published var text: String = ""
}
struct EditorView: View {
#ObservedObject var viewModel: EditorViewModel
var body: some View {
VStack {
TextEditor(text: $viewModel.text)
.frame(height: 80)
.padding()
.background(Color.red)
Spacer()
}
}
}
struct ContentView: View {
var body: some View {
EditorView(
viewModel: EditorViewModel()
)
}
}
Related
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!
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.
On iOS 15, if you display a List of VStacks with a Text and DatePicker as below
#main
struct WeirdListDatePickerProblem: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
let listRows: [Int] = (0...100).map { $0 }
var body: some View {
List(listRows, id: \.self) { v in
RowView()
}
}
}
struct RowView: View {
#State var date: Date = Date()
var body: some View {
VStack {
Text("If you remove this then the problem disappears")
DatePicker("", selection: $date, displayedComponents: [.date])
.labelsHidden()
}
}
}
then the screen is mangled as below.
It becomes worse the more you scroll. This has been seen on the simulator and on a real device using iOS 15.5 with Xcode 13.4.1.
If you remove the Text then the problem disappears.
How, on iOS 15, can you display list items with a Text and DatePicker without the above happening?
try this, works for me:
the issue goes away for me, when I add a .frame(width: 333, height: 88) to the RowView(), or its VStack{...}. It seems the List view needs a height for its rows to work properly. I also noticed it is much faster to scroll with the added frame.
Hoping someone may know of a solution for this animation issue as I can't find a way to make it work!
Im using ForEach within LazyVStack within ScrollView. I have a .searchable modifier on the scrollview. When I enter/cancel the search field the navigation bar and search field animate upwards/downwards but my scrollview jumps without animation.
if I add .animation(.easeInOut) after .searchable it animates correctly. However there's two issues, its deprecated in iOS 15.0, and it animates the list items in crazy ways as they appear and are filtered etc.
When using a List it also works but can't be customised in the way I need. This issue is present in simulator, in previews and on device.
Does anyone know how I can get this to animate correctly without resorting to using List (Which doesn't have the customisability I need for the list items)?
Thanks for your help!
A slimmed down version of what I'm doing to recreate the issue:
import SwiftUI
struct ContentView: View {
#State var searchText: String = ""
var body: some View {
NavigationView {
ScrollView(.vertical) {
CustomListView()
}
.navigationTitle("Misbehaving ScrollView")
.searchable(text: $searchText, placement: .automatic)
// This .animation() will fix the issue but create many more...
// .animation(.easeInOut)
}
}
}
struct CustomListView: View {
#State private var listItems = ["Item 0", "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10"]
var body: some View {
LazyVStack(alignment: .leading, spacing: 10) {
ForEach(listItems, id: \.self) { item in
CustomListItemView(item: item)
.padding(.horizontal)
}
}
}
}
struct CustomListItemView: View {
#State var item: String
var body: some View {
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 20, style: .continuous)
.foregroundColor(.green.opacity(0.1))
VStack(alignment: .leading, spacing: 4) {
Text(item)
.font(.headline)
Text(item)
.font(.subheadline)
}
.padding(25)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
An even more basic example that displays the same issue:
import SwiftUI
struct SwiftUIView: View {
#State var text = ""
var body: some View {
NavigationView {
ScrollView {
Text("1")
Text("2")
Text("3")
Text("4")
Text("5")
Text("6")
}
}
.searchable(text: $text)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
We need to animate ScrollView geometry changes synchronously with searchable text field appearance/disappearance, which as seen are animatable.
There are two tasks here: 1) detect searchable state changes 2) animate ScrollView in correct place (to avoid unexpected content animations as already mentioned in question)
A possible solution for task 1) is to read isSearching environment variable:
.background(
// read current searching state is available only in
// child view level environment
SearchingReaderView(searching: $isSearching)
)
// ...
struct SearchingReaderView: View {
#Binding var searching: Bool
#Environment(\.isSearching) private var isSearching
var body: some View {
Text(" ")
.onChange(of: isSearching) {
searching = $0 // << report to perent
}
}
}
and for task 2) is to inject animation right during transition by modifying transaction:
ScrollView(.vertical) {
CustomListView()
}
.transaction {
if isSearching || toggledSearch {
// increased speed to avoid views overlaping
$0.animation = .default.speed(1.2)
// needed to animate end of searching
toggledSearch.toggle()
}
}
Tested with Xcode 13.4 / iOS 15.5 (debug slow animation for better visibility)
Test code on GitHub
I'm new to swiftUI here and I want to try out to pass data between two views. But it doesn't seem to work.
I'm using Xcode 13.2 & iOS 15 for the simulator.
This is my code for the first view:
struct ContentView: View {
#State var myName: String = ""
var body: some View {
NavigationView {
VStack {
TextField("Enter your name", text: $myName)
Text(self.myName)
NavigationLink(destination: BView(myName: self.$myName), label: {
Image(systemName: "arrowshape.turn.up.left")
})
}//: VSTACK
.padding(.horizontal, 20)
}//:NAVIGATION VIEW
}
}
This is code for the second view:
struct BView: View {
#Binding var myName: String
var body: some View {
NavigationView {
Text("BView")
Text(self.myName)
}//:NAVIGATION VIEW
}
}
I want myName to be input in the first page which is ContentView() and then pass down the input data to BView().
Unfortunately, once I run it on the simulator, the input data doesn't;t show up.
Your code is fine just add VStack in BView.
struct BView: View {
#Binding var myName: String
var body: some View {
NavigationView {
VStack { // HERE
Text("BView")
Text(self.myName)
}
}//:NAVIGATION VIEW
}
}
Please use #EnvironmentObject to pass the data to view.
https://developer.apple.com/documentation/swiftui/environmentobject