iOS 앱 개발 부트캠프/TIL

TIL 14일차 - 네비게이션 컨트롤러와 테이블뷰

iosstudyletsgo 2024. 10. 2. 18:06

오늘은 입력받은 데이터를 테이블 뷰로 띄우는 것을 연습하였다.

import UIKit

protocol SecondViewControllerDelegate: AnyObject {
    func passDataBack(_ data: String)
}

class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var receivedDataLabel: UILabel!
    @IBOutlet weak var dataTextField: UITextField!
    @IBOutlet weak var tableView: UITableView!
    
    weak var delegate: SecondViewControllerDelegate?
    
    var receivedData: String?
    var dataList: [String] = [] //데이터를 저장할 배열
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        //첫번째 화면에서 전달받은 데이터를 라벨에 표시
        if let data = receivedData {
            receivedDataLabel.text = data
        } else {
            receivedDataLabel.text = "No data received"
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataList.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = dataList[indexPath.row]
        return cell
    }
    
    @IBAction func addDataButtonTapped(_ sender: UIButton) {
        if let newData = dataTextField.text {
            dataList.append(newData) //배열에 데이터 추가
            tableView.reloadData()  //테이블뷰 업데이트
            dataTextField.text = "" //텍스트 필드 초기화
        }
    }

}

지난번에 했던 코드에서 테이블 뷰와 테이블 뷰에 띄울 데이터를 저장할 배열 dataList와 테이블뷰의 메서드를 추가하였다. 그리고 값을 입력하면 첫번째 화면으로 넘어가던 지난번과 달리 이번엔 값을 테이블뷰로 보여줄 것이기 때문에 제출 버튼을 누르면 테이블 뷰에 값이 업데이트 되도록 IBAction을 바꾸었다.

첫번째 테이블뷰 메서드는 numberOfRowsInSection이 사용된 메서드인데 테이블뷰의 각 섹션에서 몇 개의 행(row)을 추가할 지 결정하는 메서드이고 두번째 메서드는 cellForRowAt이 사용된 메서드이고 각 행에 들어갈 셀의 내용을 설정하는데 쓰인다.

코드를 완전히 뜯어 고친게 아니라서 이 상태에서 실행을 하면 첫번째 화면에서 입력한 값이 두번째 화면의 Label에 들어가고, 두번째 화면에서 입력한 값만 테이블뷰에 한 행씩 들어가게 될 것이다.

하지만 에러가 난다..

역시나 한 번에 되지 않았다. 에러를 보니 식별자를 Cell로 설정한 셀을 사용할 수 없다는 에러가 나온 것 같다. 테이블 뷰만 추가하고 안에 셀을 추가해 식별자를 Cell이라고 입력하는 과정을 거치지 않고 코드만 작성했으니 당연한 수순이다.

다음과 같이 테이블뷰를 선택해 인스펙터에서 Prototype Cell을 추가해준 뒤 prototype Cell을 선택해 인스펙터에서 식별자를 설정해 주었다. 그 후 실행하니 화면이 예상한대로 출력 되었다. 

첫번째 화면에서 입력한 값이 두번째 화면의 Label에 들어갔고, 두번째 화면에서 추가한 값들은 테이블 뷰에 추가 되었다. 이제 첫번째 값도 Label이 아닌 테이블뷰에 들어가도록 수정해서 깔끔하게 만들 것이다.

라벨에 표시하던 것을 dataList에 바로 추가되도록 수정하여 첫번째 화면에서 넘어온 값이 바로 테이블뷰에 표시되도록 하였고, 값이 아무것도 입력이 없을때 각각 No data received와 No data entered가 표시되도록 하였다.

여기까지 무난히 잘 된 것 같은데, Back을 눌러 처음 화면으로 간 뒤 다시 값을 이어서 입력하려니 dataList가 초기화 되어서 테이블 뷰에 입력 되어 있던 것들이 전부 날아가버렸다.

따라서 테이블뷰의 데이터가 날아가지 않도록 dataList를 첫번째 뷰컨트롤러에서 선언하려고 한다.

import UIKit

class ViewController: UIViewController, SecondViewControllerDelegate {

    
    @IBOutlet weak var dataTextField: UITextField!
    
    var dataList: [String] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()

    }
    
    @IBAction func goToSecondScreen(_ sender: UIButton) {
        //segue를 통해 두번째 화면으로 이동
        performSegue(withIdentifier: "goToSecondVC", sender: self)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "goToSecondVC" {
            let destinationVC = segue.destination as! SecondViewController
            //두 번째 화면에 데이터를 전달
            destinationVC.receivedData = dataTextField.text
            destinationVC.secondDataList = dataList
            //Delegate 설정
            destinationVC.delegate = self
            dataTextField.text = ""
        }
    }

    func passDataBack(_ dataList: [String]) {
        self.dataList = dataList
    }

첫번째 뷰컨트롤러를 다음과 같이 수정하였다. 데이터가 잘 넘어가는지 확인하기 위한 Label은 더이상 필요 없어서 지우고, prepare부분에 데이터를 입력 받아 저장한 dataList를 두번째 뷰컨트롤러에 넘기는 destinationVC.secondDataList = dataList 부분을 추가하였다.

그리고 두번째 화면에서 첫번째 화면으로 값을 다시 넘길때, 넘겨받은 데이터를 저장하기 위해 passDataBack 메서드에서 매개변수인 dataList를 배열로 바꾸었다.

import UIKit

protocol SecondViewControllerDelegate: AnyObject {
    func passDataBack(_ dataList: [String])
}

class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var dataTextField: UITextField!
    @IBOutlet weak var tableView: UITableView!
    
    weak var delegate: SecondViewControllerDelegate?
    
    var receivedData: String?
    var secondDataList: [String] = [] //외부에서 전달받는 데이터리스트
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        //첫번째 화면에서 전달받은 데이터를 라벨에 표시
        if let data = receivedData, !data.isEmpty {
            //receivedDataLabel.text = data
            secondDataList.append(data)
        } else {
            //receivedDataLabel.text = "No data received"
            secondDataList.append("No data received")
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return secondDataList.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = secondDataList[indexPath.row]
        return cell
    }

    
    @IBAction func addDataButtonTapped(_ sender: UIButton) {
        if let newData = dataTextField.text, !newData.isEmpty {
            secondDataList.append(newData) //배열에 데이터 추가
            tableView.reloadData()  //테이블뷰 업데이트
            dataTextField.text = "" //텍스트 필드 초기화
        } else {
            secondDataList.append("No data entered")
            tableView.reloadData()
            dataTextField.text = ""
        }
    }
    
    @IBAction func backButtonTapped(_ sender: UIButton) {
        delegate?.passDataBack(secondDataList) //데이터 전달
        self.navigationController?.popViewController(animated: true) //이전 화면으로 이동
    }

}

다음은 두번째 뷰컨트롤러를 수정하였다.

첫번째 뷰컨트롤러와 데이터를 주고받을 protocol의 매서드 매개변수 타입을 배열로 변경하였고, 첫번째 화면으로 넘어가기 위한 pop 설정과 데이터를 전달할 delegate 구문도 추가하였다.

이 부분에서 시간이 굉장히 오래 걸렸는데, segue로 연결하면 생기는 네비게이션 컨트롤러의 Back 버튼을 눌렀을 때도 값이 유지되는 줄 알았는데 아무리 해도 첫 화면으로 넘어가고 나면 dataList가 자꾸 초기화 되었다. 이유를 찾아봐도 delegate 설정을 통해 첫번째 뷰컨트롤러로 값을 전달하는 이야기만 나오지 내가 겪는 문제에 대해선 찾질 못했다.

결국 명확한 이유를 찾지 못하고 Back 버튼이 아닌 일반 버튼 하나를 추가해 뒤로 돌아가서 다시 입력하는 방법을 선택했다.

그렇게 완성한게 이 화면이다. 첫번째 화면에서 입력하는 것, 두번째 화면에서 입력하는 것, 아무 값도 입력하지 않을 때 No data가 뜨는 것, 그리고 뒤로가기 버튼을 눌러 첫 화면으로 돌아가 다시 입력해도 데이터가 유지되는 것이 모두 잘 작동하였다. 

어느정도 내가 원하는 기능을 구현하는 게 가능해진 것 같은데, 네비게이션 컨트롤러의 저 < Back 버튼 눌렀을 때 왜 값이 초기화 된 건지 몰라서 더 찾아봐야 할 것 같다. 그리고 테이블 뷰에서 한 행만 쓰는게 아니라 여러 행도 만들고 싶은데 찾아보니 조금 어려워 보여서 그것도 다시 찾아봐야 할 것 같다.

'iOS 앱 개발 부트캠프 > TIL' 카테고리의 다른 글

TIL 16일차 - 자료 구조  (1) 2024.10.07
TIL 15일차 - 뷰컨트롤러와 프로토콜  (0) 2024.10.04
TIL 13일차  (2) 2024.09.30
TIL 12일차  (2) 2024.09.27
TIL 11일차  (1) 2024.09.26