My views are not updating

Hello, I tried to post this yesterday, but let me see if I can put it out better this time.

I deal out some cards (images)

With an .onTapgesture (3 taps) I want to discard (remove from hand) a selected card. My code updates the array correctly by removing the selected card, the selected card is added to the discard pile array, but the selected card is still showing in the hand while the last card is now not showing.

I pressed 3 times on the 8 of diamonds. The last card (6 of spades) is not showing, but the 8 still is.

When I go back to my menu screen then choose to show the hand again, it then shows it correctly.

Here are the sequence of screens:

Thanks for your advice!

Kelly

It would be helpful if you provide the code you have since we need to see what you are doing in code.

The array storing the cards that are displayed should be declared as a @State property in the code that controls that View. Alternatively if you have an ObservableObject class that controls your data the array should be a @Published property. When you make a change to a @Published property, any View relying on that will be redrawn.

Sorry, I am not able to post here. The code is in my attempt to post yesterday.

Thanks for looking at this.

Kelly

What you have posted previously is insufficient to determine what you are doing.

You should be able to post your code in a reply. Don’t post as images since that just means that nobody can easily copy the code and check it.

When you paste code in, paste it as text and then format it as a code block. To do that, copy the code from Xcode and paste in your reply. Select the code you just pasted in and then from the compose post toolbar above tap the Pre-formatted text button which looks like this </>. What that does is insert three back-ticks ``` above and below the code block to format it nicely. You could manually insert those backticks if you wanted to but they must be the first characters on the line and the only characters on that line. The backtick character is on the same key as the tilde ~ just under the ESC key on a QWERTY keyboard.

Here is my code:
import SwiftUI

struct p1Hand: View {
    @EnvironmentObject var cardPositions: CardPositions
    @EnvironmentObject var p1cards: P1Cards
    @EnvironmentObject var lprvars: LPRvars
    
    var body: some View {
        ZStack {
            playerHandBackground()
            ForEach(0..<p1cards.p1hand.count, id: \.self) { cardnum in cardView(showcard: p1cards.p1hand[cardnum], cardPos: cardPositions.dr1Pos[cardnum], index: cardnum)}
        }
        .navigationTitle("Player 1's Hand").font(.footnote)
    }
}

import SwiftUI

struct cardView: View {
    @EnvironmentObject var cardPositions: CardPositions
    @EnvironmentObject var carddecks: CardDecks
    @EnvironmentObject var p1cards: P1Cards
    @GestureState private var pressing: Bool = false
    @State private var expand: Bool = false
    @State var showcard: String
    @State var cardPos: CGPoint
    @State var index: Int
    
    // arguments passed in: (cardName, cardPos)
    var body: some View {
        Image(showcard).resizable().frame(width: 40, height: 60).position(cardPos)
        //  3 taps -> discard: put on discard pile and remove from hand
            .onTapGesture(count: 3, perform: {
                print("discard card: \(showcard), index: \(index)")
                carddecks.discarddeck.append(showcard)
                print("before: \(p1cards.p1hand)")
                p1cards.p1hand.remove(at: index)
                print("after: \(p1cards.p1hand)")
            })
       }
}

Again, the code you have supplied is insufficient to successfully debug anything. These are the errors I receive with each struct you have provided.

For anyone to successfully assist you, the supporting code needs to be provided to make the App work so that we can see what you are trying to do.
ie. the contents of CardPositions, P1Cards, LPRvars and the definition of playerhandBackground

If you are not willing to provide all of the code then there is little anyone can do to help you.

Chris, sorry got tied up on non-swift activities. I have spent some time culling out just the code of my specific problem. I have a lot of images in my Assets that represent the shuffler and the cards. I am not sure how to send the Assets.

So when I run this code set, i shuffle the cards and it assigns cards to an array (player1cards). Then when I try to ‘discard’ by tapping a card 3 times it works for removing from the player1cards array, but does not take off the screen. It does take off the last card showing, which it is not supposed to unless I discard it.

But here are all the code blocks:
// cardsApp.swift
// cards

import SwiftUI
@main
struct cardsApp: App {
    @StateObject private var decks = Decks()
    @StateObject private var player1cards = Player1Cards()
    @StateObject private var cardlocations = CardLocations()
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(decks)
                .environmentObject(player1cards)
                .environmentObject(cardlocations)
        }
    }
}

ContentView file:

import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack{Color.cyan.ignoresSafeArea()
            NavigationStack {
                List() {
                    Group {
                        NavigationLink(destination: shuffleAndDeal(), label: {Text("Shuffle & Deal >").bold().fontWeight(.heavy).foregroundColor(.black)}).listRowBackground(Color(.systemTeal))
                        NavigationLink(destination: player1hand(), label: {Text("Player 1's Hand >").bold().fontWeight(.heavy).foregroundColor(.black)}).listRowBackground(Color(.systemTeal))
                    }                    
                }
                .navigationTitle("Menu")
                .listStyle(.automatic)
            }

        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
//  player1hand.swift
//  cards

import SwiftUI
class Decks: ObservableObject {
    @Published var twodecks: [String] = ["2_of_spades", "3_of_spades", "4_of_spades", "5_of_spades", "6_of_spades", "7_of_spades", "8_of_spades", "9_of_spades", "10_of_spades", "jack_of_spades", "queen_of_spades", "king_of_spades", "ace_of_spades", "2_of_hearts", "3_of_hearts", "4_of_hearts", "5_of_hearts", "6_of_hearts", "7_of_hearts", "8_of_hearts", "9_of_hearts", "10_of_hearts", "jack_of_hearts", "queen_of_hearts", "king_of_hearts", "ace_of_hearts"]
    @Published var discarddeck: [String] = ["2_of_clubs"]
}
class Player1Cards: ObservableObject {
    @Published var p1hand: [String] = []
    }
class CardLocations: ObservableObject {
    @Published var dr1Pos: [CGPoint] = [CGPoint(x: 30, y: 75), CGPoint(x: 50, y: 75), CGPoint(x: 70, y: 75), CGPoint(x: 90, y: 75), CGPoint(x: 110, y: 75), CGPoint(x: 130, y: 75), CGPoint(x: 150, y: 75), CGPoint(x: 170, y: 75), CGPoint(x: 190, y: 75), CGPoint(x: 210, y: 75), CGPoint(x: 230, y: 75), CGPoint(x: 250, y: 75), CGPoint(x: 270, y: 75), CGPoint(x: 290, y: 75), CGPoint(x: 310, y: 75), CGPoint(x: 330, y: 75), CGPoint(x: 350, y: 75), CGPoint(x: 370, y: 75)]
}

struct shuffleAndDeal: View {
    @EnvironmentObject var decks: Decks
    @EnvironmentObject var player1cards: Player1Cards
    @EnvironmentObject var cardlocations: CardLocations
    
    var body: some View {
        Button(action: {
            decks.twodecks.shuffle()
            var counter = 0
            while counter < 10 {
                player1cards.p1hand.append(decks.twodecks[counter])
                counter += 1
            }
        }, label: { VStack {
            Spacer()
            Image("Shuffle").resizable().frame(width: 150, height: 150)
            Text("Shuffle!").font(.largeTitle).foregroundColor(.orange)
        }})
    }
}
        
struct player1hand: View {
    @EnvironmentObject var decks: Decks
    @EnvironmentObject var player1cards: Player1Cards
    @EnvironmentObject var cardlocations: CardLocations
            
    var body: some View {
        ZStack {
            ForEach(0..<player1cards.p1hand.count, id: \.self) { cardnum in cardViewV2(showcard: player1cards.p1hand[cardnum], cardPos: cardlocations.dr1Pos[cardnum], index: cardnum)}
        }
        .navigationTitle("Player 1's Hand").font(.footnote)
    }
}

struct cardViewV2: View {
    @EnvironmentObject var cardpositions: CardLocations
    @EnvironmentObject var decks: Decks
    @EnvironmentObject var player1cards: Player1Cards
    @GestureState private var pressing: Bool = false
    @State private var expand: Bool = false
    @State var showcard: String
    @State var cardPos: CGPoint
    @State var index: Int
    
    // arguments passed in: (cardName, cardPos)
    var body: some View {
        Image(showcard).resizable().frame(width: 40, height: 60).position(cardPos)
        //  3 taps -> discard: put on discard pile and remove from hand
            .onTapGesture(count: 3, perform: {
                print("discard card: \(showcard), index: \(index)")
                decks.discarddeck.append(showcard)
                print("before: \(player1cards.p1hand)")
                player1cards.p1hand.remove(at: index)
                print("after: \(player1cards.p1hand)")
            })
    }
}

struct player1hand_Previews: PreviewProvider {
    static var previews: some View {
        player1hand()
            .environmentObject(Decks())
            .environmentObject(CardLocations())
            .environmentObject(Player1Cards())
            
    }
}

I have pondered this for quite a while and appreciate yours or any others advice and suggestions!

Thanks,
Kelly

Hi Kelly,

The easiest way to make the project available for testing (which will include the assets) is to compress it at the top level folder and post it to either Dropbox or to Google Drive and create a share link from either one of those sites which you can paste in a reply.

To compress the project, locate the project folder in Finder and then right click on the top level folder and select Compress ProjectName. The top level folder is the one that contains the file named ProjectName.xcodeproj and the folder named ProjectName.

Upload that zip file to either Dropbox or Google Drive and then create a share link then copy that link and paste that in a reply to this thread.

If you choose to use Google Drive then when you create the Share link, ensure that the “General Access” option is set to “Anyone with the Link” rather than “Restricted”.

Chris, here is the project I uploaded to a google drive folder.

thanks,
Kelly

Cheers Kelly. I’ve got your code. I’ve had a very quick look and I can see that the hand is not updating correctly. I’m away from home at present and at an event all day with a long drive home. I’ll take a closer look tomorrow morning my time.

Hi Kelly,

I’ve just finished having a look at your project (was too busy this morning).

I took the liberty of renaming some of your structs and variables so that they follow the swift convention which is

  • structs and classes should be Capitalised which means that each word in the name should be capitalised. ie: shuffleAndDeal should be ShuffleAndDeal.
  • variable names should begin with a lowercase word and then each subsequent word in the name should be capitalised. ie:
    twodecks should be twoDecks.
  • methods (functions) should also be named in the same format as variables which means the first word begins with a lowercase letter and subsequent words in the name should be capitalised.
  • structs are better to be in their own file in the interests of making things easier to maintain. There is no additional overhead in Xcode so it makes things tidier

Hope that helps you going forward.

Defining the card positions in the way that you have is going to make your life a misery since it is very difficult to maintain.

What you need to do is to synthesise the position of the card by taking advantage of the cardIndex in the ForEach loop (I changed it to cardIndex from cardnum since it is an index so my thinking is to call it that to be clear).

What I have done is rewritten CardViewV2 to be just a View, meaning that it has very little logic and just displays the card and positions it correctly (I made the frame slightly bigger as I felt that the cards were a little too small). How that works is that the x: position has an initial position of 40 and what is added to that is the cardIndex value multiplied by 25 (which it the offset value between each card). So when the first card gets drawn on the screen, it starts at 40 and added to that is zero multiplied by 25 which is 0 so that x: value remains as 40. When the index value is 1 then you have 40 plus 1 * 25 so the x: value is now 65… then 90, then 115… you get the idea. That’s what I mean by synthesising.

This allows you to get rid of the CardLocations class if you so choose.

The reason that the displayed cards in Player1Hand are not updating correctly is that the .onTapGesture is disassociated with the View of the cards. It should be in the Player1Hand View so that the Gesture knows exactly what card has been tapped. SwiftUI can be tricky.

Have a look at the rearranged code to see if you can understand what I have done. If there is anything you don’t understand then please ask and I will clarify.

Download the project at the link below and save it somewhere different to where your current project is so you don’t lose what you already have.

Good luck with the project.

Chris, wonderful, thank you very much! So it is the onTapGesture!! Ideally, need to add a longPressGesture and a drag gesture to the cards as well. (They were working fine before.) I appreciate your tidy comments, I normally do most of that, but I was sloppy culling out the code for this problem project. I will be working through this later on today.

So, Chris, making great headway incorporating your suggestions. I have the long press gesture & discard working great.

Now, I am stuck with being able to also drag a card. For each card, I need to discard it, move it to a different group of cards and I move cards around on the screen to group them. That is why I was defining and wanting to maintain card positions.

In earlier versions of this I am able to just drag a card around, but not able to add this feature to what you have given me.

Thoughts?
Kelly

@foremank this conference talk I gave goes into the details Chris mentioned about proper capitalization and more, and especially why it’s important

1 Like

Hi Kelly,

The thing is that anything you do with regard to your App design needs to be such that it will work on other iPhone or iPad devices that are of varying sizes so the key will be to dynamically position the player card hands. It may be that you need to define your card as a struct so that is has properties such as name, position on the screen, which hand it belongs to and whatever other property that may be pertinent. If you hard code positions on the screen you will probably find that it won’t look right on different device sizes.