Can't call ViewController method from AppDelegate - ios

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.

Related

Delegation to another view controller is not working

My sender class for delegation:
import UIKit
protocol tapDelgation:class {
func tapConfirmed(message:String)
}
class ViewController: UIViewController {
weak var delegate:tapDelgation?
#IBAction func deligateSenderAction(_ sender: Any) {
var data = "hello world"
print(data)
self.delegate?.tapConfirmed(message: data)
}
}
My reciever class:
import UIKit
class NextViewController: UIViewController {
weak var vc:ViewController? = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
vc?.delegate = self
}
}
extension NextViewController : tapDelgation {
func tapConfirmed(message: String) {
print(message)
}
}
What is expected: A button on sender vc is pressed and from reciever vc a console print would be popped. But in vain, nothing happens. Does any one know why it is happening? If it is not possible then why?
It looks like a memory management problem to me.
First problem: Creating a view controller with a default initializer like ViewController() is almost never the right thing to do. because it won't have any view contents.
You don't explain how your NextViewController and your ViewController get created and displayed.
It looks like NextViewController has a weak reference to ViewController, and ViewController's delegate point is also weak (delegate references should almost always be weak.)
This line:
weak var vc:ViewController? = ViewController()
Will cause NextViewController to create an instance of ViewController that isn't owned by anybody, so it will immediately be deallocated and the vc variable will go back to being nil. By the time you get to NextViewController's viewDidLoad, vc will be nil, so the optional binding in the line vc?.delegate = self won't do anything.
NextViewController's vc variable should almost certainly be a strong reference, not weak, but you don't show how ViewController ever gets displayed to the screen, so it isn't clear what you're trying to do.
weak var vc:ViewController? = ViewController()
Remove weak if you don't set the vc somewhere else and any other instance doesn't keep a strong reference to it.
If there is another instance with a strong reference, please share the related code.
The answer from the https://stackoverflow.com/users/205185/duncan-c is totally correct unless there is any other code which affects the presentation of the NextViewController and reference to the vc: ViewController
I changed viewController to SenderViewController but no luck and Sender and receiver is connected via navigation controller. i.e. If i press a button on sender a recieve comes via push transition. my aim was to since it is triggered an IBAction then the second view controller would implements the tap confirmed function. thanks for your answer. Learned a lot :)
Due to this comment, you need to implement prepareForSegue() method in your ViewController (original one) and set the vc property of the "next" view controller there instead of = ViewController() in the "next" make the extension on the ViewController:
extension ViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextController = segue.destinationViewController as! NextViewController
nextController.vc = self
}
}
Explanation based on the comment:
You get a new instance of the NextViewController with the new instance of the ViewController instantiated on its init (instead of passing the original instance of ViewController to it). That's where you can ge a strange behaviour with delegation.
weak var vc:ViewController? = ViewController()
Remove weak for vc it will release the view controller memory after disappear

Understanding why using weak deallocates stored properties after being instantiated inside a function

Even after reading Swift's documentation about ARC, I'm still having trouble understanding why a property was set to nil even when it's instantiated within a function. I have the following sample code:
import UIKit
class ItemListViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView()
}
}
And here is the sample code for the test run:
import XCTest
#testable import PassionProject
class ItemListViewControllerTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func tests_TableViewIsNotNil_AfterViewDidLoad(){
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "ItemListViewController")
let sut = viewController as! ItemListViewController
_ = sut.view
XCTAssertNotNil(sut.tableView)
}
func tests_loadingView_SetsUITableViewDataSource(){
}
}
Inside the function that is being tested, we've created the correct instances and I understand that calling the view property fires the viewDidLoad() method. I'm not sure why the test states that the tableView property is nil when running the test. I'd like to understand ARC from this perspective and realize what is happening under the hood when the test states that the tableView property is nil. We've clearly instantiated the UITableView object inside viewDidLoad(). I understand that this test passes when removing "weak" from the tableView property but I don't understand why. Any explanation would be greatly appreciated to understand this better.
I'm still having trouble understanding why a property was set to nil even when it's instantiated within a function
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView()
}
Then you haven't understood what weak means. It means: this reference has no ability to keep this object alive. Thus, if nothing else keeps this object alive (i.e. retains it), the object will die no matter how it is instantiated.
Your code creates the table view: UITableView(). It assigns the table view to a variable: tableView = UITableView(). But that variable is weak. It has no power to keep this table view alive. And you didn't do anything else that would keep this table view alive. Therefore the table view instantly dies, vanishing in a puff of smoke, and leaving tableView pointing at nothing — and so tableView is reset to nil.
The usual thing, however, is to talk like this:
super.viewDidLoad()
let tv = UITableView(frame:someRect)
self.view.addSubview(tv)
tableView = tv
That makes all the difference in the world. The tableView variable is still weak, and still can't keep the table view alive. But we added the table view as a subview to self.view, and now its superview keeps it alive.
And that is the reason why outlets are often weak. It's because they refer to views that have superviews that already keep them alive, so there is no need for a second reference (the outlet property) to keep them alive as well.

Swift: call function from storyboard view controller I presented?

Ok, Ive tried the usual methods here but can't figure out this relatively simple issue -
I have a view controller that I present here:
self.presentViewController((self.storyboard?.instantiateViewControllerWithIdentifier("createEvent"))!, animated: true, completion: nil)
And I need to call a function in my original main VC from this createEvent VC. I have tried putting:
weak var superv: MainViewController!
in the create event class then doing something like this:
let create = self.storyboard?.instantiateViewControllerWithIdentifier("createEvent")
create.superv = self //error here
Then in create doing:
superv.updateThings()
But I get an error trying to tie the presented VC to my MainViewController. What am I doing wrong here?
I have seen use of protocols but I would like to avoid that. What is the simplest way to do this?
This is what I did:
In main:
var create = UIViewController()
create = (self.storyboard?.instantiateViewControllerWithIdentifier("createEvent"))!
create.delegate = self //error here
In create:
weak var delegate: CreateEventDelegate!
protocol CreateEventDelegate: class {
func doSomeFunction ()
}
And the error is value of type uiviewcontroller has no member delegate
The function in main I need to call is:
self.tableView.reloadData()
Error:
Use protocols. I know you said you don't want to but its so simple and convenient.
In your createEvent vc:
weak var delegate: CreateEventDelegate!
Then at the bottom of the file (outside the create event VC class)
protocol CreateEventDelegate: class {
func doSomeFunction ()
}
Then in the main VC class conform to the CreateEventDelegate protocol and add this:
create.delegate = self
Then you can call delegate.doSomeFunction() in your create event VC

Changing IBOutlet in UIViewController from UIWindow in Swift

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.

Access var from rootViewController in CollectionViewCell in Swift

I would like to access a variable from my rootViewController from within a different viewController (it‘s a CollectionViewCell).
window!.rootViewController = ViewController()
I declare the var like so:
import UIKit
class ViewController: UIViewController {
var testString : String = "Test";
override func viewDidLoad() {
[…]
And try to access it this way:
import UIKit
class MainCollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
[…]
super.init(frame: frame)
let mainView = self.window!.rootViewController
var testStringFromMainView = mainView.test
[…]
But all I keep getting is:
Type of expression is ambiguous without more context
Strange thing is, when I try for example
mainView.view.backgroundColor = UIColor.redColor()
it works.
I can’t figure out what I am doing wrong. Any help is appreciated!
You must conditionally cast the rootViewController. Your current code only knows that it is a UIViewController, but in order for it to use your variable, it needs to know that it is an instance of your subclassed view controller, ViewController.
Replacing your MainCollectionViewCell.init with this should fix the problem:
if let cvc = self.window!.rootViewController as? ViewController {
var testStringFromMainView = cvc.test
}
Please note that due to the conditional unwrapping, which is much safer than forced unwrapping, this code will not be executed if the rootViewController is not an instance of class ViewController. In other words, you need to look into global variables if your app will have multiple view controllers.
It doesn't work because rootViewController is a type of UIViewController and doesn't have a test property. Anyway, that doesn't matter as you shouldn't be trying to do what you're trying to do - it isn't appropriate for the cell to be trying to navigate up to the root view controller. Anything you need in the cell should be passed (see dependency injection) from the root view controller 'down' to through the view controllers to the cell. In this way your code is logical and the dependencies are obvious. What you're trying to do is hide a dependency in the bottom of your view hierarchy. You can make it work (with a cast), but you shouldn't.

Resources