Load iOS tab content only once - iphone

I'm writing an iOS app that provides a native tabbed interface to a bunch of WebViews that display content from my website. I want re-tapping the same tab to refresh the page, so I implemented (word choice? I'm a Java guy...) UITabBarDelegate in my ViewController and have this:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
AbstractPageController *otherController = (AbstractPageController *)[[self viewControllers] objectAtIndex:[[tabBar items] indexOfObject:item]];
[otherController loadPage];
}
Which loads a page in the appropriate WebView.
And, of course, to load the page in the first place (on startup), I have this in my AbstractPageController
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadPage];
}
As you might imagine, this results in the page being loaded twice if a new tab is selected. How do I avoid this? Right now I think the best idea is to remove the loadPage call from viewDidLoad, but that would mean that on startup the page didn't get loaded (right?). And it seems hacky to special-case that. What do you recommend?

I decided that the cleanest way was to simply have tabBardidSelectItem only do any work if the same tab was re-tapped. Thus:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
NSUInteger newIndex = [[tabBar items] indexOfObject:item];
if (newIndex == [self selectedIndex])
//etc
}
Works great, and doesn't feel hacky!

I believe if you invoke the method -loadView rather than your own instance method, the view will remain loaded upon presentation and the duplication of loading will not occur.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html

Related

iOS didSelectTabBarItem knowing what item was selected prior

I have an IOS app with a UITabBar and have its delegate set to my class.. the didSelectTabBarItem properly fires and all is right with the world. However I do have some conditional code that has to occur when the UITabBarItem selected is after one particular UITabBarItem IE.. if the user clicks on tab bar item 3, and they were currently on tab bar item 2 I have to do a little extra code, that I would not have to do if the user selected tab bar item 3 and was previously on tab bar item 1.
So, is there anyway programmatically (other than keeping direct track via my program via a state variable, to know what was the previously selected item was on a tab bar when a new tab bar item is selected?
Yes it is possible, through key-value-observing (KVO).
note This answer is in regard to a UITabBar not a UITabBarController. Tab bar controller delegates have methods you are looking for (as mentioned by rdelmar).
To start, observe your tab bar like so:
- (void)viewDidLoad{
[super viewDidLoad];
[self.tabBar addObserver:self forKeyPath:#"selectedItem" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
}
I think you can already see where I'm going based on my using both options old & new. Then simply observe the change instead of using the delegate method, like so:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"selectedItem"] && [object isKindOfClass:[UITabBar class]]){
UITabBar *bar = (UITabBar *)object; // The object will be the bar we're observing.
// The change dictionary will contain the previous tabBarItem for the "old" key.
UITabBarItem *wasItem = [change objectForKey:NSKeyValueChangeOldKey];
NSUInteger was = [bar.items indexOfObject:wasItem];
// The same is true for the new tabBarItem but it will be under the "new" key.
UITabBarItem *isItem = [change objectForKey:NSKeyValueChangeNewKey];
NSUInteger is = [bar.items indexOfObject:isItem];
NSLog(#"was tab %i",was);
NSLog(#"is tab %i",is);
}
// handle other observings.
}
Remember to remove yourself as observer in both viewDidUnload and dealloc, since viewDidUnload may never be called.
I don't know if this can be done in a way other than what you suggested (a state variable), if you're not using a UITabBarController. If you are using a tab bar controller, then you can do this in the delegate of the tab bar controller:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
if (viewController == [self.tabBarController.viewControllers objectAtIndex:2 && self.tabBarController.selectedIndex == 1]) {
NSLog(#"Do special programming");
}
return YES;
}
This method is called before the switch is made (unlike the UITabBar method didSelectTabBarItem), so the selected index will be the index of the tab that was active before you touched the new tab.
There may be better ideas but one way to do is by by creating a NSString object in your AppDelegate to store the name of the class of the current view controller so that you can read the string from your next view controller and check the previously selected item.
In you AppDelegate.h declare a string and synthesize it.
#property (strong, nonatomic) NSString * preSelectedViewController;
And in all your UIViewControllers which are set as items for your UITabViewController do this
in .h files
#import "AppDelegate.h"
in .m files include this in your viewWillAppear: method
AppDelegate * delegate1 =(AppDelegate *) [[UIApplication sharedApplication] delegate];
if (delegate1.preSelectedViewController ==nil)
{
delegate1.preSelectedViewController=NSStringFromClass( [self class]);
}
NSLog(#"previous %#",delegate1.preSelectedViewController);
//include 2nd_viewcontroller.h file and this if statement in your 3rd_viewcontroller(i.e. where you want to check and do your other programming)
if ([delegate1.preSelectedViewController isEqualToString:NSStringFromClass([2nd_ViewController class]) ]) {
//do your little extra code
}
delegate1.preSelectedViewController=NSStringFromClass( [self class]);
NSLog(#"present %#",delegate1.preSelectedViewController);
Guess this will work for you
Why not storing the lastSelectedIndex in an iVar and in
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
you have both values on your hands.
You might even (never tried it) use
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
so you have the currently selected view controller index view selectedIndex and then via an additional method you can find the index of the to be selected index of the viewController.
I found that this works with ReactiveCocoa:
#import <ReactiveCocoa/ReactiveCocoa.h>
// ...
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
#weakify(self);
[RACObserve(appDelegate, tabBarController.tabBar.selectedItem) subscribeNext:^(UITabBarItem *selectedTab) {
#strongify(self);
NSUInteger selectedIndex = [appDelegate.tabBarController.tabBar.items indexOfObject:selectedTab];
NSLog(#"selected index: %lu", (unsigned long)selectedIndex);
}];

How to change the action of the first tabbaritem (when clicked)

I'm trying to show a tableviewcontroller as the first page, with the tabbarcontroller in it.
After it is shown I would like to change the action of the first item in the tabbarcontroller to show a menu.
I have created the menu, I only need to find a way to change the click event of the first item on the tabbarcontroller.
I've been searching for hours now, and help would be greatly appreciated!
Thanks in advance,
Tommy
*edit: I found the solution, I will post it tomorrow!
Implement
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item{
if(tabBar.selectedItem.tag == 0)
{
RootViewController *objRootViewController = [[RootViewController alloc]init];
[self.navigationController pushViewController:objRootViewController animated:YES];
[objRootViewController release];
}
}
I found out a good way to handle this problem...
I used the UITabBarController delegate and implemented this:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if([viewController.title isEqualToString:#"YourTitleHere"])
{
// do your stuff here
}else
{
return YES;
}
}
this Worked great for me, very happy that I finally found a solution! Hope this helps others as well!

custom control overlay on UIWebView on single Tap/Touch in iphone

http://startupmeme.com/wp-content/uploads/2008/09/stanza-on-iphone.png
I want to achieve functionality like the famous Stanza application screenshot shows.
Content is displayed with the help of UIWebView in html format
A single tap on the UIWebView would show two overlay controls (top & bottom)
The bottom toolbar consists of controls & a slider to give pagination
Please help.
How would I achieve the above in following way:
How do i build up these custom controls?
On single tap, how will i overlay these controls?
How do i combine the UIToolbar(which contains controls) & the slider?
Also there is a need of transition slide effect, top header comes from top of screen, bottom control & slider come from bottom of screen. How will i achieve this?
Please help me out elaborately for (a) - (d). Some code guidance would be very helpful.
Thanks
I am doing what you are looking for in a project I've been working on recently.
Basically I have a UIWebView inside of a UINavigationController with a UIToolbar as a sibling to the UIWebView. I setup the layout of all of this using IB to create a nib linked to my view controller class. I presume you know how to use IB and how to correctly setup all of the delegates, IBOutlets, etc. so will not cover that here.
You need to implement the appropriate delegates in your view controller for this to all work. Here is my interface for the view controller class:
#interface MyViewController : UIViewController <UIWebViewDelegate, UIGestureRecognizerDelegate> {
}
#property (retain) IBOutlet UIWebView *webView;
#property (retain) IBOutlet UINavigationItem *navigationItem;
#property (retain) IBOutlet UIToolbar *toolbar;
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer;
// UIGestureRecognizerDelegate methods
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
// UIWebViewDelegate methods
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
#end
Technically what you are asking about only requires the UIGestureRecognizerDelegate implementation but I presume you will be doing something interesting in your webView and thus will want to also implement the UIWebViewDelegate
I found it was critical to implement the following for these three UGestureRecognizer delegate methods to make this all work since the UIWebView is also recognizing taps:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}
I register a UITapGestureRecognizer on the webView inside my controller's viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
// I perform other appropriate initialization here like adding or removing items from the navigation bar or toolbar
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapFrom:)];
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.delegate = self;
[self.webView addGestureRecognizer:tapRecognizer];
[tapRecognizer release];
}
Here is my handleTapFrom callback:
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer
{
CGPoint location = [recognizer locationInView:self.navigationController.topViewController.view];
CGRect bounds = self.navigationController.topViewController.view.bounds;
if (location.x < bounds.size.width / 5.0) {
// This is in the left most quadrant
// I implement code here to perform a "previous page" action
} else if (location.x > bounds.size.width * 4.0 / 5.0) {
// This is in the right most quadrant
// I implement code here to perform a "next page" action
} else if ((location.x > bounds.size.width / 3.0) && (location.x < bounds.size.width * 2.0 / 3.0)) {
// This is in the middle third
BOOL hidden = [self.navigationController isNavigationBarHidden];
// resize the height of self.webView1, self.webView2, and self.webView3 based on the toolbar hiding / showing
CGRect webViewControllerFrame = self.webView.frame;
webViewControllerFrame.size.height += (hidden ? -44 : 44);
self.webView.frame = webViewControllerFrame;
// I hide all of the upper status bar and navigation bar, and bottom toolbar for a clean reading screen
[[UIApplication sharedApplication] setStatusBarHidden:!hidden];
[self.navigationController setNavigationBarHidden:!hidden animated:YES];
self.toolbar.hidden = !hidden;
// I perform content relayout here in the now modified webView screen real estate
}
}
This basic framework got me going and hopefully answers your question or at least points you in the right direction.
Notes:
I have not yet dealt with the fact that I may interpret a tap as "show/hide the status/nav/toolbars" AND the UIWebView may act on this same tap as a clicked link, etc. I am planning on looking into that shortly…
The transparency you show in the Stanza screen shot is not a big deal. You will have to play with the alpha settings on the various nav/toolbars to accomplish that plus change the resizing code on the UIWebView above. I in fact periodically overlay an additional semi-transparent toolbar for temporary status, alerts, etc and do not resize the UIWebView so that it is underneath and still somewhat visible through this alert toolbar.
bg clearly understands iPhone mechanics, so definitely +1 there, but I might know another way.
Overlay a button (or view) onto the top.
Register a tap catcher however you want (Recognizer, touches ended with [[event touches] tapCount] or IB, or manual action... basically grab the taps).
After you get the tap, save where it was, call a function that notifies your web view to split (or if it's like stanza, says to add the overlay) then literally send the touch through the views by "faking" a touch, similar to this: Is there a way to pass touches through on the iPhone?
or create your own target-action method and send it (probably easier).
If you understand the SDK, the above should make sense to you. Specifically the views and everything related (except the graphics, you can skip that reading)

Objective-C on iPhone - tab bar crash

I'm relatively new to Objective-C and coding. I've tried doing a little dev on my own but have now become stuck on what is probably a rookie error.
I've created a tab bar controller with 5 views, one such view is a UIWebView. I've got the Webview working and it loads, but when I select a different tab, the app crashes. Please find my code below and any help would be appreciated!
#import <UIKit/UIKit.h>
#interface LiveViewController : UIViewController {
IBOutlet UIWebView *liveView;
}
#property (nonatomic, retain) UIWebView *liveView;
#end
#import "LiveViewController.h"
#implementation LiveViewController
#synthesize liveView;
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
/*
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization.
}
return self;
}
*/
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[self.liveView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.britishseapower.co.uk/live/"]]];
[super viewDidLoad];
}
- (void)webViewDidStartLoad:(UIWebView *)liveView
{
// starting the load, show the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)liveView
{
// finished loading, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)liveView:(UIWebView *)liveView didFailLoadWithError:(NSError *)error
{
// load error, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// report the error inside the webview
NSString* errorString = [NSString stringWithFormat:
#"<html><center><font size=+5 color='red'>An error occurred:<br>%#</font></center></html>",
error.localizedDescription];
[self.liveView loadHTMLString:errorString baseURL:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
if ( [self.liveView loading] ) {
[self.liveView stopLoading];
}
self.liveView.delegate = nil; // disconnect the delegate as the webview is hidden
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)dealloc {
[liveView release];
[UIWebView release];
[LiveViewController release];
[super dealloc];
}
#end
Many thanks,
Ryan
[UIWebView release]; [LiveViewController release];
This is what make your app crash.
It's not valid to send a release message to a class itself.
What you've done with [liveView release]; is enough (with the call to [super dealloc];.)
You should also set the delegate to nil in the dealloc method as in the viewWillDisappear method self.liveView.delegate = nil;. This way you're sure to avoid any further message sent to the LiveViewController from the UIWebView.
You should read a bit more of documentation on Objective-C to better understand how it works.
Not sure if this is related but I noticed that you aren't setting yourself as the delegate anywhere in code which means that it must be connected in Interface Builder. Now when the view disappears, you are breaking that connection, but if the view were to re-appear and wasn't previously unloaded that connection will remain broken.
One of the most common reasons why an app may crash is to refer to or send a message to an object that has been already released from the memory. And this type of bug can be easily located using NSZombieEnabled and looking into the console message. So if you haven't already tried that, that's the first thing you must do.
The problem could be in LiveViewController but could be in the other view controllers as well. I wouldn't believe the problem is 100% in LiveViewController because the view controller wouldn't try releasing its view when the view is not shown unless it gets a memory warning. And you run the app using the simulator, it's unlikely it will have a memory warning unless you simulate one.
You would probably know that a view controller never create a view unless the view is used by an object. One of the other view controllers may have a silly bug in its view loading process which causes a crash. Or, you might have released another view controller by mistake. Make 100% sure that the other view controllers have no problem showing their views on their own, when you keep changing between their views (without showing LiveViewController).
So what I would do is to try NSZombieEnabled and check if it accesses a released object, and if it does, what the object is. Also, I will make a double check that the problem is related to LiveViewController. If it doesn't help I would log a message when LiveViewController and its liveView is deallocated (for liveView you need to subclass it). Because delegate property almost always does not retain an object, if the LiveViewController object is released (which shouldn't happen) and liveView still has a reference to it in the delegate property it will make a crash.
Crashes like this are almost always related to releasing an object that has already been released and deallocated.
Let XCode help you find the error. In XCode 4:
- In the toolbar, select the scheme list, and select 'Edit Scheme'
- Select the 'Run Your.app' in the list on the left.
- Under 'Environment Variables', add the following name/value pairs in the appropriate columns:
CFZombieLevel 3
NSZombieEnabled YES
Now when debug your app, you will get a message telling when -release is called on an object that already has a -retainCount of zero. Now you have a good clue to start your investigation.
Note that these flags prevent objects from being deallocated, so it is best to turn them on as needed to prevent out of memory errors.

Using a url scheme internally with a UIWebView to change a UILabel

I've got a UIWebView which shows up a couple of screens into a UINavigationController:
First View > Second View > View with UIWebView + UILabel
Now, I display a certain page in that web view, which has a link back to my app....
myapp://foofoo
I know you can set up a custom URL with (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url and some info.plist poking, but how would change the UILabel, which is one the same screen as UIWebView, by simply clicking the myapp://foofoo link?
The best way to do this would be through a custom link, and then use the UIWebView delegate method -webView:shouldStartLoadWithRequest:navigationType: to trap requests. When you see a request come through with your link in it, you know your action has been triggered.
UIWebView Expose Objective C to JavaScript
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"scheme = %#",[[request URL] scheme]);
if([[[request URL] scheme]isEqualToString:#"myscheme"])
{
[messageLabel setText:#"HELLO!"];
return NO;
}
else return YES;
}
Make sure your view controller conforms to the UIWebViewDelegate protocol for this to work.