SwiftUI iOS14 - Disable keyboard avoidance - swiftui

Is there a way to disable the native keyboard avoidance on iOS14?
There is no keyboard avoidance in iOS13, so I want to implement my own, but when I do the native one on iOS14 is still active, so both my implementation and the native one run at the same time, which I don't want.
My deployment target is iOS13, so I need a solution for both iOS13 and iOS14.

You can use if #available(iOS 14.0, *) if you want to adapt the iOS 14 code so it compiles on iOS 13.
Here is an adapted version of this answer to work on both iOS 13 and iOS 14:
struct ContentView: View {
#State var text: String = ""
var body: some View {
if #available(iOS 14.0, *) {
VStack {
content
}
.ignoresSafeArea(.keyboard, edges: .bottom)
} else {
VStack {
content
}
}
}
#ViewBuilder
var content: some View {
Spacer()
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
}
}

Related

Navigation bug when dismissing view while focussing on empty .searchable() modifier

When trying to navigate back from a view using the environment Dismiss value while also focussing on an empty searchable modifier the view you navigated back to becomes unresponsive. This is due to an empty UIView blocking any interaction with the view as seen in this screenshot:
Empty UIView blocking view after navigating back
This only occurs when the searchbar is focussed and empty when trying to navigate back. When there's a value in the searchbar everything works:
GIF of the bug
Am I doing something wrong here?
Tested on Xcode 14.2 iPhone 14 Pro (iOS 16.0) simulator.
import SwiftUI
struct MainPage: View {
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
Text("Main view")
NavigationLink(destination: DetailView()) {
Text("Click me")
}
}
}
}
}
struct DetailView: View {
#Environment(\.dismiss) private var dismiss
#State private var searchText = ""
var body: some View {
VStack {
Text("Detail view")
Button("Go back") {
dismiss()
}
}
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
}
}
This bug only seems to happen when using NavigationStack or NavigationView with a .navigationViewStyle(.stack). When using NavigationView without a navigationViewStyle it seems to work fine. Currently I can work around this using the latter but I would prefer to use NavigationStack as NavigationView has become deprecated since iOS 16.0.
Any help is appreciated.

SwiftUI extra space at top of list above section header. On testing device only

I have been trying to figure out what is causing the space at the top of the screen in my production app, so I made this test app to see if it is a bug or not. The code works as intended on a simulator but when a testing device runs the code it adds extra space. The space goes away after you start scrolling, and does not comeback until the view reloads. I have tried restarting the device and other devices. I took out .navigationTitle and .navigationBarTitleDisplayMode and it did not fix the problem. So far my best guess is that there is some problem with changing the section header in .onAppear(). Changing it to .task() seems to be a workaround for now.
struct DetailView: View {
#State var item: Item
#State private var headerText = "Header"
var body: some View {
List {
Section(header: Text("\(headerText)")) {
Text("Text")
}
HStack {
Text("Red Text")
}.listRowBackground(Color.red)
// Change to .task instead
}.onAppear {
headerText = "Change Header"
}
}
}
Edit: Here is the code for the list view, it is the default new project setup.
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink(destination: DetailView(item: item), label: {
Text(item.timestamp!, formatter: itemFormatter)
})
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
In my case this behaviour was caused by .navigationViewStyle(StackNavigationViewStyle()).
Looks like it's a bug with NavigationView in SwiftUI 4 since it wasn't like this before.
If you are on iOS 16 use NavigationStack instead NavigationView. This fixed all my problems and I didn't even need to use the navigationViewStyle anymore.
NavigationView is deprecated in favor of NavigationStack.

SwiftUI navigationBarTitle not resetting to .large returning from Toolbar set to .inline

There are a few posts regarding SwiftUI .inline not resetting to .largeTitle when navigation returns to the parent:
For example:
Navigation bar title stays inline in iOS 15
and
Navigationbar title is inline on pushed view, but was set to large
While earlier posts seem to suggest this has been corrected, I'm running into the same problem, even in iOS 16, but I'm not using a < Back button, instead I'm using "Cancel" (and not show, "Save") on my DestinationView. My goal is to mimic Apple's practice of showing a modal view when adding data, but a show-style push on the navigation stack when viewing and editing existing data (e.g. Contacts app, Reminders app, Calendar app). The brief code below illustrates the problem without adding extra code to handle data updating (e.g. #EnviornmentObject).
When I run this in the Live Preview in Xcode 14.0.1, scheme set to iPhone 13 Pro, no problems. Click a NavLink, return from destination, and ContentView shows .large navigationBarTitle. BUT when I run in the simulator or on a 13 Pro device, returning to Home from a NavigationLink remains .inline unless I pull down on the list. If I switch to iPhone 14 Pro, the live preview looks fine, but the simulator shows a short of abrupt switch from inline back to large, not a smooth animation. Am I doing something wrong in the setup here or is there a bug in the implementation, noting that the behavior oddly holds to .inline on return home to ContentView, if I use this in either a simulator or device for iPhone 13 Pro. Thanks for guidance & insight!
struct ContentView: View {
#State private var sheetIsPresented = false
var items = ["Item1", "Item2", "Item3"]
var body: some View {
NavigationStack {
List {
ForEach(items, id: \.self) { item in
NavigationLink(item, destination: DestinationView(item: item))
.padding()
}
}
.navigationBarTitle("Home", displayMode: .large)
.listStyle(.plain)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
sheetIsPresented.toggle()
} label: {
Image(systemName: "plus")
}
}
}
}
.sheet(isPresented: $sheetIsPresented) {
NavigationStack {
DestinationView(item: "New!")
}
}
}
}
struct DestinationView: View {
var item: String
#Environment(.dismiss) private var dismiss
var body: some View {
List {
Text(item)
}
.toolbar {
ToolbarItem (placement: .navigationBarLeading) {
Button("Cancel") {
dismiss()
}
}
}
.listStyle(.plain)
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden()
}
}

GeometryReader acting weird when presenting a modal on iOS 16. Bug or new behavior?

I'm seeing a weird behavior that is affecting one of my views in SwiftUI after upgrading to iOS 16.
Just to give some context, here is the stack:
Xcode 14
Simulator or real device on iOS 15.5 and 16
Considering the minimum reproducible code below:
struct ContentView: View {
#State private var isPresented: Bool = false
var body: some View {
GeometryReader { reader in
VStack(spacing: 36) {
Text("Screen frame:\n\(String(describing: reader.frame(in: .global)))")
.multilineTextAlignment(.center)
Button {
isPresented.toggle()
} label: {
Text("Open modal")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
.onReceive(NotificationCenter.default.appDidBecomeActive()) { _ in
print(reader.frame(in: .global))
}
.onReceive(NotificationCenter.default.appDidEnterBackground()) { _ in
print(reader.frame(in: .global))
}
}
.sheet(isPresented: $isPresented) {
modalView
}
}
private var modalView: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding()
}
}
extension NotificationCenter {
func appDidBecomeActive() -> AnyPublisher<Notification, Never> {
publisher(for: UIApplication.didBecomeActiveNotification).eraseToAnyPublisher()
}
func appDidEnterBackground() -> AnyPublisher<Notification, Never> {
publisher(for: UIApplication.didEnterBackgroundNotification).eraseToAnyPublisher()
}
}
As soon the view starts, it's possible to see the frame available due to the GeometryReader. Then following the steps:
Open the modal view
Send the app to the background
Open the app again
Close the modal
It's possible to see that the frame changed, and the values match with the 3D effect when a view is presenting another view, and it's never changing again to the right values unless you send the app again to the background or switch views (e.g. using a TabView).
I don't find anything on iOS release notes talking about it, so I supposed it must be a bug (I've filled out a bug report already).
On iOS 15, the frame value keeps stable at the same value.
I have a couple of views relying on the value of a GeometryReader, and it's causing my view to deform because of this issue. Does anyone know a way to force the recalculation for the GeometryReader for this case?
Any help is appreciated.
The issue won't occur if you control the display of the sheet with the new presentationDetents method, provided you do not request to cover the entire screen.
I modified your code as follows:
.sheet(isPresented: $isPresented) {
if #available(iOS 16, *) {
modalView
.presentationDetents([.fraction(0.99)])
}
else {
modalView
}
}
The issue will remain if you request .fraction(1), i.e. covering the whole screen.

SwiftUI - edgesIgnoringSafeArea behaves differently in iOS 13.4

I've noticed that in my app .edgesIgnoringSafeArea renders my view differently in iOS 13.3 vs. iOS 13.4.
In my ContentView I have a modifier of .edgesIgnoringSafeArea(.top) applied. This displayed correctly in all iOS 13 versions leading up to 13.4. Now in the GM of 13.4 the top and bottom of the view is getting cut off.
Here's my ContentView
struct ContentView: View {
#EnvironmentObject var session: SessionStore
func getUser() {
session.listen()
}
var body: some View {
Group {
ZStack {
TabView {
ExploreView().tabItem {
Image(systemName: "house.fill")
Text("Explore")
}.tag(1)
FestivalsView().tabItem {
Image(systemName: "globe")
Text("Festivals")
}.tag(2)
ProfileView().tabItem {
Image(systemName: "person.crop.circle.fill")
Text("Profile")
}.tag(3)
}
.accentColor(Color("wdwPurple"))
.edgesIgnoringSafeArea(.top)
}
}.onAppear(perform: getUser)
}
}
Here's how it displays:
Any ideas?
In fact, on iOS GM 13.4 look like is correct, because is ignore top safe area
I removed the modifier and it seemed to display correctly. Like other people have said, the simulator isn't a great indicator of how things actually render on an actual device.