Sistema di Commenti e Risposte Nidificate ad Albero in SwiftUI: Guida Completa

Realizzare una sezione commenti professionale richiede molto più di una semplice lista piatta: gli utenti si aspettano di poter rispondere a commenti specifici, visualizzare un albero di risposte indentato con frecce grafiche, mettere like sia ai propri commenti che a quelli altrui e avere una barra di input contestuale con il tag "Rispondendo a @username".

1. Il Modello Dati Ricorsivo in Swift

In SwiftUI, modelliamo il commento come una struttura gerarchica che supporta figli nidificati:

import Foundation

public struct CommentItem: Identifiable, Codable {
    public let id: Int
    public let userId: Int
    public let userName: String
    public let userAvatarUrl: String?
    public let content: String
    public var likesCount: Int
    public var isLikedByMe: Bool
    public let parentId: Int?
    public let replyToUserName: String?
    public let createdAt: Date
    public var replies: [CommentItem] = []
}

2. Rendering Grafico dell'Albero con Frecce e Banner Contestuale

Per dare l'aspetto moderno e pulito tipico di app come Threads o TikTok, le risposte di secondo livello vengono renderizzate con una freccia angolare curva (arrow.turn.down.right) e un margine sinistro dedicato:

import SwiftUI

struct CommentRowView: View {
    let comment: CommentItem
    let onReplyTap: (CommentItem) -> Void
    let onLikeTap: (CommentItem) -> Void
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // Commento Principale
            singleCommentCard(comment: comment, isReply: false)
            
            // Risposte Nidificate (Replies)
            if !comment.replies.isEmpty {
                VStack(spacing: 6) {
                    ForEach(comment.replies) { reply in
                        HStack(alignment: .top, spacing: 8) {
                            Image(systemName: "arrow.turn.down.right")
                                .font(.system(size: 11, weight: .bold))
                                .foregroundStyle(Color.secondary.opacity(0.6))
                                .padding(.top, 6)
                            
                            singleCommentCard(comment: reply, isReply: true)
                        }
                        .padding(.leading, 20)
                    }
                }
            }
        }
    }
    
    private func singleCommentCard(comment: CommentItem, isReply: Bool) -> some View {
        HStack(alignment: .top, spacing: 10) {
            Circle()
                .fill(Color.gray.opacity(0.3))
                .frame(width: isReply ? 28 : 34, height: isReply ? 28 : 34)
            
            VStack(alignment: .leading, spacing: 4) {
                HStack {
                    Text(comment.userName)
                        .font(.system(size: 13, weight: .bold))
                    
                    if let replyTo = comment.replyToUserName {
                        Text("▸ @\(replyTo)")
                            .font(.system(size: 11, weight: .semibold))
                            .foregroundStyle(Color.blue)
                    }
                    Spacer()
                    Text("2m")
                        .font(.system(size: 10))
                        .foregroundStyle(Color.secondary)
                }
                
                Text(comment.content)
                    .font(.system(size: 13))
                
                HStack(spacing: 16) {
                    Button("Rispondi") {
                        onReplyTap(comment)
                    }
                    .font(.system(size: 11, weight: .bold))
                    .foregroundStyle(Color.blue)
                    
                    Button {
                        onLikeTap(comment)
                    } label: {
                        HStack(spacing: 3) {
                            Image(systemName: comment.isLikedByMe ? "heart.fill" : "heart")
                                .foregroundStyle(comment.isLikedByMe ? Color.red : Color.secondary)
                            Text("\(comment.likesCount)")
                                .font(.system(size: 11))
                        }
                    }
                }
                .padding(.top, 2)
            }
        }
        .padding(10)
        .background(Color(.secondarySystemBackground))
        .clipShape(RoundedRectangle(cornerRadius: 12))
    }
}

Vuoi integrare un feed di commenti interattivo nella tua app?

Creiamo componenti UI SwiftUI fluidi con architetture serverless e database relazionali ad alte prestazioni.