Swift/Golang API: проблема с извлечением из БД при заполнении представления

В настоящее время я сталкиваюсь с ошибкой, из-за которой первоначальный счетчик лайков для этих цитат по умолчанию равен 0 и обновляется до правильного значения только тогда, когда я нажимаю кнопку «Мне нравится». Очевидно, мой метод likeQuoteAction работает правильно, и я предполагаю, что метод getLikeCountForQuote работает, потому что его ответ от моего API имеет код состояния 200:

Исходный код для этого запроса GET:

    // GET /quoteLikes/:id - get the number of likes for a specific quote by ID
    r.GET("/quoteLikes/:id", func(c *gin.Context) {
        idStr := c.Param("id")
        id, err := strconv.Atoi(idStr)
        if err != nil {
            c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": "Invalid quote ID."})
            return
        }

        var likes int
        err = db.QueryRow("SELECT likes FROM quotes WHERE id = $1", id).Scan(&likes)
        if err == sql.ErrNoRows {
            c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"message": "Quote not found."})
            return
        } else if err != nil {
            log.Println(err)
            c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "Failed to retrieve likes count from the database."})
            return
        }

        c.IndentedJSON(http.StatusOK, gin.H{"id": id, "likes": likes})
    })

Это структура, которую я использую для заполнения цитат текстом, автором и лайками. Здесь мне нужно что-то изменить, чтобы начальный подсчет лайков был точным:

struct SingleQuoteView: View {
    @EnvironmentObject var sharedVars: SharedVarsBetweenTabs
    let quote: Quote
    @AppStorage("likedQuotes", store: UserDefaults(suiteName: "group.selectedSettings"))
    private var likedQuotesData: Data = Data()
    
    @AppStorage("bookmarkedQuotes", store: UserDefaults(suiteName: "group.selectedSettings"))
    private var bookmarkedQuotesData: Data = Data()
    
    @State private var isLiked: Bool = false
    @State private var isBookmarked: Bool = false
    @State private var likes: Int = 0 // Assuming this is what it's grabbing for the value, without updating until the like button is pressed
    @State private var isLiking: Bool = false
    
    init(quote: Quote) {
        self.quote = quote
        self._isBookmarked = State(initialValue: isQuoteBookmarked(quote))
        self._isLiked = State(initialValue: isQuoteLiked(quote))
    }
    private func getQuoteLikeCountMethod(completion: @escaping (Int) -> Void) {
        getLikeCountForQuote(quoteGiven: quote) { likeCount in
            completion(likeCount)
        }
    }
    
    private func getLikeCountForQuote(quoteGiven: Quote, completion: @escaping (Int) -> Void) {
        guard let url = URL(string: "omitted for stackoverflow") else {
            completion(0)
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let data = data,
               let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
               let likeCount = json["likeCount"] as? Int {
                completion(likeCount)
            } else {
                completion(0)
            }
        }.resume()
    }

    
    var body: some View {
        VStack {
            HStack {
                Text("\"\(quote.text)\"")
                    .font(.title3)
                    .foregroundColor(colorPalettes[safe: sharedVars.colorPaletteIndex]?[1] ?? .white)
                    .padding(.bottom, 2)
                    .frame(alignment: .leading)
                Spacer()
            }
            
            if let author = quote.author, author != "Unknown Author", !author.isEmpty, author != "NULL", author != "" {
                HStack {
                    Spacer()
                    Text("— \(author)")
                        .font(.body)
                        .foregroundColor(colorPalettes[safe: sharedVars.colorPaletteIndex]?[2] ?? .white)
                        .padding(.bottom, 5)
                        .frame(alignment: .trailing)
                }
            }
            
            HStack {
                HStack {
                    Button(action: {
                        likeQuoteAction()
                        toggleLike()
                    }) {
                        Image(uiImage: resizeImage(UIImage(systemName: isLiked ? "heart.fill" : "heart")!, targetSize: CGSize(width: 75, height: 27))!)
                            .foregroundColor(isLiked ? .yellow : .gray)
                    }
                    
                    // Display the like count next to the heart button
                    Text("\(likes)")
                }
                
                Button(action: {
                    toggleBookmark()
                }) {
                    Image(uiImage: resizeImage(UIImage(systemName: isBookmarked ? "bookmark.fill" : "bookmark")!, targetSize: CGSize(width: 75, height: 27))!)
                        .foregroundColor(isBookmarked ? .yellow : .gray)
                }
                
                if #available(iOS 16.0, *) {
                    let authorForSharing = (quote.author != "Unknown Author" && quote.author != "NULL" && quote.author != "" && quote.author != nil) ? quote.author : ""
                    let wholeAuthorText = (authorForSharing != "") ? "\n— \(authorForSharing ?? "Unknown Author")" : ""
                    
                    ShareLink(item: URL(string: "https://apps.apple.com/us/app/quote-droplet/id6455084603")!, message: Text("From the Quote Droplet app:\n\n\"\(quote.text)\"\(wholeAuthorText)")) {
                        Image(uiImage: resizeImage(UIImage(systemName: "square.and.arrow.up")!, targetSize: CGSize(width: 75, height: 27))!)
                    }
                } else {
                    // Fallback on earlier versions
                }
                
                Spacer()
            }
        }
        .padding()
        .background(ColorPaletteView(colors: [colorPalettes[safe: sharedVars.colorPaletteIndex]?[0] ?? Color.clear]))
        .cornerRadius(20)
        .shadow(radius: 5)
        .padding(.horizontal)
        .onAppear {
            isBookmarked = isQuoteBookmarked(quote)

            getQuoteLikeCountMethod { fetchedLikeCount in
                likes = fetchedLikeCount
            }
            isLiked = isQuoteLiked(quote)
        }
    }
    
    private func toggleBookmark() {
        isBookmarked.toggle()
        
        var bookmarkedQuotes = getBookmarkedQuotes()
        if isBookmarked {
            bookmarkedQuotes.append(quote)
        } else {
            bookmarkedQuotes.removeAll { $0.id == quote.id }
        }
        saveBookmarkedQuotes(bookmarkedQuotes)
    }
    
    private func toggleLike() {
        isLiked.toggle()
        
        var likedQuotes = getLikedQuotes()
        if isLiked {
            likedQuotes.append(quote)
        } else {
            likedQuotes.removeAll { $0.id == quote.id }
        }
        saveLikedQuotes(likedQuotes)
    }
    
    private func likeQuoteAction() {
        guard !isLiking else { return }
        isLiking = true
        
        // Check if the quote is already liked
        let isAlreadyLiked = isQuoteLiked(quote)
        
        // Call the like/unlike API based on the current like status
        if isAlreadyLiked {
            unlikeQuote(quoteID: quote.id) { updatedQuote, error in
                DispatchQueue.main.async {
                    if let updatedQuote = updatedQuote {
                        // Update likes count
                        self.likes = updatedQuote.likes ?? 0
                    }
                    self.isLiking = false
                }
            }
        } else {
            likeQuote(quoteID: quote.id) { updatedQuote, error in
                DispatchQueue.main.async {
                    if let updatedQuote = updatedQuote {
                        // Update likes count
                        self.likes = updatedQuote.likes ?? 0
                    }
                    self.isLiking = false
                }
            }
        }
    }
    
    private func isQuoteLiked(_ quote: Quote) -> Bool {
        return getLikedQuotes().contains(where: { $0.id == quote.id })
    }
    
    private func getLikedQuotes() -> [Quote] {
        if let quotes = try? JSONDecoder().decode([Quote].self, from: likedQuotesData) {
            return quotes
        }
        return []
    }
    
    private func saveLikedQuotes(_ quotes: [Quote]) {
        if let data = try? JSONEncoder().encode(quotes) {
            likedQuotesData = data
        }
    }
    
    private func isQuoteBookmarked(_ quote: Quote) -> Bool {
        return getBookmarkedQuotes().contains(where: { $0.id == quote.id })
    }
    
    private func getBookmarkedQuotes() -> [Quote] {
        if let quotes = try? JSONDecoder().decode([Quote].self, from: bookmarkedQuotesData) {
            return quotes
        }
        return []
    }
    
    private func saveBookmarkedQuotes(_ quotes: [Quote]) {
        if let data = try? JSONEncoder().encode(quotes) {
            bookmarkedQuotesData = data
        }
    }
}

Для контекста каждый из этих SingleQuoteView соответствует одной из цитат в моем приложении:

Пожалуйста, дайте мне знать, если необходимы дополнительные разъяснения.

Слишком много недостающих частей, чтобы можно было скомпилировать и запустить тест вашего кода. Кажется, у вас есть два способа получить likes: один с помощью .onAppear с помощью getQuoteLikeCountMethod, который является просто бесполезной оберткой вокруг getLikeCountForQuote, а другой с помощью Button(action: { likeQuoteAction() ..., который, я думаю, отличается. Вы можете попробовать добавить print("--> нравится: \(лайки)") в .onAppear и других местах, чтобы увидеть, получите ли вы то, что ожидаете likes. Тогда покажи нам, что у тебя получится.

workingdog support Ukraine 16.06.2024 00:40

Справедливый комментарий @workingdogsupportUkraine. Да, это довольно расплывчатый вопрос, особенно если учесть, что он подключается не только к проекту Swift, но и к моему API. Однако я почти уверен, что проблема связана только с частью Swift. Чтобы прояснить, почему существует два метода получения лайков, это потому, что один предназначен для цитат, понравившихся пользователю, которые хранятся в настройках пользователя по умолчанию, как и цитаты, добавленные в закладки. Другой метод получения лайков связан с подсчетом лайков для конкретной цитаты, и именно здесь возникает проблема.

Daggerpov 17.06.2024 03:34

@workingdogsupportUkraine В настоящее время то, что я получаю, распечатывая likes из .onAppear(), когда я первоначально загружаю цитаты, следующее (значения лайков для каждой из 4 загруженных цитат): лайков 0 лайков 0 лайков 0 лайков 0 Затем, после того, как мне понравилась цитата , он отображается правильно, как счетчик (8) в текстовом поле. Хотя, когда я переключаю TabView и возвращаюсь к этому TabView, он печатает это: лайков 0 лайков 0 лайков 0 лайков 8 Очевидно, здесь он получает правильное количество лайков после того, как ему понравилось, но, как ни странно, на самом деле он ставит все 0 как текстовые значения для каждой цитаты считаются.

Daggerpov 17.06.2024 03:38

Я не вижу TabViews в вашем коде, поэтому невозможно сказать, что происходит, в частности, как вы передаете соответствующие переменные. Я предполагаю, что в .onAppear вы печатаете с замыканием getQuoteLikeCountMethod { ... print(...) }, потому что вне его вы всегда получите ноль.

workingdog support Ukraine 17.06.2024 07:56

Прежде чем if let data = data, let json = try?... добавьте отпечаток для отладки. Вам нужно посмотреть, что происходит: print("Получено для: (url.absoluteString): (ответ) и строковых данных: (String(data: data ?? Date(),coding: .utf8)")` и посмотреть, есть ли они действительно называются и каковы возвращаемые значения...

Larme 17.06.2024 11:29

@workingdogsupportUkraine Я намеренно не включил его, чтобы сосредоточиться на том, что имеет отношение к моему вопросу. Представления вкладок просто соответствуют кнопкам вкладок, которые вы видите внизу скриншота моего симулятора iPhone. Передается 0 переменных, влияющих на эту ошибку, поскольку я передаю только одну, относящуюся к цветам (см. код, sharedVars.colorPaletteIndex.

Daggerpov 20.06.2024 03:22

@workingdogsupportUkraine Я напечатал значение в конце .onAppear, после строки isLiked = isQuoteLiked(quote).

Daggerpov 20.06.2024 03:25
func getQuoteLikeCountMethod(...) является асинхронным, поэтому попробуйте печатать внутри замыкания, например: getQuoteLikeCountMethod { fetchedLikeCount in likes = fetchedLikeCount print("----> likes: \(likes)") } а не после, как вы говорите.
workingdog support Ukraine 20.06.2024 04:10

Привет @workingdogsupportUkraine, извините за поздний ответ; Я был занят своей повседневной работой. Распечатывая количество лайков в onAppear(), я действительно получаю правильные значения (например, 17), когда переключаю представление вкладок обратно в это представление «Капельки». Однако когда я печатаю внутри getQuoteLikeCountMethod, как вы недавно прокомментировали, счетчик лайков всегда равен 0, и это значения, отображаемые рядом с кнопками лайков на экране.

Daggerpov 01.07.2024 01:02

@workingdogsupportUkraine Хотя это происходит только после того, как я нажимаю кнопки «Нравится», а затем переключаюсь и возвращаюсь к этому представлению. Когда я впервые открываю приложение, счетчик лайков в getQuoteLikeMethod и в начале метода .onAppear() всегда равен 0. @Larme Я попробовал ваш оператор печати, но получил несколько синтаксических ошибок, поэтому их изменение на следующее решило их: print("Got for: \(url.absoluteString): \(String(describing: response)) and stringified data: \(String(data: data ?? Data(), encoding: .utf8) ?? "nil")")

Daggerpov 01.07.2024 01:04

@Larme На самом деле я получил правильный ответ, распечатанный вместе с вашим оператором печати (который я немного изменил в своем комментарии выше): Got for: {censored URL for stackoverflow}/quoteLikes/232: Optional(<NSHTTPURLResponse: 0x6000002c67e0> { URL:{censored URL for stackoverflow}/quoteLikes/232 } { Status Code: 200, Headers { "likes": 16 Однако, к сожалению, он отображает только количество лайков, равное 0, используя код, который я предоставил в вопросе. Как вы думаете, какое изменение мне нужно внести, чтобы оно действительно правильно отображало количество лайков, полученное из моего API?

Daggerpov 01.07.2024 01:09

Я понятия не имею о Swift, но, может быть, вы читаете неправильное свойство JSON? API возвращается c.IndentedJSON(http.StatusOK, gin.H{"id": id, "likes": likes}) >> {"id": 123, "likes": 5}. Код Swift пытается прочитать likeCount, которого не существует let likeCount = json["likeCount"] as? Int, так и должно быть let likeCount = json["likes"] as? Int

wakumaku 02.07.2024 09:42

@wakumaku Отличный улов! Это то, что решило мою проблему. Не могли бы вы опубликовать это как ответ, чтобы я мог наградить вас наградой?

Daggerpov 02.07.2024 09:58
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
13
157
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я понятия не имею о Swift, но, может быть, вы читаете неправильное свойство JSON?

API возвращает:

    c.IndentedJSON(http.StatusOK, gin.H{"id": id, "likes": likes})
    // {"id": 123, "likes": 5}

Код Swift пытается прочитать LikeCount:

let likeCount = json["likeCount"] as? Int

Так и должно быть let likeCount = json["likes"] as? Int

Похоже на несоответствие API между клиентом и сервером.

Вы ожидаете

               let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
               let likeCount = json["likeCount"] as? Int {

в клиенте, но отправка

        c.IndentedJSON(http.StatusOK, gin.H{"id": id, "likes": likes})

с сервера - было бы полезно увидеть весь ответ выше.

Чтобы избежать таких проблем, вы можете использовать OpenAPI описание вашего сервиса, например, используя Swift OpenAPI Generator на клиенте и swag на сервере.

Другие вопросы по теме