PushWoosh UIWebview - ios

Ready to make a fool out of myself:
I have this skeleton app, that has the PushWoosh notification classes in place. It works fine. I'm able to send a push message to my app.
For this to work, in my AppDelegate, there is a method called
- (void) onPushAccepted:(PushNotificationManager *)pushManager withNotification:(NSDictionary *)pushNotification
that allows me to fire off stuff when a notification is accepted.
Meanwhile, in my ViewController, I have a method like this:
-(void)loadURL{
NSLog(#"testing");
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:TOPIC]]];
[webView reload];
}
This one works fine when called from the ViewController itself.
However, when I try to call this method from within the 'onPushAccepted' method in the appDelegate, the webView does not show the desired URL, although, as indicated by the logging, the method IS called.
I guess this shows that I'm lacking some fundamental understanding of the working of this all.
Therefore, I would be satisfied with some some strings that would make this work, but I would be really happy with an explanation on the why and how behind it.
I tried putting the onPushAccepted: in the ViewController, but that didn't work at all, although I included the necessary "PushNotificationManager.h" in the ViewController.m.
I'm confused, and need your help.
I think your answer will get me close to getting the basics.
Thanks ahead!

In this specific case Sjakelien was using a Single View Application. The confusion was that the attempt to instantiate the ViewController in AppDelegate by doing the following ViewController * vc = [[ViewController alloc]init]; [vc loadURL]; did not work.
In this case the solution is the get the ViewController that is displayed on screen by using
- (void) onPushAccepted:(PushNotificationManager *)pushManager withNotification:(NSDictionary *)pushNotification {
ViewController *vc = (ViewController*)self.window.rootViewController;
[vc loadURL];
}
Applications with a different setup such as a UINavigationController need to take different actions.
A few choices:
popToRootViewController and instantiate a new instance of ViewController and push it onto the navigation stack
Push a new instance of ViewController onto the navigation stack without using popToRootViewController
Present the ViewController in a modal
Modify the model of the application

Related

Pushing a new VC into the navigationController seems to have no effect

This problem sounds quite basic but I don’t understand what I am overlooking.
I am trying to push a new view controller into a navigation controller, however the topViewController remains unaffected.
#import "TNPViewController.h"
#interface TNCViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
#implementation TNCViewController
-(void)userDidSelectNewsNotification:(NSNotification*)note
{
TNPViewController *nextViewController = [[TNPViewController alloc] init];
[[self navigationController] pushViewController:nextViewController animated:YES];
UIViewController *test = [[self navigationController] topViewController];
}
The test shows an instance of TNCViewController instead of TNPViewController. How is this possible?
UPDATE
Thanks for everyone's participation. The method name indicating notifications is a red herring. I found the problem, as Stuart had mentioned previously but deleted later on. (As I have high reputation score, I still can see his deleted post).
My initial unit test was this:
-(void)testSelectingNewsPushesNewViewController
{
[viewController userDidSelectNewsNotification:nil];
UIViewController *currentTopVC = navController.topViewController;
XCTAssertFalse([currentTopVC isEqual:viewController], #"New viewcontroller should be pushed onto the stack.");
XCTAssertTrue([currentTopVC isKindOfClass:[TNPViewController class]], #"New vc should be a TNPViewController");
}
And it failed. Then I set a breakpoint and tried the test instance above and it still was showing the wrong topviewcontroller.
At least the unit test works if I change
[[self navigationController] pushViewController:nextViewController animated:YES];
to
[[self navigationController] pushViewController:nextViewController animated:NO];
A better solution is to use an ANIMATED constant for unit tests to disable the animations.
This doesn't really answer your question about why your navigationController is not pushing your VC. But it is a suggestion about another possible approach.
You could instead add a new VC on the Storyboard and simply activate the segue when the userDidSelectNewsNotification method is activated. Then change the information accordingly to the event in the VC, specially since you are initializing it every time anyway.
This is something of a stab in the dark, but the issue is hard to diagnose without more information.
I see you're trying to push the new view controller in response to a notification. Are you sure this notification is being handled on the main thread? UI methods such as pushing new view controllers will fail (or at least behave unpredictably) when not performed on the main thread. This may also go some way to explaining the odd behaviour of topViewController returning an unexpected view controller instance.*
Ideally, you should guarantee these notifications are posted on the main thread, so they will be received on that same thread. If you cannot guarantee this (for example if you're not responsible for posting the notifications elsewhere in your code), then you should dispatch any UI-related code to the main thread:
- (void)userDidSelectNewsNotification:(NSNotification *)note
{
dispatch_async(dispatch_get_main_queue(), ^{
TNPViewController *nextViewController = [[TNPViewController alloc] initWithNibName:#"TNPViewController" bundle:nil];
[self.navigationController pushViewController:nextViewController animated:YES];
});
}
Also, it appears you are not initialising TNPViewController using the designated initialiser (unless in your subclass you are overriding init and calling through to initWithNibName:bundle: from there?). I wouldn't expect this to cause the transition to fail entirely, but may result in your view controller not being properly initialised.
In general, you might be better creating your view controllers in a storyboard and using segues to perform your navigation transitions, as #Joze suggests in his answer. You can still initiate these storyboard segues in code (e.g. in response to your notification) with performSegueWithIdentifier:, but again, be sure to do so on the main thread. See Using View Controllers in Your App for more details on this approach.
*I originally wrote an answer trying to explain the unexpected topViewController value as being a result of deferred animated transitions. While it is true that animated transitions are deferred, this does not prevent topViewController from being set to the new view controller immediately.

Saving or returning to previous iOS scenes

I have an iOS app running right now in a storyboard with 3 viewcontrollers. The first one (initial view) features a play button to start a music stream and image for album cover of currently playing song. This scene has a a navigation controller and a bar button on it that will lead the user to the next view...
A list view populated with hard coded stream's that the user can choose from. Very simple and working fine still.
After choosing one, the user goes to a preview page that tells them about the stream before it begins to play. Still working like a charm until they want to continue from here.
If they user selects the stream from the preview page, the app "should" return the user to the initial ViewController and swap out the playing stream for the one selected. At first, I was mistakenly creating a new instance of the initial viewController, and after fixing that mistake, now have a few more questions someone might be able to help me with.
here is the IBAction for the button to select the stream:
- (IBAction)returnHome:(id)sender
{
[[self navigationController] popToRootViewControllerAnimated:YES]; // goes back to first view on the stack
}
Before finding this logic, I was using the prepareForSegue and setting the stream value of the destination to be what was selected. I was also trying to save the state of the first view but was unsure how to pass it down the line (or retain it) since I am moving through 3 ViewControllers and using a modal segue so they can go back if they choose to not pick a new stream.
Any advice will help, but please refrain from simply posting a link to the references. I have been there for 3 days straight and they do not speak a very beginner friendly lingo in the iOS reference docs.
You want to be able to inform Your first VC that user changed track?
You can simply use NSNotificationCenter.
First You have to "tune in", i.e. in viewDidLoad, Your view controller to listen to particular notifications:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(changeStream:) name:#"UserWantToChangeStream" object:nil];
}
and implement method:
- (void)changeStream:(NSNotification *)notification
{
NSString *newStreamName = notification.object;
/* Change the stream code */
}
Don't forget to stop listening to the notifications:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Then You post notification after user has performed an action:
[[NSNotificationCenter defaultCenter] postNotificationName:#"UserWantToChangeStream" object:#"new_stream_name"];
In the example I pass NSString new_stream_name but You can pass any object.
Your first view controller will be informed.
Have you considered the usage of unwinding segues? they are a little bit tricky to understand and use, but they surely can help you out.
First of all, you have to create an IBAction on your FIRST viewcontroller (== the view controller where you want to "land" and pass your list selection to) that takes a UIStoryboardSegue as single parameter and leave the implementation empty. For example
- (IBAction) returnToHome:(UIStoryboardSegue*) segue{;}
Then, in your preview page view controller (in storyboard), drag a segue from your button (the one which is triggerng the IBAction that pops the navigation controller) to the little exit symbol in the lower right side of the view controller. A menu should pop out asking for returnToHome method. Delete from the button the previous IBAction as well (the one you called -(IBAction)returnHome:(id)sender)
In this way you should be able to do the same thing as before (popping back to the root view controller) without solving the problem BUT! if you implement an override of prepareForSegue:sender in your last view controller you'll have a reference to the root view controller where you can do whatever you need.
- (void) prepareForSegue:(UIStoryboardSegue*) segue sender:(id)sender
{
ViewController *vc = (ViewController *)segue.destinationViewController;
[vc.audioplayer pause];
vc.stream = self.stream;
}
This is a cleaner way to achieve the same result, since you're not making assumpions on your viewcontrollers hierarchy (what if tomorrow you'll add another view controller BEFORE the first one? The app will surely crash).
By the way, if you're into Storyboard/Segue business, check out my library which really simplifies storyboard work when it comes to "passing parameters": https://github.com/stefanomondino/SMQuickSegue (you can install it via cocoapods with pod 'SMQuickSegue')
I have figured out at least how to get it to work. I had a second navigation controller in the storyboard and after deleting that it successfully went back 2 views instead of just one.
Here is my logic for retaining the stream data and bringing it back to the initial view controller if anyone is curious.
- (IBAction)returnHome:(id)sender
{
ViewController *vc = (ViewController *)[self.navigationController.viewControllers objectAtIndex:0];
[vc.audioplayer pause];
vc.stream = self.stream;
[self.navigationController popToRootViewControllerAnimated:YES];
}
since I now only have one Navigation Controller, the index for the initial will be 0. I can grab the initial view and put a reference to it in vc.
Then I simply access the audioplayer from it, and if it is still playing, stop it and load up the scene.
Then in the initial ViewController logic, I have the viewDidAppear method to load the new stream selection into the display labels and images.
- (void)viewDidAppear:(BOOL)animated
{
// If there is no stream selected (first run)
// set the stream to RAPstation default
if (self.stream == nil)
{
self.stream = [[Stream alloc]initWithName:#"RAPstation" ...];
}
self.snameLabel.text = self.stream.sName;
self.sdescLabel.text = self.stream.sDesc;
}
Not sure if this is the right but it definitely works for now. If anyone can help make it cleaner please feel free.

Programmatically Selecting a TabBarController View?

In order to get my custom menu up and running, I've ended up using a UITabBarController and need to change the view displayed programmatically, vs the standard tabbed menu on screen.
Everything is working as expected except on thing. I am attempting to use:
[self setSelectedIndex:index];
This code is inside my UITabBarController subclass in a custom delegate method. (This is so I can programmatically adjust the view when interacting with my menu). However, while this code is called, it doesn't do anything?
Does it HAVE to be called from one of the tabbed views? I was hoping to run it from inside the TabBarController to avoid repeating the code in each tabbed sub controller.
UPDATE:
Just found that using [self setSelectedIndex:index]; works fine in viewDidLoad. But when it is called inside the delegate method, it doesn't change view. It is using the right index number and getting called, but not doing anything from that method.
Also, it seems the tab controller is a different object when I log self in viewDidLoad vs my delegate method. So why would I be loosing the reference to the original controller?
It's just a UITabBarController in a container in another view controller.
Delegate Code:
#Interface
#protocol SLMenuDelegate <NSObject>
#required -(void)menuDidChangeViewToIndex:(NSInteger)index;
#end
#property (nonatomic, assign) id<SLMenuDelegate>menuDelegate;
#Implementation
#synthesize menuDelegate;
self.menuDelegate = [self.storyboard instantiateViewControllerWithIdentifier:#"TabBarViewController"];
[menuDelegate menuDidChangeViewToIndex:[self.menuItemButtons indexOfObject:sender]];
UITabBarController
-(void)menuDidChangeViewToIndex:(NSInteger)index
{
[self setSelectedIndex:index];
}
Setting breakpoints and running NSLogs and there is no question that the method gets called and all code runs.
Try using delayed performance:
dispatch_async(dispatch_get_main_queue(), ^{
[self setSelectedIndex:index];
});
I didn't manage to find a solution to the exact issue, but I found an equally good way of resolving my issue simply.
I stopped using a delegate to send my button tap message and change the view. Instead I did the following:
SLTabBarViewController *tabBar = [self.childViewControllers objectAtIndex:0];
[tabBar setSelectedIndex:[self.menuItemButtons indexOfObject:sender]];
This gets the embedded tab bar controller and I simply directly change the view from the original view controller from which the button tap comes from.
It may not be an intelligent solution, but its a simple and functional one which doesn't create any problems.

Stacked UINavigationController

I'm new in iOS and I'm working with Storyboards.
I have an application with some views.
My rootViewController (1) is a UINavigationController that connects to other views. At one point in the application (2), I include a component (SWRevealViewController, Facebook Side Menu Style) that is an instance of UINavigationController, so I have two UINavigationControllers nested within each other. I want to remove or change the first UINavigationController by the new one (2), and just have only one. All views are accessed via custom segues.
Detailed Image Here
I think the solution is to change the rootViewController before loading the view (2), and set the second UINavigationController as the main of the application.
Detailed Image Here
I tried to solve it accessing by:
[UIApplication delegate].window.rootViewController = myController;
but I only have nil or a empty window.
I read many post that the solution could be in my AppDelegate in the method
- (void) applicationDidFinishLaunching: (UIApplication *) application I can't understand how to apply it to my structure, because that method is called when the application is launched.
I think that my workflow application is wrong.Any feedback or help is welcome!
Thanks in advance.
It's fine to change the root view controller from another controller, but your code is wrong. It should be:
[UIApplication sharedApplication].delegate.window.rootViewController = myController;
If you're doing this action from a controller whose view is currently on screen, you can do it this way instead:
self.view.window.rootViewController = myController;
This should work as long as myController has been properly instantiated.
You could possibly remove (1) or off load it into another view controller that is hidden and once the user goes back to a point where you want (1) back you can load it back in. This could be done in the - (void) applicationDidFinishLaunching: (UIApplication *) application.

IOS 5 Opening a specific ViewController from the AppDelegate

I am really new to IOS so I apologize if this questions is not worded clearly. I have tried searching around but I have not found exactly what I am looking for.
basically in my AppDelegate applicationDidBecomeActive method, I am making a call to my webservice to make sure that the user is still a valid user, and to pull down some refrehsed data, or kick them back to the login page if they are no longer valid.
The part that I am having trouble with is the second part. How can I load and show and specific ViewController(in this case the loginViewController) when the user is found to be invalid? I want to let the normal viewController flow happen when they are valid, which is is doing fine, but I can not figure out how to launch a specific viewController when I need to from AppDelegate.
Any ideas?
I think I got it! I used this code in the AppDelegate to display the ViewController I needed.
UIViewController *loginController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
UINavigationController *loginNavController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"LoginNavController"];
UIViewController *currentVC = self.window.rootViewController;
_window.rootViewController = loginNavController;
[currentVC presentViewController:loginNavController animated:NO completion:nil];
For simplicity, lets say you have a one view app (not nav controller, not tab bar controller - the solution scales but easier to explain). When you get the appDelegate message that the app launched, then make a UIImageView the root view and show your launch image (user thinks you are still booting up). Try to log in, and do this in some other object (not a view controller). If you succeed, you make your desired view the rootView, and users sees it. If the login fails, then you makea login window the rootView. The key here is to have an object that is driving this and can interact with the appDelegate. You could also add this functionality to the appDelegate itself.

Resources