navigationDestination(isPresented:destination:) iOS16 doesn't send to view - ios

Im trying to put a navigationDestination(isPresented:destination:) for a button on Xcode14/iOS16, it executes the func and so when I press the button but it doesn't send me to the new view and also i cant see if it is passing the information from one view to another.
Button(action: {watchGame(name: "Cuphead")} , label: {
Image("abzu")
.resizable()
.scaledToFit()
.frame(width: 240, height: 135)
}).navigationDestination(isPresented: $isGameViewActive)
{gameView(url: url,
titulo: titulo,
studio: studio,
calificacion: calificacion,
anoPublicacion: anoPublicacion,
descripcion: descripcion,
tags: tags,
imgsUrl: imgsUrl)
}
func watchGame(name:String) {
juegoEncontrado.search(gameName: name)
DispatchQueue.main.asyncAfter(deadline: .now() + 1){
print("Cantidad E: \(juegoEncontrado.gameInfo.count)")
print("juego \(juegoEncontrado.gameInfo[0].title)")
print(isGameViewActive)
if juegoEncontrado.gameInfo.count == 0 {
isGameInfoEmpty = true
}else{
url = juegoEncontrado.gameInfo[0].videosUrls.mobile
titulo = juegoEncontrado.gameInfo[0].title
studio = juegoEncontrado.gameInfo[0].studio
calificacion = juegoEncontrado.gameInfo[0].contentRaiting
anoPublicacion = juegoEncontrado.gameInfo[0].publicationYear
descripcion = juegoEncontrado.gameInfo[0].description
tags = juegoEncontrado.gameInfo[0].tags
imgsUrl = juegoEncontrado.gameInfo[0].galleryImages
isGameViewActive = true
}
}
}
watchGame is my function to search the name given and return all the information strings that I need to give back to my new view, the isGameViewActive toggle is inside this function so don't worry about that, any idea on how can I solve this?
I tried putting the button or the hole view inside a NavigationStack but when I do that all the content disappears from the screen, Im not understanding this new navigation schemes.
The function inside the button executes but it doesn't send me to the other view on the .navigationDestination

Related

Focused TextField ends up covered by the keyboard in SwiftUI

What I'm trying to accomplish
A vertical list of TextField views that user enters values in one at a time. After entering a value in a text field and pressing keyboard submit button the focus moves to the next text field.
The problem I'm facing
The keyboard obstructs the active text field. I was hoping that if I used ScrollView it would automagically scroll to the next focused text field, but that's not the case.
Then I decided to try using ScrollViewReader's ScrollViewProxy object to scroll to the focused text field, which works! But not quite. It scrolls to the selected text field, but not enough to go over the keyboard. It seems to always be just a little bit underneath the top of the keyboard (regardless of whether there's keyboard toolbar or autocorrection bar or nothing).
Here's a screenshot of roughly where the text field ends up after I call proxy.scrollTo(focusedInput):
Here's where I would want it to end up:
Minimal code sample
struct ContentView: View {
#State var inputsValues: [String]
#FocusState var focusedInput: Int?
init() {
inputsValues = (0..<30).map { _ in "" }
}
var body: some View {
ScrollViewReader { proxy in
ScrollView {
VStack {
ForEach((0..<inputsValues.count), id: \.self) { i in
TextField("Value", text: $inputsValues[i])
.focused($focusedInput, equals: i)
.submitLabel(.next)
.id(i)
.onSubmit {
if (i + 1) < inputsValues.count {
focusedInput = i + 1
proxy.scrollTo(focusedInput)
} else {
focusedInput = nil
}
}
}
}
}
}.toolbar {
ToolbarItem(placement: .keyboard) {
Text("This is toolbar")
}
}
}
}
Chaning two+ states in one closure (when they are related somehow or affects one area) are handled not very good (or not reliable). The fix is to separate them in different handlers.
Tested with Xcode 13.3 / iOS 15.4
Here is main part:
VStack {
// ... other code
.onSubmit {
// update state here !!
if (i + 1) < inputsValues.count {
focusedInput = i + 1
} else {
focusedInput = nil
}
}
}
}
.onChange(of: focusedInput) {
// react on state change here !!
proxy.scrollTo($0)
}
Complete test module is here

How can I simulate a tap/swipe gesture on some part of the screen programmatically in SwiftUI?

I have a SwiftUI app that behaves similarly to Tinder (built using this guide - or see source code). You just swipe a stack of cards left and right. I have a "thumbs up" and "thumbs down" button as alternatives to swiping, but when they are pressed, I'd like it to swipe the card away for the user as if they had swiped it themselves.
Are there any ways to do this in SwiftUI without UIKit?
If your thumbs up and down buttons were part of the CardView then it would be quite simple to achieve: just repeat whatever code executes on gesture inside a button action. Something along these lines:
// CardView.swift
Button (action: {
// hardcoded for simplicity
self.translation = .init(width: 100, height: 0)
self.swipeStatus = .like
// a hacky way to "wait" for animation to complete
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3, execute: {
self.onRemove(self.user)
})
}) {
Image(systemName: "hand.thumbsup")
.foregroundColor(.green)
}
But, if your buttons reside outside of the CardView, I think you'll have to re-architect your views a little bit. I would start by converting swipeStatus from #State into a #Binding, so its value can be passed down from the parent view. Then you will need to declare a state variable in your ContentView, which you can then tie together with swipeStatus binding. Something like:
// ContentView.swift
#State var currentSwipeStatus: LikeDislike = .none
....
CardView(
swipeStatus: self.users.last == user
? $currentSwipeStatus
: .constant(.none),
user: user,
onRemove: { removedUser in
self.users.removeAll { $0.id == removedUser.id}
self.swipeStatus = .none
})
Within your card view you'll need to react to the bound value changing. I just appended onChange to the main VStack:
.onChange(of: swipeStatus) { newState in
if newState == .like {
self.translation = .init(width: 100, height: 0)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3, execute: {
self.onRemove(self.user)
})
}
}
I feel like there's a more elegant way of achieving this. I'm not a big fan of the onRemove callback, asyncAfter hack, and the fact that they render all cards at once.
But at least that's a start.

Is it possible in SwiftUI to only allow one onTapGesture input at a time?

I'm new to Swift so please excuse my ignorance on this topic. I'm trying to write a simple Tic Tac Toe game as a personal project to learn Swift but I keep running into an issue where if the user were to hit multiple spaces at exactly the same time, the game would place the "X" in both spots.
I have it set so that once a space is tapped by the user, a var named "playerTurn" toggles to false to prevent the user from tapping another spot until after the AI has done its turn, at which point, it toggles playerTurn back to true. The toggle happens within the "updateBlock()" func. Every time the user taps on a space, an if statement checks whether it's the player's turn or not.
The problem is that if the user taps on two or more spaces as just the right time, the function in both spaces will execute because it did not have enough time to toggle playerTurn to false. I have a piece of the code below. I'm sure I'm missing something obvious but I just can't seem to figure out how to get it to work the way I need. Any assistance is much appreciated!
HStack {
Spacer()
Image(iconArray[blockValue[0]])
.resizable()
.aspectRatio(1, contentMode: .fit)
.background(Color.accentColor)
.cornerRadius(20)
.scaleEffect(0.9)
.onTapGesture {
if playerTurn == true && zeroBlocksExist[0] == true {
// Ensure that it is the player's turn and that the space is vacant before updating the space with the new value
updateBlock(blockPosition: 0)
}
}
Image(iconArray[blockValue[1]])
.resizable()
.aspectRatio(1, contentMode: .fit)
.background(Color.accentColor)
.cornerRadius(20)
.scaleEffect(0.9)
.onTapGesture {
if playerTurn == true && zeroBlocksExist[1] == true {
// Ensure that it is the player's turn and that the space is vacant before updating the space with the new value
updateBlock(blockPosition: 1)
}
}
Image(iconArray[blockValue[2]])
.resizable()
.aspectRatio(1, contentMode: .fit)
.background(Color.accentColor)
.cornerRadius(20)
.scaleEffect(0.9)
.onTapGesture {
if playerTurn == true && zeroBlocksExist[2] == true {
// Ensure that it is the player's turn and that the space is vacant before updating the space with the new value
updateBlock(blockPosition: 2)
}
}
Spacer()
}
// updateBlock function
func updateBlock(blockPosition: Int) {
DispatchQueue.global().async {
if movesCount < 9 {
if playerTurn == true {
playerTurn.toggle() // Disable player turn
titleText = "CPU's turn"
restartButtonState.toggle() // Disable the restart button until after the CPU does its turn
blockValue[blockPosition] = 1 // Put the X icon in the requested space
if allowSounds {
playAudio(soundName: "pop-1", soundExt: "mp3") // Play the pop sound effect
}
checkVacantBlocks() // Check vacant blocks and update the array
} else if playerTurn == false {
blockValue[blockPosition] = 2 // Put the O icon
playerTurn.toggle()
titleText = "Your turn"
restartButtonState.toggle() // Enable the restart button
checkVacantBlocks()
}
movesCount += 1
print("Moves made: ", String(movesCount))
} else if movesCount >= 9 {
print("Game board full!")
checkWinnings()
}
return
}
}

Creating an image Gallery with next and previous buttons in Xcode

I'm new to the development of Swift. I've done several courses but I have some questions that I can't find.
I want to design an application that has 50 or more images, with a previous and next button to go through them in a matrix.
I've looked at random snippets of sample code, but I'm having a hard time figuring out how to do it. What's the easiest and most efficient method to go back and forth between these images in a view? The other problem I have is that I want to be able to save the image when it is selected.
Surely the answer to this is easy for you, but I'm a beginner, and I can't find a solution, after two days of searching and trying.
Thanks in advance!
Finotoni
There is some steps you need to do:
Get/ define your images
Have methods (or another way) of getting the previous and next image
Displaying the current image
Here is an really easy example that should help you understand:
struct ContentView: View {
// Your images
#State var images = ["image1", "image2", "image3"]
// Some indicator on what image is the current one
#State var imageIndicator: Int = 0
var body: some View {
HStack {
// Prev one
Button(action: {
// Call a method that gets you the previous image
if self.imageIndicator == 0 {
self.imageIndicator = self.images.count - 1
} else {
self.imageIndicator = self.imageIndicator - 1
}
}) {
Image(systemName: "arrow.left")
}
// Image with indicator to what image to display
Image(self.images[self.imageIndicator]).resizable().frame(width: 100, height: 100)
// Next one
Button(action: {
// Call a method that gets you the next image
if self.imageIndicator == self.images.count - 1 {
self.imageIndicator = 0
} else {
self.imageIndicator = self.imageIndicator + 1
}
}) {
Image(systemName: "arrow.right")
}
}
}
}
Produces this:

Dynamic view with swiftUI

I'm trying to make a dynamic view to answer some questions. Those questions are created from my back office, ant the next question depends on the current user answer. So I have to build my view as things progress.
Here is a example :
First Question :
Are you alive ? : | Are you sure ?
|
YES/NO (no chosen) | YES/NO
I made a subview with the different type of question, and a container view to embed the path like that :
Here is the code of my container View, the white block is embed in a Scrollview
#ObservedObject var VM:SurveyBrowserViewModel
#State private var offsetX:CGFloat = 0
#State private var opacity:Double = 1
#State var myView:AnyView = AnyView(EmptyView())
var body: some View {
ZStack{
Image("background_survey_canyon")
.resizable()
backgroundTriangle
.opacity(0.65)
ScrollView(showsIndicators:false){
myView
.padding(.top, 55 + safeAreaTop)
.padding(.bottom, 60 + safeAreaBottom)
.offset(x : offsetX , y: 0)
.opacity(opacity)
.onReceive(VM.nextQuestionPassthrough) { (v) in
self.myView = v
}
.onReceive(VM.nextQuestionValuePassthrough) { (v) in
self.offsetX = v
self.opacity = 0
withAnimation(.spring()) {
self.offsetX = 0
self.opacity = 1
}
}
}
.frame(maxWidth: .infinity,maxHeight: .infinity)
}
}
and here is the viewModel section which updates the View state, current question is the question which needs to be displayed according the good type.
var nextQuestionPassthrough = PassthroughSubject<AnyView,Never>()
/// Get The good view to match the correct question tyoe
/// - Parameter answerSelected: Binding bool to hide/show next button visibility
private func getGoodView(){
guard (currentQuestion != nil) else { nextQuestionPassthrough.send(AnyView(EmptyView())); return }
switch QuestionType.intToQuestionType(value: currentQuestion!.type!){
case .YesOrNo :
if currentQuestionViewModel == nil{
currentQuestionViewModel = YesNoQuestionViewModel(question: currentQuestion!,temporaryId: temporaryId)
}
nextQuestionPassthrough.send(AnyView(YesNoQuestionView(VM: currentQuestionViewModel as! YesNoQuestionViewModel,answerSelected: nextViewState! )))
}
}
I've tried different way to achieve what I'm expecting :
I want a animation like a auto scroll coming form right to left, but it's cosmetic and it's not my main problem.
My problem is that when i send view from the nextQuestionPassthrough the init section from my new view is called but not the appear one. So all state are saved as we can see on the picture ( yes and no answer are two #State set to false at initialization but stay selected when I send the new view. I tried several ideas, like a published view instead of a #State but the behavior is the same.
I'm thinking about a new approch, with my container embed two views to make the good animation and the good life cycle ( init then appear )
Has anyone ever studied this problem or done something similar?

Resources