I read if you instantiate a class (UIViewController) directly, via an initializer, IBOutlets won't be connected. My problem is I'm referencing a nil IBOutlet
, causing a crash.
I have a lot of observers that update views in vc1
, vc2
, and vc3
(the "pages" in UIPageViewController
). To keep my code clean, I want to add all the observers in the UIPageViewController
class and not in the viewDidLoad
of vc1
, vc2
, & vc3
.
However, I can't reference the IBOutlets
of subviews from UIPageViewController
because they come up nil. Does anyone have advice on how to set up UIPageViewController
in a way I can access the subview's IBOutlets
?
I have had trouble in other apps updating page views that are not visible on the screen, (ex: updating a view in vc3
while vc1
is on screen). Is UIPageViewController
a bad idea for this sort of goal? Should I just make one large view that the user can pan across?
// UIPageViewController.swift
var vc1: ViewController1 = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return (storyboard.instantiateViewController(withIdentifier: "ViewController1") as? ViewController1)!
}()
var vc2: ViewController2 = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return (storyboard.instantiateViewController(withIdentifier: "ViewController2") as? ViewController2)!
}()
var vc3: ViewController3 = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return (storyboard.instantiateViewController(withIdentifier: "ViewController3") as? ViewController3)!
}()
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if viewController == vc2 {
return vc1
} else if viewController == vc3 {
return vc2
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if viewController == vc1 {
return vc2
} else if viewController == vc2 {
return vc3
}
return nil
}
func viewDidLayoutSubviews() {
// Adding observer for ViewController1
NotificationCenter.default.addObserver(self, selector: #selector(vc1.handleEventDataChange), name: .eventDataChanged, object: nil)
}
-
// ViewController1.swift
@IBOutlet weak var scoreBoard: UIView!
func viewDidLoad() {
super.viewDidLoad()
}
func handleEventDataChange() {
if scoreBoard.alpha != 0 { // <-- Crash here
// update scoreboard
}
}
EDIT:
In addition to JRTurton's answer, I also discovered that I was adding observers in the wrong class.
NotificationCenter.default.addObserver(self, selector: #selector(vc1.handleEventDataChange), name: .eventDataChanged, object: nil)
should be
NotificationCenter.default.addObserver(vc1, selector: #selector(vc1.handleEventDataChange), name: .eventDataChanged, object: nil)
You’re loading your view controllers from the storyboards, so the outlets will be getting connected (assuming it’s all wired up properly in the storyboard).
The most likely cause of the issue is that the methods are being called before the view controllers have loaded their view so they haven’t had a chance to connect anything yet.
Externally called functions that have side effects on outlets should probably begin with a check of isViewLoaded
to prevent this issue. You should ideally be configuring model objects to pass to your view controller, which the view controller can either act on immediately (if the view is loaded) or use for configuration at viewDidLoad
(Also, view did layout sub views is not a great place to be adding observers - that can get called a lot)