I know there have been a couple questions asked similar to this, but I think my issues is a little different, so bear with me.
I have a tabbed view app with table views set in 2 of the 3 tabs. I want to be able to refresh just the table view of the selected tab when the app wakes back up. I have tried using the notification center to tell my tab to refresh, but it makes the other tab crash because it is stealing the view from my progress hud. I will put some code here, so hopefully I can find a solution that will work for me.
Tab 1 ViewController
- (void)viewDidLoad {
// becomeActive just calls viewDidAppear:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(becomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[super viewDidLoad];
}
-(void)viewDidAppear:(BOOL)animated {
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Updating";
HUD.detailsLabelText = #"Please Wait";
[HUD showWhileExecuting:#selector(refreshData) onTarget:self withObject:nil animated:YES];
}
Tab 2 ViewController
- (void)viewDidLoad
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.navigationItem.title = [defaults valueForKey:#"companyName"];
self.view.backgroundColor = background;
tableView.backgroundColor = [UIColor clearColor];
tableView.opaque = YES;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// becomeActive just calls viewDidAppear
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(becomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
if (_refreshHeaderView == nil) {
EGORefreshTableHeaderView *view = [[EGORefreshTableHeaderView alloc] initWithFrame:CGRectMake(0.0f, 0.0f - self.tableView.bounds.size.height, self.view.frame.size.width, self.tableView.bounds.size.height)];
view.delegate = self;
[self.tableView addSubview:view];
_refreshHeaderView = view;
[view release];
}
// update the last update date
[_refreshHeaderView refreshLastUpdatedDate];
}
-(void)viewDidAppear:(BOOL)animated {
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Updating";
HUD.detailsLabelText = #"Please Wait";
[HUD showWhileExecuting:#selector(updateBoard) onTarget:self withObject:nil animated:YES];
// update the last update date
[_refreshHeaderView refreshLastUpdatedDate];
}
With this setup, my application will refresh tab 2 just fine, assuming that tab 2 was the last tab open before you closed the application. If I switch to tab 1 when I close, upon restarting the app, the notification calls tab 2 first and steals the view out from under my progress hud, so when I try to call the viewDidAppear method, the view is null and the app crashes. I either need to figure out how to implement the notification center better so it doesn't crash the other tab, or a better way overall to just refresh when the app becomes active. Looking forward to a good answer. Thanks in advance.
EDIT
When I run with this setup, it aborts inside the MBProgressHUD
- (id)initWithView:(UIView *)view {
// Let's check if the view is nil (this is a common error when using the windw initializer above)
if (!view) {
[NSException raise:#"MBProgressHUDViewIsNillException"
format:#"The view used in the MBProgressHUD initializer is nil."]; // <-- Aborts on this line, so they know it can happen
}
id me = [self initWithFrame:view.bounds];
// We need to take care of rotation ourselfs if we're adding the HUD to a window
if ([view isKindOfClass:[UIWindow class]]) {
[self setTransformForCurrentOrientation:NO];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceOrientationDidChange:)
name:UIDeviceOrientationDidChangeNotification object:nil];
return me;
}
Main problem here that notifications are received by all views, whether they will appear or not. Good practice would be to notify only those views which will appear in screen
you can use application delegate's (void)applicationWillEnterForeground:(UIApplication *)application method to handle change in application instead of notification.
if problem is all about refreshing tab that is on screen then keeping track of change in tab in AppDelegate and using that when application enters foreground would be better idea over notification.
Other option is to use viewWillAppear method in tabbarcontroller. When this method gets called, you can refresh current tab.
There really isn't much out there to help answer this question. #Learner gave me an idea that ultimately led to how I resolved this issue.
Since all of the logic I have works, the issue was to prevent the HUD from stealing the view from the other one since they both get called when they respond to the notification event. So in my -becomeActive event for both tabs, I added a check if the selectedIndex was a match for the current tab, if not, no refresh. Worked like a charm.
-(void)becomeActive:(NSNotification *)notification {
// only respond if the selected tab is our current tab
if (self.tabBarController.selectedIndex == 1) { // just set the number to your tab index
[self viewDidAppear:YES];
}
}
Related
After a long period of trying and searching the Internet and StackOverflow I could not find the answer I need.
My Problem is a warning:
Warning: Attempt to present <PAPasscodeViewController: 0x81cc8a0> on <ServerViewController: 0x75864b0> whose view is not in the window hierarchy!
I am using a Custom Control called "PAPasscodeViewController", the thing itself is working but when I try to display the view on the ApplicationDidBecomeActive method it displays this warning in the Consoleoutput.
I am using this method to keep track of the Notification:
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(wait)
name:UIApplicationDidBecomeActiveNotification object:nil];
my -(void)wait is containing this:
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
NSLog(#"waiting");
[self performSelector:#selector(enterPasscode) withObject:nil afterDelay:.3f];
The App is having a RootViewController and a GeneralViewController, if the User already downloaded a Config package the GeneralViewController gets pushed in after starting as a modalView like this (in the viewDidLoad):
GeneralView = [[GeneralViewController alloc] initWithNibName:#"GeneralViewController" bundle:[NSBundle mainBundle]];
GeneralView.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentViewController:GeneralView animated:YES completion:nil];
In the GeneralView the User has more Options to open new Views through Buttons and they get pushed in through IBActions like the GeneralViewController.
So the Problem is occuring when this happens:
I am starting the App, Config Package was already installed and the User wants to track his ServerData on "ServerViewController", so he touches the appropriate Button for this.
Then the View gets pushed in (its now RootViewController (managed by AppDelegate)-> GeneralViewController(pushed in)-> ServerViewController(pushed in)), everything works fine.
Now when the User goes back to his Homescreen I am closing the modal View "ServerViewController" as follows:
-(void)viewDidAppear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(close:)
name:UIApplicationDidEnterBackgroundNotification object:nil];}
-(IBAction)close:(id)sender{
[self dismissViewControllerAnimated:YES completion:nil];
}
so when the App enters Backgrounding the ServerViewController gets dismissed, after resuming the App the GeneralViewController is now the active View Controller, and in his -(void)viewDidAppear I am calling the -(void)wait from above, it calls this function:
- (void)enterPasscode{
PAPasscodeViewController *passcodeViewController = [[PAPasscodeViewController alloc] initForAction:PasscodeActionEnter];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
passcodeViewController.backgroundView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStyleGrouped];
}
passcodeViewController.delegate = self;
passcodeViewController.passcode = [configArray objectAtIndex:3];
passcodeViewController.simple = YES;
[self presentViewController:passcodeViewController animated:YES completion:nil];
}
This is working fine but then I am getting the annoying warning from above (here again so you dont have to scroll up)
Warning: Attempt to present <PAPasscodeViewController: 0x75d1c10> on <ServerViewController: 0x818b390> whose view is not in the window hierarchy!
actually this is confusing because I think my GeneralViewController is now the Active ViewController again...
Maybe I am just blinded by the simplicity of this Problem... but maybe someone here can help me understand.
I did NOT find any solution in any other post here in StackOverflow or other sites concerning that exact problem!
You have to remove self as observer when your ServerViewController is not shown. Add the following code to ServerViewController:
- (void)viewDidDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
My problem;
Hide the default camera controls and overlay it with my my own. This is made with the property cameraOverlayView. I also was having problem triggering the takePicture method.
(Question solved in the comments and in the edits. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
Here is what came to be the solution:
I have two UIViewController. The main ViewController and the CustomOverlay (for the camera controls).
I the ViewController I declare the source type and the overlay for may camera control like this:
- (void)viewDidLoad
{
// notification from the CustomOverlay Controller that triggers the eTakePicture method
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(eTakePicture:) name:#"eTakePicture" object:nil];
daysBtn.delegate = self;
daysBtn.hidden = YES;
picker = [[UIImagePickerController alloc] init];
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
picker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
picker.showsCameraControls = NO;
picker.navigationBarHidden = YES;
picker.wantsFullScreenLayout = YES;
picker.delegate = self;
overlay = [[CustomOverlay alloc] initWithNibName:#"CustomOverlay" bundle:nil];
// Overlay for the camera controls, note the "= overlay.view", the ".view" was important
// because the overlay is a new UIViewcontroller (with xib) so you have to call the
// view. Most tutorials that I saw were based on UIView so only "= overlay" worked.
picker.cameraOverlayView = overlay.view;
[self presentModalViewController:picker animated:NO];
[super viewDidLoad];
}
Now on the CustomOverlay, which is a UIViewController I have the take picture button and want this button to trigger a method in the main ViewController:
- (IBAction)shoot:(id)control {
[[NSNotificationCenter defaultCenter] postNotificationName:#"eTakePicture" object:self];
}
And back to the main ViewController:
-(void)eTakePicture:(NSNotification *)notification
{
[picker takePicture];
}
All the code above will change a little more once I review it, specially the first block where I have to have a condition to check if cameraSourceType is available.
Hope that helps somebody out there. Any question, just ask.
I have a view with a navigation bar in my iphone app. The navigation bar has a back button on it to go the the previous view. Within the view is a webview. If the webview is loading while the user presses the back button on the navigation bar, the app crashes with the error message:
[WebViewController respondsToSelector:]: message sent to deallocated instance
I am assuming that I release some things when the view unloads. I believe my viewWillDisapear method takes care of stopping the webview from loading... But still, when I press back while the view is not fully loaded, it crashes.
It seems to happen when I let the view finish loading about 20% of the time - and only if I press the back button really quickly after the page finishes loading.
Here is the code that I believe runs during this shinanigans....:
- (void)viewDidLoad {
[super viewDidLoad];
webView.scalesPageToFit = YES;
[webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString: url]]];
[addressBar setText:url];
}
and then when the webview is loading:
- (void)webViewDidStartLoad:(UIWebView *)webView {
if (first) {
//mtns is a uiimageview
mtns.alpha = .8;
first = NO;
}
[activityIndicator startAnimating];
}
and after a suggestion, I added this method:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.webView stopLoading];
self.webView.delegate = nil;
}
I don't know why it is crashing so I am sort of at a loss
Thanks,
R
You could override -viewWillDisappear: to stop the UIWebView from loading and set the delegate to nil.
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.webView stopLoading];
self.webView.delegate = nil;
}
This may be because of before the execution code that you have written in viewwillDisapper application crashes.try to put breakpoint and check whether viewwillDisapper method is called or not.If its not called then write the code that mark has suggested before the application navigates to second view controller.
You have to be sure to set the delegate property of the web view to nil before it goes away. Do this both in your dealloc and viewDidUnload methods, since viewDidUnload is not always called before dealloc. What’s happening is that after your view controller is deallocated, the web view still has a reference to its address, and is trying to send it a message (-respondsToSelector:).
- (void)dealloc {
webView.delegate = nil;
[super dealloc];
}
- (void)viewDidUnload {
webView.delegate = nil;
[super viewDidUnload];
}
I have a TabBarController with 2 tabs, in one is a MapView and in the other one a simple TableView in a NavigationController. Both display Data from the same source. If any Data in the table is tapped, I add a DetailViewController to the NavigationController and show more details. Now on the MapView I also want to open this DetailViewController when the Data is tapped in the map. What's the best way to do this? I tried some with Notification but this doesn't work well because the TableViewController is finished loading (and registered as an observer) after the Notification is sent.
Here's my code:
MapViewController:
- (IBAction)goToNearestEvent:(id)sender {
if (currentNearestEvent) {
[[self tabBarController] setSelectedIndex:1];
NSDictionary *noteInfo = [[NSDictionary alloc] initWithObjectsAndKeys:currentNearestEvent, #"event", nil];
NSNotification *note = [NSNotification notificationWithName:#"loadDetailViewForEvent" object:self userInfo:noteInfo];
[[NSNotificationCenter defaultCenter] postNotification:note];
[noteInfo release];
}
}
TableViewController:
- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(loadDetailViewForEvent:)
name:#"loadDetailViewForEvent"
object:nil];
}
- (void)loadDetailViewForEvent:(NSNotification *)note {
Event *e = [[note userInfo] objectForKey:#"event"];
[self loadEventDetailViewWithEvent:e];
}
So I'm very new to iOS / Cocoa programming. Maybe my approach is the wrong choice. So I hope anybody could tell me how to solve such things the right way.
I forgot to declare my structure clearly:
- UITabBarController
- MapView (1)
- NavigationControllerContainer
- NavigationControllerView (2)
- TableView
I want to push a new View from the MapView (1) to the NavigationControllerView (2).
If you're going to use notifications, the fix is to force the second tab to be "created" before it's displayed.
Something like:
UIViewController *otherController = [[[self tabBarController] viewControllers] objectAtIndex:1];
otherController.view; // this is magic;
// it causes Apple to load the view,
// run viewDidLoad etc,
// for the other controller
[[self tabBarController] setSelectedIndex:1];
I don't have access to my code, but I did something similar to:
[[self.tabBarController.viewControllers objectAtIndex:1] pushViewController:detailView animated:YES];
Give this a try and let me know.
I think the observer/notification pattern is the right one. However, you normally want "controllers" to observe "model" objects.
I would create a Model object that contains the selected Event.
When each viewController is loaded, it looks at the "Model" object and directs itself to the selected event.
When any of the viewControllers changes the selected event, it does so in the Model, and then the notification propagates to the other(s) controllers.
My issue is that the back button will not restore its visibility if my web request does not finish before or soon after ViewWillAppear has fired.
I have a navigation based iPhone 4.0 application used a simple Root and Detail view setup.
I am working with data that is returned from a webservice so when I push my detail view in its ViewDidLoad function I call my web service method in a separate thread and the Iphone lifecycle does its thing on the main thread. I must disable/hide the back button until the web request has finished (or failed) so I call self.navigationItem.hidesBackButton = YES; in ViewDidLoad and self.navigationItem.hidesBackButton = NO; in the delegate function which fires once my web request has finished or failed.
I already tried the following:
[self.navigationItem performSelectorOnMainThread:#selector(setHidesBackButton:) withObject:NO waitUntilDone:NO];
[self.navigationItem setHidesBackButton:NO];
[self.view setNeedsDisplay];
[self.navigationController.view setNeedsDisplay];
UINavigationItem *nav = self.navigationItem;
nav.hidesBackButton = NO;
Root View Controller Push Code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ArticleViewController *articleViewController = [[ArticleViewController alloc] initWithNibName:#"ArticleViewController" bundle:nil];
NewsArticle *newsArticle = [newsItems objectAtIndex:indexPath.row];
articleViewController.articleID = newsArticle.newsID;
[self.navigationController pushViewController:articleViewController animated:YES];
[newsArticle release];
[articleViewController release];
}
Details View Controller Code:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
id scrollView = [[[self webContent] subviews] objectAtIndex:0];
if([scrollView respondsToSelector:#selector(setBackgroundColor:)] )
{
[scrollView performSelector:#selector(setBackgroundColor:)
withObject:[UIColor blackColor]];
}
[self getNewsArticle];
}
//Fires when the web request has finished
- (void) finish:(NewsArticle *)newsArticleFromSvc {
self.navigationItem.hidesBackButton = NO;
self.newsArticle = newsArticleFromSvc;
[self bindNewsArtice];
}
Any help is GREATLY appreciated I can hardly ##$&^ believe that hiding a button in a UI could cause me this much wasted time.
Try use this method of UINavigationItem :
- (void)setHidesBackButton:(BOOL)hidesBackButton animated:(BOOL)animated
I wasn't able to solve this problem. Instead I tweaked my App Logic to make hiding he back button not necessary.