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.
Related
I need to open the (Detail view) when I open the application from push notifications, I tried to find the answer but I could not.
I attached the image that shows the View I need to open (green view). Please guide me.
You can try below approch:
Add this method in the AppDelegate Class:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
// Open Your Details view controller directly using segue
// You can pass details id and then fetch details and display in the view.
SettingViewController *moreVC = [[SettingViewController alloc] initWithNibName:#"SettingViewController" bundle:nil];
UINavigationController *navigationRootController = [[UINavigationController alloc] initWithRootViewController:moreVC];
[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:navigationRootController animated:YES completion:NULL];
}
This is another approch:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
// Register the below observer in the rootviewcontroller so its registered first and than Pass NSDictionary
[[NSNotificationCenter defaultCenter] postNotificationName:#"pushModalPopUp" object:userInfo];
}
Window.rootView > A > B > C
This is a simple approach:
When you receive notification, add a key to NSUserDefault (e.g: "NeedShowViewC" = true), then load view controller A
In A view controller's viewDidLoad, check for that key ("NeedShowViewC"), if it is true, load view B, and so on.
When you finish showing view controller C, set that key back to false or remote it.
Just a note on Code Hunterr's 1st approach:
Initiating a new viewcontorller (your target "green" VC) + new UINavigationVC:
SettingViewController *moreVC = [[SettingViewController alloc] initWithNibName:#"SettingViewController" bundle:nil];
UINavigationController *navigationRootController = [[UINavigationController alloc] initWithRootViewController:moreVC];
These will create new instances of those two VCs. Although this might not be a problem functionally for you, it could potentially create a memory management issue. If a user is already viewing the green details view when they close the app, then receive a push, and as a result open the app and you auto-navigate them to the green VC again you'll have 2 instances of the green VC in memory (you can check the Xcode debugger memory map to see this in practice). If you do this again and again and the ARC doesn't release the previous instances of the VCs (depends on your MVC implementation), you might end up bloating your memory usage.
Also, displaying the details VC directly without the table view before will mess up your 'backwards' navigation as the new NavVC you just created wouldn't have the table view in its VC stack.
The alternative is:
1) Get the push notification using either didReceiveRemoteNotification() or the new (in iOS 10) UNUserNotificationCenterDelegate
2) Using the app's root view controller, grab the navigation controller of your first tab -> the one that leads to your green details view.
3) Then roll back your navigation stack-> this will make sure that no matter what VC/view the user was last open on, your starting the navigation from scratch and de-allocating the details VCs (or any future VCs your add that are deeper than the tableVC) before allocating a new one.
4) Then tell the tableVC, which is the rootVC of the navVC you just grabbed to show the details of the row you need. You can do this by adding a public func/API to the tabelVC or use a notification that the tableVC observes (as suggested in Code Hunterr's 2nd approach).
Example (its in swift but it should be easy to translate to ObjC):
//grab the navVC - in this example I'm assuming your tabVC is the rootVC of the app you need the 1st VC of the tabVC's array
if let topNavController = UIApplication.shared.keyWindow?.rootViewController?.viewControllers.first as? UINavigationController {
//grab the content VC (which is your table VC)
if let topContentController = topNavController.viewControllers.first as? MyAppsTableView {
//pop the navVC stack all the way back to its root (the table)
topNavController.popToRootViewController(animated: false)
//tell your table view to show detail of a certain row
topContentController.pleaseShowItem(item: xyzzy)
}
}
I want to pass string data from second view to first view.
My first view contains a UITableView with 4 rows. If user taps on 1st row, programmatically, I push a new view controller.
For example:
if(indexPath.row == 0)
{
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"NewView"];
[self.navigationController pushViewController:controller animated:YES];
}
My second view is a tableviewController, wherein user can select any one option from second view which should get passed to first view.
There is no back button as special; since I have used Navigation controller I get navigation back button.
So when user presses navigation back button, data from second view should get passed to first view.
There is multiple ways for doing that.
You can use NSUserDefaults for storing data.
You can use Delegate Methods.
I would recommend against using NSUserDefaults as you do not need persistent behavior, you want to handle messaging between 2 objects. NSUserDefaults are designed to support customization based on a user's preferences. See the snippet from the documentation below.
I would recommend delegation as it is a design pattern used throughout Apple's code and would be very beneficial for you to know well!
The NSUserDefaults class provides a programmatic interface for
interacting with the defaults system. The defaults system allows an
application to customize its behavior to match a user’s preferences.
For example, you can allow users to determine what units of
measurement your application displays or how often documents are
automatically saved. Applications record such preferences by assigning
values to a set of parameters in a user’s defaults database. The
parameters are referred to as defaults since they’re commonly used to
determine an application’s default state at startup or the way it acts
by default.
There are a number of ways of going this.
Create a weak instance variable in the second view that references the first view and use that to get the data to the first view: weak ViewController* firstView. Remember to add an instance variable to capture the data to firstView.
This works but introduces tight coupling between the views. They are now dependent on each other to meet the required functionality.
if (indexPath == 0) {
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"NewView"];
MyController *myController = (MyController *)controller;
myController.firstView = self;
[self.navigationController pushViewController:controller animated:YES];
}
Use a block as a completion handler the second view can use call once the data in question has been selected. Define a block on the secondView. Now in your if statement set the completion handler block o the secondView. Now when the data is selected call the completion handler block.
Use the delegate pattern and define a delegate protocol and delegate property for secondView. The firstView will implement the delegate protocol. So when the data is collected you would call in the secondView.
[delegate SecondViewDidCollectData:myData];
Property on SecondView
#property (weak, nonatomic) id<SecondViewDelegate> delegate;
Delegate Protocol defined in SecondView header.
// define the protocol for the delegate
#protocol SecondViewDelegate
// define protocol functions that can be used in any class using this delegate
-(void)SecondViewDidCollectData:(NSData *)data;
#end
You can achieve this using following ways;
You can use NSUserDefaults for storing data.
You can pass the data like below :
yourViewController *vc = [[YourViewController alloc]init];
vc.xyzString = "yourStringToPass";
[self.navigationController popToViewController: vc animated:YES];
Hope this will help.
Add below Code in your tableView didSelectRowAtIndexPath Method.
[self.navigationController.viewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if([obj isKindOfClass:[firstViewController class]]) {
firstViewController *objfirstViewController = obj;
objfirstViewController.xyzString = "yourString";
[self.navigationController popToViewController: objfirstViewController animated:YES];
*stop = YES;
}
}];
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.
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)
I am trying to log data from the FirstView (one text field and one button) to SecondView (UiTableView) for example contacts list. I would like to briefly explain my code.
I have added a NavigationController with TabBar in the AppDelegate.m;
UINavigationController *pointer = [[UINavigationController alloc] init];
self.window.rootViewController = pointer;
[pointer setNavigationBarHidden:NO];
[pointer pushViewController:self.viewController animated:YES];
And I have used a [self.navigationController pushViewController:push animated:YES]; to push the data to the second screen but it always go to another NavigationController view as loop with BACK button, then I have used [self.navigationController popToRootViewControllerAnimated:YES]; which it does not pass the data to the another view. Unless I close the program completely an open it back again. So is there a way to push and pop at the same time? Or could you please show me another way?
Thanks in advance.
Note I have shared my code in this link;
Mycode
You can try to create a singleton class that manages this data. Then you just call the same singleton class throughout your code, it should keep the same state throughout the lifetime of your app.
Or, a simpler way is to Set up "myArray" as a property in your App Delegate.. Then, in your class' implementation file, do something like this:
UIApplicationDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.myArray = newArray;
this assumes myArray and newArray has been properly initialized.