[UIKit] Firebase Chat App: Fetching Data

Junyoung Park·2022년 9월 19일
0

UIKit

목록 보기
40/142
post-thumbnail
post-custom-banner

Swift: Firebase Chat App Part 10 - Fetching Users & Photos (Real-time) - Xcode 11 - 2020

Firebase Chat App: Fetching Data

구현 목표

  • 프로필 이미지 리턴
  • 유저 정보 데이터베이스 저장
  • 검색 쿼리 데이터로 UI 패치

구현 태스크

  1. 파이어베이스 로그인 시 유저 정보 및 프로필 이미지 스토리지에 저장하기
  2. 유저 디폴트 정보를 통해 스토리지 URL 정보 패치
  3. 검색 바 텍스트 바탕으로 패치 데이터 쿼리
  4. 패치 결과에 따른 UI 패치

핵심 코드

        StorageManager.shared.downloadURL(for: path) { [weak self] result in
            guard let self = self else { return }
            switch result {
            case .success(let url):
                self.downloadImage(imageView: imageView, url: url)
            case .failure(let error):
                print(error.localizedDescription)
                break
            }
        }
  • 프로필 뷰의 프로필 사진을 업데이트하기 위한 함수
  • 스토리지 매니저 클래스의 downloadURL을 호출
  • 파이어베이스 스토리지에 저장된 해당 유저 이메일의 프로필 URL을 비동기적으로 패치
    func downloadURL(for path: String, completionHandler: @escaping (Result<URL, Error>) -> Void) {
        let reference = storage.child(path)
        reference.downloadURL { result in
            switch result {
            case .failure(let error):
                completionHandler(.failure(StorageErrors.failedtToGetDownloadUrl))
            case .success(let url):
                completionHandler(.success(url))
            }
        }
    }
  • 컴플리션 핸들러를 통해 해당 URL 리턴
    func downloadImage(imageView: UIImageView, url: URL) {
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard let self = self else { return }
            guard
                let data = data,
                error == nil else {
                return
            }
            let image = UIImage(data: data)
            DispatchQueue.main.async {
                imageView.image = image
            }
        }
        .resume()
    }
  • 리턴받은 URL을 통해 이미지 패치
  • 메인 스레드에서 이미지 넣기
    func insertUser(with user: ChatAppUser, completionHandler: @escaping (Bool) -> Void) {
        database.child(user.safeEmail).setValue([
            "first_name" : user.firstName,
            "last_name" : user.lastName,
            "platform" : user.platform.rawValue,
            "userIdentifier" : user.userIdentifier ?? ""
        ]) { error, reference in
            guard error == nil else {
                print("Failed to write to database")
                completionHandler(false)
                return
            }
            
            /*
             users =>
             [
                [
                    "name":
                    "safe_email":
                ],
                [
                    "name":
                    "safe_email":
                ]
             ]
             */
            
            self.database.child("users").observeSingleEvent(of: .value) { snapshot in
                if var usersCollection = snapshot.value as? [[String : String]] {
                    // append to user dict
                    let newElement = [
                        "name" : user.firstName + " " + user.lastName,
                        "email" : user.safeEmail,
                        "platform" : user.platform.rawValue
                    ]
                    usersCollection.append(newElement)
                    
                    self.database.child("users").setValue(usersCollection) { error, _ in
                        guard error == nil else {
                            completionHandler(false)
                            return
                        }
                        completionHandler(true)
                    }
                } else {
                    // create that dictionary
                    let newCollection: [[String:String]] = [
                        [
                            "name" : user.firstName + " " + user.lastName,
                            "email" : user.safeEmail,
                            "platform" : user.platform.rawValue
                        ]
                    ]
                    self.database.child("users").setValue(newCollection) { error, _ in
                        guard error == nil else {
                            completionHandler(false)
                            return
                        }
                        completionHandler(true)
                    }
                }
            }
        }
    }
  • 유저 생성 시 삽입되는 함수 insertUser 변경
  • 이름, 이메일, 플랫폼 정보가 담긴 오브젝트가 users 디렉토리에 추가
  • 딕셔너리 구조에 따라 파이어베이스 데이터베이스에 추가
    func getAllUsers(completionHandler: @escaping (Result<[[String : String]], Error>) -> ()) {
        database.child("users").observeSingleEvent(of: .value) { snapshot in
            guard let value = snapshot.value as? [[String : String]] else {
                completionHandler(.failure(DatabaseErrors.failedToFetch))
                return
            }
            
            completionHandler(.success(value))
        }
    }
  • 데이터베이스 매니저 클래스 함수
  • 유저 생성 시 기록한 유저 데이터를 모두 패치
    private var users = [[String : String]]()
    // firebase items
    private var hasFetched = false
    // check whether it has been checked or not
    private var results = [[String : String]]()
  • NewConversationViewController 사용 변수
  • 파이어베이스에서 패치한 users 및 패치 여부를 판단하는 플래그 비트 hasFetched, 검색 쿼리문에 따라 필터링된 결과값을 리턴하는 results
extension NewConversationViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        guard
            let text = searchBar.text,
            !text.replacingOccurrences(of: " ", with: "").isEmpty else {
            return
        }
        searchBar.resignFirstResponder()
        results.removeAll()
        spinner.show(in: view)
        searchUsers(query: text)
    }
    
    private func searchUsers(query: String) {
        // check if array has Firebase results
        
        // if it does: filter
        if hasFetched {
            filterUsers(with: query)
        } else {
            // if not, fetch them filter
            DatabaseManager.shared.getAllUsers { [weak self] result in
                guard let self = self else { return }
                switch result {
                case .success(let users):
                    self.hasFetched = true
                    self.users = users
                    self.filterUsers(with: query)
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
        
        // update the UI: either show results or show no result
    }
    
    private func filterUsers(with term: String) {
        guard hasFetched else { return }
        spinner.dismiss()
        let results: [[String : String]] = users.filter { user in
            guard let name = user["name"]?.lowercased() as? String else {
                return false
            }
            if term.count > name.count {
                // "Junyeong Park" "Park"
                return false
            } else {
                for offset in 0...(name.count-term.count) {
                    let currentIndex = name.index(name.startIndex, offsetBy: offset)
                    let lastIndex = name.index(currentIndex, offsetBy: term.count)
                    let subtext = name[currentIndex..<lastIndex]
                    if term.lowercased() == subtext {
                        return true
                    }
                }
                return false
            }
        }
        self.results = results
        updateUI()
    }
    
    private func updateUI() {
        if results.isEmpty {
            self.noResultsLabel.isHidden = false
            self.tableView.isHidden = true
        } else {
            self.noResultsLabel.isHidden = true
            self.tableView.isHidden = false
            self.tableView.reloadData()
        }
    }
}
  • 유저 검색한 텍스트에 따라 파이어베이스에서 패치한 users를 필터링, 검색 결과를 보여줄지 결정하는 함수
  • filterUsers를 통해 대소문자에 관계없이 검색 쿼리문과 일치하는 모든 유저의 이름을 리턴
  • 검색 결과가 있을 때 테이블뷰 UI 업데이트, 없다면 상응하는 UI 그리기 → 유무에 따른 isHidden 토글링

구현 화면

profile
JUST DO IT
post-custom-banner

0개의 댓글