iOS/Xcode, UI

Protocol과 Delegate Pattern을 이용해 Data를 가져와 표시하는 화면 구성하기.

KemiKIM 2022. 9. 21. 14:45

(Macbook Air M1 8-256 Monterey 12.5.1 Xcode 13.1)

 

Why?

당근마켓이나 번개장터 같은 App에서 게시물을 작성할 때 보면,

물건의 카테고리를 선택하면 그 전 화면으로 돌아와 선택한 카테고리가 표시되죠.

 

그래서 이번 포스팅에서는 Data를 전달받아 Button에 표시하는 방법을 구현해보려고 합니다.

 

Stroyboard에서 ViewController를 만들어서 Data를 전달받는 건 알겠는데,

코드로 화면을 만들어서 Data를 어떻게 전달받아서 표시해야할까 고민하다가 이번에 직접 구현해보았습니다.

 

Stroyboard 사용을 최소화하기 위해, 기초적인 부분만 만들고 나머지는 코드로 구현해보려고 합니다.

 

 

 


 

 

 

(1) 새로운 프로젝트를 생성해서, 기본 세팅을 시작합니다.

 

 

Navigation Controller를 View Controller에 연결해 주고,

Button을 만든 뒤 실행되는지 확인해봅니다.

Stroyboard에 Navigation Controller를 생성한 이유는,

코드로 화면을 생성할 때 그 화면에만 따로 Navigation Bar를 만들 수 없기 때문입니다.

 

 

 

 

 


 

 

 

 

(2) 카테고리 선택 화면을 만들기 위해 새로운 swift file을 생성합니다.

//CategoryController.swift

import Foundation
import UIKit


class CategoryViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

 

 

 

 


 

 

 

 

 

(3) ViewController에 돌아가 다음 코드 작업을 진행합니다.

//ViewController.swift

@IBOutlet weak var button: UIButton!

@IBAction func buttonTapped(_ sender: UIButton) {

    let CategoryVC = CategoryViewController()
        
    CategoryVC.modalPresentationStyle = .overFullScreen
        
    self.navigationController?.pushViewController(CategoryVC, animated: true)
        
    }

- button에 outlet을 설정해준 이유는 추후 버튼의 이름이 바뀌어야하기 때문입니다.

 

- Data를 전달하고, 코드로 된 화면으로 전환하려면

상수에 해당 코드파일(CategoryViewController)을 담아 준뒤 전달하는 과정이 필요합니다.

 

- modalPresentationStyle을 .overFullScreen으로 설정해준 이유는

카테고리화면이 나올 때 화면이 꽉차게끔 설정해야하기 때문입니다.

 

- Data를 전달하면서, Navigation Bar를 유지하려면 present가 아닌, pushviewcontroller를 사용해야 합니다.

 

 

 

 

 


 

 

 

이제 CategoryViewController.swift로 이동하여 작업해야 합니다.

 

이 파일에 해야할 것은 작업은 4가지입니다

(4) TableView, TableViewCell 생성 후 AutoLayout 설정하기.

(5) Delegate와 Datasource 생성

(6) 간단히 Navigation Bar Custom (선택사항)

(7) Delegate Pattern 구현

 

 


 

 

(4)-1. Table 생성

TableView 생성한 후, TableView에 나타낼 임의의 Cell 3개 정도를 만듭니다.

//CategoryViewController.swift

let tableView = UITableView()
let testArr = ["1", "2", "3"]

 

 

 

(4)-2. TableView를 화면을 추가한 뒤 AutoLayout 맞추는 작업을 합니다. 


여기선 편의를 위해 함수에 담아 viewDidLoad()에 올릴 예정입니다.

//CategoryViewController.swift

func tableViewConfigure() {

    view.addSubview(tableView)
    tableView.translatesAutoresizingMaskIntoConstraints = false
    tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
    tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
    tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
 
 }

 

 

 

(4)-3 .설정한 함수를 활성화하기 위해 viewDidLoad에 추가해줍니다.

//CategoryViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()
        
    tableViewConfigure()
	
 }

 

 

 


 

 

 

 

(5)-1. testArr상수에 담은 배열을 표시해야하기 때문에,

Delegate와 Datasource 채택하는 작업이 필요합니다.

 

extension을 통해 추가해줍니다.

//CategoryViewController.swift

extension CategoryViewController: UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        //code
    }
    
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //code
    }

}

- numberOfRowsInSection은 TableView에 나타날 정보의 갯수를 반환(return)합니다. 

- cellForRowAt을 매개변수로 받는 함수는 해당 cell에 어떤 Data를 나타낼지를 반환(return)합니다. 

 

 

 

(5)-2. 작업 전, TableView 대리자를 위임해야합니다.

//CategoryViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()
    
    tableView.delegate = self
    tableView.dataSource = self
    
    tableViewConfigure()
    
    }

 

 

(5)-3. TableView에게 어떠한 요소를 출력해야하는지 알려줘야하기에,

속성 method를 작성한 후 viewDidLoad()에 추가합니다.

//CategoryViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()
    
    tableView.delegate = self
    tableView.dataSource = self
    
    tableViewConfigure()
    attribute()
    
    }
    
func attribute() {
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    }

- Identifier의 이름은 추후 활용되야하기 때문에 굉장히 중요합니다.

 

 

 

(5)-4. extension 부분을 작성합니다.

//CategoryViewController.swift

extension CategoryViewController: UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return testArr.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
        
        cell.textLabel?.text = testArr[indexPath.row]

        return cell
    }
    
}

- 저는 미리 만들어놓은 testArr 배열의 갯수를 반환(return)하였습니다.

- dequeueReusableCell를 통해 위 속성 함수에 선언되어 있던 Identifier의 이름을 연결하고, 해당 셀에 대해서 각 행별로 배열에 있던 값이 들어가도록 작성합니다.

 

 

 

 

 


 

 

 

(선택사항)

(6) 이제 네비게이션 바를 간단히 커스텀해보는 작업을 해봅시다.

//CategoryViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()
    
    tableView.delegate = self
    tableView.dataSource = self
    
    naviCustom()

    tableViewConfigure()
    attribute()
    
    }
    
    func naviCustom() {
        
    	self.navigationController?.navigationBar.topItem?.title = ""
    	self.navigationController?.navigationBar.tintColor = .black
    	self.navigationItem.title = "카테고리 선택"
        
        }

기존에 있던 back버튼의 글자를 없애고, 글자의 색상을 검정으로 바꾼 뒤, main title을 "카테고리 선택"으로 변경하였습니다.

 

 

 


 

 

이제 시뮬레이터 실행으로 중간체크를 해봅니다.

 

 

아직 Protocol과 Delegate Pattern을 사용하지않았기 때문에 1,2,3을 눌러도 눌린 자국만 있을 뿐

아무 반응이 없을 것입니다.

 

이제 Protocol과 Delegate Pattern을 사용하여 Data를 전달해봅시다.

 

카테고리 컨트롤러로 이동하여 프로토콜을 생성해봅시다. 

 

 

 

 

 


 

 

 

 

(7)-1 프로토콜을 만든 뒤 내부 함수를 구현합니다.

//CategoryViewController.swift

protocol VCProtocol: AnyObject {
    func VCdelegate(data: String)
}

문자를 전달하는 것이기 때문에 String을 사용합니다.

 

 

 

 

(7)-2 델리게이트 변수를 하나 생성해줍니다.

//CategoryViewController.swift

var delegate: VCProtocol?

 

 

 

 

(7)-3 extension에 didSelectRowAt함수를 사용하여 Data전달하는 코드를 작성합니다.

//CategoryViewController.swift

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
    delegate?.VCdelegate(data: testArr[indexPath.row])
        
    self.navigationController?.popViewController(animated: true)
    
    }

- Protocol에 선언한 data를 testArr[indexPath.row]로 설정하여 delegate로 전달하는 작업을 마친 뒤,

Navigation Bar이기 때문에 popViewController로 화면을 전환해줍니다.

 

 

 

(7)-4 거의 완성되었습니다. 이제 ViewController로 이동해서 해당 Protocol을 채택해줍시다.

//ViewController.swift

class ViewController: UIViewController, VCProtocol {
    func VCdelegate(data: String) {
        button.setTitle(data, for: .normal)
    }

- 맨 처음 button Outlet을 선언한 이유가 여기서 사용해야하기 때문이였습니다.

 

 

 

(7)-5 마지막으로 버튼탭에 delegate를 선언해줍니다.

//ViewController.swift

@IBAction func buttonTapped(_ sender: UIButton) {

    let CategoryVC = CategoryViewController()
    
    CategoryVC.delegate = self
        
    CategoryVC.modalPresentationStyle = .overFullScreen
        
    self.navigationController?.pushViewController(CategoryVC, animated: true)
        
    }

 

 

 

 

 


 

 

 

 

 

시뮬레이터를 완성된 모습을 확인해봅시다.

1을 누르면 전 화면으로 다시 돌아와 1로 바뀌는 모습을 잘 볼 수 있습니다.

그 외에 숫자도 잘 작동하네요.

 

세부적으로 카테고리 선택 화면에서 체크표시까지 해보고 싶었는데, 그건 다음에 해보겠습니다. :)

 

 

 

 

 

 


 

 

 

 

이번 내용을 구현하면서 스스로 다시 공부해야하는 부분이 있었습니다.

1. Protocol과 Delegate에 대해 다시 한 번 개념 정리하기.

2. 카테고리 선택 화면에서 체크표시를 하면서 전 화면으로 넘어올 수 있도록 코드 수정해보기.

 

 

오류가 있거나, 피드백이 있다면 언제든지 댓글로 알려주세요!

감사합니다 :)

 

 

 

참고자료:

https://roniruny.tistory.com/139

https://velog.io/@haero_kim/iOS-정면돌파-02.-코드로-Navigation-Controller-조작하기

https://vincentgeranium.github.io/ios,/swift/2019/04/21/Swift-And-iOS-Study.html

https://feelsodev.tistory.com/6