[iOS/UIKit] 프로젝트 트러블슈팅

이번 글에서는 앱을 만드는 프로젝트 과정에서 발생한 문제와 해결과정에 대해 작성할 예정이다.

프로젝트 내용은 TMDB API를 이용해 영화에 대한 데이터를 관리할 수 있는 앱을 만드는 것이다.

해당 프로젝트를 개발하면서 해결하는데 가장 시간이 오래 걸렸거나, 새로운 개념을 알게되었거나, 기본적인 개념이었는데 실제 개발시 적용하지 못해서 아쉬웠었던 사례 5가지를 소개할 예정이다.

물론 내가 해결한 방법이 정답은 아닐 수 있지만, 어떤한 생각의 흐름으로 해결을 했는지를 위주로 다뤄보려고 한다.


Push로 이동했을 때 View의 비정상적 동작

  • 문제

    • 프로필 설정화면에서 선택된 이미지를 프로필 이미지 선택뷰로 넘기면서 push로 화면전환을 하는 과정에서 화면이 버벅거리는 문제가 발생

      // ProfileSettingViewController => 이미지를 전달하는 View
      @objc
      private func imageSettingButtonTapped() {
        let vc = ImageSettingViewController()
        vc.imageContents = profileSettingView.imageSettingButton.image(for: .normal)
        navigationController?.pushViewController(vc, animated: true)
      }
      // ImageSettingViewController => 이미지를 받아서 이동되는 View
      import UIKit
      
      class ImageSettingViewController: BaseViewController {
        private var imageSettingView = ImageSettingView()
        var imageContents: UIImage?
      
        override func loadView() {
            view = imageSettingView
        }
      
        override func configureEssential() {
            navigationItem.title = "프로필 이미지 설정"
        }
      
        override func configureView() {
            imageSettingView.previewImage.setImage(imageContents, for: .normal)
        }
      }
  • 해결 시도

    • 이미지를 전달하는 과정에서 문제가 있다고 판단해서 ViewController로 이미지 데이터를 전달하는게 아니라 BaseView를 상속받는 ImageSettingView로 바로 넘겨보기 시도

      @objc
      private func imageSettingButtonTapped() {
        let vc = ImageSettingViewController()
        vc.imageSettingView.imageContents = profileSettingView.imageSettingButton.image(for: .normal)
        navigationController?.pushViewController(vc, animated: true)
      }

      ⇒ 실패

    • BaseView와 BaseViewController를 사용하는 과정에서 데이터 전달에 문제가 있다고 판단해서 이러한 커스텀뷰를 사용하지 않고 ViewController에서만 구성

      ⇒ 실패

  • 문제 발견

    • ImageSettingViewController는 다음과 같은 BaseViewController라는 커스텀뷰를 상속받은 구조

      import UIKit
      
      class BaseViewController: UIViewController {
      
        override func viewDidLoad() {
            super.viewDidLoad()
      
            configureView()
            configureEssential()
        }
      
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
        }
      
        func configureView() {
            view.backgroundColor = .cineBlack
        }
      
        func configureEssential() { }
      }
    • 여기서 view의 backgroundColor을 결정해주는 코드가 configureView()라는 메서드에 작성된 상태

    • 그러나, ImageSettingView에서 configureView라는 메서드를 재정의해서 사용할 때 다음과 같이 super.configureView()를 하지 않은 상태로 사용

      override func configureView() {
        imageSettingView.previewImage.image = imageContents
      }
    • 결론적으로 view의 backgroundColor가 세팅되지 않아서 view가 로드될 때 정상적으로 표시되지 않고 버벅거리는 현상이 발생

  • 해결

    • 앞으로 BaseViewController를 상속받는 모든 뷰에서 super.configureView() 를 해주기엔 비효율적이라고 생각해서, 다음과 같이 BaseViewController의 viewDidLoad() 에 기본으로 적용해놓는 방식으로 해결

      class BaseViewController: UIViewController {
      
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .cineBlack
      
            configureView()
            configureEssential()
        }
      
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
        }
      
        func configureView() { }
      
        func configureEssential() { }
      }

TableView Cell안의 버튼과의 상호작용

  • 문제

    • 다음 뷰는 TableView로 이루어져 있으며, 프로필을 보여주는 영역이 첫번째 cell

    • 해당 cell의 imageView나 button은 tapAction을 가지고 있는데, 인식을 하지 못함

      Main
  • 해결 시도

    • 탭을 했다는 데이터를 전달하는 과정에서 문제가 있다고 판단해서 클로저, 델리게이트, tag를 사용해서 탭을 했다는 사실을 넘겨주려고 함

    • 클로저를 통해 시도

      //  ProfileTableViewCell.swift
      
      var buttonTapped: (() -> Void)?
      
      movieBoxButton.addTarget(self, action: #selector(movieButtonTapped), for: .touchUpInside)
      
      @objc
      private func movieButtonTapped() {
        print(#function)
        buttonTapped?()
      }
      
      //  CinemaViewController.swift
      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            if indexPath.row == 0 {
                guard let cell = cinemaView.tableView.dequeueReusableCell(withIdentifier: ProfileTableViewCell.id, for: indexPath) as? ProfileTableViewCell else { return UITableViewCell() }
                cell.backgroundColor = .clear
      
                cell.imageViewTapped = {
                    print("버튼 탭")
                }
      
                //...
      }

      ⇒ 실패

    • 델리게이트를 통해 시도

      //  ProfileTableViewCell.swift
      
      var delegate: ButtonTappedDelegate?
      
      movieBoxButton.addTarget(self, action: #selector(movieButtonTapped), for: .touchUpInside)
      
      @objc
      private func movieButtonTapped() {
        print(#function)
        delegate?.movieBoxButtonTapped()
      }
      
      //  CinemaViewController.swift
      
      protocol ButtonTappedDelegate {
        func movieBoxButtonTapped()
      }
      
      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            if indexPath.row == 0 {
                guard let cell = cinemaView.tableView.dequeueReusableCell(withIdentifier: ProfileTableViewCell.id, for: indexPath) as? ProfileTableViewCell else { return UITableViewCell() }
                cell.backgroundColor = .clear
      
                cell.delegate = self
                //...
      }
      
      extension CinemaViewController: ButtonTappedDelegate {
        func movieBoxButtonTapped() {
            print("버튼 탭")
        }
      }

      ⇒ 실패

    • tag를 사용

      //  CinemaViewController.swift
      
      @objc
      private func movieBoxButtonTapped(sender: UIButton) {
        print("이건될라나아ㅏ")
      }
      
      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
          if indexPath.row == 0 {
              guard let cell = cinemaView.tableView.dequeueReusableCell(withIdentifier: ProfileTableViewCell.id, for: indexPath) as? ProfileTableViewCell else { return UITableViewCell() }
              cell.backgroundColor = .clear
      
              cell.movieBoxButton.tag = indexPath.row
              cell.movieBoxButton.addTarget(self, action: #selector(movieBoxButtonTapped), for: .touchUpInside)
      }

      ⇒ 실패

  • 문제 발견

    • tapAction이나 데이터를 전달하는 것의 문제가 아니고, tableView Cell 안에서 addSubView를 해줄 때 contentView가 아닌 cell 자체에 addSubView를 해주고 있었음

      //  ProfileTableViewCell.swift
      
      override func configureHierarchy() {
                // addUbView가 문제!!
          addSubview(roundBackgroundView)
      }
      
      movieBoxButton.addTarget(self, action: #selector(movieButtonTapped), for: .touchUpInside)
      
      @objc
      private func movieButtonTapped() {
        print(#function)
      }
  • 해결

    • contentView에 addSubView를 해주면서 해결

      //  ProfileTableViewCell.swift
      
      override func configureHierarchy() {
          contentView.addSubview(roundBackgroundView)
      }
      
      movieBoxButton.addTarget(self, action: #selector(movieButtonTapped), for: .touchUpInside)
      
      @objc
      private func movieButtonTapped() {
        print(#function)
      }
    • tableView나 collectionViewCell의 view의 위계관계에 대해 알게 되었다.

      Main
  • 학습한 것

    • tableView의 View의 위계 ⇒ CellContentView 안에 View가 있는 형태를 기억

TableView Cell안의 버튼과의 상호작용

  • 문제

    • 각 tableViewCell에 collectionView를 넣어주고, delegate에서 각 collectionView를 분기처리를 해준 이후, 셀을 움직이면 런타임 에러 발생
    Main
    • register를 잘 한것들도 확인했고, 분기처리는 다음과 같이 함

      extension CinemaViewController: UITableViewDelegate, UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 3
        }
      
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      
            tableView.tag = indexPath.row
      
            if indexPath.row == 0 {
                guard let cell = tableView.dequeueReusableCell(withIdentifier: ProfileTableViewCell.id, for: indexPath) as? ProfileTableViewCell else { return UITableViewCell() }
      
                let tapGesture = UITapGestureRecognizer(target: self, action: #selector(backgroundViewTapped))
                cell.roundBackgroundView.addGestureRecognizer(tapGesture)
                cell.roundBackgroundView.isUserInteractionEnabled = true
      
                return cell
            } else if indexPath.row == 1 {
                guard let cell = tableView.dequeueReusableCell(withIdentifier: RecentSearchTableViewCell.id, for: indexPath) as? RecentSearchTableViewCell else { return UITableViewCell() }
      
                cell.searchWordsCollectionView.delegate = self
                cell.searchWordsCollectionView.dataSource = self
                cell.searchWordsCollectionView.register(RecentWordsCollectionViewCell.self, forCellWithReuseIdentifier: RecentWordsCollectionViewCell.id)
                cell.searchWordsCollectionView.showsHorizontalScrollIndicator = false
                cell.searchWordsCollectionView.reloadData()
      
                cell.clearButton.addTarget(self, action: #selector(clearButtonTapped), for: .touchUpInside)
      
                if searchList.isEmpty {
                    cell.emptyLabel.isHidden = false
                    cell.searchWordsCollectionView.isHidden = true
                    cell.clearButton.isHidden = true
                } else {
                    cell.emptyLabel.isHidden = true
                    cell.searchWordsCollectionView.isHidden = false
                    cell.clearButton.isHidden = false
                }
      
                return cell
            } else {
                guard let cell = tableView.dequeueReusableCell(withIdentifier: TodayMovieTableViewCell.id, for: indexPath) as? TodayMovieTableViewCell else { return UITableViewCell() }
      
                cell.movieCollectionView.delegate = self
                cell.movieCollectionView.dataSource = self
                cell.movieCollectionView.register(MovieCollectionViewCell.self, forCellWithReuseIdentifier: MovieCollectionViewCell.id)
                cell.movieCollectionView.showsHorizontalScrollIndicator = false
                cell.movieCollectionView.reloadData()
      
                return cell
            }
        }
      }
      
      extension CinemaViewController: UICollectionViewDelegate, UICollectionViewDataSource {
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            if cinemaView.tableView.tag == 1 {
                return searchList.count
            } else {
                return 20
            }
        }
      
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            if cinemaView.tableView.tag == 1 {
                guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecentWordsCollectionViewCell.id, for: indexPath) as? RecentWordsCollectionViewCell else { return UICollectionViewCell() }
                let item = searchList[indexPath.item]
      
                cell.configureData(item: item)
      
                cell.removeButton.tag = indexPath.item
                cell.removeButton.addTarget(self, action: #selector(removeButtonTapped), for: .touchUpInside)
      
                return cell
            } else {
                guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MovieCollectionViewCell.id, for: indexPath) as? MovieCollectionViewCell else { return UICollectionViewCell() }
      
                return cell
            }
        }
      }
  • 해결 시도

    • 런타임 에러가 나는 경우: 두 번째 스크롤뷰를 움직이려고 할 때

    • 셀을 재사용할 일이 없는 경우에는 문제가 되지 않았음

    • BreakPoint를 통해 두 번째 셀을 움직였을 때, 세 번째 셀의 guard문에 잡히는 것을 확인

      Main
- 즉, indexPath.row가 1에 해당하는 셀을 움직였는데 indexPath.row가 2에 해당하는 셀에 잡힘
- 각 셀을 탭했을 때 태그값을 확인해보니 전부 2가 찍힘

```swift
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print(tableView.tag)
}
```
  • 문제 발견

    • 현재 tableView의 tag에 indexPath.row값을 넣어주는 식으로 한게 문제
    • 현재 분기처리로는 cell이 그려질때마다 맨 마지막에 그려진 시점의 indexPath.row가 tableView.tag에 할당이 됨으로 모든 cell의 tag가 같은 값을 가지게 된것임
  • 해결

    • 각 collectionView의 tag에 tableView의 index값을 넣어주고, collectionView의 tag값에 따라 분기처리를 해줬어야 함

      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      
        // 이렇게 하는게 아니라
        // tableView.tag = indexPath.row
      
        if indexPath.row == 0 {
            guard let cell = tableView.dequeueReusableCell(withIdentifier: ProfileTableViewCell.id, for: indexPath) as? ProfileTableViewCell else { return UITableViewCell() }
      
            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(backgroundViewTapped))
            cell.roundBackgroundView.addGestureRecognizer(tapGesture)
            cell.roundBackgroundView.isUserInteractionEnabled = true
      
            return cell
        } else if indexPath.row == 1 {
            guard let cell = tableView.dequeueReusableCell(withIdentifier: RecentSearchTableViewCell.id, for: indexPath) as? RecentSearchTableViewCell else { return UITableViewCell() }
      
            // 이렇게 각 collectionView의 tag에 indexPath값을 넣어줌
            cell.searchWordsCollectionView.tag = indexPath.row
            cell.searchWordsCollectionView.delegate = self
            cell.searchWordsCollectionView.dataSource = self
            cell.searchWordsCollectionView.register(RecentWordsCollectionViewCell.self, forCellWithReuseIdentifier: RecentWordsCollectionViewCell.id)
            cell.searchWordsCollectionView.showsHorizontalScrollIndicator = false
            cell.searchWordsCollectionView.reloadData()
      
            cell.clearButton.addTarget(self, action: #selector(clearButtonTapped), for: .touchUpInside)
      
            if searchList.isEmpty {
                cell.emptyLabel.isHidden = false
                cell.searchWordsCollectionView.isHidden = true
                cell.clearButton.isHidden = true
            } else {
                cell.emptyLabel.isHidden = true
                cell.searchWordsCollectionView.isHidden = false
                cell.clearButton.isHidden = false
            }
      
            return cell
        } else {
            guard let cell = tableView.dequeueReusableCell(withIdentifier: TodayMovieTableViewCell.id, for: indexPath) as? TodayMovieTableViewCell else { return UITableViewCell() }
      
            // 이렇게 각 collectionView의 tag에 indexPath값을 넣어줌
            cell.movieCollectionView.tag = indexPath.row
            cell.movieCollectionView.delegate = self
            cell.movieCollectionView.dataSource = self
            cell.movieCollectionView.register(MovieCollectionViewCell.self, forCellWithReuseIdentifier: MovieCollectionViewCell.id)
            cell.movieCollectionView.showsHorizontalScrollIndicator = false
            cell.movieCollectionView.reloadData()
      
            return cell
        }
      }
      extension CinemaViewController: UICollectionViewDelegate, UICollectionViewDataSource {
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
      
            // 이게 아니라
            // if cinemaView.tableView.tag == 1 { ... }
      
            // 이렇게 collectionView의 tag값으로 분기처리
            if collectionView.tag == 1 {
                return searchList.count
            } else {
                return 20
            }
        }
      
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
      
            // 이게 아니라
            // if cinemaView.tableView.tag == 1 { ... }
      
            // 이렇게 collectionView의 tag값으로 분기처리
            if collectionView.tag == 1 {
                guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecentWordsCollectionViewCell.id, for: indexPath) as? RecentWordsCollectionViewCell else { return UICollectionViewCell() }
                let item = searchList[indexPath.item]
      
                cell.configureData(item: item)
      
                cell.removeButton.tag = indexPath.item
                cell.removeButton.addTarget(self, action: #selector(removeButtonTapped), for: .touchUpInside)
      
                return cell
            } else {
                guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MovieCollectionViewCell.id, for: indexPath) as? MovieCollectionViewCell else { return UICollectionViewCell() }
      
                return cell
            }
        }
      }
  • 학습한 것

    • TableView 안에 CollectionView가 들어왔을 경우 분기처리 방법

UserDefaults와 역 값전달

  • 문제

    • 모달 시트가 올라온 상태에서 특정 값을 UserDefaults에 저장하고, 해당 시트를 닫았을 때의 View의 tableView에 저장된 값을 보여주고 싶었음

    • 처음에는 시트가 dismiss될 때 UserDefaults에 저장하고, 밑바탕뷰가 viewWillAppear될 때 reloadData를 하는 방식으로 했는데, 시트를 내리면 viewWillAppear가 호출이 안되는 생명주기상의 문제가 있었음

      // sheet
      
      @objc
      private func doneButtonTapped() {
        UserDefaultsManager.shared.nickname = profileSettingSheetView.nicknameTextField.text ?? ""
        UserDefaultsManager.shared.saveImage(UIImage: profileSettingSheetView.profileImageView.image ?? UIImage(), "profileImage")
        dismiss(animated: true)
      }
      // ViewController
      
      // sheet를 닫으면 viewWillAppear기 실행이 안돼서 업데이트가 바로 안됨
      override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
      
        cinemaView.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
      }
  • 해결 시도

    • 값 전달하는 방식을 NotificationCenter으로 변경해서 시도했으나

    • NotificationCenter 역시 옵저버를 add하는 시점이 있어야 하는데, 그 시점을 정하지 못하는 동일한 문제 존재

      // sheet
      
      private func reSaveValue() {
        guard let imageValue = profileSettingSheetView.profileImageView.image,
        let nicknameValue = profileSettingSheetView.nicknameTextField.text else { return }
      
        NotificationCenter.default.post(
            name: NSNotification.Name("ReSaveValue"),
            object: nil,
            userInfo: [
                "image": imageValue,
                "nickname": nicknameValue
            ]
        )
      }
      
      @objc
      private func doneButtonTapped() {
        reSaveValue()
        dismiss(animated: true)
      }
      // ViewController
      
      // 어차피 얘를 어디선가 호출해야할텐데
      private func receiveReSaveValue() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(reSaveValueReceivedNotification),
            name: Notification.Name("ReSaveValue"),
            object: nil
        )
      }
      
      @objc
      private func reSaveValueReceivedNotification(value: NSNotification) {
        if let image = value.userInfo!["image"] as? UIImage,
           let nickname = value.userInfo!["nickname"] as? String{
            UserDefaultsManager.shared.setImage(UIImage: image, "profileImage")
            UserDefaultsManager.shared.nickname = nickname
      
            cinemaView.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
        }
      }
  • 문제 발견

    • NotificationCenter나 단순히 UserDefaults에 값을 저장하는 방식으로 sheet를 dismiss하면 해당 저장된 값을 적용하는 순간을 지정하기가 까다로움
  • 해결

    • 클로저를 통한 값전달을 활용함으로써, 애초에 sheet를 띄우면서 값을 받을 준비를 해서, 해당 시점에 값을 넘겨받는 방법을 선택

    • 동시에 원래는 UserDefaults에 저장된 데이터를 TableView의 cell에 바로 넣어줬었는데, 별도의 프로퍼티를 만들어서 한 번 더 거치게 만들어줌 ⇒ tableView 밖에서도 사용하기 위해

      //  CinemaViewController.swift
      
      import UIKit
      import Kingfisher
      
      final class CinemaViewController: BaseViewController {
      
        private var cinemaView = CinemaView()
        private var searchList: [String] = []
        private var movieList: [MovieDetail] = []
        // 아래와같이 클래스의 프로퍼티로 만들어줌
        private var imageContents: UIImage?
        private var nicknameContents: String?
      
        override func loadView() {
            view = cinemaView
        }
      
        override func viewDidLoad() {
            super.viewDidLoad()
      
            callRequest()
            receiveSearchText()
            searchList = UserDefaultsManager.shared.searchList
            // 이 함수!
            saveUserDefaultsValue()
        }
      
        //...
      
        // 여기서 UserDefaults의 값을 위의 프로퍼티에 담음
        private func saveUserDefaultsValue() {
            // UserDefaults에 저장된 이미지, 닉네임 데이터 담기
            if let imageData = UserDefaults.standard.data(forKey: "profileImage") {
                imageContents = UIImage(data: imageData)
            }
            nicknameContents = UserDefaultsManager.shared.nickname
        }
      @objc
      private func backgroundViewTapped(sender: UITapGestureRecognizer) {
        if sender.state == .ended {
            let vc = ProfileSettingSheetViewController()
      
            // 기존의 이미지, 닉네임을 sheet로 옮기기
            if let imageData = UserDefaults.standard.data(forKey: "profileImage"),
               let image = UIImage(data: imageData) {
                vc.imageContents = image
            }
            vc.nicknameContents = UserDefaultsManager.shared.nickname
      
            let group = DispatchGroup()
      
            // 다시 저장한 닉네임 데이터 받기
            group.enter()
            vc.reSaveImage = { value in
                UserDefaultsManager.shared.saveImage(UIImage: value, "profileImage")
                self.imageContents = value
                group.leave()
            }
      
            // 다시 저장한 이미지 데이터 받기
            group.enter()
            vc.reSaveNickname = { value in
                UserDefaultsManager.shared.nickname = value
                self.nicknameContents = value
                group.leave()
            }
      
            // 한번에 UI업데이트를 위해 DispatchGroup 사용
            group.notify(queue: .main) {
                self.cinemaView.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
            }
      
            let nav = UINavigationController(rootViewController: vc)
            present(nav, animated: true)
        }
      }

      ⇒ 모든 데이터를 받았을 때 바로 UI 업데이트를 해주기 위해 DispatchGroup를 이용해줌

    • sheet뷰에선 매개변수를 통해 새로 저장할 이미지와 닉네임을 VC로 넘겨줌

      import UIKit
      
      final class ProfileSettingSheetViewController: BaseViewController {
      
        private var profileSettingSheetView = ProfileSettingSheetView()
        var imageContents: UIImage?
        var nicknameContents: String?
        // 이 두개의 프로퍼티로 값 넘겨줌!
        var reSaveNickname: ((String) -> Void)?
        var reSaveImage: ((UIImage) -> Void)?
      
        //...
      
      @objc
      private func doneButtonTapped() {
        // 완료버튼을 눌렀을 때 값을 매개변수로 넘겨주면서 VC에서 정의한 클로저를 실행
        reSaveImage?(profileSettingSheetView.profileImageView.image ?? UIImage())
        reSaveNickname?(profileSettingSheetView.nicknameTextField.text ?? "")
        dismiss(animated: true)
      }
  • 학습한 것

    • 각 상황에 맞는 역값전달 방법 사용

TableViewCell의 재사용 문제

  • 문제

    • 검색 화면에서 TableView를 사용하고 있는데, 장르 태그라는 UILabel의 데이터가 올바르지 않거나, cell 안의 버튼을 누를때마다 변하는 문제

      Main
  • 해결 시도

    • genreList.count 즉, 장르의 개수에 따라서 장르 Label을 표시하는 조건이 다른데, 이 조건문을 여러 형태로 변형 시도

      if data.genreList.count == 1 {
        firstGenreLabel.text = SearchTableViewCell.genre[data.genreList[0]]
        genreStackView.addArrangedSubview(firstGenreLabel)
      } else if data.genreList.count >= 2 {
        firstGenreLabel.text = SearchTableViewCell.genre[data.genreList[0]]
        secondGenreLabel.text = SearchTableViewCell.genre[data.genreList[1]]
        genreStackView.addArrangedSubview(firstGenreLabel)
        genreStackView.addArrangedSubview(secondGenreLabel)
      } else {
            // ...
      }
  • 문제 발견

    • 현재 로직은 조건문에 걸릴때마다 addArrangedSubview를 통해 데이터를 계속 추가해주고 있었음
  • 해결

    • 조건문마다 이전에 추가된 것들을 remove해줘야 함

      if data.genreList.count == 1 {
            // 이렇게
        firstGenreLabel.removeFromSuperview()
        secondGenreLabel.removeFromSuperview()
      
        firstGenreLabel.text = SearchTableViewCell.genre[data.genreList[0]]
        genreStackView.addArrangedSubview(firstGenreLabel)
      } else if data.genreList.count >= 2 {
            // 이렇게
        firstGenreLabel.removeFromSuperview()
        secondGenreLabel.removeFromSuperview()
      
        firstGenreLabel.text = SearchTableViewCell.genre[data.genreList[0]]
        secondGenreLabel.text = SearchTableViewCell.genre[data.genreList[1]]
        genreStackView.addArrangedSubview(firstGenreLabel)
        genreStackView.addArrangedSubview(secondGenreLabel)
      } else {
            // 이렇게
        firstGenreLabel.removeFromSuperview()
        secondGenreLabel.removeFromSuperview()
      }
  • 학습한 것

    • removeFromSuperview() 키워드 학습