こんにちは!Popoです。
こんな方へのお勧めの記事です。
- モーダルビューやUIViewで画面間の値の受け渡しをしたい。
前回の記事では、navigationControllerを利用した画面間の値の受け渡しを解説しました。
今回はdelegateを利用した画面間の値の受け渡し方法を取り上げてみたいと思います。
delegateて理解しづらいと思いますが、使いこなせれば便利です。
是非、マスターしておきましょう。
delegateとは何か
あるクラスから、他のクラスに処理を「移譲」(Delegate)するパターンのことです。
初心者の方はなかなか理解しづらいと思いますが、たとえば、UITableViewDelegate、UITableViewDataSourceといったようにAPIでもdelegateを利用しています。(他にUITextFieldDelegateもありますね)
実は、日ごろ使用しているんですが、それがdelegateを利用していると気づいていないのです。🤔
delegateを使う理由
理論上は、「処理を他に委任することで、クラス間の依存関係を減らすことができる」です。
delegateを利用する際、下記2点で処理を記述する事になると思います。
- イベント発生を検知する処理
- イベント後にさせたい処理
イベント発生後①、イベント発生後の処理②に委譲させたいとなります。
今回の解説の場合、モーダルビューやUIViewをCLOSE後何か処理をさせたいという設計になっています。
- ①モーダルビューやUIViewをCLOSE
- ②CLOSE後何か処理をさせたい
私の使い方としては、「遷移先がモーダルビュー、UIView時に利用する」程度の考えですね。
アプリ開発の動作環境
今回、アプリ開発を行った環境になります。
項目 | バージョン |
Xcode | Version 14.3 (14E222b) |
Swift | Swift version 5.8 |
MacOS | macOS Ventura バージョン13.3.1(22E261) |
サンプルアプリのロジックを実装してみよう!
それでは、「遷移先がモーダルビュー、UIView時に利用する」サンプルアプリを開発してみましょう!
概要
ロジックのパターンとしては下記になります。
遷移元 | 遷移先 |
---|---|
遷移先のdelegateを宣言 delegateメソッドの処理記述 | protocolを定義 delegateを定義 delegateメソッド実行 |
下記の関係になります。
①イベント発生を検知する処理→遷移先
②イベント後にさせたい処理→遷移元
今回のデモアプリは、UIViewControllerの画面からUIViewの画面に遷移するアプリです。
UIView画面からUIViewController画面に戻る際に、UIViewController画面に引き継いだ値を赤字で表示します。
- UIViewController画面:OneViewController
- UIView画面:TwoView
手順1:遷移元画面
遷移元の実装したロジックの全体になります。「②イベント後にさせたい処理」側の処理になります。
全体
全体のソースコードです。
import UIKit
class OneViewController: UIViewController, ①TwoViewDelegate {
fileprivate var twoView:TwoView!
override func viewDidLoad() {
super.viewDidLoad()
let oneWorkButton = UIButton(type: .custom)
oneWorkButton.frame = CGRect(x: SCREEN_WIDTH/2 - (SCREEN_WIDTH/3.5)/2, y: SCREEN_HEIGHT/2, width: SCREEN_WIDTH/3.5, height: 40)
oneWorkButton.backgroundColor = .green
oneWorkButton.addTarget(self, action: #selector(OneViewController.oneWorkButtonClick(_:)), for: UIControl.Event.touchUpInside)
oneWorkButton.setTitle("Two画面遷移", for: .normal)
oneWorkButton.titleLabel?.font = UIFont.systemFont(ofSize: 15)
oneWorkButton.setTitleColor(.black, for: .normal)
oneWorkButton.layer.cornerRadius = 10
oneWorkButton.layer.borderColor = UIColor.gray.cgColor // 枠線の色
oneWorkButton.layer.borderWidth = 1.0 // 枠線の太さ
self.view.addSubview(oneWorkButton)
//事前に生成
self.createTwoView()
}
//ボタンタップイベント
@objc fileprivate func oneWorkButtonClick(_ sender: UIButton)
{
self.showTwoView()
}
//UIViewを生成しておく
fileprivate func createTwoView()
{
self.twoView = TwoView()
self.twoView.frame = CGRect(x: 0, y: SCREEN_HEIGHT, width: SCREEN_WIDTH, height: SCREEN_HEIGHT)
self.twoView.backgroundColor = RGBAlpa(127,255,212,1)
var baseView = UIApplication.shared.connectedScenes
.map { $0 as? UIWindowScene }
.compactMap { $0 }
.first?
.windows
.filter { $0.isKeyWindow }
.first?
.rootViewController
while ((baseView?.presentedViewController) != nil) {
baseView = baseView?.presentedViewController
}
baseView?.view.addSubview(self.twoView)
}
//Two画面close
fileprivate func closeTwoView()
{
if self.twoView == nil
{
return
}
// アニメーションして移動
UIView.animate(withDuration: 0.1,
animations: { () -> Void in
self.twoView.frame = CGRect(x: 0, y:SCREEN_HEIGHT, width: SCREEN_WIDTH, height: SCREEN_HEIGHT)
},
completion: { (finished: Bool) -> Void in
}
)
}
//Two画面表示
fileprivate func showTwoView()
{
self.twoView.backgroundColor = RGBAlpa(127,255,212,1)
② self.twoView.delegate = self
// アニメーションして移動
UIView.animate(withDuration: 0.5,
animations: { () -> Void in
self.twoView.frame = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT)
},
completion: { (finished: Bool) -> Void in
}
)
}
③
//delegate メソッド
func TwoViewClick(buttonName:String)
{
//Two画面close
self.closeTwoView()
//タイトルUILabel
let titleLabel:UILabel = UILabel()
titleLabel.frame = CGRect(x: SCREEN_WIDTH/2 - (SCREEN_WIDTH/2)/2, y: SCREEN_HEIGHT/2 - 100, width: SCREEN_WIDTH/2, height: 40)
titleLabel.font = UIFont(name: "Lato-Bold",size: CGFloat(20))
titleLabel.text = buttonName
titleLabel.textAlignment = .center
titleLabel.textColor = .red
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.backgroundColor = .clear
self.view.addSubview(titleLabel)
}
}
遷移先のdelegateを宣言
delegateを宣言します。UITableViewDelegateやUITextFieldDelegateと同じ物になります。
class OneViewController: UIViewController, ①TwoViewDelegate {
delegateにselfを設定します。
② self.twoView.delegate = self
delegateメソッドの処理記述
遷移先画面から戻る場合の遷移後の処理を記述します。
ここでは、UIViewをCLOSEしてUILabelに引き継ぎ項目を設定して表示しています。
③
//delegate メソッド
func TwoViewClick(displayName:String)
{
//Two画面close
self.closeTwoView()
//タイトルUILabel
let titleLabel:UILabel = UILabel()
titleLabel.frame = CGRect(x: SCREEN_WIDTH/2 - (SCREEN_WIDTH/2)/2, y: SCREEN_HEIGHT/2 - 100, width: SCREEN_WIDTH/2, height: 40)
titleLabel.font = UIFont(name: "Lato-Bold",size: CGFloat(20))
titleLabel.text = buttonName
titleLabel.textAlignment = .center
titleLabel.textColor = .red
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.backgroundColor = .clear
self.view.addSubview(titleLabel)
}
この場合「displayName」という引き継ぎ項目を定義しています。
「func TwoViewClick(displayName:String)」
手順2:遷移先画面
次に遷移先画面の実装です。「①イベント発生を検知する処理」の処理になります。
全体
全体のソースコードです。
import UIKit
①
// プロトコルを作る。
protocol TwoViewDelegate: AnyObject {
func TwoViewClick(displayName:String)
}
class TwoView: UIView {
② weak var delegate: TwoViewDelegate?
override func draw(_ rect: CGRect)
{
let twoBackButton = UIButton(type: .custom)
twoBackButton.frame = CGRect(x: SCREEN_WIDTH/2 - (SCREEN_WIDTH/3.5)/2, y: SCREEN_HEIGHT/2, width: SCREEN_WIDTH/3.5, height: 40)
twoBackButton.backgroundColor = .green
twoBackButton.addTarget(self, action: #selector(TwoView.twoWorkButtonClick(_:)), for: UIControl.Event.touchUpInside)
twoBackButton.setTitle("One画面遷移", for: .normal)
twoBackButton.titleLabel?.font = UIFont.systemFont(ofSize: 15)
twoBackButton.setTitleColor(.black, for: .normal)
twoBackButton.layer.cornerRadius = 10
twoBackButton.layer.borderColor = UIColor.gray.cgColor // 枠線の色
twoBackButton.layer.borderWidth = 1.0 // 枠線の太さ
self.addSubview(twoBackButton)
}
//ボタンタップイベント
@objc fileprivate func twoWorkButtonClick(_ sender: UIButton)
{
self.delegate?.TwoViewClick(displayName: "Two画面に遷移したよ!")
}
}
protocolを定義
protocolで値を引き継ぐためのメソッドとStringを定義します。
①
// プロトコルを作る。
protocol TwoViewDelegate: AnyObject {
func TwoViewClick(displayName:String)
}
delegateを定義
delegateを定義します。
② weak var delegate: TwoViewDelegate?
delegateメソッド実行
今回のアプリの場合、TwoView画面からOneViewController画面に戻る際に値を引き継ぐため、戻るボタンタップイベントでdelegateメソッドを実行します。
③
//ボタンタップイベント
@objc fileprivate func twoWorkButtonClick(_ sender: UIButton)
{
self.delegate?.TwoViewClick(displayName: "Two画面に遷移したよ!")
}
delegateで構造体を利用してみよう!!
もちろん、構造体も引き継ぐことができます。
前回使用した構造体を引き継いでみましょう!
手順1:遷移先画面
遷移先画面のロジックを実装していきましょう!
構造体を定義
今回は遷移先画面:TwoView側で定義しました。
struct oneStructList {
var A:Bool
var B:String
var C:Int
var D:twoStructList
}
struct twoStructList {
var E:Bool
var F:String
var G:String
}
fileprivate var workStructList:oneStructList = oneStructList(A: true, B: "", C: 0, D: twoStructList(E: false, F: "", G: ""))
protocolの引き継ぎ項目を構造体に変更
引継ぎ項目に構造体を定義します。
// プロトコルを作る。
protocol TwoViewDelegate: AnyObject {
func TwoViewClick(displayName:TwoView.oneStructList)
}
delegateメソッド実行
ボタンのタッチイベントにdelegateメソッドを定義します。
//ボタンタップイベント
@objc fileprivate func twoWorkButtonClick(_ sender: UIButton)
{
self.delegate?.TwoViewClick(displayName: workStructList)
}
手順2:遷移元画面
次に遷移元画面のロジックを実装します。
delegateメソッドに処理追記
遷移先画面で定義しましたdelegateメソッドでの処理内容を記述します。
引き継いだ構造体の項目から値を取得して、UILabelに設定し表示します。
//delegate メソッド
func TwoViewClick(displayName:TwoView.oneStructList)
{
//Two画面close
self.closeTwoView()
//構造体解体
let workA:Bool = displayName.A
var workA01:String = ""
if workA
{
workA01 = "true"
} else {
workA01 = "false"
}
let workB:String = displayName.B
let workC:String = String(displayName.C)
let workD:TwoView.twoStructList = displayName.D
let workE:Bool = workD.E
var workE01:String = ""
if workE
{
workE01 = "true"
} else {
workE01 = "false"
}
let workF:String = workD.F
let workG:String = workD.G
let setLabeValue:String = workA01 + "\n" + workB + "\n" + workC + "\n" + workE01 + "\n" + workF + "\n" + workG
//タイトルUILabel
let titleLabel:UILabel = UILabel()
//titleLabel.frame = CGRect(x: SCREEN_WIDTH/2 - (SCREEN_WIDTH/2)/2, y: SCREEN_HEIGHT/2 - 100, width: SCREEN_WIDTH/2, height: 40)
titleLabel.frame = CGRect(x: 0 , y: 0 , width: SCREEN_WIDTH, height: 400)
titleLabel.font = UIFont(name: "Lato-Bold",size: CGFloat(20))
titleLabel.text = setLabeValue
titleLabel.textAlignment = .center
titleLabel.textColor = .red
titleLabel.numberOfLines = 0
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.minimumScaleFactor = 0.3
titleLabel.backgroundColor = .clear
self.view.addSubview(titleLabel)
}
手順3:シミュレータ確認
簡単に構造体に変更する事ができます。
まとめ
delegateを利用して画面間の値受け渡しを行なってみました。
理解しづらい方
なんかよく理解できないな!
という方は、まずはロジックのパターンを覚えてみてください。
遷移元 | 遷移先 |
---|---|
遷移先のdelegateを宣言 delegateメソッドの処理記述 | protocolを定義 delegateを定義 delegateメソッド実行 |
流れをつかむと理解しやすいと思います。
delegateを利用する理由
メインは上記と考えています。
アプリ開発で利用してみてください!
それではまたっ!