빌더 패턴으로 UIKit Swift 커스텀 Alert 만들기

왜 빌더 패턴

  • 빌더 패턴은 생성 패턴으로 코드의 결합도를 줄이고 재사용성을 높인다는 장점이 있습니다.
  • 빌더 패턴은 사용하는 부분에서 파라미터를 모두 채워주지 않아도 필요한 값으로 생성이 가능합니다.
  • 사용하는 곳에서 파라미터 값을 모두 채워주지 않아도 되어 코드가 간결해집니다.
  • 한마디로 정리하면, Custom Alert을 편하게 사용(호출)하기 위해 빌더 패턴을 사용했습니다.

AlertBuilder

  • Custom Alert을 구현하기 위해 우선 AlertBuilder 클래스를 만듭니다.
  • AlertBuilder에는 Custom Alert을 띄울 baseViewController와 포함하고 있는 alertViewController가 필요합니다.
  • alertTitle, message, addActionConfirm과 같은 Alert의 요소들도 필요합니다.
class AlertBuilder {
  private let baseViewController: UIViewController
  private let alertViewController = CustomAlertViewController()

  private var alertTitle: String?
  private var message: String?
  private var addActionConfirm: AddAction?
}
  • 그 다음에는 AlertBuilder의 init과 함수들을 구현했습니다.
  • init에는 baseViewController 정도를 넣어줍니다.
  • setTitle, setMessage, addActionConfirm은 각 프로퍼티에 값을 넣어주는 역할을 합니다.
  • 특이점으로는 return으로 self를 리턴해서 Builder을 사용할때 이어서 호출 할 수 있게합니다.
class AlertBuilder {
  ...

  init(viewController: UIViewController) {
    baseViewController = viewController
  }

  func setTitle(_ text: String) -> AlertBuilder {
    alertTitle = text
    return self
  }

  func setMessage(_ text: String) -> AlertBuilder {
    message = text
    return self
  }

  func addActionConfirm(_ text: String, action: (() -> Void)? = nil) -> AlertBuilder {
    addActionConfirm = AddAction(text: text, action: action)
    return self
  }
}
  • AddAction은 구조체로 text와 action 프로퍼티를 갖고 있습니다.
struct AddAction {
  var text: String?
  var action: (() -> Void)?
}
  • AlertBuilder의 마지막으로는 show 함수를 구현합니다.
  • Alert 형태를 갖추도록 modalPresentationStyle과 modalTransitionStyle을 지정합니다.
  • alertViewController에 프로퍼티 값을 전달하고, baseViewController 위에 alertViewController를 띄웁니다.
class AlertBuilder {
  ...

  @discardableResult
  func show() -> Self {
    alertViewController.modalPresentationStyle = .overFullScreen
    alertViewController.modalTransitionStyle = .crossDissolve

    alertViewController.alertTitle = alertTitle
    alertViewController.message = message
    alertViewController.addActionConfirm = addActionConfirm

    baseViewController.present(alertViewController, animated: true)
    return self
  }
}

CustomAlertViewController

  • CustomAlertViewController를 구현할 차례입니다.
  • 기본적인 프로퍼티 alertTitle, message, addActionConfirm을 선언합니다.
class CustomAlertViewController: UIViewController {
  var alertTitle: String?
  var message: String?
  var addActionConfirm: AddAction?
}
  • 그리고 UI 요소들을 생성합니다.
class CustomAlertViewController: UIViewController {
  ...

  private lazy var alertView = {
    let view = UIView()
    view.layer.cornerRadius = 16
    view.backgroundColor = .secondarySystemBackground
    return view
  }()

  private lazy var titleLabel = {
    let label = UILabel()
    label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
    label.textAlignment = .center
    label.text = alertTitle
    return label
  }()

  private lazy var messageLabel = {
    let label = UILabel()
    label.font = UIFont.systemFont(ofSize: 13)
    label.textAlignment = .center
    label.text = message
    return label
  }()

  private lazy var horizontalDividerView = {
    let view = UIView()
    view.backgroundColor = .lightGray
    return view
  }()

  private lazy var confirmButton = {
    let button = UIButton()
    button.setTitleColor(.label, for: .normal)
    button.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .bold)
    button.setTitle(addActionConfirm?.text, for: .normal)
    button.addTarget(self, action: #selector(pressed), for: .touchUpInside)
    return button
  }()

  @objc func pressed() {
    addActionConfirm?.action?()
    dismiss(animated: true)
  }
}
  • 마지막으로, UI 요소들의 위치를 적절히 잡습니다.
  • 레이아웃을 잡기 위해서는 많이 사용하는 SnapKit 라이브러리를 사용했습니다.
class CustomAlertViewController: UIViewController {
  ...

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

  private func setViews() {
    view.backgroundColor = .systemBackground.withAlphaComponent(0.5)

    view.addSubview(alertView)
    alertView.snp.makeConstraints { make in
      make.width.equalTo(300)
      make.center.equalToSuperview()
    }

    alertView.addSubview(titleLabel)
    titleLabel.snp.makeConstraints { make in
      make.width.equalTo(260)
      make.centerX.equalToSuperview()
      make.top.equalToSuperview().offset(30)
    }

    alertView.addSubview(messageLabel)
    messageLabel.snp.makeConstraints { make in
      make.width.equalTo(260)
      make.centerX.equalToSuperview()
      make.top.equalTo(titleLabel.snp.bottom).offset(20)
    }

    alertView.addSubview(horizontalDividerView)
    horizontalDividerView.snp.makeConstraints { make in
      make.width.centerX.equalToSuperview()
      make.height.equalTo(0.5)
      make.top.equalTo(messageLabel.snp.bottom).offset(30)
    }

    alertView.addSubview(confirmButton)
    confirmButton.snp.makeConstraints { make in
      make.width.equalTo(300)
      make.centerX.equalToSuperview()
      make.height.equalTo(50)
      make.top.equalTo(horizontalDividerView.snp.bottom)
      make.bottom.equalToSuperview()
    }
  }
}

Custom Alert 사용

  • Custom Alert은 원하는 ViewController에서 다음과 같이 호출 할 수 있습니다.
AlertBuilder(viewController: self)
  .setTitle("알럿창")
  .setMessage("알럿창의 메시지입니다")
  .addActionConfirm("확인") {
    print("확인을 눌렀습니다.")
  }
  .show()

결과물

  • 이대로 실행해보면 다음과 같은 Custom Alert 창을 볼 수 있습니다.
custom-alert-builder-pattern