NavigationStack, NavigationLink and onLongPressGesture together - swiftui-navigationlink

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 ...

Related

In SwiftUI, supporting iOS 14 and above, how do I get a ScrollView child view to ignore the safe area

I have a VStack that contains a Rectangle. The Rectangle has an edgesIgnoresSafeArea(.top) view modifier on it to extend it through the top safe area.
import SwiftUI
struct ScrollViewSafeArea: View {
var body: some View {
ScrollView {
VStack {
Rectangle()
.fill(Color.orange)
.frame(height: 200)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
.background(Color.green.ignoresSafeArea())
}
}
Nice!
However, when I embed this inside a ScrollView, the Rectangle no longer extends through the safe area.
import SwiftUI
struct ScrollViewSafeArea: View {
var body: some View {
ScrollView {
VStack {
Rectangle()
.fill(Color.orange)
.frame(height: 200)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
.background(Color.green.ignoresSafeArea())
}
}
I can add a negative top padding to the Rectangle to extend it through the safe area, but this feels hacky to me.
Does anyone have a better way?
So I figured it out. I feel kind-of stupid actually. I wasn't ignoring the safe areas properly. Here is the code that does what I want it to do.
import SwiftUI
struct ScrollViewSafeArea: View {
var body: some View {
ScrollView {
VStack {
Rectangle()
.fill(Color.orange)
.frame(height: 200)
Spacer()
}
}
.ignoresSafeArea()
// Need to ignoreSafeArea again on the background color so
// the color extends to the horizontal edges in landscape.
.background(Color.green.ignoresSafeArea())
}
}
struct ScrollViewSafeArea_Previews: PreviewProvider {
static var previews: some View {
ScrollViewSafeArea()
}
}

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
}
}
}

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

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)
}
}

SwiftUI with ObservableObject - List having NavigationLink to details downloading it on scroll NOT on appear

I have list of cities
struct CityListView: View {
#ObservedObject private(set) var citiesViewModel: CitiesViewModel
var body: some View {
LoadingView(isShowing: .constant(citiesViewModel.cities?.isEmpty ?? false)) {
NavigationView {
List(self.citiesViewModel.cities ?? []) { city in
NavigationLink(destination: DetailView(cityName: city.name,
detailCityModel: DetailCityModel(cityId: city.id))) {
Text(city.name)
}
}
.navigationBarTitle(Text("Cities"), displayMode: .large)
}
}
}
}
and when I'm scrolling the list, the DetailCityModel inits and download data from API. How to downloading (or init DetailCityModel) on DetailView's appearance, not for showing item with NAvigationLink to DetailView?
You have to kick off the API call in onAppear() not in the initialiser of DetailView.