Using delegate's methods when delegate is a UIViewController in Swift - swift

I'm not sure if this is an Xcode 6 Swift-specific problem, but here it goes.
I have two classes, MyViewController and UtilViewController. UtilViewController has a delegate property of type UIViewController, because UtilViewController is designed to be used by all of my app's viewcontrollers. It also has a function createOrder() that will only ever be called by MyViewController. Because this function will only ever be called by MyViewController, inside of createOrder() there is a line that calls a function declared by MyViewController, like this
self.delegate!.methodInMyViewController()
However this throws an error in UtilViewController
'UIViewController' does not have a member named 'methodInMyViewController'
How can I preserve the modularity of having a UIViewController delegate, but also be able to call methods from my own viewcontrollers, which are a subclass of UIViewController?

You can cast your delegate as a MyViewController to tell the compiler that it is actually a MyViewController and can call that method.
(self.delegate! as MyViewController).methodInMyViewController()
Since delegate may not always be a MyViewController, you may want to check if it is before casting:
if self.delegate! is MyViewController{
(self.delegate! as MyViewController).methodInMyViewController()
}

Instead of (ab)using the generic delegate of UtilViewController, the createOrder()
method could take a closure (callback) parameter. For example:
func createOrder(callWhenDone:()->()) {
// do stuff ...
callWhenDone()
}
From within MyViewController, you would call the method as
utilVC.createOrder {
// called from createOrder()
self.methodInMyViewController()
}

Related

Is an event called immediately after object initialization?

Does an event procedure like override init exist but immediately after completion?
I'm hoping for something like override init_completed()
I need to perform functionality immediately after the object is fully initialized.
You could simply call a method at the end of the init() method:
struct myObject {
init() {
// Other code
otherMethod()
}
}
If your object is a subclass of UIView and exists inside a UIViewController you can of course override the viewDidLoad() method of the view controller.
For an isolated UIVIew, either awakeFromNib() or didLayoutSubviews() will be called after the view loads. There is more information here.
Other useful information available at the Swift documentation on initialisation.

Confusion Regarding How to Use Capture Lists to Avoid a Reference Cycle

My custom UIViewController subclass has a stored closure property. The closure signature is defined as to take a single argument of the same type of the class:
class MyViewController {
var completionHandler : ((MyViewController)->(Void))?
// ...
}
...the idea being, the object passing itself back as an argument of the handler, a bit like the UIAlertAction initializer.
In addition, and for convenience, I have a factory(-ish) class method:
class func presentInstance(withCompletionHandler handler:((MyViewController)->(Void)))
{
// ...
}
...that performs the following actions:
Creates an instance of the view controller,
Assigns the completion handler to the property,
Presents it modally from whatever happens to be the top/root view controller at the time of the call.
My view controller is definitely leaking: I set up a breakpoint on deinit() but execution never hits it, even way after I'm done with my view controller and it is dismissed.
I am not sure of how or where I should specify a capture list in order to avoid the cycle. Every example I have come across seems to place it where the closure body is defined, but I can't get my code to compile.
Where I declare the closure property? (how?)
var completionHandler : ((MyViewController)->(Void))?
// If so, where does it go?
Where I declare the closure parameter?
class func presentInstance(withCompletionHandler handler:((MyViewController)->(Void)))
{
// Again, where could it go?
Where I call the above function and pass the closure body?
MyViewController.presentInstance(withCompletionHandler:{
[unowned viewController] viewController in
// ...
})
// ^ Use of unresolved identifier viewController
// ^ Definition conflicts with previous value
Where I actually call the closure, towards self?
None of these will compile:
self.completionHandler?(unowned self)
self.completionHandler?([unowned self] self)
self.completionHandler?([unowned self], self)
Well, it turns out my view controller was being retained by a block, but not the one I was thinking:
class MyViewController
{
deinit(){
print("Deinit...")
}
// ...
#IBAction func cancel(sender:UIButton)
{
completionHandler(self)
// View controller is dismissed, AND
// deinit() gets called.
}
#IBAction func punchIt(sender: UIButton)
{
MyWebService.sharedInstance.sendRequest(
completion:{ error:NSError? in
self.completionHandler(self)
// View controller is dismissed, BUT
// deinit() does NOT get called.
}
)
}
...so it is the closure passed to MyWebService.sharedInstance.sendRequest() that was keeoing my view controller alive. I fixed it by adding a capture list like this:
MyWebService.sharedInstance.sendRequest(
completion:{ [unowned self] error:NSError? in
However, I still don't quite understand why the short-lived completion handler, passed to the web service class, executed once, and disposed, was keeping my view controller alive. That closure, not stored anywhere as a property, should be deallocated as soon as it is exited, right?
I must be missing something. I guess I'm still not fully thinking in portals closures.

Can I use MCBrowserViewControllerDelegate with GameViewController class instead of ViewController class?

I don't think so. The error I receive states Type 'GameViewController' does not conform to protocol 'MCBrowserViewDelegate' https://developer.apple.com/library/prerelease/ios/documentation/MultipeerConnectivity/Reference/MCBrowserViewController_class/index.html
Assuming that GameViewController is a subclass of UIViewController, you certainly can since MCBrowserViewController is a subclass of UIViewController as well.
The error you are receiving is saying that you are not conforming to the delegate protocols required to use MCBrowserViewController. This means that in order to use a MCBrowserViewController, you first need to add MCBrowserViewDelegate to your class declaration similar to the following.
class GameViewController: UIViewController, MCBrowserViewDelegate {
You will also want to set your GameViewController to be the delegate within viewDidLoad or wherever you create it.
// create the MCBrowserViewController
let browserViewController = MCBrowserViewController(...)
browserViewController.delegate = self
self.presentViewController(browserViewController, animated: true, completion:nil)

Swift Delegation

I'm having trouble wrapping my head around delegation in Swift. After reading some guides, I was able to set it up delegation between two ViewControllers, but I'm not understanding how it works. In my first view controller, I have a a label that displays what has been entered in the second view controller which contains a text field and a button (that returns to the first view controller). Here is the code for the first view controller:
#IBOutlet weak var labelText: UILabel!
func userDidEnterInformation(info: String) {
labelText.text = info;
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "transition"){
let secondVC: SecondViewController = segue.destinationViewController as! SecondViewController;
secondVC.delegate = self;
}
}
Here's the code for the second view controller:
protocol DataEnteredDelegate{
func userDidEnterInformation(info: String);
}
#IBOutlet weak var userText: UITextField!
var delegate: DataEnteredDelegate? = nil;
#IBAction func buttonPressed(sender: AnyObject) {
let information = userText.text!;
delegate!.userDidEnterInformation(information);
self.navigationController?.popToRootViewControllerAnimated(true);
}
My understanding is that in the text inside the text field gets stored in the information constant, then the userDidEnterInformation method from the protocol is called, with the method being defined inside the first view controller. This method then changes the label inside the first view controller. The thing is, I'm not sure what is happening in the prepareForSegue function. Specifically, I'm not sure what's the purpose of secondVC.delegate = self.
I would appreciate any sort of clarity on delegation.
The diagram is simple but can help you understand what's going on.
FirstViewController must conform to the DataEnteredDelegate protocol you have defined (see Sumit's answer). When using secondVC.delegate = self, you are saying that for the segue transition with the destination being a SecondViewController, the attribute delegate of that SecondViewController instance will be set to this instance of FirstViewController, thus delegating things from SecondViewController to your FirstViewController as made possible by the DataEnteredDelegate protocol.
The protocol you created in second viewcontroller is an Interface. You must implement your first view controller with the DataEnteredDelegate protocol.
class FirstViewController:UIViewController, DataEnteredDelegate{
func userDidEnterInformation(info: String) {
//stub
}
}
If the delegate of the second VC is not set in prepareForSegue() it remains nil. The second VC is then unable to call the first VC.
On a side note, if the delegate is nil your code will crash because delegate! is trying to unwrap an optional binding with the value of nil. It's better to first unwrap the delegate variable:
if let handler = delegate {
handler.userDidEnterInformation(information)
}
Alternatively, you could use Swift's Optional Chaining, calling userDidEnterInformation only if delegate is not nil.
delegate?.userDidEnterInformation(information);
In addition it is recommended to declare the delegate weak, to prevent retain cycles:
weak var delegate: DataEnteredDelegate?
Delegates and Protocols
Do not try to figure out how the dictionary definition of “delegate” fits with the concept of delegation in Swift. It doesn't.
Delegation in Swift is an agreement between two players—a sensing object and a requesting object. The “delegate” is the “requesting object.” Just think “asker” or “requester” every time you see “delegate” and it will make a lot more sense. Here is their agreement...
The Sensing Object (Second View Controller):
I have data from some event that took place. I will publish instructions (a protocol) on how you may access that data. If you want it, you must do three things.
You must declare in your class type that your class abides by my protocol.
You must write the functions that I describe in my protocol. I don't care what those functions do but the function type must match what I publish.
In YOUR code, you must set MY “delegate” (think “asker”) property to point to you. {secondVC.delegate = self} That way I can call one of YOUR functions to deliver the data.
After that, when I get some data, I will call one of the functions in your object that I told you to write. My call to your function will contain the data you are looking for as one of the arguments. {delegate!.userDidEnterInformation(information)} Note: delegate! (asker!) is YOU.
The Delegate (Requesting) Object (First View Controller):
O.K. You've got a deal.

Swift – Using popViewController and passing data to the ViewController you're returning to

I have an optional bool variable called showSettings on my first view controller which is called ViewController, and I'm popping from SecondViewController back to ViewController.
Before I pop, I want to set the bool to true. Seems wrong to instantiate another view controller since ViewController is in memory.
What's the best way to do this? I'm not using storyboards, if that's important for your answer.
Thanks for your help
So I figured it out, based mostly from this post – http://makeapppie.com/2014/09/15/swift-swift-programmatic-navigation-view-controllers-in-swift/
In SecondViewController, above the class declaration, add this code:
protocol SecondVCDelegate {
func didFinishSecondVC(controller: SecondViewController)
}
Then inside of SecondViewContoller add a class variable:
var delegate: MeditationVCDelegate! = nil
Then inside of your function that your button targets, add this:
self.navigationController?.popViewControllerAnimated(true)
delegate.didFinishSecondVC(self)
What we're doing here is doing the pop in SecondViewController, and not passing any data, but since we've defined a protocol, we're going to use that in ViewController to handle the data.
So next, in ViewController, add the protocol you defined in SecondViewController to the list of classes ViewController inherits from:
class ViewController: UIViewController, SecondVCDelegate { ... your code... }
You'll need to add the function we defined in the new protocol in order to make the compiler happy. Inside of ViewController's class, add this:
func didFinishSecondVC(controller: SecondViewController) {
self.myBoolVar = true
controller.navigationController?.popViewControllerAnimated(true)
}
In SecondViewController where we're calling didFinishSecondVC, we're calling this method inside of the ViewController class, the controller we're popping to. It's similar to if we wrote this code inside of SecondViewController but we've written it inside of ViewController and we're using a delegate to manage the messaging between the two.
Finally, in ViewController, in the function we're targeting to push to SecondViewController, add this code:
let secondVC = secondViewController()
secondVC.delegate = self
self.navigationController?.pushViewController(secondVC, animated: true)
That's it! You should be all set to pass code between two view controllers without using storyboards!
_ = self.navigationController?.popViewController(animated: true)
let previousViewController = self.navigationController?.viewControllers.last as! PreviousViewController
previousViewController.PropertyOrMethod
I came across this while looking for a way to do it. Since I use Storyboards more often, I found that I can get the array of controllers in the navigation stack, get the one just before the current one that's on top, check to see if it's my delegate, and if so, cast it as the delegate, set my methods, then pop myself from the stack. Although the code is in ObjC, it should be easily translatable to swift:
// we need to get the previous view controller
NSArray *array = self.navigationController.viewControllers;
if ( array.count > 1) {
UIViewController *controller = [array objectAtIndex:(array.count - 2)];
if ( [controller conformsToProtocol:#protocol(GenreSelectionDelegate)]) {
id<GenreSelectionDelegate> genreDelegate = (id<GenreSelectionDelegate>)controller;
[genreDelegate setGenre:_selectedGenre];
}
[self.navigationController popViewControllerAnimated:YES];
}
Expanding upon the answer by Abdul Baseer Khan:
For cases where the current view controller may have been loaded by different types of previous view controller, we can use the safer as? call instead of as!, which will return nil if the controller is not what we were looking for:
let previousVC = self.navigationController?.viewControllers.last as? AnExampleController
previousVC?.doSomething()
Although, you would need to repeat that for each different view controller that could load the current view controller.
So, you may want to, instead, implement a protocol to be assigned to all the possible previous view controllers:
protocol PreviousController: UIViewController {
func doSomething()
}
class AnExampleController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class AnotherController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class CurrentController: UIViewController {
// ...
func goBack() {
let previousVC = self.navigationController?.viewControllers.last as? PreviousController
previousVC?.doSomething()
}
}