Skip to content

Commit

Permalink
feat: SearchView 도시 검색 기능 구현
Browse files Browse the repository at this point in the history
- 근처 인기 여행지(nearCities)를 서버로부터 도시 정보, 이미지 받아와서 출력하도록 변경
- 도시 검색시 나타나던 뷰 수정
  - 도시 이미지를 Mock Image -> SF Symbol로 변경
  - cell 레이아웃 조정

jeremy0405/airbnb/#5
  • Loading branch information
sanghyeok-kim committed May 31, 2022
1 parent 601365c commit c70dd71
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 33 deletions.
4 changes: 4 additions & 0 deletions iOS/AirbnbApp/AirbnbApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
1E71CBDA283B9D9A00F6D2D0 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 1E71CBD9283B9D9A00F6D2D0 /* Alamofire */; };
1E71CBDC283BB93600F6D2D0 /* SearchHomeSearchBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E71CBDB283BB93600F6D2D0 /* SearchHomeSearchBarDelegate.swift */; };
1EAB24E22845B68100AAA121 /* Int+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAB24E12845B68100AAA121 /* Int+Extension.swift */; };
1ED648A1283BD40E00AA5380 /* SearchHomeCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED648A0283BD40E00AA5380 /* SearchHomeCollectionViewDataSource.swift */; };
1ED648A5283BD48500AA5380 /* CityViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED648A4283BD48500AA5380 /* CityViewCell.swift */; };
1ED648A7283BDCBA00AA5380 /* SearchHomeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED648A6283BDCBA00AA5380 /* SearchHomeHeaderView.swift */; };
Expand Down Expand Up @@ -60,6 +61,7 @@

/* Begin PBXFileReference section */
1E71CBDB283BB93600F6D2D0 /* SearchHomeSearchBarDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHomeSearchBarDelegate.swift; sourceTree = "<group>"; };
1EAB24E12845B68100AAA121 /* Int+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Extension.swift"; sourceTree = "<group>"; };
1ED648A0283BD40E00AA5380 /* SearchHomeCollectionViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHomeCollectionViewDataSource.swift; sourceTree = "<group>"; };
1ED648A4283BD48500AA5380 /* CityViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityViewCell.swift; sourceTree = "<group>"; };
1ED648A6283BDCBA00AA5380 /* SearchHomeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHomeHeaderView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -175,6 +177,7 @@
isa = PBXGroup;
children = (
1ED648A8283C7EC900AA5380 /* UIColor+Extension.swift */,
1EAB24E12845B68100AAA121 /* Int+Extension.swift */,
6C1AB744283C9C7900A0314F /* UIFont+Extension.swift */,
);
path = Extension;
Expand Down Expand Up @@ -463,6 +466,7 @@
1ED648FD283F9FFA00AA5380 /* ViewModelBindable.swift in Sources */,
1ED6491F2843BEAC00AA5380 /* ImageManager.swift in Sources */,
1E71CBDC283BB93600F6D2D0 /* SearchHomeSearchBarDelegate.swift in Sources */,
1EAB24E22845B68100AAA121 /* Int+Extension.swift in Sources */,
6C60EADB2843A5A000EF82F9 /* SearchCollectionViewDataSource.swift in Sources */,
6CB1D8C8284359E80051EF2F /* PopularCollectionViewDataSource.swift in Sources */,
1ED648A7283BDCBA00AA5380 /* SearchHomeHeaderView.swift in Sources */,
Expand Down
21 changes: 21 additions & 0 deletions iOS/AirbnbApp/AirbnbApp/Source/Extension/Int+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Int+Extension.swift
// AirbnbApp
//
// Created by 김상혁 on 2022/05/31.
//

import Foundation

extension Int {
func convertIntoTime() -> String {
if self / 60 == 0 {
return "\(self)"
}

let hour = self / 60
let minute = self % 60

return minute > 30 ? "\(hour + 1) 시간" : "\(Double(hour) + 0.5) 시간"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ private extension CityViewCell {
addSubview(cityImageView)

cityImageView.snp.makeConstraints { make in
make.top.leading.equalToSuperview()
make.height.width.equalTo(74)
make.top.bottom.leading.equalToSuperview()
make.centerY.equalToSuperview()
make.width.equalTo(cityImageView.snp.height)
}
}

Expand Down Expand Up @@ -89,7 +90,7 @@ extension CityViewCell {
cityTitleLabel.text = text
}

func setDistanceLabel(text: Int) {
distanceLabel.text = "\(text)"
func setDistanceLabel(text: String) {
distanceLabel.text = "차로 \(text) 거리"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,29 @@ import UIKit

final class PopularCollectionViewDataSource: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

var mockCity: [String] = ["서울", "광주", "부산", "대구"]
var nearCities: [SearchHomeEntity.City] = []

let imageManager = ImageManager()

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return mockCity.count
return nearCities.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CityViewCell.identifier, for: indexPath) as? CityViewCell else {
return UICollectionViewCell()
}
guard let image = UIImage(named: "mockimage.png") else { return cell }
let item = nearCities[indexPath.item]

cell.setCityImageView(image: image)
cell.setCityTitleLabel(text: mockCity[indexPath.item])
let imageUrl = URL(string: item.imageUrl)
imageManager.fetchImage(from: imageUrl) { image in
DispatchQueue.main.async {
cell.setCityImageView(image: image ?? UIImage())
// TODO: image가 nil일 경우 handling하는 에러 구현하기
}
}
cell.setCityTitleLabel(text: item.cityName)
cell.setDistanceLabel(text: item.time.convertIntoTime())
return cell
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,24 @@ class SearchResultViewCell: UICollectionViewCell {
return "\(self)"
}

private lazy var imageContainerView: UIView = {
let view = UIView()
view.layer.cornerRadius = 10
view.layer.borderWidth = 1
view.layer.borderColor = UIColor.Custom.gray3.cgColor
view.clipsToBounds = true
return view
}()

private lazy var cityImageView: UIImageView = {
let image = UIImageView()
image.layer.cornerRadius = 10
image.clipsToBounds = true
image.image = UIImage(named: "Mockimage.png")
return image
let imageView = UIImageView()
imageView.image = UIImage(systemName: "mappin.and.ellipse")?
.withAlignmentRectInsets(UIEdgeInsets(top: -21, left: -23, bottom: -21, right: -23))
imageView.tintColor = .Custom.gray3
return imageView
}()

private lazy var descriptionLabel = CustomLabel(text: "양재동, 서초구, 서울특별시",
font: .NotoSans.regular,
private lazy var descriptionLabel = CustomLabel(font: .NotoSans.regular,
fontColor: .Custom.gray1)

private lazy var informationStackView: UIStackView = {
Expand All @@ -39,7 +47,8 @@ class SearchResultViewCell: UICollectionViewCell {

override init(frame: CGRect) {
super.init(frame: frame)
layoutCityImageView()
// layoutCityImageView()
layoutImageContainerView()
layoutInformationStackView()
}

Expand All @@ -53,22 +62,28 @@ class SearchResultViewCell: UICollectionViewCell {

private extension SearchResultViewCell {

func layoutCityImageView() {
addSubview(cityImageView)
func layoutImageContainerView() {
addSubview(imageContainerView)
imageContainerView.addSubview(cityImageView)

imageContainerView.snp.makeConstraints { make in
make.top.bottom.leading.equalToSuperview()
make.centerY.equalToSuperview()
make.width.equalTo(imageContainerView.snp.height)
}

cityImageView.snp.makeConstraints { make in
make.top.leading.equalToSuperview()
make.height.width.equalTo(74)
make.centerX.centerY.equalToSuperview()
}
}

func layoutInformationStackView() {
addSubview(informationStackView)

informationStackView.snp.makeConstraints { make in
make.leading.equalTo(cityImageView.snp.trailing).offset(16)
make.leading.equalTo(imageContainerView.snp.trailing).offset(16)
make.trailing.equalToSuperview()
make.centerY.equalTo(cityImageView.snp.centerY)
make.centerY.equalTo(imageContainerView.snp.centerY)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import MapKit

final class SearchViewController: UIViewController, MKLocalSearchCompleterDelegate {

private let viewModel: NearCityViewModel

private var searchedLocations = PublishRelay<[MKLocalSearchCompletion]>()

private lazy var searchBarDelegate = SearchBarDelegate()
Expand All @@ -23,11 +25,21 @@ final class SearchViewController: UIViewController, MKLocalSearchCompleterDelega
return searchController
}()

init(viewModel: NearCityViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private lazy var popularCollectionViewDataSource = PopularCollectionViewDataSource()
private lazy var searchCollectionViewDataSource = SearchCollectionViewDataSource()

private lazy var popularCollectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: SectionLayoutFactory.createPopularDestinationLayout())
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: SectionLayoutFactory.createPopularDestinationLayout(isHeaderExist: true))
collectionView.register(CityViewCell.self, forCellWithReuseIdentifier: CityViewCell.identifier)
collectionView.register(PopularHeaderView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
Expand All @@ -37,7 +49,7 @@ final class SearchViewController: UIViewController, MKLocalSearchCompleterDelega
}()

private lazy var searchResultCollectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: SectionLayoutFactory.createPopularDestinationLayout())
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: SectionLayoutFactory.createPopularDestinationLayout(isHeaderExist: false))
collectionView.register(SearchResultViewCell.self, forCellWithReuseIdentifier: SearchResultViewCell.identifier)
collectionView.dataSource = self.searchCollectionViewDataSource
collectionView.isHidden = true
Expand Down Expand Up @@ -78,6 +90,11 @@ private extension SearchViewController {
self?.searchResultCollectionView.isHidden = true
self?.popularCollectionView.isHidden = false
}

viewModel.bind { [weak self] cities in
self?.popularCollectionViewDataSource.nearCities = cities
self?.popularCollectionView.reloadData()
}
}

func configureSearchController() {
Expand All @@ -98,7 +115,8 @@ private extension SearchViewController {
view.addSubview(searchResultCollectionView)

searchResultCollectionView.snp.makeConstraints { make in
make.top.leading.trailing.bottom.equalToSuperview()
make.top.equalTo(view.safeAreaLayoutGuide).offset(16)
make.leading.trailing.bottom.equalToSuperview()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ final class SearchHomeCollectionViewDataSource: NSObject, UICollectionViewDataSo
}

cell.setCityTitleLabel(text: item.cityName)

// TODO: 시간, 분 나누는 로직 추가해서 String으로 넘겨주기 "0시간 00분"
cell.setDistanceLabel(text: item.time)
cell.setDistanceLabel(text: item.time.convertIntoTime())
return cell

case .themeJourney:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ final class SearchHomeViewController: UIViewController {
private func bind() {
searchBarDelegate.tapTextField
.bind { [weak self] in
self?.navigationController?.pushViewController(SearchViewController(), animated: true)
self?.navigationController?.pushViewController(SearchViewController(viewModel: NearCityViewModel()), animated: true)
}

viewModel.bindHeroBanner { [weak self] banner in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,28 @@
import UIKit

enum SectionLayoutFactory {
static func createPopularDestinationLayout() -> UICollectionViewCompositionalLayout {
static func createPopularDestinationLayout(isHeaderExist: Bool) -> UICollectionViewCompositionalLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalWidth(0.3)
heightDimension: .fractionalWidth(0.23)
)

let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = .init(
top: 0,
leading: 0,
bottom: 0,
bottom: 16,
trailing: 0
)

let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitem: item, count: 1)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets.leading = 15
section.contentInsets.trailing = 15

guard isHeaderExist else {
return UICollectionViewCompositionalLayout(section: section)
}

section.boundarySupplementaryItems = [
NSCollectionLayoutBoundarySupplementaryItem(
Expand All @@ -34,6 +39,7 @@ enum SectionLayoutFactory {
elementKind: UICollectionView.elementKindSectionHeader,
alignment: .topLeading)
]

return UICollectionViewCompositionalLayout(section: section)
}

Expand Down

0 comments on commit c70dd71

Please sign in to comment.