Objective-C : Keep a View Controller active when dismissed? - ios

In my chatting application, I have a ChatViewController.m that allows users to message with the QuickBlox framework.
When a user sends an image, a background upload begins and a UIProgressView displays the progress of the upload.
But what if the user backs out of that view during the upload, and returns in, say, 10 seconds while the upload is still happening. I want the UIProgressView to still be active and accurate based on that upload. But dismissing the ViewController doesn't allow me to do that.
Can someone suggest what I should be doing in this situation?
EDIT: This is how I present the ChatViewController.m, depending on the chat selected from the CollectionView:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.destinationViewController isKindOfClass:ChatViewController.class]){
ChatViewController *destinationViewController = (ChatViewController *)segue.destinationViewController;
if(self.createdDialog != nil){
destinationViewController.dialog = self.createdDialog;
self.createdDialog = nil;
}else{
QBChatDialog *dialog = [ChatService shared].dialogs[((UICollectionViewCell *)sender).tag];
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
appDelegate.dialog = dialog;
}
}
}
EDIT 2: I have implemented the ViewController as a singleton in my didSelectItemAtIndexPath. But now, the app presents only a black screen.
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
QBChatDialog *dialog = [ChatService shared].dialogs[indexPath.row];
ChatViewController *chatView = [[ChatViewController alloc] init];
chatView.dialog = dialog;
[self presentViewController:chatView animated:YES completion:nil];
}

You should change the way you display your chat view controller. Don't use a segue.
Instead, set up your chat view controller as a singleton. Set up an IBAction or other code triggered by the user selecting an item in your collection view.
In that IBAction or didSelectItem code, fetch a reference to your singleton, configure it as needed, and present it modally yourself using presentViewController:animated:completion:
That way your view controller will preserve it's contents between views.
As the other poster said in a comment, you might pull the download logic out of your view controller and into a separate download manager class. It depends on whether you need the ability to do asynchronous downloads in places other than your chat view controller.

I'm assuming based on your question that you are creating the view controller each time that you are presenting it. Instead of re-allocating and creating a new view controller each time you present the messaging view, make only one view controller that you can call and present from any other view controller.
A couple possible ways to do this are:
Create a singleton that has the messaging view controller as a property
Add the messaging view controller as a property on your route view controller
Make the messaging view a singleton itself so only one gets created in the entire life of the application
Doing any of these will make sure that the view persists each time that it's dismissed.
If that still doesn't fix the problem, you may be resetting the view in viewDidLoad or viewDidAppear which I don't think is actually what you want to do.

Related

How to use presentViewController to go to a viewcontroller and perform or invoke a method in it in ios

I have a couple of "ViewControllers" and one for Update a picture in an iOS app.
The first one has a button when tapped asks if the user wants to use gallery photo or camera.
Now i am presenting this controller by using presentViewController on self.
But when the second view controller is presented i want to set the UIImagePicker source according to what the user has passed in.
I have made 2 different methods. one with camera source and one with "photoslibrary".
I don't know how to invoke one of these methods based on the use choice from the previous controller.
Am i going the right way with this approach? or should i just have one controller?
Basically there are two ways of passing data to a view controller.
Storyboard
If you are working with storyboard segues (that is, control drag from your "root" view controller to the destination view controller, choose a transition style and define a identifier), you can present the view controller
via
[self performSegueWithIdentifier:#"yourSegueIdentifier" sender:self];
Most of the times your destination view controller will be a custom class, so define a public property to hold the data you want to pass through. Then implement the following in your "root" view controller
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Setup the location menu delegate
if([segue.identifier isEqualToString:#"yourSegueIdentifier"]) {
// The custom class of your destination view controller,
// don't forget to import the corresponding header
ViewControllerCustomClass *vc = segue.destinationViewController;
// Set custom property
vc.chosenImageId = self.chosenImageId;
// Send message
[vc message];
}
}
Hints:If your destination view controller is the root view controller of a navigationViewController you can access it via [[segue.destinationViewController childViewControllers] objectAtIndex:0]; Additionally, as senderis an id, you can "abuse" it to pass any object through, just as a NSDictionary, for example.Also note, that when I am referring to the root view controller, I am talking of the view controller from which we segue to the destination from.
Programmatically
ViewControllerCustomClass *vc = [[ViewControllerCustomClass alloc] init];
vc.chosenImageId = self.chosenImageId;
// If you want to push it to the navigation controller
[self.navigationController pushViewController:vc animated:YES];
// If you want to open it modally
[self presentViewController:vc animated:YES completion:nil];
You can use inheritance, make your previous controller superClass, and invoke method in presentViewController in viewDidload.

UINavigationController: presenting view controller while dismissing another controller is in progress on iPad

I have a view that requires user to be logged in. When the user attempts to open that view an he is not logged in I will call the login view for him to login and after he is done I will call the original view that he intended to see.
On iPhone this works fine as I push view controllers there.
But on iPad where I present view controller this does not work. It says that dismissal in progress, can't show new controller. Here is the code:
- (void) buttonPressed
{
if (!userLoggedIn) { // userLoggedIn getter calls new screens of login if needed
return; // this is executed if user declined to login
}
MyViewController *temp = [[MyViewController alloc] init];
[self.navigationController presentViewController:temp animated:YES]; // this returns warning that dismissal in progress and does not work
}
What can I do about that? On iPhone all of my logic works fine, but on iPad it fails. I use it in many places and completely rewriting code is not good.
EDIT: more code:
- (BOOL) userLoggedIn {
// code omitted
[centerController presentViewController:navController animated:YES completion:nil];
// code omitted
[centerController dismissViewController:navController animated:YES]; // setting to NO does not fix my problem
return YES;
}
EDIT2:
This is the code for iPad. I have removed iPhone-related code. What it does on iPhone - instead of presenting controller it uses pushing, and in that situation everything works fine.
You cannot present another view as long as the dismissing of your 1st view is not complete. The animation of dismissing view should be completed before presenting new view. So, either you can set its animation to NO while dismissing, or use
performSelector:withObject:afterDelay:
and present the next view after 2-3 seconds.
Hope this helps.
You've not posted enough code to really see what you're doing, but one approach to the problem of dismissing and pushing view controllers clashing in this way is to make a the pop+posh into a single atomic operation operation, rather then seqential operations.
You can do this by using the setViewControllers:animated: method on UINavigationController. This allows you to effectively remove one or more view controllers, and add one or more view controllers, all as one cohesive operation, with one seamless animation.
Here's a simple example:
[self.navigationController pushViewController:loginController];
// ... later on, when user login is validated:
NSMutableArray *viewControllers =
[self.navigationController.viewControllers copy];
[viewControllers removeLastObject];
[viewControllers addObject:[[MyNewViewController alloc] init]];
[self.navigationController setViewControllers:viewControllers animated:YES];
If you do things this way, your code will be more predictable, and will work across iPhone and iPad.
For more info, see the API docs.
Update
Since your problem involves a modal dialog on top, try using setViewControllers:animated:NO to change the nav controller stack underneath the modal login dialog before you dismiss the modal.

iOS Tabs speaking to each other

I have an app where there are two basic functions, delineated by being on separate tabs on a UITabBarController. Each tab has a particular button that when pressed should, as a separate function to its normal activity, also asynchronously tell the other tab to nil its data. How can I access one tab from the other?
This would be a good use for notifications (as in NSNotification, not local or push notifications).
You should have some sort of model for each view controller in each tab. Each model can publish its notification while registering for the other. This way, neither view controller or model needs to actually know about the other directly.
When the user taps a button, the view controller tells its model to publish its notification. The other one will get the notification and act accordingly.
See the docs for NSNotificationCenter and NSNotification for details.
Definition of "tabs" for UITabBarController
// define controllers for each tab
UIViewController *viewController1 = [[UIViewController alloc] init];
UIViewController *viewController2 = [[UIViewController alloc] init];
// define tab bar controller, "self" is a UITabBarController
self.viewControllers = [NSArray arrayWithObjects: viewController1, viewController2, nil];
From this point, if you need to access a particular tab, you want to do so via the "viewController1" or "viewController2" objects. Each of these UIViewController objects would presumably have access to certain data in your application.
You can access each of your viewControllers from TabBarController's viewControllers property, and iterate through them. Zero all but the live one (self).
Put it into a GCD dispatch queue for asynchronicity.
- (IBAction)pushButton:(id)sender {
NSLog (#"%# %#",self,NSStringFromSelector(_cmd));
//do normal stuff here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (id viewController in [[self tabBarController] viewControllers]) {
if ((viewController != self)
&& ([viewController respondsToSelector:#selector(zeroData)])) {
[viewController performSelector:#selector(zeroData)];
}
}
});
}
- (void) zeroData
{
NSLog (#"%# %#",self,NSStringFromSelector(_cmd));
//each view controller should zero out it's own data
//in a method called "zeroData"
}
If you try this and look at the logs, you will see that it leaves the current vc alone but sends zeroData to the others...
Are your tabs core-data driven? If you use a NSFetchedResultsController then you get notifications for free through the NSFetchedResultsControllerDelegate protocol. Otherwise, NSNotification like rmaddy suggests.

How to keep an NSArray available when switching to other view controller

Hi in VC1 I have an NSMutableArray displaying results. I want to keep that array alive even when users clicks to a different tab (so they don't have to search again) until the user searches again.
I have a strong pointer to it, but it seems to unload when I leave the view.
Not much code to show (_resultsArray is set from a previous controller using delegates, so it loads with the results already)
- (void)viewDidLoad
{
[super viewDidLoad];
_resultsTableView.dataSource=self;
_resultsTableView.delegate=self;
[self.navigationController.navigationBar setHidden:YES];
}
//then standard tableview delegate methods...
This code is to try to figure out how to segue tab bar to share info.
(in prepareforsegue)
Currently in Search VC. Now I have results I want to give to resultsIndexVC. The code below attempts this.
This is placed in current (search VC) prepare for segue.
ResultsIndexViewController* vc = [[ResultsIndexViewController alloc] init];
UITabBarController* tbc = [segue destinationViewController];
vc = (ResultsIndexViewController *)[[tbc customizableViewControllers] objectAtIndex:1];
vc.SearchDelegate=self;//crash here (normally works if using regular segue)
vc.resultsArray = _temporaryResultsArray;
vc.originalQuery=_queryArray;
Thanks
Issue is that I was pushing a VC. Instead I used the tabbar (which doesnt release the object when switching tabs)

How do you set a delegate for a protocol in a viewcontroller in another tab?

I would like some expert MVC design feedback please:
I have a UITabBarController with 2 tabs, each of the tabs leads to a Nav controller with a stack of VC.
The last view Controller on the 1st tab path will show an image. I would like that image to be stored in a table and viewed anytime the second tab is selected.
How do I send this image from the 1st tab-> NaVController->last VC (image VC) to 2nd tab -> NavController->table VC?
I have a couple of options:
1- Create a class method in the tableVC and have the imageVC call that class method and pass that image to be saved directly into user defaults. This seems to go against MVC
2- Create a protocol in imageVC with a method and delegate property and have the table VC adopt that method to save the image in an array. The problem here is that the only place to set the delegate is in ViewDidLoad:
[[[[self.tabBarController.viewControllers objectAtIndex:0] viewControllers] objectAtIndex:1] setDelegate:self];
The problem here is that if the user select the second tab from the start, the app will crash because obviously the viewControllers have not been loaded on the nav stack for the first tab. In the same token, if the user sees 1 image first and then selects the second tab, it will execute and set the delegate but without saving that first selected image.
There must be an easier way.....
Thanks in advance
KB
I think that you should separate your data model from your view controllers a little more. Following the MVC approach, a model should not know about how its represented. So, making a change to the model in ImageVC should not involve direct calls to any other VC.
I'd create a separate entity, e.g. SelectedImages, and add images from ImageVC to it, and have TableVC read from it when it needs to display itself. SelectedImages could be a property in the app delegate, a Core Data entity, a singleton class, or something like that.
You could have the AppDelegate be the delegate for both of the VCs. Then when first tab selects an image it can do some sort of [self.delegate setTheImage:theImage]; call. And the second tab can have a call to its delegate (the AppDelegate) that goes something like theImage = [self.delegate getTheImage];. If an image is not yet selected then have the AppDelegate pass nil and check for theImage == nil before displaying the second tab (and maybe put up some sort of interface object that let's the user know what is going on).
Here is some code for setting up the AppDelegate as the Delegate:
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
self.delegate = appDelegate;
Then your AppDelegate can have a property for the image like 'theImage':
-(void) setTheImage:(UIImage *)image:
{
self.theImage = image;
}
-(UIImage *) getTheImage
{
if(self.theImage)
return self.theImage;
else
return nil;
}
Hope that helps.
option 2 variant :
The MainVC could adopt the ImageVC protocol and set the image to TableVC.
TableVC have to be MainVC attribute

Resources