Changing IBOutlet in UIViewController from UIWindow in Swift - ios

I have a UIViewController with some IBOutlets. I also have a UIWindow that needs to access those IBOutlets in the first UIViewController. However, whenever I try to access it from my UIWindow, the variables are nil. Here's some of my code.
class ViewController: UIViewController {
#IBOutlet weak var playPauseLabel: UILabel!
#IBOutlet weak var playButton: UIButton!
#IBOutlet weak var pauseButton: UIButton!
func functionA() {
println(playPauseLabel)
println(playButton)
println(pauseButton)
}
}
class WindowClass: UIWindow {
func resetPlayPause() {
var vc = ViewController()
vc.functionA()
}
}
Now when I call func resetPlayPause() from WindowClass, all three IBOutlets are nil. I've read around on the other SO threads on this issue but haven't found any solutions. How can I access and modify those IBOutlets in ViewController from WindowClass?

The simple answer is that resetPlayPause() is constructing a new instance of ViewController each time it's called, and unless your ViewController has an initializer that loads itself from a storyboard or xib, none of your outlets will be connected. If your ViewController does have an initializer that does this, it won't connect its outlets until its view has been loaded. You can do this simply by calling vc.view in resetPlayPause() before you toggle the buttons.
The harder answer is that having your window access a view controller's ivars directly seems like a really bad idea. Either move your resetPlayPause() method somewhere other than in a window subclass, or have it call a method on ViewController that will toggle the buttons internally.

The answer is to use NSNotificationCenter. Works like a charm.

Related

IBOutlet References found nil when containing view controller is presented programmatically [duplicate]

This question already has answers here:
Instantiate and Present a viewController in Swift
(18 answers)
Closed 2 years ago.
I am building a running app and am trying to programmatically present a view controller when the start run button is tapped. However, when I tap the button after setting up the presenting view controller, any reference to an IBOutlet is found nil when unwrapping in the order that they are called.
It's worth noting that when I connect the two view controllers with "show" via storyboard instead, that everything works fine, but I want to present the view controller programmatically and in fullscreen rather than the cardlike presentation by default.
From the presenting view controller:
#IBAction func startRunTapped(_ sender: Any) {
let inRunVC = CurrentRunVC()
inRunVC.modalPresentationStyle = .fullScreen
self.present(inRunVC, animated: true, completion: nil)
}
From the presented view controller:
import UIKit
import MapKit
class CurrentRunVC: LocationVC {
#IBOutlet weak var sliderImageView: UIImageView!
#IBOutlet weak var swipeBGImageView: UIImageView!
#IBOutlet weak var durationLabel: UILabel!
#IBOutlet weak var paceLabel: UILabel!
#IBOutlet weak var distanceLabel: UILabel!
#IBOutlet weak var pauseButton: UIButton!
var startLocation: CLLocation!
var lastLocation: CLLocation!
var timer = Timer()
var runDistance = 0.0
var pace = 0
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
let swipeGesture = UIPanGestureRecognizer(target: self, action: #selector(endRunSwiped(sender:)))
sliderImageView.addGestureRecognizer(swipeGesture) //error unwrapping here
sliderImageView.isUserInteractionEnabled = true // and here
swipeGesture.delegate = self as? UIGestureRecognizerDelegate
}
//errors also on any other reference to an IBOutlet
I've confirmed that all IBOutlets are connected properly.
Storyboard:
Thanks in advance
You are not grabbing the correct storyboard instance of CurrentVC but you are creating a new one. Instead of let inRunVC = CurrentRunVC(), use
let runVC = self.storyboard?.instantiateViewController(withIdentifier: "CurrentRunVC") as! CurrentRunVC //set the identifier in the storyboard identity inspector
Your view controller's outlets are defined in the storyboard, and created when the view controller is loaded from the storyboard. When you just initialise a new view controller in this line:
let inRunVC = CurrentRunVC()
Then the system will look for a xib file with the same name as the view controller, which doesn't exist, which means it just loads a blank view with no connected outlets. All those implicitly unwrapped optionals are now nil.
Either make a new segue from the button to the same view controller, but a modal presentation, or add a reference to the presented view controller in the storyboard and then load it using the instatiate with identifier method of the current storyboard.

Swift: UIControl in TableViewController found nil when called by ViewController

I have two viewControllers in the storyboard, a ViewController (root) and a TableViewController, that are linked by a push segue.
The TableViewController acts as a setting page of the app and multiple UIControls such as UISegmentedControl and UISwitch are put in it. These UIControls are linked to the TableViewController via IBOutlets.
#IBOutlet weak var mySegmentedControl: UISegmentedControl!
#IBOutlet weak var mySwitch: UISwitch!
However, when I call these UIControls in the ViewController by:
let tableView: TableViewController = TableViewController.init(nibName: nil, bundle: nil)
if tableView.mySwitch.isOn {
//perform actions
}
Error pops up at the if-statment:
fatal error: unexpectedly found nil while unwrapping an Optional value
I thought it was because the TableViewController has not been loaded when the UIControls are being called, thus I have tried calling the viewDidLoad() of tableView first, but the error still remains.
What should I do? Appreciate any suggestions.
You do not directly call viewDidLoad, this is done by UIKit after the view is being loaded. And the view is loaded when the view needs to be displayed.
If you need to access the outlets before, you can force loading by
tableView.loadViewIfNeeded()
But remember:
Maybe it's better to do the outlet stuff inside viewDidLoad of the involved controller, and not from outside.
You should not name the controller variable tableView. That name suggest to be a view and not a controller. Better name it tableViewCtrl or so.

Can ViewController be generic which contains IBOutlets etc?

Can my viewcontroller be generic and still contain IBOutlet?
class AViewController<T> : SuperViewController, Decodable where T : Decodable {
#IBOutlet weak var leftButton: UIButton!
#IBOutlet weak var rightButton: UIButton!
var pop : T?
}
will this impleemntation works? it throws me an error. Type AViewController does not conform to decodable.Any ideas?
Simply remove the , Decodable. There is no reason to indicate that the view controller class itself conforms to Decodable.
Your error is gone with:
class AViewController<T> : SuperViewController where T : Decodable {
#IBOutlet weak var leftButton: UIButton!
#IBOutlet weak var rightButton: UIButton!
var pop : T?
}
This is completely independent of why you are also declaring that T must be Decodable. That's a completely separate question I'm not going to address.
View controllers that contain IBOutlets are view controllers that you will manipulate in Interface Builder. There are two problems that you face with this.
An Interface Builder view controller is created using the required init(coder:) initializer, which cannot instantiate generic objects, as it does not have any context in which it knows what T should be at run time.
When you create an instance of AViewController in code you will specify the concrete type
let aVC = AViewController<String>()
but you cannot do such a thing in the Identity Inspector in IB. This is not allowed:
What you can do is create a storyboard specific view controller that inherits from your generic view controller, but specifies the concrete type
class AStringViewController: AViewController<String> {
}
And use AStringViewController in IB in the identity inspector and hook up the outlets as required.
If you find that this isn't really what you want and your view controller needs to be more dynamic then you will have to create your view controller in code and you will not be able to use outlets.
Another alternative is to have a non-generic view controller with your outlets and use child view controllers that are created in code that have the generics you need.

Can't call ViewController method from AppDelegate

In AppDelagate I call the following in a method
func example() {
ViewController().test()
}
and In my ViewController the method is
func test() {
testView.userInteractionEnabled = true
buttonTapped(UIButton())
restartTimer()
}
but it crashes whenever I call the method, due to a nil error with testView. testView is a view in ViewController and not in AppDelegate, but I don't know how to make it so the method executes like how it would if i called it in the ViewController.
An IBOutlet is nil until the view controller calls viewDidLoad. viewDidLoad did not occur since you tried to instantiate the class directly via the call ViewController().
Thus, testView is nil, as expected. Your AppDelegate should not be responsible for the ViewController logic anyhow.
As you may know by now, your crash is happening due the fact you're accessing a IBOutlet before it get the chance to be initialized. So I'm guessing the testView is declared sort of like this:
#IBOutlet weak var testView: UIView!
Solution A
You can turn it optional instead to avoid this crash:
#IBOutlet weak var testView: UIView?
And change the syntax in your VC to:
testView?.userInteractionEnabled = true
Solution B
If you really need to turn the User Interaction off at this point, you can force the view to load:
let myVc = ViewController()
_ = myVc.view // this will force the myVc.view to be loaded
myVc.test()
This way your IBOutlets will have the chance to initialize before you run the test() method.

How to connect ViewController.swift to ViewController in Storyboard?

I made a new view controller in Storyboard and made a new .Swift file that inherits UIViewController. However, my viewDidLoad() is not being called.
What do I need to do to connect my view controller in my story board to my .swift code so viewDidLoad() is called?
I see there's this: How to connect storyboard to viewcontroller
However, in a default project, FirstViewController(storyboard) doesn't have FirstViewController in that box, it is empty, yet viewDidLoad() is still called. It doesn't make sense.
Choose your ViewController in the storyboard or scene list (mine is called Circuit).
Click on the Identity Inspector on the right
Select Custom Class > Class > Type name of your swift file.
DONE :)
You have your ViewControler created with some Objects in it (UILabel, UIButton, UIImage...)
1 - You need to link your ViewControler in your story board to your ViewController.swift , to do this follow the pictures
2 - In the class filed put the name of the ViewController class. With that you just linked your storyBoard view controller to your viewController.swift class
Class = ViewController.swift
3 - Now you need to cretae the variables you want to asign(UILabel, UIButton ... that you have in your storyboard): in this example:
class MovieDetailViewController: UIViewController, DetailView {
var presenter: MovieDetailPresenter!
var movie: Movie?
#IBOutlet weak var lblMovieYear: UILabel!
#IBOutlet weak var lblMovieTitle: UILabel!
#IBOutlet weak var movieImage: UIImageView!
#IBOutlet weak var lblMovieRating: UILabel!
#IBOutlet weak var lblMovieOverview: UILabel!
}
4 - To link the UILabel in the story board to your UILabel variable or your UIButton in your story board to your UIButton var, follow the next images
First select the view controller in the storyboard
Second select the parragraf icon in the right up corner ands clicked 'Assistant', it will open a screen of your ViewControler.swift
Now you just need to drag the variable to the corresponding object and you will be done.
Remember to do this with all variables, you will need to create a variable for each object you have in the storyboard
Connect storyboard and view controller
You should set ViewController in Class` field

Resources