Balbas Code

Swiftでクロージャを使ってPopupを順番に閉じる

公開日: 2024-10-26 09:23:43
更新日: 2024-10-26 09:39:15

本日はクロージャを使ったPopupの遷移について記載していきます。
クロージャをしっかり理解していないとこちらのコードは想定通りに動かないのでまとめました。

クロージャとは・・・
遷移先の処理が完了した時(Popupを閉じた時等)遷移元で通知を受け取って処理を行う仕組みです。
今回行ったのはPopupを順番に開いていき、閉じた通知に反応させて自身を閉じるという流れです。

ViewController

TestPopupView1を開きOKボタンを押下

TestPopupView2を開きOKボタンを押下

TestPopupView3を開く


OKボタンを押下

TestPopupView3を閉じる

TestPopupView2を閉じる

TestPopupView1を閉じる

ViewControllerで完了メッセージを表示



ViewController


    /// TestPopupViewへ遷移する
func showPopupButtonTapped() {
let popup1 = TestPopupView1()
popup1.modalPresentationStyle = .overCurrentContext
// ■■■クロージャが完了した時に呼ばれる処理
popup1.onComplete = { [weak self] in
self?.showAlertMessage()
}
present(popup1, animated: false, completion: nil)
}

// アラートを表示する関数
func showAlertMessage() {
// UIAlertControllerを作成
let alert = UIAlertController(title: "クロージャ完了", message: "クロージャの処理が完了しました。", preferredStyle: .alert)
// OKボタンを作成
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
// アクションをアラートに追加
alert.addAction(okAction)
// アラートを表示する
present(alert, animated: true, completion: nil)
}

遷移先(TestPopupView1)から通知を受け取るとアラートメッセージを表示します。



TestPopupView1


import UIKit

class TestPopupView1: UIViewController {

var onComplete: (() -> Void)?

private let popupView = UIView()

override func viewDidLoad() {
super.viewDidLoad()
setupBackgroundView()
setupPopupView()
}

private func setupBackgroundView() {
let backgroundView = UIView(frame: self.view.bounds)
backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
view.addSubview(backgroundView)
}

private func setupPopupView() {
popupView.backgroundColor = UIColor.purple
popupView.layer.cornerRadius = 10
popupView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(popupView)

NSLayoutConstraint.activate([
popupView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
popupView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
popupView.widthAnchor.constraint(equalToConstant: view.bounds.width - 20),
popupView.heightAnchor.constraint(equalToConstant: 600)
])

let okButton = UIButton(type: .system)
okButton.setTitle("OK", for: .normal)
okButton.translatesAutoresizingMaskIntoConstraints = false
okButton.addTarget(self, action: #selector(okButtonTapped), for: .touchUpInside)
popupView.addSubview(okButton)

NSLayoutConstraint.activate([
okButton.centerXAnchor.constraint(equalTo: popupView.centerXAnchor),
okButton.centerYAnchor.constraint(equalTo: popupView.centerYAnchor)
])
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// ポップアップを画面下端よりも外側に初期位置を設定(画面の高さ分だけ下に移動)
popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)

// ポップアップをスライドイン(元の位置へ移動)
UIView.animate(withDuration: 0.3) {
self.popupView.transform = .identity
self.view.layoutIfNeeded()
}
}

@objc private func okButtonTapped() {
// TestPopupView2 を重ねて表示
let popup2 = TestPopupView2()
popup2.modalPresentationStyle = .overCurrentContext

// TestPopupView2が閉じられた際に、自分も閉じるようにする ■■■completion{}に実行後に行う処理を記載する
popup2.onComplete = { [weak self] in
self?.animateClose {
self?.dismiss(animated: false, completion: {
self?.onComplete?() // 遷移元(ViewController)に完了通知
})
}
}
present(popup2, animated: false, completion: nil)
}

// 閉じるアニメーション(画面外にスライド)
private func animateClose(completion: @escaping () -> Void) {
UIView.animate(withDuration: 0.3, animations: {
// ポップアップを画面外(下端よりも下)に移動
self.popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)
}, completion: { _ in
completion()
})
}
}

こちらはPopupのレイアウトやアニメーションがあり長く書きましたが、重要なのはこちらです。



    override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// ポップアップを画面下端よりも外側に初期位置を設定(画面の高さ分だけ下に移動)
popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)

// ポップアップをスライドイン(元の位置へ移動)
UIView.animate(withDuration: 0.3) {
self.popupView.transform = .identity
self.view.layoutIfNeeded()
}
}

@objc private func okButtonTapped() {
// TestPopupView2 を重ねて表示
let popup2 = TestPopupView2()
popup2.modalPresentationStyle = .overCurrentContext

// TestPopupView2が閉じられた際に、自分も閉じるようにする ■■■completion{}に実行後に行う処理を記載する
popup2.onComplete = { [weak self] in
self?.animateClose {
self?.dismiss(animated: false, completion: {
self?.onComplete?() // 遷移元(ViewController)に完了通知
})
}
}
present(popup2, animated: false, completion: nil)
}

// 閉じるアニメーション(画面外にスライド)
private func animateClose(completion: @escaping () -> Void) {
UIView.animate(withDuration: 0.3, animations: {
// ポップアップを画面外(下端よりも下)に移動
self.popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)
}, completion: { _ in
completion()
})
}

遷移先(TestPopupView2)から通知を受け取ると自身を閉じて、遷移元(ViewController)に通知を送ります。


TestPopupView2


    override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// ポップアップを画面下端よりも外側に初期位置を設定(画面の高さ分だけ下に移動)
popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)

// ポップアップをスライドイン(元の位置へ移動)
UIView.animate(withDuration: 0.3) {
self.popupView.transform = .identity
self.view.layoutIfNeeded()
}
}

@objc private func okButtonTapped() {
// TestPopupView3 を重ねて表示
let popup3 = TestPopupView3()
popup3.modalPresentationStyle = .overCurrentContext

// TestPopupView3が閉じられた際に、自分も閉じるようにする ■■■completion{}に実行後に行う処理を記載する
popup3.onComplete = { [weak self] in
self?.animateClose {
self?.dismiss(animated: false, completion: {
self?.onComplete?() // 遷移元(TestPopupView2)に完了通知
})
}
}
present(popup3, animated: false, completion: nil)
}

// 閉じるアニメーション(画面外にスライド)
private func animateClose(completion: @escaping () -> Void) {
UIView.animate(withDuration: 0.3, animations: {
// ポップアップを画面外(下端よりも下)に移動
self.popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)
}, completion: { _ in
completion()
})
}

こちらも全体としては省略しますが、クロージャ通知とアニメーションの重要な部分だけ記載
遷移先(TestPopupView3)から通知を受け取ると自身を閉じて、遷移元(TestPopupView1)に通知を送ります。



TestPopupView3


    override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// ポップアップを画面下端よりも外側に初期位置を設定(画面の高さ分だけ下に移動)
popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)

// ポップアップをスライドイン(元の位置へ移動)
UIView.animate(withDuration: 0.3) {
self.popupView.transform = .identity
self.view.layoutIfNeeded()
}
}

@objc private func okButtonTapped() {
// 自分を閉じて、上に通知 ■■■completion{}に実行後に行う処理を記載する
animateClose {
self.dismiss(animated: false, completion: {
self.onComplete?() // 遷移元(TestPopupView2)に完了通知
})
}
}

// 閉じるアニメーション(画面外にスライド)
private func animateClose(completion: @escaping () -> Void) {
UIView.animate(withDuration: 0.3, animations: {
// ポップアップを画面外(下端よりも下)に移動
self.popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)
}, completion: { _ in
completion()
})
}

こちらも同様。OKボタンを押下すると自身を閉じて、遷移元(TestPopupView2)に通知します。



実行結果
swift_closureswift_closureswift_closureswift_closure

OKボタンを押下→順番に閉じていきViewControllerでメッセージを表示
swift_closure


今回苦労したのが、Popupがまとめて閉じられてしまうという事が起きておりました。
原因を調査した結果、閉じるアニメーションが実装されていないという(笑)

それの調査に結構時間がかかりましたが、苦労した分勉強になりました。
クロージャによる通知の受け取りはすごい便利なので使いこなせるようにしておかないとな。