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.
Related
I'm trying to call the reloadData() method to update a table view in an external class (different view controller), using the method viewDidDisappear().
I can already update the table view when the view where it is located is loaded or appears:
class OrderHistoryController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var orderTable: UITableView!
//called when order table is loaded or appears
override func viewDidLoad() {
super.viewDidLoad()
self.orderTable.delegate = self
self.orderTable.dataSource = self
self.orderTable.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
self.orderTable.delegate = self
self.orderTable.dataSource = self
self.orderTable.reloadData()
}
// ...
}
But I want the table to be reloaded when another view disappears.
class OrderDocumentationController: UIViewController {
override func viewDidDisappear(_ animated: Bool) {
OrderHistoryController().orderTable.reloadData()
return
}
// ...
}
I get the fatal error:
Unexpectedly found nil while implicitly unwrapping an Optional value.
Guess it is just a rookie mistake. Any ideas? Thank you in advance!
When you call OrderHistoryController().orderTable.reloadData(), this will create a new OrderHistoryController instance, that will have not orderTable outlet connected hence resulting in the crash.
There are differnt ways to achieve what you want:
Store a reference to the OrderHistoryController instance and use this
Maybe better: Implement some custom delegation mechanism
Use the NotificationCenter to send a message that then will result in a refresh
No need to reload orderTable if OrderHistoryController appears when OrderDocumentationController disappears. Since, self.orderTable.reloadData() is called in OrderHistoryController::viewDidAppear()
UPDATE:
A better approach would be to have OrderDocumentationController provide a block which is called when the modal controller has completed.
So in OrderDocumentationController, provide a block property say with the name onDoneBlock.
In OrderHistoryController you present as follows:
In OrderHistoryController, create OrderDocumentationController
Set the done handler for OrderDocumentationController as: OrderDocumentationController.onDoneBlock={[OrderDocumentationController dismissViewControllerAnimated:YES completion:nil]};
Present the OrderDocumentationController controller as normal using [self OrderDocumentationController animated:YES completion:nil];
In OrderDocumentationController, in the cancel target action call self.onDoneBlock();
The result is OrderDocumentationController tells whoever raises it that it is done. You can extend the onDoneBlock to have arguments which indicate if the modal completed, cancelled, succeeded etc.
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.
I'm trying to enable or disable an #IBOutlet UIButton Item of a toolbar from a UIView.
The button should get disabled when the array that I'm using in EraseView.Swift is empty.
I tried creating an instance of the view controller but it gives me the error (found nil while unwrapping):
in EraseView:
class EraseView: UIView {
...
let editViewController = EditImageViewController()
//array has item
editViewController.undoEraseButton.enabled = true //here I get the error
...
}
I tried to put a global Bool that changed the value using it in EditImageViewController but it doesn't work:
var enableUndoButton = false
class EditImageViewController: UIViewController {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
viewDidLoad() {
undoEraseButton.enabled = enableUndoButton
}
}
class EraseView: UIView {
...
//array has item
enableUndoButton = true //here I get the error
...
}
I know it's simple but I can't let it work. Here's the situation:
The root of the problem is the line that says:
let editViewController = EditImageViewController()
The EditImageViewController() says "ignore what the storyboard has already instantiated for me, but rather instantiate another view controller with no outlets hooked up and use that." Clearly, that's not what you want.
You need to provide some way for the EraseView to inform the existing view controller whether there was some change to its "is empty" state. And, ideally, you want to do this in a way that keeps these two classes loosely coupled. The EraseView should only be informing the view controller of the change of the "is empty" state, and the view controller should initiate the updating of the other subviews (i.e. the button). A view really shouldn't be updating another view's outlets.
There are two ways you might do that:
Closure:
You can give the EraseView a optional closure that it will call when it toggles from "empty" and "not empty":
var emptyStateChanged: ((Bool) -> ())?
Then it can call this when the state changes. E.g., when you delete the last item in the view, the EraseView can call that closure:
emptyStateChanged?(true)
Finally, for that to actually do anything, the view controller should supply the actual closure to enable and disable the button upon the state change:
override func viewDidLoad() {
super.viewDidLoad()
eraseView.emptyStateChanged = { [unowned self] isEmpty in
self.undoEraseButton.enabled = !isEmpty
}
}
Note, I used unowned to avoid strong reference cycle.
Delegate-protocol pattern:
So you might define a protocol to do that:
protocol EraseViewDelegate : class {
func eraseViewIsEmpty(empty: Bool)
}
Then give the EraseView a delegate property:
weak var delegate: EraseViewDelegate?
Note, that's weak to avoid strong reference cycles. (And that's also why I defined the protocol to be a class protocol, so that I could make it weak here.)
The EraseView would then call this delegate when the the view's "is empty" status changes. For example, when it becomes empty, it would inform its delegate accordingly:
delegate?.eraseViewIsEmpty(true)
Then, again, for this all to work, the view controller should (a) declare that is conforms to the protocol; (b) specify itself as the delegate of the EraseView; and (c) implement the eraseViewIsEmpty method, e.g.:
class EditImageViewController: UIViewController, EraseViewDelegate {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
eraseView.delegate = self
}
func eraseViewIsEmpty(empty: Bool) {
undoEraseButton.enabled = !empty
}
}
Both of these patterns keep the two classes loosely coupled, but allow the EraseView to inform its view controller of some event. It also eliminates the need for any global.
There are other approaches that could solve this problem, too, (e.g. notifications, KVN, etc.) but hopefully this illustrates the basic idea. Views should inform their view controller of any key events, and the view controller should take care of the updating of the other views.
I have some code which has to be available on all UIViewController of application. So I created a class UIViewControllerExtension: UIViewController, which will be extended by each class which I want to use as UIViewController. Which works as expected.
Now I have new screens where I have to use UITableViewController, so I can't extend same class UIViewControllerExtension, And to keep code centralized so I do not want to create another class UITableViewControllerExtension with same code, and want to have a common solution for both cases.
I tried various ways to extend generic class <T:UIViewController> so I can use it in both cases, but it didn't work (as it wouldn't compile). I did some research on internet but didn't find any solution to it. Does someone had same issue and have a solution?
I thought if there would be some solution like
class CommonViewController<T:UIViewController>: T{ //I know it doesn't compile
//...
}
Usage:
class MyHomeScreenViewController: CommonViewController<UIViewController>{
}
class MyItemListScreenViewController: CommonViewController<UITableController>{
}
I am open to any other solution if it solves my problem.
Edit: More details
1> I would like to extend viewDidLoad() method of UIViewController and UITableViewController in common way (no duplication of code as said before)
2> I would like to add some supporting methods to UIViewController (and UITableViewController), supporting methods like navigateBack, loginUser(name:String,password:String) etc..
A solution would be to extend UIViewController to add additional functionality to all UIViewControllers and override viewDidLoad in your own classes:
extension UIViewController {
func navigateBack() {
...
}
// an extension cannot override methods
// so this method gets called later in an overridden viewDidLoad
func viewDidLoadNavigate() {
...
}
}
// you own classes
class MyHomeScreenViewController: UIViewController {
// you have to make sure that all view controllers which can navigate override viewDidLoad
override viewDidLoad() {
// optional call to super
super.viewDidLoad()
// this is needed and called from the extension
viewDidLoadNavigate()
}
}
class MyItemListScreenViewController: UITableViewController {
override viewDidLoad() {
super.viewDidLoad()
viewDidLoadNavigate()
}
}
As you can see there is some code duplication but this is necessary since UITableViewController can also override viewDidLoad.
If generic inheritance is possible some day this code duplication can be reduced.
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.