結論
プロパティオブザーバーを利用して画面遷移を行う際は、イベントコントロールで正しく行えるようにする必要がある。直接遷移をさせずに、addTarget
メソッドなどを用いる。
理由
IBOutlet
が接続されているViewに遷移されると、イニシャライズされて変数にIB部品のインスタンスが入ります。その際にプロパティオブザーバーとしてのdidset
が実行されるが、このdidset
内にNavigationController
による別の画面への遷移を直接実装してしまうと、画面が遷移されてきたにも関わらず、画面を遷移するという状態になるため、意図している形には正しく遷移されない、もしくはエラーが生じる。
このエラーはaddTargetメソッドでイベントコントロールを図ることで解決できる。実行のタイミングを.touchUpInside
、つまり枠内をタッチするイベントのような形にすることで、didset
内に記述されていてもインスタンス生成時に実行されなくなる。
そして、実際の遷移はaddTarget
メソッドでセレクタをし、objc
属性のfunctionとしてNavigationController
による遷移を実装するということになる。
例
didset内に遷移を直接実装した場合
final class ViewController: UIViewController {
@IBOutlet private weak var reStartButton: UIButton! {
didSet {
RootViewContorller.root.reStart()
}
}
}
//RootViewController.swift
final class RootViewContorller {
static let root: RootViewContorller = .init()
private init(){}
func reStart(){
UserDefaults.standard.isLogined = [true, false].randomElement()!
showInitialView(window: window)
}
}
この場合は次のようなDebugが表示される
2021-05-08 10:33:25.508263+0900 RootViewControllerTest[13610:776213] Unbalanced calls to begin/end appearance transitions for < RootViewControllerTest.SecondViewContorller: 0x7fd8bb524200 >.
メッセージにあるように、遷移画面を起動/終了時の呼び出しが不安定ということ。遷移しても、即座に別の画面に遷移するような状態にあり、結果的に画面が表示されないためこのようなメッセージが表示されている。
didset内でaddTarget
を利用した場合
final class ViewController: UIViewController {
@IBOutlet private weak var reStartButton: UIButton! {
didSet {
reStartButton.addTarget(self, action: #selector(tapReStartButton(_:)), for: .touchUpInside)
}
}
@objc private func tapReStartButton(_ sender: UIResponder) {
RootViewContorller.root.reStart()
}
}
//RootViewController.swift
final class RootViewContorller {
static let root: RootViewContorller = .init()
private init(){}
func reStart(){
UserDefaults.standard.isLogined = [true, false].randomElement()!
showInitialView(window: window)
}
}
この記述だと、プロパティオブザーバーとしてdidset
内はインスタンス生成に実行されるが、addTarget
メソッドは.touchUpInside
というIBパーツの枠内をタッチした場合に実行されるような形になっているため、遷移は実行さレナい。addTarget
では遷移の中身はactionパラメータのセレクターとして指定され、objc属性以下に書かれる。
addTarget
メソッドは別のQiitaにまとめています。
まとめ
プロパティオブザーバーを利用して画面遷移を行う際は、イベントコントロールで正しく行えるようにする必要がある。直接遷移をさせずに、addTarget
メソッドなどを用いる。