티스토리 뷰

다음 앱 만들기 주제가 전광판 앱 만들기인데요. 이걸 하려면 Delegate Pattern을 이해해야 하더라구요.

그래서 간단하게 만들어 본 Delegate Pattern 예제에욧.

 

두 번째 화면의 TextField에서 입력한 내용이 첫 번째 화면의 Label에 출력되쥬?

Delegate Pattern을 이용하여 화면 간 전달을 해볼게요.

 

오늘도 기본적인 UI를 잡는 코드는 숨겨둘게요.

더보기

첫 번째 화면은 네비게이션컨트롤러를 Embed in 했습니다!

import UIKit

class FirstViewController: UIViewController {

    let myLabel = UILabel()
    let myButton = UIButton()

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

    // MARK: - Button Event
    @objc private func myBtnTapped(_ sender: UIButton) {
        // "입력화면 가기" 버튼 누르면 발생될 터치 이벤트
        let nextVC = SecondViewController()
        self.navigationController?.pushViewController(nextVC, animated: true)
    }
}


//MARK: -UI
extension FirstViewController {
    final private func configureUI() {
        setAttributes()
        addTarget()
        setConstraints()
    }
    
    final private func setAttributes() {
        myLabel.text = "출력될 메세지"

        myButton.setTitle("입력화면으로 가기", for: .normal)
        myButton.backgroundColor = .black
        myButton.setTitleColor(.white, for: .normal)
        myButton.layer.borderWidth = 1
    }
    
    final private func addTarget() {
        myButton.addTarget(self, action: #selector(myBtnTapped(_:)), for: .touchUpInside)
    }
    
    final private func setConstraints() {
        [myLabel, myButton].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }

        NSLayoutConstraint.activate([
            myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
            myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0),

            myButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
            myButton.bottomAnchor.constraint(equalTo: myLabel.topAnchor, constant: -30)
        ])
    }
}
import UIKit

class SecondViewController: UIViewController {
    
    weak var delegate: SendTextFieldDelegate?
    
    let inputTextField = UITextField()
    let inputButton = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        configureUI()
    }
    
    // MARK: - Button Event
    @objc private func inputBtnTapped(_ sender: UIButton) {
        // "메세지 입력" 버튼 누르면 발생할 터치 이벤트
        self.navigationController?.popViewController(animated: true)
    }
}

//MARK: -UI
extension SecondViewController {
    final private func configureUI() {
        setAttributes()
        addTarget()
        setConstraints()
    }
    
    final private func setAttributes() {
        inputTextField.layer.borderColor = UIColor.lightGray.cgColor
        inputTextField.layer.borderWidth = 0.5
        
        inputButton.setTitle("메세지 입력", for: .normal)
        inputButton.backgroundColor = .black
        inputButton.setTitleColor(.white, for: .normal)
    }
    
    final private func addTarget() {
        inputButton.addTarget(self, action: #selector(inputBtnTapped(_:)), for: .touchUpInside)
    }
    
    final private func setConstraints() {
        [inputTextField, inputButton].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        NSLayoutConstraint.activate([
            inputTextField.heightAnchor.constraint(equalToConstant: 50),
            inputTextField.widthAnchor.constraint(equalToConstant: 350),
            inputTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
            inputTextField.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0),
            
            inputButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
            inputButton.bottomAnchor.constraint(equalTo: inputTextField.topAnchor, constant: -30)
        ])
    }
}

Delegate Pattern을 사용할 때는 protocol을 하나 만들어주어야 해요.

해당 작업은 Delegate를 생성하는 뷰컨(여기서는 SecondViewController.swift)에서 해주도록 할게요.

※클래스의 선언 위에서 해줍니다.

protocol SendTextFieldDelegate: AnyObject {
    func inputTextField(input text: String)
}

class secondeViewController {
	// 생략
}

프로토콜을 만들 때는 아래 3가지를 지켜주면 됩니다.

 

1. 파라미터 만들기

우리는 textField에서 입력한 text 값을 가져와서 뭘 할거잖아요. 그러니까 String 형태의 파라미터를 받도록 설정해줄게요.

 

 

2. body는 생략하기

protocol의 요구 사항으로 메서드를 사용할 때는 body 부분은 채우지 않아요.

프로토콜을 채택하는 곳에서 구체적인 body를 채우도록 합니다.

 

 

3. AnyObject 채택

AnyObject라는 타입을 채택하는데요. 해당 프로토콜을 클래스에서 사용으로 한정할 때 해당 키워드를 쓴대요. 예전에는 class라는 키워드를 썼던 거 같은데, 요새는 AnyObject를 사용해요. (공부가 더 필요하겠네여,,,ㅎㅎ)


 

자 이제 프로토콜을 만들었으니까, delegate가 필요해요.

delegate는 위임자라고 하는데, 제가 느끼기에는 약간 행동대장이랄까여?

protocol 조직이 시키는 걸 하는 놈이라고 생각하면 될 것 같아요.

 

 

이 delegate라는 놈은 반드시 조직(protocol)의 수하에 있는 놈이여야 돼요. 안 그러면 저를 배신할 수도 있잖아여? 피를 볼 순 없잖아여?위에서 선언한 프로토콜 타입으로 명시해줍니다. 사실 이렇게 해야만 해당 프로토콜에 접근할 수 있어요.

var delegate: SendTextFieldDelegate?

 

그리고 "메세지 입력"이라는 버튼을 눌렀을 때, SendTextFieldDelegate의 inputTextField 메서드를 호출하여 텍스트필드에 입력된 값을 보냅니다.

    @objc private func inputBtnTapped(_ sender: UIButton) {
        let text = inputTextField.text ?? ""
        self.delegate?.inputTextField(input: text)
        self.navigationController?.popViewController(animated: true)
    }

더불어 해당 뷰컨트롤러(=SecondViewController)도 스택에서 사라지게 합니다.

 

 


 

 

 

그 후, 이제 protocol 조직의 요구사항을 어디다가 보내면 될까여?

FirstViewController에 하면 되겠져!! 그곳에 있는 레이블에 값을 나타낼 거니까요!

그러면 extension을 통해 FirstViewController에서 protocol을 채택해볼게욧!

extension FirstViewController: SendTextFieldDelegate {

}

 

그러면 아래와 같은 에러 메시지가 발생해여. 프로토콜이 꼭 하라고 한 게 있는데, 그걸 안 했다는 거죠!

Fix 버튼을 누르면?

아까 선언한 프로토콜의 요구사항을 구현하도록 나와요.

여기서는 우리가 곧 2번째 뷰컨(SecondViewController.swift)에서 받아올 값 (inputTextField에 입력된 값)을 받아서 myLabel에 나타내도록 해줍니다.

 

extension FirstViewController: SendTextFieldDelegate {
    func inputTextField(input text: String) {
        myLabel.text = text
    }
}

 

이제 아까 SecondViewController에서 SendTextFieldDelegate? 타입으로 선언해주었던 delegate를 FirstViewController가 하겠다고 선언을 해주면 되는데여, 그건 "입력화면으로 가기" 버튼을 누르는 시점에 선언되도록 하면 됩니다.

 

다음화면(= SecondViewController)이 push됨과 동시에 SecondViewController의 delegate를 FirstViewController로 하겠다고 선언하는 겁니다.

    @objc private func myBtnTapped(_ sender: UIButton) {
        let nextVC = SecondViewController()
        nextVC.delegate = self
        self.navigationController?.pushViewController(nextVC, animated: true)
    }

 

 


 

사실 delegate pattern은 UITextFieldDelegate, UITableViewDelegate 등 정말 다양한 곳에서 사용하는 패턴인데요. 

저는 특히 nextVC.delegate=self 에 대한 이해가 쉽지 않았는데요, stackOverflow를 보니 어떤 분이 delegate = self가 메세지를 보내는 행위라고 생각하면 된다네요. 

 

델리게이트가 메세지를 보내는 행위라고 생각하면 된답니다. 이런 이런 일이 일어났는데, 그 내용을 어디로 보낼까요~? 너에게 보낼게~~

이런 의미라고 합니다.

 

내가 이런이런 요구사항이 있는데, 어디로 보낼까요~~? FirstViewController에 보내도록 하세여~~

그 시점은 다음 뷰컨(= SecondViewController)으로 넘어가는 시점일 수도 있고, viewDidLoad일 수도 있겠져.

 

 

정리를 해보자면, 정보를 보낼 SecondViewController.swift에서는 

1. 프로토콜을 선언 

2. 프로토콜 타입의 delegate를 선언

3. 프로토콜의 메서드를 실행

 

 

정보를 받는 FirstViewController.swift에서는

1. 프로토콜 채택 후 요구사항(메서드) 구현

2. (적정한 시점에) delegate = self 선언

 

우리가 UITableViewDelegate를 채택할 때는 SecondeViewController에서 했던 1~3번을 애플에서 이미 해놨기 때문에 생략하고 FirstViewController에서 했던 일만 하면 됐던거져!!

 

ㅇ케이??

 

 

완성된 코드는 아래에 숨겨둘게요!!

더보기
//
//  FirstViewController.swift
//  Test
//
//  Created by 순진이 on 2022/03/17.
//

import UIKit

class FirstViewController: UIViewController {

    let myLabel = UILabel()
    let myButton = UIButton()
    

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

    // MARK: - Button Event
    @objc private func myBtnTapped(_ sender: UIButton) {
        let nextVC = SecondViewController()
        nextVC.delegate = self
        self.navigationController?.pushViewController(nextVC, animated: true)
    }
}

extension FirstViewController: SendTextFieldDelegate {
    func inputTextField(input text: String) {
        myLabel.text = text
    }
}

//MARK: -UI
extension FirstViewController {
    final private func configureUI() {
        setAttributes()
        addTarget()
        setConstraints()
    }
    
    final private func setAttributes() {
        myLabel.text = "출력될 메세지"

        myButton.setTitle("입력화면으로 가기", for: .normal)
        myButton.backgroundColor = .black
        myButton.setTitleColor(.white, for: .normal)
        myButton.layer.borderWidth = 1
    }
    
    final private func addTarget() {
        myButton.addTarget(self, action: #selector(myBtnTapped(_:)), for: .touchUpInside)
    }
    
    final private func setConstraints() {
        [myLabel, myButton].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }

        NSLayoutConstraint.activate([
            myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
            myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0),

            myButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
            myButton.bottomAnchor.constraint(equalTo: myLabel.topAnchor, constant: -30)
        ])
    }
}
//
//  SecondViewController.swift
//  Test
//
//  Created by 순진이 on 2022/03/17.
//

import UIKit

protocol SendTextFieldDelegate: AnyObject {
    func inputTextField(input text: String)
}

class SecondViewController: UIViewController {
    
    weak var delegate: SendTextFieldDelegate?
    
    let inputTextField = UITextField()
    let inputButton = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        configureUI()
    }
    
    // MARK: - Button Event
    @objc private func inputBtnTapped(_ sender: UIButton) {
        let text = inputTextField.text ?? ""
        self.delegate?.inputTextField(input: text)
        self.navigationController?.popViewController(animated: true)
    }
}

//MARK: -UI
extension SecondViewController {
    final private func configureUI() {
        setAttributes()
        addTarget()
        setConstraints()
    }
    
    final private func setAttributes() {
        inputTextField.layer.borderColor = UIColor.lightGray.cgColor
        inputTextField.layer.borderWidth = 0.5
        
        inputButton.setTitle("메세지 입력", for: .normal)
        inputButton.backgroundColor = .black
        inputButton.setTitleColor(.white, for: .normal)
    }
    
    final private func addTarget() {
        inputButton.addTarget(self, action: #selector(inputBtnTapped(_:)), for: .touchUpInside)
    }
    
    final private func setConstraints() {
        [inputTextField, inputButton].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        NSLayoutConstraint.activate([
            inputTextField.heightAnchor.constraint(equalToConstant: 50),
            inputTextField.widthAnchor.constraint(equalToConstant: 350),
            inputTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
            inputTextField.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0),
            
            inputButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
            inputButton.bottomAnchor.constraint(equalTo: inputTextField.topAnchor, constant: -30)
        ])
    }
}

이미 구현되어 있는 Delegate들을 사용하는 것보다 제가 만들어서 쓰는 게 더 헷갈리는 거 같아요!

언젠가 저도 자유롭게 사용할 수 있겠져!! 그날까지!!!

혹시 틀린 게 있으면 언제든지 알려주십셔 슨배님덜!!!

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크