Load specific View after push notifications - ios

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)
}
}

Related

UINavigation Controller subclass and push Segues

I've been looking this up for a while now, it might have a simple answer:
According to the Apple docs, past ios6, we can subclass UINavigationController. How do we perform a segue from identifier when it prevents anything that isn't a UINavigationController. Mainly:
uncaught exception 'NSGenericException', reason: 'Could not find a
navigation controllerfor segue 'profileSegue'. Push segues can only
be used when the source controller is managed by an instance of
UINavigationController.
I'm using JaSidePanels and my center panel (navigation) needed to be subclasses for a delegate as there is a menu on the left panel that I want to switch the views when clicked.
#interface CenterViewController : UINavigationController <MenuDelegate>
Basically, since this object is a CenterViewController at runtime, is there a way to cast it to its superclass? I've tried [self superclass] but that didn't work (same error).
I need to call this code in the CenterViewController. Is it possible to move it to UINavigationController?
- (void)viewDidLoad
{
RootViewController *rootViewController = (RootViewController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
MenuViewController *leftViewController = (MenuViewController *)rootViewController.leftPanel;
// Store a reference to the center view controller in the left view controller's delegate property
leftViewController.menuDelegate = self;
[super viewDidLoad];
}
- (void) MenuItemSelected: (NSString*) item
{
if ([item isEqualToString:#"Home"]) {
//These would throw the error since we're not technically a "UINavigationController"
//[self performSegueWithIdentifier: #"mapViewController" sender: nil];
} else if ([item isEqualToString:#"Profile"]) {
//[self performSegueWithIdentifier: #"profileSegue" sender: self];
}
}
EDIT:
More information with pictures.
I'm curious as to how navigation controllers should work with side panels. I've looked at many other plugins for sidepanels and here is an example. Even though it works, why does it have 2 nav controllers?
Here is our current setup:
So basically, am I thinking about this wrong in the sense that I want to push a new VC to the existing NavVC? Would it be better to push a new NavVC when a menu button is pressed? What would happen when we go into a subview from the Maps view. Would the menu be accessible via sliding?
If you look carefully at the message you will see that your problem isn't caused by subclassing UINavigationController it is because you are executing the segue against your (subclassed) UINavigationController. Push segues are executed against a UIViewController that is embedded in or managed by a UINavigationController, with the system then finding the managing UINavigationController via the view controller's navigationController property in order to execute the push.
The message you have received says
...Push segues can only
be used when the source controller is managed by an instance of
UINavigationController
In your case the source controller is an instance of UINavigationController, it isn't managed by a UINavigationController.
You haven't said exactly how your app navigation works, but I have a suspicion that a UINavigationController isn't the right tool to use anyway. If you are using the side menus to allow the user to select the central content in a random way (i.e. the user could select the first option then the fifth and then go back to the first) then you should probably just have a central view into which you present the selected view. Pushing views onto a UINavigationController will end up with a large 'stack' of views unless you pop the current view controller before pushing the new one, which is even more complicated and not the visual effect you are looking for.
You can still achieve the 'push' style transition but you will need to use a custom segue. If it were me I would probably push from the left if the user selected a menu item that was closer to the top than the current option and from the right if the new item was closer to the bottom than the current, but again I am making assumptions on how your app navigation works.
UPDATE
Yes, I think you are on the right track with the section of your updated question. Navigation controllers are for navigating a series of related views in a hierarchical manner - think of the Settings app - you select "general" or "wall paper" or whatever - each of these then has a series of views that you can navigate through; up and down a stack.
In your app it looks like home, profile and settings should each be navigation controllers. Your root view would then just be a view. Your menu would select which view controller to present in the root view - this is like a tab bar controller, except your menu takes the place of a tab bar.
You can allocate your home, profile & settings view controllers in your appDelegate and store them to properties of the appDelegate. Then you can use something like this:
- (void) MenuItemSelected: (NSString*) item
{
myappDelegate *app=(myappDelegate *)[UIApplication sharedApplication].delegate;
[delegate.currentViewController removeFromParentViewController];
UIViewController *newController;
if ([item isEqualToString:#"Home"]) {
newController=app.homeViewController;
} else if ([item isEqualToString:#"Profile"]) {
newController=app.profileViewController;
}
if (app.currentViewController != newController)
{
[app.currentViewController removeFromParentViewController];
[app.rootViewController addChildViewController:newController];
app.currentViewController = newController;
}

How to restore an iOS 7 app to a ViewController on cold start

I have coded my app to return to a specific view controller upon relaunch. I want it to pull data from Core Data. It works fine if it the app is just backgrounded. But if the app is terminated and starts cold, it goes to the top level view controller designated in AppDelegate. I've searched StackOverflow and other forums, but have not found anything addressing this particular issue.
The app delegate has opted in. I'm using storyboards. The view controller I want to re-launch to has a restoration ID in the storyboard. The view controller also has implemented encodeRestorableStateWithCoder and decodeRestorableStateWithCoder. I've set it up as Apple recommends, but it's not working on cold start.
I have 3 questions:
How do I force the app to relaunch to this specific view controller (one below the top level) on a cold launch?
Once it relaunches to the target view controller on cold launch, how do I restore the core data? Should the core data be written to disk and restored from there? Or can I just write enough info to disk (i.e., key data) to go pull the data from core data?
Is there a good tutorial for restoring from a cold launch? The ones I've found are only restoring transient data and not showing how to return a non-top level view controller.
Thank you in advance!
Adding applicable code from AppDelegate. If the user was on a profile details screen, I want to return them there even from a cold start. I'm able to confirm that the profile ID is being written to disk and I'm reading it. I do not have "Is Initial View Controller" checked for ProfileVC on the storyboard. But it still returns to ProfileVC no matter what.
NSDictionary *settings = [self readAppState];
if ([settings objectForKey:PROFILE_ID_DICT_NAME]) {
NSLog(#"AppDelegate: didFinishLaunchingWithOptions: PROFILE_ID_DICT_NAME = %#",
[settings objectForKey:PROFILE_ID_DICT_NAME]);
ProfileDetailsVC *controller = (ProfileDetailsVC *) navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
} else {
NSLog(#"AppDelegate: didFinishLaunchingWithOptions: PROFILE_ID_DICT_NAME is null");
ProfileVC *controller = (ProfileVC *)navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
}
You must assign a unique restoration identifier to all view controllers which need to be restored. This includes:
The ones you're really interested in
The root view controller
Any other view controller in between
So if you have a tab bar controller for example, with a tab containing a nav controller and it has a root controller, all 3 must have restoration identifiers for the root controller to be restored.
You can do something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
//do your core data load here
if(your core data condition){
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"someViewController"];
self.window.rootViewController = viewController;
} else {
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"someOtherViewController"];
self.window.rootViewController = viewController;
}
[self.window makeKeyAndVisible];
// Override point for customization after application launch.
return YES;
}
Careful, instantiateViewControllerWithIdentifier refers to the storyboard identifier not the class name.
First of all look at this tutorial
I think you can find something useful here NSCoding
And about switching between view controllers. Can you provide more details so we can help you with better solution. What is the purpose of switching from cold start? Fore example if user logged in or not.
UPDATE
Given that you are having data base to store all profiles, no matter local or remote database. When user opens profile you can save profile's id (id local data base or in user defaults) and then when application have started add check in route view controller
NSString *savedProfile = [[NSUserDefaults standartDefaults] valueForKey:#"savedProfile"];
if (savedProfile) {
//retrieve needed profile
//open view with this profile
}
For anyone that may be interested, here's how I solved this problem.
I'm going from ProfileVC --> ProfileDetailsVC under normal circumstances. However, if a user was on ProfileDetailsVC when the app was killed, I want to return them to it. The data on both screens is populated from core data.
Most of the tutorials I found show how to restore specific fields on a screen that is restored, but I couldn't find one to do exactly what I was trying to do.
Here's the pseudo code:
ProfileVC:
viewWillAppear:
if (savedProfile)
get profile ID from disk
read core data to get profile
set profile in ProfileDetailsVC
performSegueWithIdentifier(ProfileDetailsVC)
ProfileDetailsVC:
willMoveToParentViewController:
remove profile ID from disk
viewDidLoad:
save profile ID to disk
I can post actual code if anyone needs it.

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.

Using Split View Controller and Navigation Controller As Window Root View Controller Alternately

i have a navigation controller which is my application's root view controller. it is my main screen. I have a split view controller whose master view lists location and detail view shows the location on the map. I wanted to push the split view controller to the navigation controller, but it throw a error saying split view controller cant be pushed to navigation controller and it must be application's root view controller.
So i tried a hard way.
MyAppDelegate *myappdelegate= [[UIApplication sharedApplication] delegate];
UISplitViewController * vc= [[UISplitViewController alloc] init];
vs.viewControllers = [NSArray arraywithObjects......
....
myappdelegate.window.rootViewController= vc;
This works. It shows split view controllers without animation as expected. And i do the same steps when i was closing split view controller. i am creating a navigation controller with main screen nib and setting this my app delegate's window.rootviewController again.
And it succesfully loads main screen again.
But i suspect that this is the proper way of achieving this. Is there more eligible way of doing this?
And i couldnt release split view controller's child controllers. i made a breakpoint on my release method of child controllers. it wasnt be catched. I assumed that when i set my app's root view controller as navigation controller, the old root view controller (split view controller) must be released along with its childs.
Then i tried below code when closing split view controller.
UISplitViewController *oldviewcontroller= (UISplitViewController*) myappdelegate.window.rootViewController;
for (UIViewController *child in oldviewcontroller.viewControllers)
{
[child release];
}
//Setting the navigation controller as window.rootviewController again in here.
This code throw an error saying "[UIImageView __viewDidDisappear:]: unrecognized selector sent to instance 0x7d...."
i think because of releasing the view already, there is no imageview on viewdidDisappear event.
In brief, my question is that am i using right method to achieve this? If so, how can i successfully release all child view controllers?
I finally found a way. I probably found the error. What i have done is cutting the branch on which i am sitting. I was releasing the view controller which i am currently in:) When viewdidDisappear called, there is no such view controller. Because i throw away it to space already.Below is my working steps. I hope it will be useful to someone. But i cant stand thinking of apple may reject my app. I wish finding a suitable way.
This is my working ultimate way of using split view controller and navigation controller as window root view controller alternately.
Firstly i defined NavigationController and SplitViewController property in AppDelegate interface.
AppDelegate.h
#property (assign,nonatomic) UINavigationController * NC;
#property (assign,nonatomic) UISplitViewController *SVC;
Secondly i assign newly created NC on AppDelegate didFinishLaunch event.
AppDelegate.m
//Creating my main screen controller
//Creating my navigation controller with my view controller instance. Then
self.NC= my_navigation_controller;
self.window.rootViewController= self.NC;
Thirdly creating a splitview controller and setting as app's root view controller
MyMainScreen.m
-(void) OpenSplit()
{
//Creating my master view controller of SVC
//Creating my detail view controller of SVC
//Creating my SVC;
AppDelegate * app_delegate= [[UIApplication sharedApplication] delegate];
app_delegate.SVC= newly created my SVC;
app_delegate.window.rootViewController= app_delegate.SVC;
}
Fourthly releasing unused NC in viewDidLoad event of detail view of SVC.
MyDetailView.m
- (void) viewDidLoad()
{
...
AppDelegate * app_delegate= [[UIApplication sharedApplication] delegate];
app_delegate.NC= nil; //i dont need it now. i am releasing. Releasing Navigation Controller release as well child controllers. I learned with testing.
}
Fifthly managing close split view function.I used UIBarButton on NavigationBar in DetailView.
MyDetailView.m
-(void) closeSplitView
{
//Creating navigation controller with my main screen view controller
AppDelegate * app_delegate= [[UIApplication sharedApplication] delegate];
app_delegate.NC= newly_created_NC;
app_delegate.window.rootViewController= appdelegate.NC;
}
Sixthly handling unused split view controller in Main Screen viewDidLoad event.
MyMainScreen.m
-(void) viewDidLoad
{
AppDelegate * app_delegate= [[UIApplication sharedApplication] delegate];
app_delegate.SVC= nil; //I am releasing it because i am working with NC now.
}

Navigation Controller is null

I have a split-view app that allows a user to select and display a thumbnail of a chosen image. I have placed a UIButton in the detailViewController using Interface Builder. When this button is pressed, I would like to have it change to a full screen view of the image. I have set up a new View Controller, called FullViewController and thought I had everything connected. The problem is that the navigation controller is null. I adjusted the AppDelegate.m to the following:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch.
// Set the split view controller as the window's root view controller and display.
self.window.rootViewController = self.splitViewController;
UINavigationController *nvcontrol =[[UINavigationController alloc] initWithRootViewController:fullViewController];
[window addSubview:nvcontrol.view];
[self.window makeKeyAndVisible];
return YES;
}
This is the function in the DetailViewController.m which is called when the button is pressed. The navigation controller comes up null in here.
//Function called when button is pressed - should bring up full screen view
- (IBAction) pressFullViewButtonFunction: (id) sender{
//viewLabel.text = #"Full View";
if (fullViewController == nil){
FullViewController *fullViewController = [[FullViewController alloc] initWithNibName:#"FullViewController" bundle:[NSBundle mainBundle]];
NSLog(#"fullViewController is %#", fullViewController);
self.fullViewController = fullViewController;
}
NSLog(#"self.navigationController is %#",self.navigationController);//this is null
[self.navigationController pushViewController:self.fullViewController animated:YES];
}
I'm not sure how to fix this. I've tried adding in the couple lines in the AppDelegate, but when it runs, the table in the root view doesn't show up and it no longer properly switches between portrait and landscape views.
I have the rest of the code readily available if that would help clarify. Just let me know!
Thanks.
From the code you post it is not possible to identify the problem, but two common reasons for self.navigationController to be nil are:
you did not push the object behind self on to the navigation controller in the first place; indeed it seems so, since the navigation controller is added as a subview of the split view controller; possibly you mean the opposite... not sure...
(sub-case of 1) you showed the object behind self using presentViewControllerModally.
When I say "the object behind self" I mean the instance of the class where pressFullViewButtonFunction is defined.
If you need more help, post the code where you push your controllers on to the navigation controller...
On a side note, if you do:
UINavigationController *nvcontrol =[[UINavigationController alloc] initWithRootViewController:fullViewController];
and nvcontrol is not an ivar, then you have a leak.
Hope this helps...

Resources