Present another view immediately after dismissal - swift

In HomeKit, HMHome addAccessory() seem to present a controller of its own.
home.addAccessory(accessory) { error in
if let error = error {
else {
// present another view controller at this point
}
}
When completion handler is called everything should be done. But it doesn't seem like the controller has already been dismissed at that point.
If I tried to present a new controller immediately at the completion, nothing happens. Right now I seem to need to wait for some time before presenting a new controller, but that doesn't seem acceptable.
Is there anything I can do to queue up that operation?
If not, I guess I'll just have to present my next view with empty fields without animation before I try to add the accessory, and then fill in the information after the accessory has been added.

Are you in a view controller ?
In swift if you are in view controller to present another view controller your can use present
So you can write :
present(yourViewController, animated: true, completion: nil)

Related

Dismiss completly a view controller in middle

I've been looking for days. Here's the problem:
I have a Home view controller. When it receives a notification, it presents a NotiViewController, called noti1, by
`home.present(noti1, animated: true, completion: nil)`.
After 15sec, another notification comes, I get the top view controller topVC, present another NotiViewController, called noti2, by
`topVC.present(noti2, animated: true, completion: nil)`.
Each notiVC has an timer which waits for 30sec to dismiss itself. Now I have Home -> noti1 -> noti2.
After 15sec, noti1 runs out of time and has to be dismissed. How can I dismiss it without interupting noti2 which is being presenting?
I tried
`orderVC.beginAppearanceTransition(false, animated: false)
orderVC.willMove(toParent: nil)
orderVC.view.removeFromSuperview()
orderVC.removeFromParent()
orderVC.endAppearanceTransition()`
These code does remove the view from screen, but it leaves a UITransitionView behind, which blocks user actions.
This image is took after noti2 is removed from superview etc, so there are 2 UITransitionViews is presenting, blocking user actions.
Any idea? Thanks a lot.
Found a way. I'm not sure if I'm correct or wrong, but here is my thought: View controller must be dismissed the way it's presented before. For example if you use present(viewController, animated, completion) to present, you should use dismiss(animated, completion) to dismiss. The problem, in my case, is if you dismiss a view controller, all child view controllers will be gone too.
The way I resolved my issue is using another way to "show" the target view controller:
parentVC.view.addSubview(childVC.view)
parentVC.addChild(childVC)
childVC.didMove(toParent: parentVC)
After adding some view controllers, I got a tree parentVC -> child1 -> child2 -> child3... If I want to dismiss child2, the code in my question worked:
child2.willMove(toParent: nil)
child2.view.removeFromSuperview()
child2.removeFromParent()
There is no UITransitionView blocking user actions, but it got no animation. For my this is good enough.

Send message to parentViewController after dismissing UIViewController

This should be simple for some of you.
I am dismissing a UIViewController that is presented modally by another UIViewController from within itself. I am doing this using
[self dismissViewControllerAnimated:YES
completion:^{
// Want to access presentingViewController here.
}];
I tried logging self's navigationController, parentViewController and presentingViewController, but they all return null.
Apple's doc says :
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
According to this, the message is sent to the presentingViewController. I want to know how to catch this message. Is there a delegate call or how can I go about finding if the presentedViewController was dismissed.
There is no public API that would tell you this. You could listen to viewWillAppear: and figure out this way that the presented view controller is being dismissed, but that is messy. I will recommend what I always recommend and is considered best practice: never dismiss a view controller from itself! Create a delegate protocol for your presented view controller, set the delegate to the presenting view controller, and when there is a need to dismiss the presented from the presenting, call the delegate.
Why not put aside the presenting view controller, and access it inside the block after dismissal?
__weak id weakPresentingViewController = self.presentingViewController;
[self dismissViewControllerAnimated:YES completion: ^
{
id strongPresentingViewController = weakPresentingViewController;
if(strongPresentingViewController == nil) return;
//Do what you will with the presenting view controller.
}
This is the least error prone method, taking into account your current implementation. If the presenting view controller gets released before your block is called, you will not hit a bad access crash. Inside the block, you capture it strongly to make sure it is not released in the middle of your code.

Is there a way to avoid viewDidLoad beeing called after every segue?

i initialize tables, data etc in my main ViewController. For more settings, i want to call another Viewcontroller with:
DateChangeController *theController = [self.storyboard instantiateViewControllerWithIdentifier:#"dateChangeController"];
[self presentViewController:theController animated:YES completion:^{NSLog(#"View controller presented.");}];
And some clicks later i return with a segue (custom:)
NSLog(#"Scroll to Ticket");
[self.sourceViewController presentModalViewController:self.destinationViewController animated:YES];
My Problem:
After returning to my main ViewController, viewDidLoad is called (everytime).I read, that the ARC releasing my mainView after "going" to the other ViewController and calling the viewDidUnload Method, but i want to keep all my data and tables i initialize at the beginning..
Any solution? Thank you very much!
The problem is that you are doing this:
main view controller ->
date change controller ->
a *different* main view controller
In other words, although in your verbal description you use the words "returning to my main ViewController", you are not in fact returning; you are moving forward to yet another instance of this main view controller every time, piling up all these view controllers on top of one another.
The way to return to an existing view controller is not to make a segue but to return! The return from presentViewController is dismissViewController. You do not use a segue for that; you just call dismissViewController. (Okay, in iOS 6 you can in fact use a segue, but it is a very special and rather complicated kind of segue called an Unwind or Exit segue.)
If you do that, you'll be back at your old view controller, which was sitting there all along waiting for your return, and viewDidLoad will not be called.
So, this was a good question for you to ask, because the double call of viewDidLoad was a sign to you that your architecture was all wrong.
I think you're taking the wrong approach - viewDidLoad is supposed to be called when it is called - it's a notification to you that the view is being refreshed or initially loaded. What you want to do is move that table initialization code somewhere else, or, at least, set a Boolean variable so that it is only called once. Would it work to create an object that has your table data when viewDidLoad is first called, then to check it to see if it's already been called?

UINavigationController - Run Code Before Popping View Controller

I have a stack of UIViewController subclasses. Each modifies a NSManagedObject model. Many of them also present their own modal view controllers.
I need to save changes to the NSManagedObjectContext when a user either 'pops' the view controller or pushes the next view controller.
Currently, I'm hiding the default back button and setting my own UIBarButtonItem with a target of self and a custom action.
This works okay, but ideally, I want to use the default back button and run code before the pop. Is there a way I can run my own code before the pop?
(I'd prefer not to put code into viewWillDisappear as persisting to disk can be expensive and this method can also be triggered by modals being displayed by view controller.) Can it be done?
You can do it in viewDidDisappear, after checking that self is either 1) the second last element in self.navigationController.viewControllers (the case where the next VC just got pushed) or 2) self.navigationController is nil (the self VC just got popped).
Yes.. Navigation controller has a delegate which indicates when a view controller popped or pushed.. You can use that to do your task...
Add following method in your code:
- (void) viewWillDisappear:(BOOL)animated{
//your code here
}
I use viewWillDissappear to make any changes persistant.
If required i Use viewWillAppear to recoginze any changes (reload the data) that may have taken place while other puhed view controlers did their work.
For pop check isMovingFromParent in viewWillDisappear
func viewWillDisappear(_ animation:Bool){
super.viewWillDisappear(animation);
if isMovingFromParent {
// your code here
}
}

Delaying NSFetchedResultsController delegate methods

I have a UITableView based on a NSFetchedResultsController. To insert a new row into the table, I open up a modal view controller, and I then hit the save button which dismisses the modal view and causes my NSFetchedResultsController delegate methods (willChangeContent, didChangeObject`, etc) to fire, which animates the inserting of a new cell. All is fine, but I want the user to witness this animation, and by the time the modal view has disappeared, the animation has already completed.
How can I delay this animation until the modal view completely disappears, so that the user can witness the animation?
This is a good question with or without the NSFetchedResults controller - you have a table vc that's observing a model, and you want the user to see an animated change after a pop or dismiss from another view controller.
There's probably a better way, but the thing I did in a similar situation recently was to make the table vc do the model update itself, based on a delegate message from the subsidiary (pushed or modally presented) vc.
So, in the table vc:
AddingVC *addingVC = [[AddingVC alloc] initWithDelegate:self];
[self presentModalViewController:addingVC animated:YES];
// adding to the model will happen in this vc, based on a delegate message
- (void)addingVcDidCreateAnObjectToAdd:(id)objectToAdd {
// add to your model here
}
The adding vc does this (and I'm not totally proud of this, but it works)...
- (void)thingIsReadyToAdd {
SEL selector = #selector(addingVcDidCreateAnObjectToAdd:);
[self.delegate performSelector:selector withObject:objectToAdd afterDelay:1.5];
// 1.5 is on the long side, since the vc transition is about 0.5, so 1.0 is okay
}
In my case, I used a more conventional delegate protocol, passing the addingVC as the first param, but doing so with delay requires a verbose NSInvocation, so I skipped it here. +1 for the question that's bothered me, too. I'm curious about others' solutions.