포스트

10주차 과제 (3)

이제 진짜 문제의 시작.

문득 씻다가 이런생각이 들었다.

VM에 대해서 생각을 좀 해보았는데,

우리가 VC에 TableView관련 함수를 쓸 수 밖에 없었던 가장 큰 이유는, VC에 tableview가 있었고, 그걸 TableView 관련 protocol들을 가져왔어야 했기 때문이었다.

그러다보니 자연스럽게 VC가 처리할 내용이 많아졌다.

MVVM은 이런 걸 막기위해서 나온건 아닐까 라는 생각이 들었다. 왜냐 이전 글에서도 vc는 그냥 vm의 메서드만 호출했을 뿐이다.

그렇게 본다면 TableView, CollectionView들도 그렇게 하면 되는거 아닐까? 라는 생각이 들어서 해보려고 한다.

TableView Setting

VM에서 해당 관련 메서드를 처리해보려 한다.

관련 메서드면 Tableview의 단골 손님

cellForRowAt, numberOfRowsinSection 이 두녀석이다.

이걸 VM에서 핸들링이 가능한지 해보려한다.

아직은 지식이 충분하지 않아 검색을 해보고 있는데 웹사이트를 보니 내 가설은 적용이 안되겠구나 라는 생각이 바로 들었다.

하긴 VC가 담당할 수 밖에 없는 기능들이 있긴하니까.

실제로

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
26
27
28
29
30
31
32
33
34
extension BookVM: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    }
    
    func isEqual(_ object: Any?) -> Bool { 
    }
    var hash: Int {
    }
    var superclass: AnyClass? {   
    }
    func `self`() -> Self {   
    }
    func perform(_ aSelector: Selector!) -> Unmanaged<AnyObject>! {   
    }
    func perform(_ aSelector: Selector!, with object: Any!) -> Unmanaged<AnyObject>! {   
    }
    func perform(_ aSelector: Selector!, with object1: Any!, with object2: Any!) -> Unmanaged<AnyObject>! {   
    }
    func isProxy() -> Bool {   
    }
    func isKind(of aClass: AnyClass) -> Bool {   
    }
    func isMember(of aClass: AnyClass) -> Bool {   
    }
    func conforms(to aProtocol: Protocol) -> Bool {
    }
    func responds(to aSelector: Selector!) -> Bool {   
    }
    var description: String {   
    }
}

가설대로 해보려고 extension하자마자 무수히 많이 쏟아지는 함수들

이것만 봐도 뭔가 잘못되었음을 알수있게해준다.

CleanShot 2024-05-05 at 18 10 26@2x

NSObjectProtocol을 준수하라고 뜬다. Fix를 누르자마자 무더기로 쏟아진다.

이걸보고 내린 나의 결론은 VC에서 해야한다라는 것이다.

별도로 관리하기위해 Extension 파일을 만들어 주었고, 다음과 같이 적는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func setUp() {
        resultView.tableView.delegate = self
        resultView.tableView.dataSource = self
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = resultView.tableView.dequeueReusableCell(withIdentifier: Constants.tableViewCellIdentifier) as? ResultTableViewCell else { return UITableViewCell () }
        
        
        
        return cell
    }

}

리턴을 1로 해둔 이유는, 이제 searchbar에 입력한 결과를 배열로 받아서 처리를 해야한다.

지금은 그냥 밑작업.

그전에 tableview cell autolayout 점검이 필요하므로 dummy data를 하나 만들어서 그것부터 좀 건드리고 해야할듯하다.

let dummyData = [Document(authors: ["나"], contents: "테스트", price: 12300, title: "테스트입니다", thumbnail: "", salePrice: nil)]

하나 만들어 주고

내용 추가

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
26
27
extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func setUp() {
        resultView.tableView.delegate = self
        resultView.tableView.dataSource = self
        resultView.tableView.reloadData()
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return dummyData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = resultView.tableView.dequeueReusableCell(withIdentifier: Constants.tableViewCellIdentifier) as? ResultTableViewCell else {
            return UITableViewCell()
        }
        
        let item = dummyData[indexPath.row]
        
        cell.titleLabel.text = item.title
        cell.priceLabel.text = String(item.price)
        
        return cell
    }

}

코드상에 문제가 없는데 보이지가 않는다?

cell의 문제로 판단 다시 cell로 가보자.

1
2
3
4
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        layout()
    } 

바로 이녀석이 없어서 생긴 문제였다.

왜 그동안에는 저것 없이도 가능했는가? 에 대해서는 이전에는 CustomCell을 구현할때 xib파일도 같이 생성을 했기에, 거기서 Indentifier 설정이 가능했다.

그리고 지금은 순수하게 CodeBase이기에 그것도 불가능. 그리고, initializer가 없었기 때문이다.

해당 부분을 추가해주자.

simulator_screenshot_9683B135-FBD6-4F15-9DDE-1F30F487F683

출력은 잘된다.

cell autolayout 문제도 우선 해결

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private func layout () {
        addSubview(hStackView)
        
        hStackView.snp.makeConstraints { make in
            make.leading.bottom.trailing.top.equalToSuperview()
        }
        
        titleLabel.snp.makeConstraints { make in
            make.leading.equalTo(hStackView.snp.leading)
            make.top.equalTo(hStackView.snp.top).offset(10)
            make.bottom.equalTo(hStackView.snp.bottom).offset(-10)
        }
        
        priceLabel.snp.makeConstraints { make in
            make.leading.equalTo(titleLabel.snp.trailing)
            make.trailing.equalTo(hStackView.snp.trailing).offset(-10)
            make.top.equalTo(hStackView.snp.top).offset(10)
            make.bottom.equalTo(hStackView.snp.bottom).offset(-10)
        }
    }

wtflayout 개발한사람 진짜 칭찬해야한다.

검색시 Tableview에 보이기.

그전에 return type을 고쳐주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func fetchRequest(queryValue: String) -> AnyPublisher<[Document], Error> { // modified
        
        let urlString = "https://dapi.kakao.com/v3/search/book?target=title"
        let headers = ["Authorization" : "KakaoAK \(Secret.apikey)"]
        
        var urlComponent = URLComponents(string: urlString)
        urlComponent?.queryItems?.append(URLQueryItem(name: "query", value: queryValue))
        
        guard let url = urlComponent?.url else {
            return Fail(error: URLError(.badURL)).eraseToAnyPublisher() // Error 리턴
        }
        
        var request = URLRequest(url: url)
        request.allHTTPHeaderFields = headers
        let session = URLSession(configuration: .default)
        return session.dataTaskPublisher(for: request)
            .map(\.data)
            .decode(type: BookModel.self, decoder: JSONDecoder())
            .map(\.documents) // added
            .eraseToAnyPublisher()
    }

document만 가져오게한다. 원래는 data.documents 였다면 이제는 그냥 documents에 바로 접근하게 했다.

이젠 console에서 출력이 되던것을 배열에 집어넣어서 handling을 해줘야한다.

우선 @Published를 사용해야한다.

@Published var document = [Document]() document를 바꿔주었다.

그이유는 저 document가 vm에서 데이터를 받고, 다시 vc를 호출할때 값을 넘겨줄 publisher가 되기 때문이다.

VC로 돌아와서 vm으로 선언한 document를 통해 sink로 데이터를 전달할건데, 그걸 받을 변수

var tableViewList = [Document]()를 만들어 준다.

1
2
3
4
5
bookVM.$document
            .sink { [weak self] document in
                self?.tableViewList = document
            }.store(in: &cancellables)
resultView.tableView.reloadData()

그리고 viewdidload에 다음과 같이 적는다.

되어야하는데 안된다.

알고보니 비동기 방식이므로 scheduler를 통해 main thread작업도 선정을 해주고 reload도 클로저 안에넣어야 했다.

다시 수정하면,

1
2
3
4
5
6
        bookVM.$document
            .receive(on: RunLoop.main) // added
            .sink { [weak self] document in
                self?.tableViewList = document
                self?.resultView.tableView. reloadData() // modified line
            }.store(in: &cancellables)

May-05-2024 21-23-07

굿.

뭔가 공부한건 api를 사용하지 않고 combine을 사용한거라 지금 코드작성한것이 공부한것과 조금 다르긴 하지만, 그래도 조금씩 감이 온다.

포인트는 publisher를 통해 어떻게 데이터를 넘길것인가 이다.

셀 글씨 생략되는 디테일은 나중에 하는걸로.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.