I have a download running in background. It shows an UIAlertView under some fail condition.
When this alert happens, the application can be in any of the views it shows to the user, but only should be visible in one of them.
Can I delay the presentation of the UIAlertView to the moment the viewController it is associated with is displayed to the user (it's viewDidAppear method is invoked)?
Declare a property on the view controller that you want to show the view.
#interface DownloadViewController : UIViewController
{
UIAlertView *downloadAlertView;
}
#property (retain) UIAlertView *downloadAlertView;
#end
Then, when you detect the error, set the downloadAlertView property of the view controller (this will require you keeping a reference to this view controller by the object that is doing the downloading).
- (void)downloadFailed
{
UIAlertView *alertView = [[[UIAlertView alloc] init] autorelease];
alertView.title = #"Download Failed";
downloadViewController.downloadAlertView = alertView;
}
Then in your DownloadViewController implementation,
- (UIAlertView *)downloadAlertView
{
return downloadAlertView;
}
- (void)setDownloadAlertView:(UIAlertView *)aDownloadAlertView
{
// standard setter
[aDownloadAlertView retain];
[downloadAlertView release];
downloadAlertView = aDownloadAlertView;
// show the alert view if this view controller is currently visible
if (viewController.isViewLoaded && viewController.view.window)
{
[downloadAlertView show];
downloadAlertView = nil;
}
}
- (void)viewDidAppear
{
if (downloadAlertView)
{
[downloadAlertView show];
downloadAlertView = nil;
}
}
Quick explanation:
the first two methods are standard getter/setters, but the setter has added logic, so that if the view controller is currently visible, the alert is shown immediately.
if not, the alert view is stored by the view controller and shown as soon as the view appears.
Related
I am trying to load a UIImage View into a tabbar application with a global class, the image doesn't load the image the first time the rootviewcontroler is loaded, but it does show the alert. If you click on another tabbar item to load another view and come back to the first view the alert and image both show up correctly.
#import "globalNetworkCheck.h"
#import "CheckNetworkAvaliblity.h"
#import "ViewController.h"
#implementation globalNetworkCheck
static globalNetworkCheck *getNetworkStatus = nil;
static UIView *viewNetworkError = nil;
+(globalNetworkCheck *)getNetworkStatus
{
#synchronized(self)
{ if(getNetworkStatus==nil) {
// getNetworkStatus= [globalNetworkCheck new];
if ([CheckNetworkAvaliblity CheckNetwork])
{
{
[(UIView *)viewNetworkError removeFromSuperview];
viewNetworkError.backgroundColor=[UIColor clearColor];
viewNetworkError.hidden=YES;
[viewNetworkError removeFromSuperview];
[[viewNetworkError subviews]
makeObjectsPerformSelector:#selector(removeFromSuperview)];
NSLog(#"Success, Your network is available");
}
}
else
{
UIImageView *imgNoConnection = [[UIImageView alloc] initWithFrame:CGRectMake(0, 70, 320 , 449)];
viewNetworkError = [[UIView alloc]init];
[imgNoConnection setImage:[UIImage imageNamed:#"Noconnection.png"]];
viewNetworkError.frame = CGRectMake(0, 0, 320 , 449);
[viewNetworkError addSubview: imgNoConnection];
viewNetworkError.hidden = NO;
[[UIApplication sharedApplication].keyWindow.rootViewController.view addSubview:viewNetworkError];
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle: #"Connection Failed"
message: #"You are not connected to the internet. Please check your network settings and try again."
delegate: self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
viewNetworkError.hidden = NO;
NSLog(#"Sorry, Network connection is unavailable");
}}}
return getNetworkStatus;
}
#end
I am calling the class method on
-(void)viewWillAppear:(BOOL)animated {
globalNetworkCheck *obj=[globalNetworkCheck getNetworkStatus];
Remove this line:
[viewNetworkError addSubview:viewNetworkError];
Strange to me, how are you going to add viewNetworkError to viewNetworkError.
Edit
I think you mean
[self addSubview:viewNetworkError];
You are going to need somewhere to add the view. Right now this class method has no association on the view hierarchy. If you want to keep it a class method like this then you'll either need to add your error views to the window or the root view controllers view.
[[UIApplication sharedApplication].keyWindow.rootViewController.view addSubview:viewNetworkError];
or
[[UIApplication sharedApplication].keyWindow addSubview:viewNetworkError];
Without understanding the full view stack it is a bit hard to answer further. Alternatively with more rework you can put this responsibility on the AppDelegate rather than a class method like this. Create a view that your App delegate can add to the hierarchy as needed, create a public getter on your app delegate to check current network status, etc. Here is an example of how to great a HUD type view:
http://blog.mugunthkumar.com/coding/ios-code-tweetbot-like-alertpanels/
I've got an issue where an object that's creating a UIAlertView is sending a message back to its delegate, but the delegate object is crashing.
I've got it working OK in a couple of other instances, but what I'm trying to do in this case (and what's different from other similar questions) is that the object that instantiates the alert, and which acts as its delegate is not itself a view, but rather an object that is instantiated within a view. To wit, in the parent view:
#implementation
*MyCustomObject customObject;
-(void)viewDidLoad {
customObject = [[MyCustomObject alloc] init];
}
#end
And in the custom object:
-(void)DoCoolThings {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Do You Want To Do Cool Things"
message:#"...description of cool things..."
delegate:self
cancelButtonTitle:#"No Thanks"
otherButtonTitles:#"HELLS YES", nil];
[message show];
}
and
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
[self DoCoolThings];
} else {
[self GoAndCry];
}
}
When I do the alert view within a viewcontroller object, everything is fine. Doing it within this sub-object which itself has no view allows me to create the alert, but that object--which shouldn't be getting de-allocated based on the scoping--doesn't seem to want to continue to act as a delegate.
The error I'm getting is indeed a de-allocation message, but I feel strongly that this is not the problem because if I remove the alert, all the other stuff--specifically, it's a wrapper for a storekit purchase process--works fine, and all those delegate methods work happily.
I've got a solution which will allow me to move the Alert into the parent view's methods, but I was hoping not to have to. Is this limitation real, or is it my imagination? IE am I doing something else wrong?
I have a scenario where my tableview reload is not working. Please let me know if I have modeled my design correctly or I need to change it.
I have a core view controller and each tab bar view controller is inherited from this core view controller
Each of the tab bar view controller has a tableview and implements the UITableViewDelegate and UITableViewDataSource protocol.
If the app is launched for the first time, In core view controller's viewDidLoad , I have a method to fetch data from web. In the child view controller's viewDidLoad method I have a method which populates the data source for the tableview.
So the issue is on the first launch, data is fetched and saved successfully in Core data but it does not reload the tableview so its empty.
So Currently as per the below code, on First launch I see the Alert view then the loading view and I see the data getting saved in Core Data. But the first tab bar's table view loads up empty with no data and the reason being, its [reload is not being called]. But on next launch the data is there.
Below is the code
Core View COntroller
- (void)viewDidLoad
{
[super viewDidLoad];
if (!self.myManagedObjectContext) {
//Get Shared Instance of managedObjectContext
self.myManagedObjectContext = [LTDataModel sharedInstance].mainObjectContext;
//Check if managed object context has required data
......
if (<data not found> {
[self fetchDataIntoContext];
}
}
-(void)fetchDataIntoContext {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Initializing..."
message:#"This is your first launch of App"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[message show];
UIView *loadingView = [[UILoadingView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:loadingView];
dispatch_queue_t fetchEventQ = dispatch_queue_create("Fetcher", NULL);
dispatch_async(fetchEventQ, ^{
<add data to core data>
[[LTDataModel sharedInstance] save];
dispatch_async(dispatch_get_main_queue(), ^{
[[self.view.subviews lastObject] removeFromSuperview];
});
});
}
Child View Controller
--(void)setMyArrayOfEvents:(NSMutableArray *)myArrayOfEvents {
if (_arrayOfMyEvents != arrayOfMyEvents) {
_arrayOfMyEvents = arrayOfMyEvents;
[self.eventTableView reloadData];
}
}
-(void) viewDidLoad {
[super viewDidLoad];
//Get Shared Instance of managedObjectContext if it does not exist
if (!self.myManagedObjectContext) {
self.myManagedObjectContext = [LTDataModel sharedInstance].mainObjectContext;
}
//Populate the array which feeds the tableview
[self populateTableViewArrayFromContext];
}
-(void)populateTableViewArrayFromContext
{
<Fetch data dfrom Core Data ......>
self.myArrayOfEvents = [[NSMutableArray alloc] initWithArray:localArray];
}
I solved the issue. The issue was not with Child VC's viewDidLoad not being called. Before the background fetching of data finishes the loading view is removed.
Hence the solution was I added the function that populates the data source arrays in the core (parent) view controller with empty body. The Child VC implements the functionality. And called the method before I remove the loading view. Look at code below that I changed in coew view controller
CORE VIEW CONTROLLER
-(void)fetchDataIntoContext {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Initializing..."
message:#"This is your first launch of App"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[message show];
UIView *loadingView = [[UILoadingView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:loadingView];
dispatch_queue_t fetchEventQ = dispatch_queue_create("Fetcher", NULL);
dispatch_async(fetchEventQ, ^{
<add data to core data>
[[LTDataModel sharedInstance] save];
dispatch_async(dispatch_get_main_queue(), ^{
**[self populateTableViewArrayFromContext];**
[[self.view.subviews lastObject] removeFromSuperview];
});
});
}
-(void)populateTableViewArrayFromContext {
//empty the implemetation is in Child VC
}
I am working on an user activation errors, I have a NSObject class that gets call if an error is returned from the DB.
I show an alertview that has a method called when the user presses the UIButton on the alert view. This is what the method looks like.
//ErrorHandling.m
//
case 1: {
NSLog(#"ERROR = 1");
message = [[UIAlertView alloc] initWithTitle:#"Error 1:"
message:#"The activation request failed."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
message.tag = myAlertViewsTag;
[self performSelector:#selector(showAlertViewAndMessage) withObject:message afterDelay:0.3]; // set timer to give any huds time to load so I can unload them correctly
}
break;
//
- (void)showAlertViewAndMessage {
[SVProgressHUD dismiss];
[message show];
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (alertView.tag == myAlertViewsTag) {
if (buttonIndex == 0) {
if (receivedResponseData != nil) {
if (errorCodeValue == 1) {
[[self errorDataDelegate] passErrorDataToRoot:receivedResponseData];
}
// incase the user is further on in the navigationstack bring them back to the rootview
[self.currentNavigationController popToRootViewControllerAnimated:YES];
}
}
}
so far all this code works, accept for the delegate/protocol request... I have checked and double checked my code, however I think maybe I am missing something that maybe you can see. This is what my Delegate and Protocol looks like.
//errorHandling.h
#protocol RecivedErrorData <NSObject>
- (void)passErrorDataToRoot:(NSData *)errorData;
#end
//Protocol/delegate
__weak id <RecivedErrorData> errorDataDelegate;
//Protocol/delegate
#property (weak, nonatomic) id <RecivedErrorData> errorDataDelegate;
//errorHandling.m
//delegate / protocols
#synthesize errorDataDelegate;
[[self errorDataDelegate] passErrorDataToRoot:receivedResponseData];
//RootViewController.h
#import "ErrorHandling.h"
#interface RootViewController : UIViewController <RecivedErrorData> {
// error handling for activations
ErrorHandling *errorHandling;
//RootViewController.m
-(void)viewDidLoad {
errorHandling = [[ErrorHandling alloc] init];
[errorHandling setErrorDataDelegate:self];
}
#pragma ErrorProtocol
- (void)passErrorDataToRoot:(NSData *)errorData {
NSLog(#"WORKED");
}
So thats my code for the protocol and delegate, it almost works when the button is clicked it just never maked it to passErrorDataToRoot delegate method.
I am wondering if its an error in initialization, ErrorHandling.h is initialized originally when the app starts up inside the rootView, then when I get an error from a request I call ErrorHandling.m from a class called EngineRequest.m using alloc init etc... that's the only thing I can think of, that because of this extra allocation im dealing with another method but I am not sure this is the reason? I thought delegates and protocols were used to avoid this issue of reallocation.
I have a UINavigationController inside a UITabBarController. The navigationcontroller has a UITableView and a form for editing items. The problem is that if a tab is tapped during editing, the form is just cleared and the user is dumped back to the UITableView.
Is there a way I can add a prompt to confirm navigation away from the edit view?
First, declare a BOOL in your .h to store the editing state. Also declare a temporary variable we will use later for storing the selected row.
BOOL isEditing;
NSUInteger selectedRow;
In your viewDidLoad, initialize the boolean to NO
- (void)viewDidLoad {
// initialization
isEditing = NO;
[super viewDidLoad];
}
You can then conform your view controller to UITextFieldDelegate and UIAlertViewDelegate. The text field delegate allows the controller to receive callbacks when editing ends and begins for the text fields and the alert view delegate allow it to receive callbacks when an alert view is dismissed.
#interface MyController : UIViewController <UITextFieldDelegate, UIAlertViewDelegate>
You then also need to set all the text field's delegates to be assigned to the controller. So in your cellForRowAtIndexPath when you add the text fields, just add this:
textField.delegate = self;
Once you have this, you are all set up to receive callbacks from the text field - so now implement the following two methods like so:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
isEditing = YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
isEditing = NO;
}
Now the key here is to make a separate method for pushing the next view, so just do something like this (like you would normally when the table view row is selected):
- (void)showNextView {
// in this method create the child view controller and push it
// like you would normally when a cell is selected
// to get the selected row, use the `selectedRow` variable
// we declared earlier.
}
You now need to implement the table view callback when the user selects a row - in this method we test if they are editing and show them a prompt if they are. If they aren't, we go to the next view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
selectedRow = [indexPath row];
if (isEditing) {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Continue Editing?"
message:#"Continue Editing or discard edits"
delegate:self
cancelButtonTitle:#"Discard"
otherButtonTitles:#"Continue"];
[alert show];
[alert release];
return;
}
[self showNextView];
}
Finally, we need to implement the alert view delegate callback for when the alert view is dismissed:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [alertView cancelButtonIndex]) return; // stay editing
[self showNextView];
}
Hope that all makes sense and is helpful to you!
Since you are using a UINavigationController, if you are pushing this "form" onto the stack you could set
#property(nonatomic) BOOL hidesBottomBarWhenPushed
That way the tab bar would be hidden until they are done with the form.
I solved this eventually by using a custom UIBarButtonItem which looks like a back arrow.