I have just started learning SwiftUI and have created a basic View as follows:
import SwiftUI
struct ContentView: View {
#State var searchText = ""
#State var items = 0...5
var body: some View {
NavigationStack {
List {
ForEach(items, id: \.self) { item in
Text("Item \(item)")
}
}
.searchable(text: $searchText)
.navigationTitle("Test items")
}
}
}
When I tap the search field and the keyboard appears, the List jumps down the screen briefly, before animating. This can be seen in the following video:
https://reddit.com/link/10tql80/video/8mjpo7c0g8ga1/player
I am using Xcode 14.2 and running the app on an iPhone 11 Pro (iOS 16.3).
I would be grateful if someone could let me know what I'm doing wrong here.
Thank you for your help in advance.
I have reduced the problem to its simplest form and the issue persists. I expect the animation to work smoothly without the content first jumping down the screen.
Related
I'm experiencing a visual bug when using a List and TextFields in SwiftUI. After focusing on a TextField in the List and then removing the focus (I've tried various methods of doing this, like Buttons in the List rows/keyboard toolbar etc.), there is a visual bug where the black view behind the keyboard dismisses, hangs for a second over the safe area, then suddenly disappears, causing the animation not to appear smooth. Also, if you scroll to the bottom of the List and perform the same steps, after the black view lingers for a second, it then suddenly disappears again but causes the List to 'snap' down a bit and again ruins the animation.
Here is a minimal demonstration of the issue:
struct ContentView: View {
#State var text: String = ""
#FocusState private var focused: Int?
var body: some View {
List {
ForEach(0..<50) { item in
HStack {
TextField("", text: $text)
.id(item)
.focused($focused, equals: item)
.background(.white)
Button {
focused = nil
} label: {
Text("Hide")
}
}
}
}
.scrollContentBackground(.hidden)
.background(.red)
}
}
One solution is using the List in a VStack with something below the List, but this is something I'd like to avoid with my UI. Also, I could use a ScrollView, but there is then a separate issue, where after dismissing the keyboard, the extra padding under the List produced by the keyboard avoidance stays there until you try and scroll the List again. I would also like to use the native swipe actions in my actual project. Finally, ignoring the safe area of the List/ScrollView works but then this disables keyboard avoidance, which is something that I'd like to keep.
Note: experienced on iOS 16.0 and 16.1
I am not 100% sure why this is happening, since I have not worked a lot with lists. But I guess it has something to do with that you are not explicitly telling your view to ignore the safe area. Adding .edgesIgnoringSafeArea(.bottom) to your list when leaving focus, and then changing it to trailing on entering focus seems to solve it.
struct ContentView: View {
#State var text: String = ""
#State var ignoreSafeArea = false
#FocusState private var focused: Int?
var body: some View {
List {
ForEach(0..<50) { item in
HStack {
TextField("", text: $text)
.id(item)
.focused($focused, equals: item)
.background(.white)
Button {
focused = nil
} label: {
Text("Hide")
}
}
}
.onChange(of: focused) { focused in
if focused == nil {
ignoreSafeArea = true
} else {
ignoreSafeArea = false
}
}
}.edgesIgnoringSafeArea(ignoreSafeArea ? .bottom : .trailing)
}
}
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.
My problem is that TabView is not updating.
I want to log-in and then re-render another view (Profile Screen) instead of (Login Screen) in the same TabItem.
The TabView goes to ProfileScreen only after i kill the app and reopen it.
Here's the TabView code:
import SwiftUI
struct ContentView: View {
#State var userToken: String = UserDefaults.standard.string(forKey: "UserToken") ?? ""
var body: some View {
TabView{
HomeScreen(text: .constant("")).tabItem({ Image(systemName: "house") }).tag(0)
Text("Cart").tabItem({ Image(systemName: "cart") }).tag(1)
if userToken.isEmpty {
LoginScreen().tabItem({ Image(systemName: "person") }).tag(2)
}
else {
ProfileScreen().tabItem({ Image(systemName: "person") }).tag(2)
}
}
}
}
Things I have tried:
Passing different #State values to both screens
Rendering Profile Screen in LoginScreen but it renders on top of the TabView
If you're using the latest SwiftUI, consider using #AppStorage instead of #State:
#AppStorage("UserToken") var userToken: String = ""
If you are modifying the value of userToken in any other view, for example may be LoginView, you need a mechanism to inform the content view about that update.
You can consider using LocalNotifications here for sending the update to ContentView so that the view reloads on status update.
I have a crash happening unpredictably when I'm presenting a sheet over a view that is already in a sheet. I have been able to slowly strip out parts of my view and custom types until I've got the very simple and generic view structure below that still exhibits the crash.
The crash happens when I interact with the TextField then interact with one of the buttons that shows the sub-sheet. It sometimes takes a lot of tapping around between the buttons and the text field to trigger the crash, and sometimes it happens right away (as in the GIF below). Sometimes I can't get the crash to happen at all, but my users keep reporting it.
In the gif below the crash occurs the minute the bottom button is pressed. You can see the button never comes out of its "pressed" state and the sheet never appears.
Xcode doesn't give any helpful info about the crash (screenshots included below).
I've only gotten it to happen on an iPhone XR running 13.4.1 and Xcode 11.4.1. I have tried on an iPhone 6s and several simulators and can't trigger the crash, but users have reported it on several devices.
struct ContentView: View {
#State var showingSheetOne: Bool = false
var body: some View {
Button(action: { self.showingSheetOne = true }) {
Text("Show")
}
.sheet(isPresented: $showingSheetOne) {
SheetOne(showingSheetOne: self.$showingSheetOne)
}
}
}
struct SheetOne: View {
#Binding var showingSheetOne: Bool
#State var text = ""
var body: some View {
VStack {
SheetTwoButton()
SheetTwoButton()
SheetTwoButton()
TextField("Text", text: self.$text)
}
}
}
struct SheetTwo: View {
#Binding var showing: Bool
var body: some View {
Button(action: {
self.showing = false
}) {
Text("Hide")
.frame(width: 300, height: 100)
.foregroundColor(.white)
.background(Color.blue)
}
}
}
struct SheetTwoButton: View {
#State private var showSheetTwo: Bool = false
var body: some View {
Button(action: { self.showSheetTwo = true } ) {
Image(systemName: "plus.circle.fill")
.font(.headline)
}.sheet(isPresented: self.$showSheetTwo) {
SheetTwo(showing: self.$showSheetTwo)
}
}
}
I ran into a similar problem a few weeks ago. Turns out that when I presented the new sheet with the keyboard open it would lead to a crash.
I found using UIApplication.shared.endEditing() before showing the second sheet would solve the problem
UPDATE
For iOS 14 I’ve created an extension because the above function is no longer available
extension UIApplication {
static func endEditing() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
}
The usage is similar UIApplication.endEditing()
I'm using this in Swift 5.7, iOS 16.0:
#if !os(watchOS)
import UIKit
func dismissKeyboard() {
// Dismiss text editing context menu
UIMenuController.shared.hideMenu()
// End any text editing - dismisses keyboard.
UIApplication.shared.endEditing()
}
#endif
However, since iOS 16.0 there's now a warning:
'UIMenuController' was deprecated in iOS 16.0:
UIMenuController is deprecated. Use UIEditMenuInteraction instead.
I haven't yet figured out how to use UIEditMenuInteraction to do the same.
I'm trying to show a dynamic List with rows containing Toggle elements. The Toggles are laid out correctly initially, but their layout breaks when scrolling them in and out of view (i. e. upon cell reuse).
Minimal example code:
import SwiftUI
struct SwitchList: View {
var body: some View {
List(0..<20) { _ in
SwitchRow(value: Bool.random())
}
}
}
struct SwitchRow: View {
#State var value: Bool
var body: some View {
Toggle(isOn: $value) {
Text("A switch row")
}
}
}
Screen recording demonstrating the issue:
(This is using iOS 13.2.2 (17B102) on the Simulator.)
Am I doing something wrong, or is this a bug? How do I get the Toggles to show correctly?
This is a bug/regression in iOS 13.2+
Working - iOS 13.1 (17A844) / Xcode 11.1 (11A1027)
Broken - iOS 13.2.2 (17B102) / Xcode 11.2.1 (11B500)
Broken - iOS 13.3 beta (17C5032d) / Xcode 11.3 beta (11C24b)
Submit feedback to Apple
Workaround
This bug only appears to affect the List initializers which take a data parameter. This code is functionally equivalent, but is not affected by the bug.
struct SwitchList: View {
var body: some View {
List {
ForEach(0..<20) { _ in
SwitchRow(value: Bool.random())
}
}
}
}
I was able to reproduce the problem, but could not find out why this happens. When I use a ScrollView() with a Divider() I don't have the Problem anymore. Here is the code:
struct SwitchList: View {
var body: some View {
ScrollView {
ForEach(1...50, id: \.self) { item in
VStack {
SwitchRow(value: Bool.random())
Divider()
}
}
}
}
}
struct SwitchRow: View {
#State var value: Bool
var body: some View {
Toggle(isOn: $value) {
Text("A switch row")
}
}
}
I hope this helps!