APP-iOS3rd / PJ3T2_Mymory

멋쟁이사자처럼 iOS 앱스쿨 3기 팀 프로젝트
10 stars 3 forks source link

Feat/follow list #257

Closed jeonguk29 closed 7 months ago

jeonguk29 commented 7 months ago

PR 가이드라인

PR Checklist

PR 날릴 때 체크 리스트

PR Type

어떤 종류의 PR인가요?

연관되는 issue 정보를 알려주세요

Issue Number: #254

PR 설명하기

이 PR에 대해 간략하게 소개해주세요!

사용자별로 각 팔로우, 팔로워 사용자 리스트를 보여주고 각각의 프로필에 접근할 수 있게 재귀 호출로 타고 타고 들어갈 수 있게 구현했습니다.

어떻게 작동하나요? code 기반으로 설명해주세요

현 사용자, 혹은 다른 유저의 uid 만 있으면 팔로우, 팔로잉 유저 리스트를 가져와 표시합니다.

//  UserStatusCell.swift

import SwiftUI

struct UserStatusCell: View {

    @ObservedObject var authViewModel: AuthService = .shared
    @StateObject var followerFollowingViewModel: FollowerFollowingViewModel = FollowerFollowingViewModel()
    @State var uid: String
    @State var memoCount: Int?
    @State private var isFollowFollowingListActive = false
    var body: some View {
        HStack {
            VStack {
                Text("\(memoCount ?? 0)")
                    .font(.bold16)
                Text("메모")
                    .font(.light14)
            }
            .frame(maxWidth: .infinity)
            Divider()

            NavigationLink(destination: FollowFollowingList(followerFollowingViewModel: followerFollowingViewModel, uid: $uid, choiceTab: 0)) {
                VStack {
                    Text("\(authViewModel.followerCount)")
                        .font(.bold16)
                    Text("팔로워")
                        .font(.light14)
                }
                .frame(maxWidth: .infinity)
            }
            .environmentObject(followerFollowingViewModel)

            Divider()

            NavigationLink(destination: FollowFollowingList(followerFollowingViewModel: followerFollowingViewModel, uid: $uid, choiceTab: 1)) {
                VStack {
                    Text("\(authViewModel.followingCount)")
                        .font(.bold16)
                    Text("팔로잉")
                        .font(.light14)
                }
                .frame(maxWidth: .infinity)
                .padding(.leading, 10)
            }
            .environmentObject(followerFollowingViewModel)

        }
        .onAppear(perform: {
            Task { @MainActor in

                self.memoCount = await AuthService.shared.fetchUserMemoCount(with: uid)

                await followerFollowingViewModel.fetchFollowingUserList(with: uid)
                await followerFollowingViewModel.fetchFollowerUserList(with: uid)
            }
        })
        .frame(maxWidth: .infinity)
        .padding(.vertical,18)
    }
}

onAppear이 될때 followerUserList,followingUserList를 데이터를 가져옵니다.

//  FollowerFollowingViewModel.swift

import SwiftUI

class FollowerFollowingViewModel: ObservableObject {

    @Published var followerUserList: [User] = []
    @Published var followingUserList: [User] = []

    init() {}

    func fetchFollowingUserList(with id: String) async -> Void {

        do {
            let document = try await COLLECTION_USER_Following.document(id).getDocument()

            if document.exists {
                var userList: [User] = []

                for (documentID, userData) in document.data() ?? [:] {
                    // 문서 ID 값을 이용하여 users 컬렉션에서 해당 사용자의 데이터 가져오기
                    let userDocument = try await COLLECTION_USERS.document(documentID).getDocument()

                    if let userData = userDocument.data() {
                        // User 객체 생성 및 데이터 매핑
                        let user = User(
                            email: userData["email"] as! String,
                            id: userData["id"] as! String,
                            name: userData["name"] as? String ?? "",
                            profilePicture: userData["profilePicture"] as? String
                            // 필요한 다른 속성들을 추가로 매핑
                        )

                        userList.append(user)
                    }
                }

                // 메인 스레드에서 UI 업데이트
                DispatchQueue.main.async {
                    self.followingUserList = userList
                }
            }
        } catch {
            print("에러 발생: \(error)")
        }
    }

    func fetchFollowerUserList(with id: String) async -> Void {
        //guard let userID = user.id else { return }

        do {
            let document = try await COLLECTION_USER_Followers.document(id).getDocument()

            if document.exists {
                var userList: [User] = []

                for (documentID, userData) in document.data() ?? [:] {
                    // 문서 ID 값을 이용하여 users 컬렉션에서 해당 사용자의 데이터 가져오기
                    let userDocument = try await COLLECTION_USERS.document(documentID).getDocument()

                    if let userData = userDocument.data() {
                        // User 객체 생성 및 데이터 매핑
                        let user = User(
                            email: userData["email"] as! String,
                            id: userData["id"] as! String,
                            name: userData["name"] as? String ?? "",
                            profilePicture: userData["profilePicture"] as? String
                            // 필요한 다른 속성들을 추가로 매핑
                        )

                        userList.append(user)
                    }
                }

                // 메인 스레드에서 UI 업데이트
                DispatchQueue.main.async {
                    self.followerUserList = userList
                }
            }
        } catch {
            print("에러 발생: \(error)")
        }
    }

}

followerUserList,followingUserList의 각 User 정보를 List를 통하여 뿌리고 사용자를 누르면 해당 사용자의 프로필을 들어갈 수 있습니다. 이러한 구조 때문에 재귀호출 구조가 가능하고 뒤로 계속 이동할 수 있습니다.

//
//  FollowFollowingList.swift

import SwiftUI
import Kingfisher

struct FollowFollowingList: View {

    @ObservedObject var followerFollowingViewModel: FollowerFollowingViewModel
    @StateObject var otherUserViewModel = OtherUserViewModel()
    @Environment(\.presentationMode) var presentationMode
    @State private var goingUserProfile = false
    @Binding var uid: String
    @State var choiceTab: Int

    var body: some View {

        List {
            if choiceTab == 0 {
                ForEach(followerFollowingViewModel.followerUserList, id: \.id) { user in
                    NavigationLink {
                        OtherUserProfileView(memoCreator: user)
                            .padding(.horizontal)
                            .environmentObject(otherUserViewModel)
                    } label: {
                        UserListRow(user: user)
                    }
                }
                .onMove(perform: move)
            } else {
                ForEach(followerFollowingViewModel.followingUserList, id: \.id) { user in
                    NavigationLink {
                        OtherUserProfileView(memoCreator: user)
                            .padding(.horizontal)
                            .environmentObject(otherUserViewModel)
                    } label: {
                        UserListRow(user: user)
                    }
                }
                .onMove(perform: move)
            }

        }
        .navigationTitle(Text(choiceTab == 0 ? "팔로워" : "팔로잉"))
    }

    func move(indices: IndexSet, newOffset: Int) {
        if choiceTab == 0 {
            followerFollowingViewModel.followerUserList.move(fromOffsets: indices, toOffset: newOffset)
        } else {
            followerFollowingViewModel.followingUserList.move(fromOffsets: indices, toOffset: newOffset)
        }
    }
}

struct UserListRow: View {
    let user: User

    var body: some View {
        HStack {
            if let imageUrl = user.profilePicture, let url = URL(string: imageUrl) {
                KFImage(url)
                    .resizable()
                    .scaledToFill()
                    .clipped()
                    .clipShape(.circle)
                    .frame(width: 45, height: 45)
            } else {
                Circle()
                    .frame(width: 45, height: 45)
                    .foregroundStyle(Color(hex: "d9d9d9"))
            }

            Text(user.name)
                .font(.body)
                .foregroundColor(.black)
                .padding(.vertical)
        }
    }
}

가능하다면 추가해주세요

변경 사항 스크린샷 혹은 화면 녹화

https://github.com/APP-iOS3rd/PJ3T2_Mymory/assets/54401641/eb872781-3059-4752-b954-6f919a7fc5e0

스크린샷

Test 여부

Test 정보

//예시
let testDatas: [TestData] = [.init(...),...]

기타 언급해야 할 사항들