How can I present an overlay above the keyboard in SwiftUI? - cocoa-touch

I have a screen in my app that automatically displays a keyboard when the screen is presented. The screen allows users to enter their mobile number. In order to select a dialling code, the user needs to tap a button which will then trigger the presentation of an overlay.
Problem
The overlay is being presented, but it's showing up behind the currently present keyboard.
Question
How can I make this overlay be the very top view?
There is no way for me to use the zIndex modifier on the keyboard for obvious reasons. I'm wondering if there is a way to make the overlay the top view when it's about to be presented, or if the overlay can be added to the window.
Thanks in advance

You should probably only have one source of input at any given time — either the keyboard should be presented to enter a number, or the overlay should be presented to pick the dialing code, not both. Here's an example which hides the keyboard when overlay appears, and vice versa. (Keyboard dismissal code from this answer.)
struct ContentView: View {
#State private var number = ""
#State private var showingOverlay = false
var body: some View {
GeometryReader { proxy in
ZStack(alignment: .top) {
// Just here to force ZStack to use the whole screen
Rectangle()
.fill(Color.clear)
VStack(alignment: .leading) {
Button("Select Dialing Code") {
UIApplication.shared.endEditing()
self.showingOverlay = true
}
TextField("Enter your number", text: self.$number, onEditingChanged: {
if $0 { self.showingOverlay = false }
})
.keyboardType(.phonePad)
}
Overlay(showing: self.$showingOverlay)
.frame(height: 400)
.offset(x: 0, y: proxy.size.height + (self.showingOverlay ? -300 : 100))
.animation(.easeInOut)
}
}
}
}
struct Overlay: View {
#Binding var showing: Bool
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 15)
.fill(Color(.systemGray4))
Button("Dismiss") {
self.showing = false
}
}
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}

Related

NavigationStack, NavigationLink and onLongPressGesture together

I have a view inside a NavigationStack (iOS16+) that shows a horizontal list of items (game). When tapping on it the app will proceed to the next screen in my app. But I also want to be able to show a sheet to be able to edit the details of game. I got both working, just not together. The edit sheet should be shown after a long press, but as soon as I add the onLongPress gesture, I no longer can scroll through my list, nor does the NavigationLink work. How to get this working?
.navigationDestination(for: Game.self) { game in
TestView(game: game)
}
(...)
// Show and edit game details
func gameDetailsView(for game: Game) {
gamesViewModel.selectedGame = game
showingGameDetailsView.toggle()
}
(...)
#ViewBuilder var horizontalList: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(gamesViewModel.filteredGames(with: filterText)) { game in
NavigationLink(value: game) {
gameCard(game)
.listRowBackground(Color.clear)
.frame(width: uiProperties.isiPad ? getScreenRectangle().width / 3 : getScreenRectangle().width - 30)
.frame(height: uiProperties.isiPad ? getScreenRectangle().height / 1.5 : getScreenRectangle().height / 1.6)
.onLongPressGesture(minimumDuration: 0.3) {
gameDetailsView(for: game)
}
// .simultaneousGesture(LongPressGesture(minimumDuration: 0.3).onEnded { hasEnded in
// if hasEnded { gameDetailsView(for: game) }
// })
}
}
}
.padding(.horizontal)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Another solution would be to have two destinations for game, but I don't know if that's possible and how to do that ...

SwiftUI NavigationLink not displaying view until physical rotation then all is ok?

The opening view of App is blank with a Back Button. However, the intended view will appear after complete rotation of physical device (before touching anything) or by tapping the Back Button on the displayed empty view.
This is a weird bug. After that rotation (to landscape and back to protrait) everything is peachy-keen-o.
I am aware that adding: .navigationViewStyle(StackNavigationViewStyle()) to the NavigationView will sorta resolve the problem. But this solution is a poor fix for my App's needs.
Is there a command to intentionally rotate device in code to resolve this? Or some other fix to get that first view to appear without counting on the user to rotate the device?
I've tried various solutions with no luck.
Below is a quick setup of the problem.
struct FirstView: View {
#Binding var viewNum: Int?
var body: some View {
VStack {
Text("View One")
.padding(20)
Button {
viewNum = 2
} label: {
Text("Go to 2nd view")
}
}
.onAppear {
viewNum = 1
}
}
}
struct MainView: View {
#State var selection: Int? = 1
var body: some View {
// VStack {
NavigationView {
List {
NavigationLink("First View", tag: 1, selection: $selection){ FirstView(viewNum: $selection)}
NavigationLink("Second View", tag: 2, selection: $selection){
Text("View 2").padding(20)
Button {
selection = 1
} label: {
Text("Go to first view")
}
}
}
}
// .navigationViewStyle(StackNavigationViewStyle())
//}
}
}

SwiftUI sheet modal background not working with edgesIgnoreSafeArea(.all) - WHY?

I'm running iOS 13 simulator with a SwiftUI sheet modal - trying to paint the sheet background and ignore safe areas - but there's still white sheet showing. Any ideas on how to do this properly?
var body: some View {
VStack(alignment: .center, spacing: 0) {
... redacted ...
} // VStack
.background(Color.gray.edgesIgnoringSafeArea(.all))
iPhone Simulator with sheet modal
See the image (attached)
Any ideas what I'm doing wrong?
Expand top container (VStack in your case) to full sheet area, like
VStack {
// ... your content
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // << here !!
.background(Color.gray.edgesIgnoringSafeArea(.all))
Best way to paint the background is using a ZStack:
var body: some View {
ZStack {
// replace with the color you want
Color.green
.edgesIgnoringSafeArea(.all)
VStack {
// put view's Content here
}
}
}

Can I disable Navigationlink for a specified set of cells in my SwiftUI list?

I am working on a contacts app in SwiftUI for work which will display hundreds of phone numbers. Some contacts have a detail view with address and alternate numbers, while some contacts do not.
For example, Harry Smith will have basic phone extension listed in row while his cell number and address are listed in detail view.
But the number for the Second Floor Bathroom only needs the basic phone extension in the row, a detail view containing cell number and address for the bathroom is unnecessary.
How do I disable navigation link for those contacts who do not have pertinent detail info?
I prefer to add NavigationLink on condition.
I will have a "Cell" with some contents. (image/file detail)
A - build a "content" View:
struct MediumFileContentRow: View {
var file: MediumFile
var body: some View {
VStack (alignment: .leading) {
let (name, attribs) = file.getInfo() // get from model..
Text(name)
Spacer()
Text(attribs)
}// VStack
}
}
B - Your cell (MediumFileRow) will conditionally add link (NavigationLink) "around" our content view, and will give back different Views using AnyView:
struct MediumFileRow: View {
var file: MediumFile
var body: some View {
let enabled = NavigationLink(destination: Detail(t: file.name!)) {
MediumFileContentRow(file: file)
}
let disabled = MediumFileContentRow(file: file)
return file.isEmpty ? AnyView(disabled) : AnyView(enabled)
}
All (almoast..) The code:
struct MediumFileContentRow: View {
var file: MediumFile
var body: some View {
VStack (alignment: .leading) {
let (s,sd) = file.displayed()
Text(s)
Spacer()
Text(sd).font(.system(size: 9.0))
}// VStack
}
}
struct MediumFileRow: View {
var file: MediumFile
var body: some View {
let enabled = NavigationLink(destination: Detail(t: file.name!)) {
MediumFileContentRow(file: file)
}
let disabled = MediumFileContentRow(file: file)
return file.isEmpty ? AnyView(disabled) : AnyView(enabled)
}
...
..
In list You will have:
var body: some View {
NavigationView{
....
List {
ForEach(mediumFiles, id: \.self) { file in
MediumFileRow(file: file)
}
}
......
advantage: You can easily customise "disabled" cells.

How to disable ScrollView Bounce In SwiftUI

Any Modifier available to stop bounce of ScrollView in swiftUI ?
struct RoomDetailsView: View {
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
}
}
I tried below code but it not work for me. looks like it deprecated
ScrollView(alwaysBounceVertical: true) {
Image("test")
Text("Hello Text")
...
...
}
try using this line of code:
UIScrollView.appearance().bounces = false
You can use it like this:-
struct RoomDetailsView: View {
init() {
UIScrollView.appearance().bounces = false
}
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
}
}
Or you can write this line in AppDelegate to apply this behaviour throughout into your app.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UIScrollView.appearance().bounces = false
}
You may use SwiftUI-Introspect library:
ScrollView {
// some content
}
.introspectScrollView { scrollView in
scrollView.alwaysBounceVertical = false
}
Set the ScrollView's showsIndicators parameter equal to false so the user interacting with your view doesn't activate the scroll indicator (it can happen even without scrolling enabled).
ScrollView(showsIndicators: false)
In the onAppear modifier for the ScrollView, add this line.
UIScrollView.appearance().bounces = false
In the onDisappear modifier for the ScrollView, add this line.
UIScrollView.appearance().bounces = true
If you set UIScrollView.appearance().bounces equal to false in the init, it will prevent all of your app's ScrollViews from bouncing. By setting it equal to true in the onAppear modifier and equal to false in the onDisappear modifier, you ensure that it only effects the one ScrollView.
Your ScrollView should look something like this.
ScrollView(showsIndicators: false) {
...
...
...
}
.onAppear {
UIScrollView.appearance().bounces = false
}
.onDisappear {
UIScrollView.appearance().bounces = true
}
A better solution would be to use viewbuilder and create your own scrollview that doesn't bounce when the content size is less than scrollview frame size.
import SwiftUI
struct BounceVerticalDisableScrollView<Content: View>: View {
#State private var alwaysBounceVertical = false
let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
GeometryReader { scrlViewGeometry in
ScrollView {
content
.background(
GeometryReader {
// calculate height by clear background
Color.clear.preference(key: SizePreferenceKey.self,
value: $0.frame(in: .local).size.height)
}.onPreferenceChange(SizePreferenceKey.self) {
self.alwaysBounceVertical = $0 < scrlViewGeometry.size.height
}
)
}
// disable scroll when content size is less than frame of scrollview
.disabled(self.alwaysBounceVertical)
}
}
}
// return size
public struct SizePreferenceKey: PreferenceKey {
public static var defaultValue: CGFloat = .zero
public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
So, that you can use the scrollview as:
var body: some View {
VStack (alignment: .leading, spacing: 10) {
BounceVerticalDisableScrollView{
VStack (alignment: .leading, spacing: 10) {
....
....
}.padding()
}
}
}
Apple introduced an new modifier named scrollBounceBehavior with iOS 16.4 that can be used to prevent the ScrollView from bouncing when the content is smaller than the screen.
https://developer.apple.com/documentation/swiftui/view/scrollbouncebehavior(_:axes:)
struct RoomDetailsView: View {
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
.scrollBounceBehavior(.basedOnSize)
}
}