🟨 구현 화면
🟨 SearchResultViewController.swift
- searchbar 에서 검색한 결과 반영
import UIKit
class SearchResultsViewController: UIViewController {
var users: [TwitterUser] = []
private let searchResultsTableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
table.register(UserTableViewCell.self, forCellReuseIdentifier: UserTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(searchResultsTableView)
configureConstraints()
searchResultsTableView.dataSource = self
searchResultsTableView.delegate = self
}
func update(users: [TwitterUser]) {
self.users = users
DispatchQueue.main.async { [weak self] in
self?.searchResultsTableView.reloadData()
}
}
private func configureConstraints() {
let searchResultsTableViewConstraints = [
searchResultsTableView.topAnchor.constraint(equalTo: view.topAnchor),
searchResultsTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
searchResultsTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
searchResultsTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
]
NSLayoutConstraint.activate(searchResultsTableViewConstraints)
}
}
extension SearchResultsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.identifier, for: indexPath) as? UserTableViewCell
else { return UITableViewCell() }
let user = users[indexPath.row]
cell.configure(with: user)
return cell
}
}
extension SearchResultsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
}
🟨 SearchViewController.swift
- 서치 기능 사용
- 서치바에서 검색 결과를 반영하기 위해 SearchResultViewController 연결
import UIKit
class SearchViewController: UIViewController {
private let searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: SearchResultsViewController())
searchController.searchBar.searchBarStyle = .minimal
searchController.searchBar.placeholder = "Search with @usernames"
return searchController
}()
private let promptLabel: UILabel = {
let label = UILabel()
label.text = "Search for users and get connected"
label.textAlignment = .center
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: 32, weight: .bold)
label.textColor = .placeholderText
return label
}()
let viewModel: SearchViewViewModel
init(viewModel: SearchViewViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(promptLabel)
navigationItem.searchController = searchController
searchController.searchResultsUpdater = self
configureConstraints()
}
private func configureConstraints() {
let promptLabelConstraints = [
promptLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
promptLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
promptLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
]
NSLayoutConstraint.activate(promptLabelConstraints)
}
}
extension SearchViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let resultsViewController = searchController.searchResultsController as? SearchResultsViewController,
let query = searchController.searchBar.text
else { return }
viewModel.search(with: query) { users in
resultsViewController.update(users: users)
}
}
}
🟨 DatabaseManager
- collectionUsers(search query: String) 메서드 생성
- query로 받은 값을 사용하여 Database 내 검색
- 검샌한 결과를 TwitterUser 타입으로 반환
import Foundation
import Firebase
import FirebaseFirestoreSwift
import FirebaseFirestoreCombineSwift
import Combine
class DatabaseManager {
static let shared = DatabaseManager()
let db = Firestore.firestore()
let usersPath: String = "users"
let tweetsPath: String = "tweets"
...
func collectionUsers(search query: String) -> AnyPublisher<[TwitterUser], Error> {
db.collection(usersPath).whereField("username", isEqualTo: query)
.getDocuments()
.map(\.documents)
.tryMap { snapshots in
try snapshots.map({
try $0.data(as: TwitterUser.self)
})
}
.eraseToAnyPublisher()
}
}
🟨 SearchViewViewModel
- DatabaseManager에서 생성한 collectionUsers 메서드 사용
- 이를 통해 서치바에서 받은 텍스트를 검색
import Foundation
import Combine
class SearchViewViewModel {
var subscriptions: Set<AnyCancellable> = []
func search(with query: String, _ completion: @escaping ([TwitterUser]) -> Void) {
DatabaseManager.shared.collectionUsers(search: query)
.sink { completion in
if case .failure(let error) = completion {
print(error.localizedDescription)
}
} receiveValue: { users in
completion(users)
}
.store(in: &subscriptions)
}
}
🟨 UserTableViewCell.swift
- searchResultsViewController 에서 사용할 테이블 뷰 셀이다.
- configure(with user) 메서드를 통해 각 UI 요소에 값을 할당한다.
import UIKit
class UserTableViewCell: UITableViewCell {
static let identifier: String = "UserTableViewCell"
private let avatarImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 25
imageView.layer.masksToBounds = true
imageView.clipsToBounds = true
imageView.backgroundColor = .red
return imageView
}()
private let displayNameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 18, weight: .bold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let usernameLabel: UILabel = {
let label = UILabel()
label.textColor = .secondaryLabel
label.font = .systemFont(ofSize: 16, weight: .regular)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(avatarImageView)
contentView.addSubview(displayNameLabel)
contentView.addSubview(usernameLabel)
configureConstraints()
}
private func configureConstraints() {
let avatarImageViewConstraints = [
avatarImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
avatarImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
avatarImageView.heightAnchor.constraint(equalToConstant: 50),
avatarImageView.widthAnchor.constraint(equalToConstant: 50),
]
let displayNameLabelConstraints = [
displayNameLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 20),
displayNameLabel.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor)
]
let usernameLabelConstraints = [
usernameLabel.leadingAnchor.constraint(equalTo: displayNameLabel.trailingAnchor, constant: 10),
usernameLabel.centerYAnchor.constraint(equalTo: displayNameLabel.centerYAnchor),
]
NSLayoutConstraint.activate(avatarImageViewConstraints)
NSLayoutConstraint.activate(displayNameLabelConstraints)
NSLayoutConstraint.activate(usernameLabelConstraints)
}
func configure(with user: TwitterUser) {
avatarImageView.sd_setImage(with: URL(string: user.avatarPath))
displayNameLabel.text = user.displayName
usernameLabel.text = user.username
}
required init?(coder: NSCoder) {
fatalError()
}
}
'Clone App > Twitter' 카테고리의 다른 글
[Twitter Clone] Show Tweet in Home View (0) | 2024.06.12 |
---|---|
[Twitter Clone] Connect to profileView (0) | 2024.06.12 |
[Twitter Clone] Load Profile Data to profileView (0) | 2024.06.12 |
[Twitter Clone] Connect to Firebase Storage (0) | 2024.06.12 |
[Twitter Clone] Add ProfileDataFormView (0) | 2024.06.11 |