포스트

10주차 과제 (4)

코드 리팩토링

아무생각없이 구글링을 하다가 보완할점을 발견하여 보강을 하려 한다.

출처 우연히 발견한건데 이분글에는 없는게 뭘까 라는 생각이 든다.

combine을 사용하면서 publisher로 선언한 변수를 그냥 계속 쓰는듯하다.

나는 그러지않고 vc에서 다시 배열을 만들어서 거기에 집어넣었는데, 글을 참고하여 더 Combine스럽게 바꿔보려한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
func callRequest(query: String) {
        
        NetworkManager.shared.fetchRequest(queryValue: query).sink { completion in
            switch completion {
            case .finished:
                print("success")
            case .failure(let error):
                print(error)
            }
        } receiveValue: { [weak self] documents in
            self?.document = documents
        }.store(in: &cancellables)
    }

지금 receiveValue를 사용해서 처리했는데 이걸 그냥 바로 프로퍼티에 할당하는 assgin을 사용해서 고쳐본다.

NetworkManager를 전부 갈아 엎는다. (거기에 있는 코드를 vm으로 이동.)

그리고

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
func transform(input: Input) {

        input.searchPublisher.sink { [weak self] value in
            self?.fetchRequest(queryValue: value)
        }.store(in: &cancellables)
        
    }
    
    func fetchRequest(queryValue: String) {
        
        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  // 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)
            .replaceError(with: [])
            .assign(to: \.document, on: self)
            .store(in: &cancellables)
    }

다음과 같이 수정.

document의 프로퍼티에 넣고, 아무것도 없을땐 빈배열 리턴.

그걸 transform에서 실행.

1
2
3
4
5
6
//vc
bookVM.$document
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.resultView.tableView.reloadData()
            }.store(in: &cancellables)

좀 더 괜찮아 졌다.

정리

코드 작성순서보단 코드의 흐름으로 정리한다.

  • before
    • vc에서는 vm transform 호출
    • vm에서 fetchRequest를 호출, searchPublisher를 통해 가져온 searchBar의 value를 쿼리에 주입.
    • NetworkManager를 통해 fetchRequest Method로 처리
    • transform의 결과로 vm의 document에 값이 저장됨.
    • 저장된 값을 publisher를 통해 vc의 list에 다시 저장.
      • publisher가 가능한 이유는 @Published 때문
    • list를 사용하여 tableview에 보여짐
  • after
    • vc에서는 vm transform 호출 (상동)
    • vm에서 fetchRequest를 호출, searchPublisher를 통해 가져온 searchBar의 value를 쿼리에 주입. (상동)
    • vm 내부 fetchRequest 함수를 통해 assign을 통해 바로 document에 저장.
    • 별도의 저장 없이 document를 사용하여 tableview에 보여짐.

우선 Sequence가 줄었다. 그리고 list를 vc에서 하나 더 만들 필요가 없다. (메모리 관리가 더 좋다.) NetworkManager의 기능을 vm이 바로 관리.

상세페이지 구현

이제 셀을 클릭했을때 해당 페이지를 볼 상세페이지를 구현해야 한다.

디자인 소질이 없으므로 과제 디자인 그대로 사용하려한다

CleanShot 2024-05-06 at 01 23 52@2x

4개의 섹션으로 나누면 될듯하다.

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class DetailViewController: UIViewController {

    private var titleView = TitleView()
    private var imageView = ImageView()
    private var bodyView = BodyView()
    private var buttonView = ButtonView()
    
    private lazy var vStackView: UIStackView = {
        var stackView = UIStackView(arrangedSubviews: [
        titleView,
        imageView,
        bodyView,
        buttonView,
        UIView()
        ])
        stackView.axis = .vertical
        stackView.spacing = 20
        
        return stackView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        layout()
    }
    
    private func layout() {
        view.addSubview(vStackView)
        
        vStackView.snp.makeConstraints { make in
            make.top.equalTo(view.snp.top).offset(100)
            make.leading.trailing.bottom.equalToSuperview()
        }
        
        titleView.snp.makeConstraints { make in
            make.height.equalTo(90)
        }
        
        imageView.snp.makeConstraints { make in
            make.height.equalTo(350)
        }
        
        bodyView.snp.makeConstraints { make in
            make.height.equalTo(180)
        }
        
        buttonView.snp.makeConstraints { make in
            make.height.equalTo(90)
        }
    }
    
}

일단은 기존과 같이 느낌만 살려둔다.

여기는 사실 닫기 담기 버튼만 구현하면 되는 부분이다.

위에 view관련된것을 private로 선언했는데, 생각해보니 그렇게 하면 안될것같아서 private를 다 지웠다.

private를 사용하게되면 화면전환시 프로퍼티 접근이 안됨.

didselectRowat 문제 해결

1
2
3
4
5
6
7
8
9
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        let detailVC = DetailViewController()
        let item = bookVM.document[indexPath.row]
        
        detailVC.titleView.titleLabel.text = item.title
        
        self.present(detailVC, animated: true)
    }

별에별 설정을 다해봤지만 되지않았다.

설정한 내역.

1
2
3
cell.isUserInteractionEnabled = true
tableView.isUserInteractionEnabled = true
tableView.allowsSelection = true

마지막걸 true하고나서 발생한 에러

1
SnapKit/ConstraintMakerRelatable.swift:85: Fatal error: Expected superview but found nil when attempting make constraint `equalToSuperview`.

어디가 문제일까 고민했는데 알고보니 아까 페이지 디자인하다 말았던 거기서 에러가 발생해서 crash가 나는것같다.

역시나 buttonView를 디자인 하다 만게 원인이었다.

멍청하게도

1
2
func layout () {
        addSubview(hStackView) // 추가를 안함

addsubview를 했다고 생각하고 해서 생긴 문제였다.

당연히 없는데 레이아웃을 잡으려고하니 문제가 발생한것이다.

레이아웃은 뒷전으로 하고 기능에 집중한다.

BodyView scrollview 설정

을 하나 보고 참고했다.

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
35
36
37
38
39
40
41
class BodyView: UIView {
    
    private var scrollView: UIScrollView = {
        var view = UIScrollView()
        view.isScrollEnabled = true
        view.showsVerticalScrollIndicator = true
        return view
    }()
    
    var bodyLabel = TextLabel().makeLabel(value: "body")
    
    override init(frame: CGRect) {
        super.init(frame: .zero)
        layout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func layout () {
        addSubview(scrollView)
        scrollView.addSubview(bodyLabel)
        
        scrollView.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(10)
            make.leading.equalToSuperview().offset(10)
            make.trailing.equalToSuperview().offset(-10)
            make.bottom.equalToSuperview().offset(-10)
        }
        
        bodyLabel.snp.makeConstraints { make in
            make.top.equalTo(scrollView.snp.top)
            make.leading.equalTo(scrollView.snp.leading)
            make.trailing.equalTo(scrollView.snp.trailing)
            make.bottom.equalTo(scrollView.snp.bottom)
            make.width.equalTo(scrollView)
        }
    }
    
}

오늘은 여기까지

May-06-2024 05-04-53

디자인은 마지막에 다듬어보는걸로…

그 다음은 Combine + Coredata를 한번 다뤄봐야겠다.

MVC로 했으면 오래걸리지도 않을 과제인데 확실히 안해본걸로 다루다보니 빡세지만 그만큼 재미있는듯하다.

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