iOS) Xib 로 만든 커스텀뷰에서 액션 연결
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?()
}
}
📌 화면전환 말고 다른 액션들도! 공통적인 역할을 한다면 커스텀뷰에서 정의하면 효율적이라고 생각이 드네요! 매번 재사용할 때 뷰 컨트롤러에서 액션을 설정해주지 않아도 되니까요!