[SwiftUI] TwitterClone: ExploreView 2

Junyoung ParkΒ·2022λ…„ 11μ›” 17일
0

SwiftUI

λͺ©λ‘ 보기
107/136
post-thumbnail
post-custom-banner

πŸ”΄ Let's Build Twitter with SwiftUI (iOS 15, Xcode 13, Firebase, SwiftUI 3.0)

TwitterClone: ExploreView 2

κ΅¬ν˜„ λͺ©ν‘œ

  • 검색 λ·° κ΅¬ν˜„

κ΅¬ν˜„ νƒœμŠ€ν¬

  • μœ μ € 정보 패치
  • λ·° λͺ¨λΈμ„ ν†΅ν•œ μœ μ € 정보 UI ν‘œμ‹œ 및 이미지 λ‹€μš΄λ‘œλ“œ

핡심 μ½”λ“œ

func fetchUsers(completion: @escaping(Result<[UserModel], Error>) -> Void) {
        Firestore.firestore().collection("users")
            .getDocuments { snapshot, error in
                guard
                    let documents = snapshot?.documents,
                    error == nil else {
                    if let error = error {
                        completion(.failure(error))
                    } else {
                        completion(.failure(URLError(.badURL)))
                    }
                    return
                }
                let users = documents.compactMap({try? $0.data(as: UserModel.self)})
                completion(.success(users))
            }
    }
  • 검색 λ·° λͺ¨λΈμ΄ μ΄λ‹ˆμ…œλΌμ΄μ¦ˆλ  λ•Œ μ‚¬μš©ν•˜λŠ” μ„œλΉ„μŠ€ λ§€λ‹ˆμ € 클래슀의 퍼블릭 ν•¨μˆ˜
  • λ°μ΄ν„°λ² μ΄μŠ€ μ €μž₯된 λͺ¨λ“  μœ μ € 정보λ₯Ό 기본적으둜 패치
import Foundation

class ExploreViewModel: ObservableObject {
    @Published var users = [UserModel]()
    private let service = UserService()
    
    init() {
        fetchUsers()
    }
    
    func fetchUsers() {
        service.fetchUsers { [weak self] result in
            switch result {
            case .success(let users): self?.users = users
            case .failure(let error): print(error.localizedDescription)
            }
        }
    }
}
  • ν•΄λ‹Ή μœ μ € 데이터λ₯Ό 톡해 @Pbulished ν”„λ‘œν† μ½œμ„ λ”°λ₯΄κ³  μžˆλŠ” μœ μ € 정보 λ°°μ—΄ 데이터에 ν•΄λ‹Ή 값을 μ£ΌκΈ°
import SwiftUI

struct ExploreView: View {
    @ObservedObject private var viewModel = ExploreViewModel()
    @State private var showProfileView: Bool = false
    @State private var selectedUser: UserModel?
    var body: some View {
        ZStack {
            VStack {
                ScrollView {
                    LazyVStack {
                        ForEach(viewModel.users) { user in
                            UserRowView(user: user)
                                .onTapGesture {
                                    selectedUser = user
                                    showProfileView.toggle()
                                    
                                }
                        }
                    }
                }
            }
            .navigationTitle("Explore")
            .navigationBarTitleDisplayMode(.inline)
        }
        .background(
            NavigationLink(destination: ProfileLoadingView(user: $selectedUser), isActive: $showProfileView, label: {
                EmptyView()
            })
        )
    }
}
  • ν•΄λ‹Ή 값을 κ΅¬λ…ν•˜κ³  μžˆλŠ” 검색 λ·°: ν•΄λ‹Ή 데이터λ₯Ό 톡해 리슀트 UI 그리기
  • 리슀트 별 λ„€λΉ„κ²Œμ΄μ…˜ 링크 μƒμ„±μœΌλ‘œ μΈν•œ λΆˆν•„μš”ν•œ μ΄λ‹ˆμ…œλΌμ΄μ¦ˆλ₯Ό 막고 있기 λ•Œλ¬Έμ— 각 둜우 λ·°λ₯Ό 클릭할 λ•Œλ§ˆλ‹€ selectedUser, showProfileView λ“± κ΄€λ ¨ ν”„λ‘œνΌν‹° λ³€μˆ˜ 값을 μ œμ–΄
  • selectedUser 값은 λ°”μΈλ”©μœΌλ‘œ λ„˜κΈΈ λ•Œ μ˜΅μ…”λ„λ‘œ λ„˜κΈΈ 수 밖에 μ—†μœΌλ―€λ‘œ κ³§λ°”λ‘œ ν”„λ‘œν•„ 뷰둜 λ„˜μ–΄κ°€λŠ” 게 μ•„λ‹ˆλΌ λ°”μΈλ”©μœΌλ‘œ λ„˜μ–΄κ°„ 값을 if let으둜 λ°›μ•„μ€€ λ’€ μ‚¬μš©ν•˜λŠ” λ‘œλ”© 뷰둜 λ„˜κΈ°κΈ°
import SwiftUI

struct ProfileLoadingView: View {
    @Binding var user: UserModel?
    
    init(user: Binding<UserModel?>) {
        self._user = user
    }
    var body: some View {
        ZStack {
            if let user = user {
                ProfileView(user: user)
            }
        }
    }
}
  • ZStack을 톡해 ν•΄λ‹Ή μœ μ € 값이 μ‹€μ œ 값이 μžˆλ‹€λ©΄ μ‹€μ œ ν”„λ‘œν•„ λ·° ꡬ성
import SwiftUI

struct ProfileView: View {
    @ObservedObject private var viewModel: ProfileViewModel
    @State private var selectedFilter: TweetFilterViewModel = .tweets
    @Namespace private var animation
    
    init(user: UserModel) {
        _viewModel = ObservedObject(wrappedValue: ProfileViewModel(user: user))
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            if let profileImage = viewModel.profileImage {
                ProfileHeaderView(profileImage: profileImage)
            }
            actionButtons
            userInfoDetail
            tweetFilterBar
            tweetsView
            Spacer()
        }
        .toolbar(.hidden)
    }
}
  • μ‹€μ œ νŒŒλΌλ―Έν„°λ‘œ λ„˜μ–΄μ˜¨ μœ μ € 정보λ₯Ό 톡해 μœ μ € 이미지 및 μœ μ € 이름 λ“± 데이터 UI ν‘œμ‹œ
  • ObservedObject둜 κ΄€μ°° 쀑인 λ·° λͺ¨λΈ λ‚΄μ˜ 데이터 νΌλΈ”λ¦¬μ…”λ‘œ 이미지 패치 ν›„ λ‹€μ‹œ λ·° λ Œλ”λ§
import Foundation
import SwiftUI

class ProfileViewModel: ObservableObject {
    let user: UserModel
    @Published var profileImage: UIImage?
    
    init(user: UserModel) {
        self.user = user
        downloadProfileImage(with: user.profileImageURL)
    }
    
    private func downloadProfileImage(with urlString: String) {
        guard let url = URL(string: urlString) else { return }
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard
                let response = response as? HTTPURLResponse,
                response.statusCode >= 200,
                response.statusCode < 400,
                error == nil,
                let data = data,
                let image = UIImage(data: data) else { return }
            DispatchQueue.main.async { [weak self] in
                self?.profileImage = image
            }
        }
        .resume()
    }
}
  • μ΄λ‹ˆμ…œλΌμ΄μ¦ˆλ  λ•Œ 받은 μœ μ € 데이터λ₯Ό 톡해 λ™μ‹œμ— 이미지 패치
  • 이미지 퍼블리셔에 ν•΄λ‹Ή 값을 리턴 ν›„ ν•΄λ‹Ή 값을 κ΄€μ°° 쀑인 ν”„λ‘œν•„ λ·°μ—μ„œ ν•΄λ‹Ή 이미지λ₯Ό λ Œλ”λ§
  • 검색 뷰의 둜우 λ·°λ₯Ό κ΅¬μ„±ν•˜κ³  μžˆλŠ” μž¬μ‚¬μš© μ…€ UserRowView λ˜ν•œ ν”„λ‘œν•„ 뷰의 둜직과 λ§ˆμ°¬κ°€μ§€λ‘œ μ΄λ‹ˆμ…œλΌμ΄μ¦ˆλ  λ•Œ 건넀받은 μœ μ € 데이터λ₯Ό 톡해 λ·° λͺ¨λΈμ„ μ΄λ‹ˆμ…œλΌμ΄μ¦ˆ, λ™μ‹œμ— 이미지 패치 및 퍼블리셔 ꡬ독을 ν†΅ν•œ UI 패치

κ΅¬ν˜„ ν™”λ©΄

ν˜„ μ‹œμ μ—μ„œλŠ” 이미지λ₯Ό λ‹€μš΄λ‘œλ“œλ°›μ„ λ•Œ μŠ€ν”Όλ„ˆ, λ””ν΄νŠΈ 이미지 등을 주지 μ•Šκ³  λ‹€μš΄λ‘œλ“œλ°›μ€ λ’€ κ³§λ°”λ‘œ 화면에 λ‚˜νƒ€λ‚˜κΈ° λ•Œλ¬Έμ— UXκ°€ μžμ—°μŠ€λŸ½μ§€ λͺ»ν•˜λ‹€. 만일 λ°œμ „μ‹œν‚¨λ‹€λ©΄ μŠ€μΌˆλ ˆν†€ λ·°, μŠ€ν”Όλ„ˆ 등을 톡해 "ν˜„μž¬ λ‹€μš΄λ‘œλ“œλ°›κ³  있음"을 μœ μ €μ—κ²Œ μ•”μ‹œν•  수 μžˆμ„ 것이닀!

profile
JUST DO IT
post-custom-banner

0개의 λŒ“κΈ€