iOS) Xib 로 만든 커스텀뷰에서 액션 연결

4 minute read

Xib 로 커스텀뷰를 만들어 봤는데 액션도 연결해봅시다! 다음의 글을 읽고 보시는걸 추천해요!

iOS) 커스텀 뷰 Xib 연결하기

다양한 방법이 있고 각각 장단점이 있다고 생각해요!

1. delegate pattern

2. NSNotification

3. 뷰 컨트롤러에서 커스텀 뷰 요소 호출

📌 CustomNavigationBar.swift

커스텀뷰는 다음과 같이 작성했어요!(Xib 연결을 View 의 Custom Class 설정으로 해준 상태에요!)

import Foundation
import UIKit

class CustomNavigationBar: UIView {
    
    // MARK: - UBIoutlet Properties
    
    @IBOutlet weak var profileButton: UIButton!
    
    // MARK: - Initializer
    
    override init(frame: CGRect) {
        super.init(frame: frame)
//        customInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
//        customInit()
    }
    
    // ✅ File's Owner 의 Custom Class 를 설정했을 때 사용하는 코드
//    func customInit() {
//        if let view = Bundle.main.loadNibNamed(Const.Xib.NibName.customNavigationBar, owner: self, options: nil)?.first as? UIView {
//            view.frame = self.bounds
//            addSubview(view)
//        }
//    }
}

1. delegate pattern 사용

delegate pattern 에 대한 이해가 필요하지만 관리와 사용하기에 좋다고 생각해요!

// HomeViewController.swift

class HomeViewController: UIViewController {

    // ...

    private func initNavigationBar() {

        // ...

        navigationBar.delegate = self
    }
}

// MARK: - CustomViewDelegate

// ✅ 뷰 컨트롤러가 CustomViewDelegate 프로토콜을 채택해서 구현
extension HomeViewController: CustomViewDelegate {
    func presentToLoginViewController() {
        guard let loginVC = UIStoryboard(name: Const.Storyboard.Name.login, bundle: nil).instantiateViewController(withIdentifier: Const.ViewController.Identifier.login) as? LoginViewController else { return }
        loginVC.modalPresentationStyle = .fullScreen
        loginVC.modalTransitionStyle = .crossDissolve
        present(loginVC, animated: true, completion: nil)
    }
}
//  CustomNavigationBar.swift

// ✅ CustomViewDelegate 프로토콜 생성
protocol CustomViewDelegate {
    func presentToLoginViewController()
}

class CustomNavigationBar: UIView {

    // MARK: - Protocol
    
    public var delegate: CustomViewDelegate?

    // ...

    // ✅ profile button 을 터치하면 LoginViewController 로 화면전환.
    @IBAction func touchProfileButton(_ sender: Any) {
        self.delegate?.presentToLoginViewController()
    }
}

2. NSNotification 사용

손쉽게 사용할 수 있지만 흩어져있는 노티피케이션을 관리하기가 까다롭다고 생각해요!

// HomeViewController.swift

class HomeViewController: UIViewController {

    // ...

    private func setNotification() {
        NotificationCenter.default.addObserver(self, selector: #selector(touchProfileButton), name: NSNotification.Name(rawValue: "TouchProfileButton"), object: nil)
    }

    // MARK: - @Objc Methods

    @objc
    private func touchProfileButton() {
        guard let loginVC = UIStoryboard(name: Const.Storyboard.Name.login, bundle: nil).instantiateViewController(withIdentifier: Const.ViewController.Identifier.login) as? LoginViewController else { return }
        loginVC.modalPresentationStyle = .fullScreen
        present(loginVC, animated: true, completion: nil)
    }
}
//  CustomNavigationBar.swift

class CustomNavigationBar: UIView {
    
    // ...
    
    // ✅ profile button 을 터치하면 LoginViewController 로 화면전환.
    private func presentToLoginViewController() {
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "TouchProfileButton"), object: nil)
    }

    @IBAction func touchProfileButton(_ sender: Any) {
        presentToLoginViewController()
    }
}

3. 뷰컨트롤러에서 커스텀뷰 요소 호출

커스텀뷰의 요소를 호출하는 점이 경우에 따라서는 장점이자 단점이라고 생각이 들기도 하네요!

Xib 로 커스텀뷰를 만드는 방법은 두 가지였어요.

  • View 의 Custom Class 설정
  • File’s Owner 의 Custom Class 설정

🎃 View 의 Custom Class 설정

뷰 컨트롤러에서 커스텀 뷰의 nib 객체를 가져와서 보여주기 때문에 이 부분에서 addTarget(_:action:for:) 를 통해서 액션 등록이 가능해요!

class HomeViewController: UIViewController {
    
    // MARK: - @IBOutlet Properties

    @IBOutlet weak var customNavigationBar: UIView!

    //...

    private func initNavigationBar() {
        self.navigationController?.navigationBar.isHidden = true
        // ✅ 커스텀네비게이션바 클래스에서 View 의 Custom Class 를 설정했을 떄 사용하는 코드
        guard let loadedNib = Bundle.main.loadNibNamed(String(describing: CustomNavigationBar.self), owner: self, options: nil) else { return }
        guard let navigationBar = loadedNib.first as? CustomNavigationBar else { return }
        
        navigationBar.frame = CGRect(x: 0, y: 0, width: customNavigationBar.frame.width, height: customNavigationBar.frame.height)
        customNavigationBar.addSubview(navigationBar)

        // ✅ 커스텀 뷰에서 @IBOutlet 으로 선언한 profileButton 을 가져와서 액션 등록.
        navigationBar.profileButton.addTarget(self, action: #selector(touchProfileButton), for: .touchUpInside)
    }

    // MARK: - @Objc Methods

    @objc
    private func touchProfileButton() {
        guard let loginVC = UIStoryboard(name: Const.Storyboard.Name.login, bundle: nil).instantiateViewController(withIdentifier: Const.ViewController.Identifier.login) as? LoginViewController else { return }
        loginVC.modalPresentationStyle = .fullScreen
        present(loginVC, animated: true, completion: nil)
    }
}

🎃 File’s Owner 의 Custom Class 설정

스토리보드의 UIView class 를 CustomNavigationBar 로 설정하기 때문에 @IBOutlet 으로 가져와서 커스텀뷰의 요소에 addTarget(_:action:for:) 로 액션을 줄 수 있어요!

class HomeViewController: UIViewController {
    
    // MARK: - @IBOutlet Properties

    // ✅ UIView 의 class 를 CustomNavigationBar 로 설정.
    @IBOutlet weak var customNavigationBar: CustomNavigationBar!
    
    // ...

    private func initNavigationBar() {
        // ✅ CustomNavigationBar 클래스에서 File's Owner 의 Custom Class 를 설정했을 때 액션 등록 코드
        customNavigationBar.profileButton.addTarget(self, action: #selector(touchProfileButton), for: .touchUpInside)
    }

    // MARK: - @Objc Methods

    @objc
    private func touchProfileButton() {
        guard let loginVC = UIStoryboard(name: Const.Storyboard.Name.login, bundle: nil).instantiateViewController(withIdentifier: Const.ViewController.Identifier.login) as? LoginViewController else { return }
        loginVC.modalPresentationStyle = .fullScreen
        present(loginVC, animated: true, completion: nil)
    }
}

⁉️ 커스텀뷰는 재사용성을 위함이잖아요?

어차피 어떤 뷰에서도 프로필 버튼을 누르면 로그인화면이 뜨도록 해야할텐데 이렇게 뷰 컨트롤러에서 매번 화면전환 메서드를 불러주는건..; 어떻게 개선할 수 있을까요??

🎃 커스텀 뷰에서 화면전환 액션 등록

1️⃣ 변수로 설정

커스텀 뷰에 parentViewController 라는 UIViewController? 변수를 만들고 뷰 컨트롤러에서 설정해주도록 해보았어요!

// HomeViewController.swift

class HomeViewController: UIViewController {

    // ...

    private func initNavigationBar() {

        // ...

        // ✅ 커스텀뷰 클래스의 parentViewController 속성 접근
        navigationBar.presentingViewController = self
        // customNavigationBar.parentViewController = self
    }
}
//  CustomNavigationBar.swift

class CustomNavigationBar: UIView {
    
    // MARK: - Properties
    
    var presentingViewController: UIViewController?
    
    // ...

    // ✅ profileButton 에 addTarget() 혹은 IBAction 으로 액션을 설정해주면 된다.
    @IBAction func touchProfileButton(_ sender: Any) {
        guard let loginVC = UIStoryboard(name: Const.Storyboard.Name.login, bundle: nil).instantiateViewController(withIdentifier: Const.ViewController.Identifier.login) as? LoginViewController else { return }
        loginVC.modalPresentationStyle = .fullScreen
        loginVC.modalTransitionStyle = .crossDissolve
        
        presentingViewController?.present(loginVC, animated: true, completion: nil)
    }
}

이렇게 화면전환을 하면 뷰컨트롤러에서 현재 뷰컨트롤러만 넘겨주면 커스텀뷰에서 공통적인 작업을 등록할 수 있는 장점이 있는거 같아요!

2️⃣ 클로저 활용

// HomeViewController.swift

class HomeViewController: UIViewController {

    // ...

    private func initNavigationBar() {

        // ...

        // ✅ 화면전환 로직을 closure 로 설정
        navigationBar.presentToLoginViewClosure = {
            guard let loginVC = UIStoryboard(name: Const.Storyboard.Name.login, bundle: nil).instantiateViewController(withIdentifier: Const.ViewController.Identifier.login) as? LoginViewController else { return }
            loginVC.modalPresentationStyle = .fullScreen
            loginVC.modalTransitionStyle = .crossDissolve
            self.present(loginVC, animated: true, completion: nil)
        }
    }
}
//  CustomNavigationBar.swift

class CustomNavigationBar: UIView {

    // MARK: - Properties

    var presentToLoginViewClosure: (() -> ())?

    // ...

    // ✅ profile button 을 터치하면 LoginViewController 로 화면전환.
    @IBAction func touchProfileButton(_ sender: Any) {
        presentToLoginViewClosure?()
    }
}

📌 화면전환 말고 다른 액션들도! 공통적인 역할을 한다면 커스텀뷰에서 정의하면 효율적이라고 생각이 드네요! 매번 재사용할 때 뷰 컨트롤러에서 액션을 설정해주지 않아도 되니까요!

Categories:

Updated: