Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Team-17 iOS 잭슨] 팀17 메인 View와 해당 위치 검색하는 View 구성. #45

Open
wants to merge 20 commits into
base: team-17
Choose a base branch
from

Conversation

JacksonPk
Copy link

구현 방법

사용한 프레임워크

  • : Lottie
  • : RealmDB

구현한 패턴

  • : MVC

메인 View 구성 요소

  • Lottie를 이용하여 애니메이션 이미지를 적용해보았습니다. json 파일을 이용해 보았습니다.
  • 실제 요구사항과 다르게 검색창이 아닌 검색버튼을 눌러서 검색을 할 수 있도록 수정해보았습니다.
  • 요구사항 메인View와는 차이점이 있지만 그 이유는 제가 airbnb를 실제로 다운받아 사용해보면서 메인뷰의 레이아웃을 크게 잡을 필요는 없다고 생각했습니다.
  • 따라서 메인 View에서는 크게 Lottie 이미지 설정과 버튼이 있습니다.

검색 View 구성 요소

  • 콜렉션 뷰를 이용하여 위치에 대한 이미지와 제목을 보여줍니다.
  • UISearchController를 이용하여 검색할 내용에 대해 결과를 보여줍니다.
  • 위치에 해당한 주소는 RealmDB를 이용하였으며 fetch 쿼리를 이용해서 실시간 업데이트될 때 reloadData()를 구축했습니다.
  • 네트워크(카카오맵API)를 이용해 지도에 있는 위치를 가져올 수 있었지만 그 부분에 대해서 복잡하고 현실구현 가능성을 생각해보았을 때 사용자 모빌리티에 위치정도데이터(서울시 기준)를 저장하여서 쓰는게 좋다고 생각했습니다.

데이터베이스

  • RealmDB를 이용하였습니다.
  • DB가 요구사항에는 없었지만 저번 프로젝트에 DB를 도입한 경험을 살려서 이번 프로젝트에 적용해보고싶어서 적용했습니다.
  • 현재는 DB에 주소에 대한 이름과 썸네일이미지를 저장하도록 하였습니다.
  • 백엔드와 얘기하면서 서울시에 있는 "OO구" 에 대한 숙소만 체크하기로 하였습니다.

MVC 패턴

  • View : 콜렉션뷰의 Cell파일, Lottie애니메이션
  • Model : Location에 파일
  • Controller : Main, SearchLocation
  • Delegate : searchLocation에 필요한 콜렉션 뷰와 UISerachController를 저장.
    - 특히 Delegate 파일에서 updateSearchResults() [서치바에 텍스트 변경시 호출 함수] 에 따라 reloadData()를 하기 위한 작업으로 NotificationCenter를 적용해보았고 그로인해서 SearchLocation Controller에서 변경됨을 인지하고 reloadData()를 구현했습니다.

질문

  • 처음 혼자서 작업하고 아직 실력이 많이 부족합니다. 제가 기능을 구현 및 리팩토링을 하는데 잘 구분이 되었으면 좋겠습니다.
    1. 저는 DB를 이용해서 검색 뷰에 나타날 데이터를 표현했는데, 만약 네트워크로 구현했을 때에도 실제로 이런 정보들은 사용자 스마트폰에 저장이 되어서 재사용이 될지 궁금합니다.
    1. SearchLocationDataSource.swift 에서 UICollectionViewDataSource, UISearchResultsUpdating를 채택한 클래스를 만들었는데 저는 이 두개를 각각 나누어야 할까 고민을 해보았습니다. 하지만 검색창에 반응에 따라 콜렉션 뷰가 즉각적으로 바꾸어져야 하기 때문에 둘을 뗄수는 없다고 생각해서 하나의 클래스로 만들어보았습니다. 더 잘게 나누어야 할 필요성이 있는지 궁금합니다.
    1. MVC 패턴을 이용해서 리팩토링을 하고 있습니다만, 아직은 제가 질문할 만큼 제대로 이해하고 있지 않은 것 같습니다. 스스로 좀 더 해봐야 제 실수가 무엇이 있을지 알 것 같은데 혹시 너무 잘못된 부분이 있다면 알려주시면 감사하겠습니다.

Copy link

@mienne mienne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SearchLocationDataSource 객체 분리를 통해 역할 나눈 것은 잘하였습니다. 다만, 이 객체가 어떤 역할에 더 가까운지 고민해보셔야 할거 같아요. 역할에 따라서 Model 데이터를 가지고 있을지 분리할지에 따라 코드가 많이 바뀝니다🙂


let locationIDs = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]
let locationNames = ["강남구", "강동구", "강북구", "강서구", "관악구", "광진구", "구로구", "금천구", "노원구", "도봉구", "동대문구", "동작구", "마포구", "서대문구", "서초구", "성동구", "성북구", "송파구", "양천구", "영등포구", "용산구", "은평구", "종로구", "중구", "중랑구"]
let locationImageNames = ["CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel","CodeSquadHotel"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppDelegate에서 변수로 더미데이터를 만드는 것보다 더미 데이터를 관리할 수 있는 객체를 만드는 것은 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 넵! 따로 데이터를 가지고 있는 객체를 만들어보겠습니다!

}
}

print(Realm.Configuration.defaultConfiguration.fileURL!)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print 제거해주세요🙂

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 넵. 제가 RealmDB 파일을 확인해보려다보니 print문으로 경로를 확인하려다보니 써놓았네요. 감사합니다.

super.viewDidLoad()
}

override func viewWillAppear(_ animated: Bool) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super.viewWillAppear(animated) 빠진 이유가 있을까요? 가급적 super 메서드는 호출해주세요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵!!

}
@IBAction func searchHotels(_ sender: Any) {
let vc = self.storyboard?.instantiateViewController(identifier: "SearchHotelsViewController")
self.navigationController?.pushViewController(vc!, animated: true)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. vc! 보다 guard let , if let, 옵셔널 체이닝 등 활용해주세요. ! 앱 크래시가 날 수 있습니다!
  2. 저도 사용하지 않는 화면에서 idenfitier 잘못 입력하고 테스트 하지 않은채 실제 앱스토어 올라갔다가 크래시 비율이 올라간 경험을 한 적이 있습니다. 실수를 방지하는 것이 좋은데 뷰컨트롤러 인스턴스화 할 때 직접 SearchHotelsViewController 적는 것을 방지할 수 있습니다. ViewController 인스턴스화 할 때 다양한 방법으로 extension 할 수 있지만 제네릭 함수를 활용할 수 있습니다:)
extension UIStoryboard {
    static func create<T: UIViewController>(identifier: T.Type, name storyboardName: String) -> T {
        let identifier = String(describing: T.self)
        guard let viewController = UIStoryboard(name: storyboardName, bundle: nil).instantiateViewController(withIdentifier: identifier) as? T else {
            fatalError()
        }

        return viewController
    }
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

옵셔널에 대해 기초가 부족하다보니 guard, if let의 사용유무를 잘 모르고 있었습니다.
실제 경험을 얘기해주셔서 더 와닿았습니다. 고치도록 하겠습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

충분히 잘하고 계십니다! 자기 속도에 맞춰서 하는게 중요하죠🙂

}

func setNotificationCenter() {
NotificationCenter.default.addObserver(self, selector: #selector (SearchLocationsViewController.changeCollectionView), name: Notification.Name("cellsChanged"), object: nil)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notification.Name("cellsChanged") add, post 적어도 사용하는 곳이 2군데 일텐데 extension 하거나 따로 상수를 만들어서 관리하는 것이 좋습니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하! 넵. 확장성을 생각못하구 실행이 되냐에만 초점을 둔 것 같네요. 따로 빼도록 하겠습니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사소한 문제지만, 파일 생성 위치도 어디에다가 할 것인지 생각해볼 여지가 많습니다. 단순하게 Notification.Name으로 extension 한다면 여러 extension 관리하는 그룹에서 생성할 것인지, add, post 하고 있는 도메인 그룹이나 레이어에다가 둘 것인지 코드 한 줄을 작성하거나 파일을 생성하는 위치 등 사소하더라도 다른 사람을 설득할 수 있는 본인만의 근거를 만드는 연습을 하면 좋아요🙂

if searchLocationsController.isActive {
return filteredLocations.locations.count
}else {
allLocations = dbManager.getLocations()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dbManager.getLocations() 가져오는 위치가 적절해보이지 않습니다. SearchLocationDataSource 객체가 생성할 때 allLocations 데이터 미리 셋팅되어 있어도 문제 없을거 같아요🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

죄송합니다. 구현하려는 방법은

if searchLocationsController.isActive {            
    return filteredLocations.locations.count        
} else {           
    return allLocations.locations.count

였는데 커밋이 이상하게 된 것 같습니다.

그리구 초기세팅은 allLocations.locations.count 로 정해도 되는데
서치바를 탭 했다가 다시 취소를 누르면 그 때는 allLocations.locations.count가 다시 등장해야 돼서
if-else로 나누어보았습니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공부하는 단계이고 실수한 것도 아닌데 전혀 죄송할 필요 없습니다!

}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LocationCell.reuseIdentifier, for: indexPath) as! LocationCell
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LocationCell.reuseIdentifier, for: indexPath) as! LocationCell
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LocationCell.reuseIdentifier, for: indexPath) as? LocationCell else {
return .init()
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나중의 실수를 고치기위해서는 이렇게 바꿔야 하는거군요! 감사합니다. 항상 !를 피할수 있는 방법을 먼저 생각하겠습니다.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LocationCell.reuseIdentifier, for: indexPath) as! LocationCell
if searchLocationsController.isActive {
cell.locationNameLabel.text = filteredLocations.locations[indexPath.row].name
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약 filteredLocations.locations 데이터가 없는 상태에서 index 접근한다면 앱 크래시가 발생합니다. 데이터가 없는 경우, 예외적인 상황도 항상 고려해야 합니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

searchLocationsController.isActive가 되는 순간은 서치바를 클릭하는 순간이어서 reloadData()가 되면서 데이터가 없으면 filterdLocations가 없는데로 갱신이 되어서 Cell자체가 안보여서 앱 크래시가 뜰수 있는 상황은 없다고 생각했습니다.
혹시 제가 잘못 이해한 부분이 있을가요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 이 코드는 잘못 이해하고 있었네요. 말한 부분이 맞습니다. 죄송합니다!

cell.locationNameLabel.text = filteredLocations.locations[indexPath.row].name
cell.locationCellImageView.image = UIImage(named: filteredLocations.locations[indexPath.row].imageName)
}else {
allLocations = dbManager.getLocations()
Copy link

@mienne mienne May 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

셀이 deque 될때마다 Realm 조회하고 조회된 객체가 생성하고 있을거 같아요! DB 자원을 읽고 객체를 생성하는 비용이 생각보다 작지 않습니다. 이 곳에서 데이터 조회해서 설정하는 이유가 무엇인가요?🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하! 생각해보니 맨 처음에 allLocations에 데이터가 저장되어있다면 그걸 이용하면 되는데 deque마다 불러주는군요!!
제가 생각이 짧았습니다. 큰 이유없이 제가 잘못 한거같습니다.
이 클래스에 init()을 해서 allLocations = dbManager.getLocations()를 최초에만 쓰고 그 이후에는 dbManager를 부르는 일을 없애도록 하겠습니다.

import Foundation
import RealmSwift

private protocol DBOperations {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protocol이 private 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음.. 저는 이 DBOperations는 여기 DBManager에만 쓰기 때문에 private을 사용해서 다른 곳에서 못 부르는다고 생각했는데
굳이 private을 할 이유는 지금 검색해서보니 큰 의미가 없는 것 같네요.
항상 의미없는 옵션은 안 붙여보도록 하겠습니다.

Copy link

@mienne mienne May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protocol 사용하는 목적을 생각한다면 private 접근 제어자가 어울리지 않다고 생각했어요. 그래도 필요하니깐 있는거겠죠ㅎㅎ

@mienne
Copy link

mienne commented May 26, 2021

저는 DB를 이용해서 검색 뷰에 나타날 데이터를 표현했는데, 만약 네트워크로 구현했을 때에도 실제로 이런 정보들은 사용자 스마트폰에 저장이 되어서 재사용이 될지 궁금합니다.

넷플릭스나 유투브 다운받고 네트워크 OFF 해보세요. 오프라인 모드가 대응되어 있을거에요! API 호출되지 않는 상황도 항상 고려해야 합니다. 어떤 정보를 노출할지 따라 앱에서 캐싱하고 API 호출이 되지 않을 때 캐싱한 정보를 보여줄지 단순하게 에러 페이지로 보여줄지.. 이건 회사, 팀 정책에 달라지긴 합니다:)

SearchLocationDataSource 객체에 대한 부분은 코멘트로 남겼습니다. 짤게 쪼개야 한다고 느낀 기준이 무엇일까요? 느낌적인 느낌인지 아니면 객관적으로 역할을 나눠야 한다는 생각이 들었을까요🤔?

@JacksonPk
Copy link
Author

감사합니다. 제가 지금 PR을 받고 수정을 해야하는데 다른 브랜치로 이미 진행을 하고 있어서 꼬였네요..ㅜ
mienne 이 언급해주신 부분을 가지구 수정하면서 제가 다음 PR을 보낼 때 수정본을 가지구 다시 알려드리도록 하겠습니다.
수정하면서 궁금한 점은 제가 이 PR에 질문을 올리겠습니다!

@JacksonPk
Copy link
Author

mienne! 제가 지금 PR을 혼자서 보내면서 리뷰를 받아보니 제가 실수를 많이하고 올리기 전에 한번 더 확인했다면 바로 고칠수 있는 부분을 많이 놓친 것 같습니다.
많은 피드백 감사합니다. 기능을 구현하고 또한 피드백을 받아 고쳐보도록 하겠습니다!

@mienne
Copy link

mienne commented May 27, 2021

추가 코멘트 남겼습니다. 피드백은 생각해보고 지금 할 수 있는 것만 해보셔도 되요! 무리하지 않고 페이스 조절 잘하는게 더 중요합니다🙂

crongro pushed a commit that referenced this pull request Jun 2, 2021
crongro pushed a commit that referenced this pull request Jun 2, 2021
[FE] recoil: atoms, atom types 추가 (#45)
swing-park pushed a commit that referenced this pull request Jun 5, 2021
swing-park pushed a commit that referenced this pull request Jun 5, 2021
swing-park pushed a commit that referenced this pull request Jun 5, 2021
swing-park pushed a commit that referenced this pull request Jun 5, 2021
swing-park pushed a commit that referenced this pull request Jun 5, 2021
swing-park pushed a commit that referenced this pull request Jun 5, 2021
swing-park pushed a commit that referenced this pull request Jun 5, 2021
swing-park pushed a commit that referenced this pull request Jun 5, 2021
swing-park pushed a commit that referenced this pull request Jun 5, 2021
swing-park pushed a commit that referenced this pull request Jun 5, 2021
wheejuni pushed a commit that referenced this pull request Jun 6, 2021
할인 정책이 여러개이므로 리스트에 저장해둠. 추후 Map이나 enum을 활용하는 등의 방법으로 리팩토링 생각해봐야할듯. 논리적으로는 list보다 set이 더 어울림. 가격정책이 세분화되면 인터페이스로 변경하는 식으로 추가해볼 수 있음.
인원 수에 따른 가격 계산 추가시켜야함.

Dae-Hwa/airbnb/#45
wheejuni pushed a commit that referenced this pull request Jun 6, 2021
wheejuni pushed a commit that referenced this pull request Jun 6, 2021
wheejuni pushed a commit that referenced this pull request Jun 6, 2021
평일에만 할인되도록 정책 추가 및 기본 할인 정책으로 설정.

Dae-Hwa/airbnb/#45
crongro pushed a commit that referenced this pull request Jun 7, 2021
[FE] 라우터 및 fetch 작업
crongro pushed a commit that referenced this pull request Jun 8, 2021
crongro pushed a commit that referenced this pull request Jun 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants