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/
Related
Essentially I'm working with 3 view controllers.
Main view which starts a download. (Webview based which passes the download).
Modal download controller. (Tab based).
Downloader (HCDownload).
In the main view my download gets passed like so:
//Fire download
[activeDL downloadURL:fileURL userInfo:nil];
[self presentViewController:vc animated:YES completion:nil];
activeDL is initialized in viewDidLoad:
activeDL = [[HCDownloadViewController alloc] init];
If I removed the presentViewController, it still downloads, which is fine. Then i tap my Downloads button, it brings up the controller which defines the tabs like so:
center = [[CenterViewController alloc] init];
activeDL = [[HCDownloadViewController alloc] init];
completedDL = [[DownloadsViewController alloc] init];
activeDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Active Downloads"
image:nil //[UIImage imageNamed:#"view1"]
tag:1];
completedDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Completed Downloads"
image:nil //[UIImage imageNamed:#"view3"]
tag:2];
[self setViewControllers:[NSArray arrayWithObjects:activeDL, completedDL, nil]];
However, it is not passing the current active download. I don't know if it's a initialization problem, or my tab issue of showing the current download.
From his github, he suggests to get the current number of downloads is to call: dlvc.numberOfDownloads which for me would be
[activeDL numberOfDownloads].
I call this in the the Downloader viewWillAppear but nothing shows.
Does anybody has any suggestions or have worked with this controller?
Any help would be appreciated.
When you call:
activeDL = [[HCDownloadViewController alloc] init];
You are creating a new download controller, which has its own internal downloads array. This library, as written, has no way to pass this information from one HCDownloadViewController object to another.
Tying downloads to VC's like this will cause problems -- I recommend you rewrite this code to split that apart.
To hack around it, try to create just one HCDownloadViewController object and pass it around.
Ok so with the last comment of the other answer, "Make activeDL a member variable instead of a local variable.", got me Googling and with some tinkering and bug fixing along the way I managed to get it all up and running perfect.
I declared it all in my AppDelegate.
AppDelegate.h
#interface SharedDownloader : HCDownloadViewController <HCDownloadViewControllerDelegate>
+ (id)downloadingView;
#end
AppDelegate.m
static HCDownloadViewController *active;
#implementation SharedDownloader
+ (id)downloadingView {
if (active == nil)
active = [[HCDownloadViewController alloc] init];
return active;
}
#end
Calling to the class for downloading in my main view controller:
-(id)init{
activeDL = [SharedDownloader downloadingView];
return self;
}
//Spot where I fire the download
if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
//More code here
[activeDL downloadURL:fileURL userInfo:nil];
}
Lastly in my tab bar controller:
-(id)init {
activeDL = [SharedDownloader downloadingView];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
activeDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Active Downloads" image:nil] tag:2];
}
I believe that's all of it. In any case, thanks to Lou Franco for pointing me in the right direction.
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 have a view controller with many views and a tableview.
The tableview's cells are customized, so there is another class for setting up the cells.
In each cell there is a button. The image of this button changes depending on the cell's content (this content gets read from a DB).
Basically, when the user presses the button, it changes itself to another image, a new status is written to the DB but the tableview does not update itself automatically.
The method for the button is in the custom cell class, so I've tried to instantiate my view controller (the one with the tableview) and execute a method for updating some labels in the views and the tableview:
ViewControllerWithTable *vc = [[ViewControllerWithTable alloc] init];
[vc updateEverything];
But this doesn't work.
The same "updateEverything" method, called from the same "ViewControllerWithTable" (adding a reload button) works perfectly.
Adding the "[tableView reloadData]" in the viewWillAppear method won't work because all the action is done in the same view.
What am I missing?
EDIT: adding some code to be more clear.
This is the method I use to update the tableview. It's inside the ViewController with the embedded tableview and it works when triggered by a button in one of the views:
- (void) updateEverything {
// lots of DB writing and reading, plus label text changing inside all the views
[tableView reloadData];
}
This is the IBAction for the button press and it's in the custom cell class:
-(void) btnPresaPressed:(UIButton *)sender {
AppDelegate *deleg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
deleg.did = sender.tag;
NSString *s1 = NSLocalizedString(#"ALERT_TITLE", nil);
NSString *s2 = NSLocalizedString(#"ALERT_BODY", nil);
NSString *s3 = NSLocalizedString(#"YES", nil);
NSString *s4 = NSLocalizedString(#"NO", nil);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:s1
message:s2
delegate:self
cancelButtonTitle:s4
otherButtonTitles:s3, nil];
[alertView setTag:1];
[alertView show];
}
This method shows an alert view that calls another method, always in the custom cell class:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
AppDelegate *deleg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
DbOperations *db = [[DbOperations alloc] init];
NSString *alrtTitle = [alertView buttonTitleAtIndex:buttonIndex];
NSString *s3 = NSLocalizedString(#"YES", nil);
NSString *s4 = NSLocalizedString(#"NO", nil);
switch (alertView.tag) {
case 1:
//
break;
case 2:
if ([alrtTitle isEqualToString:s3]) {
// DB writing and reading
ViewControllerWithTable *vc = [[ViewControllerWithTable alloc] init];
[vc updateEverything];
} else if ([alrtTitle isEqualToString:s4]){
//
}
break;
case 3:
if ([alrtTitle isEqualToString:s3]) {
//
}
break;
default:
break;
}
}
In this case, the updateEverything method don't work.
EDIT after you added more code:
In the following lines:
if ([alrtTitle isEqualToString:s3]) {
// DB writing and reading
ViewControllerWithTable *vc = [[ViewControllerWithTable alloc] init];
[vc updateEverything];
you are instantiating a new view controller altogether that has nothing to do with your original view controller that displayed the table view. So, you are sending the update message to the wrong object.
What you need is a mechanism for your cell to know which is the right controller to send the message to.
One easy solution would be using NSNotificationCenter:
the view controller register itself for a certain kind of notification:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(updateEverything:)
name:kCellSentUpdateMessageNotification
object:nil];
your cell sends the notification, instead of calling the message directly:
[[NSNotificationCenter defaultCenter] postNotificationName:kCellSentUpdateMessageNotification object:nil];
OLD Answer:
You should call
[self.tableView reloadData]
from your updateEverything method implementation. This will reload the table data, effectively updating its rows appearance. The updateEverything method shall be called when tapping on the button in a row for this to work, obviously.
If that does not work, please provide more code.
Have you try to put
[[self tableView] reloadData];
I remember I had an issue like that, and this line on top solve my problem.
This is your problem:
The method for the button is in the custom cell class, so I've tried
to instantiate my view controller (the one with the tableview) and
execute a method for updating some labels in the views and the
tableview:
You created an entirely new view controller that has no knowledge of your tableview. The best thing to do is to create a property in your cell subclass and set it to your view controller when you set the cell. Then you can call your updateEverything method on that property.
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.
So I have a subclass of UITableViewController that loads some data from the internet and uses MBProgressHUD during the loading process. I use the standard MBProgressHUD initialization.
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Loading";
[HUD show:YES];
This is the result:
.
Is there any way to resolve this issue, or should I just abandon MBProgressHUD?
Thanks!
My solution was pretty simple. Instead of using self's view, I used self's navigationController's view.
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
This should work for the OP because his picture shows he's using a UINavigationController. If you don't have a UINavigationController, you might add another view on top of your UITableView, and add the HUD to that. You'll have to write a little extra code to hide/show this extra view.
An unfortunate thing with this simple solution (not counting my idea adding another view mentioned above) means the user can't use the navigation controls while the HUD is showing. For my app, it's not a problem. But if you have a long running operation and the user might want to press Cancel, this will not be a good solution.
It's probably because self.view is a UITableView, which may dynamically add/remove subviews including the headers, which could end up on top of the HUD after you add it as a subview. You should either add the HUD directly to the window, or (for a little more work but perhaps a better result) you could implement a UIViewController subclass which has a plain view containing both the table view and the HUD view. That way you could put the HUD completely on top of the table view.
My solution was:
self.appDelegate = (kmAppDelegate *)[[UIApplication sharedApplication] delegate];
.
.
_progressHUD = [[MBProgressHUD alloc] initWithView:self.appDelegate.window];
.
[self.appDelegate.window addSubview:_progressHUD];
Works like a charm for all scenarios involving the UITableViewController. I hope this helps someone else. Happy Programming :)
Create a category on UITableView that will take your MBProgressHUD and bring it to the front, by doing so it will always appear "on top" and let the user use other controls in your app like a back button if the action is taking to long (for example)
#import "UITableView+MBProgressView.h"
#implementation UITableView (MBProgressView)
- (void)didAddSubview:(UIView *)subview{
for (UIView *view in self.subviews){
if([view isKindOfClass:[MBProgressHUD class]]){
[self bringSubviewToFront:view];
break;
}
}
}
#end
A simple fix would be to give the z-index of the HUD view a large value, ensuring it is placed in front of all the other subviews.
Check out this answer for information on how to edit a UIView's z-index: https://stackoverflow.com/a/4631895/1766720.
I've stepped into a similar problem a few minutes ago and was able to solve it after being pointed to the right direction in a different (and IMHO more elegant) way:
Add the following line at the beginning of your UITableViewController subclass implementation:
#synthesize tableView;
Add the following code to the beginning of your init method of your UITableViewController subclass, like initWithNibName:bundle: (the beginning of viewDidLoad might work as well, although I recommend an init method):
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
Then you don't need to change your code you posted in your question any more. What the above code does is basically seperating the self.tableView from self.view (which was a reference to the same object as self.tableView before, but now is a UIView containing the table view as one might expect).
I've Just solved that issue manually , it has been 2 years since Chris Ballinger asked but maybe someone get used of what is going on here.
In UITableViewController i execute an HTTP method in viewDidLoad , which is running in background so the table view is loaded while the progress is shown causing that miss.
i added a false flag which is changed to yes in viewDidLoad, And in viewDidAppear something like that can solve that problem.
-(void) viewDidAppear:(BOOL)animated{
if (flag) {
[self requestSomeData];
}
flag = YES;
[super viewDidAppear:animated];
}
I had the same problem and decided to solve this by changing my UITableViewController to a plain UIViewController that has a UITableView as a subview (similar to what jtbandes proposed as an alternative approach in his accepted answer). The advantage of this solution is that the UI of the navigation controller isn't blocked, i.e. users can simply leave the ViewController in case they don't want to waiting any longer for your timely operation to finish.
You need to do the following changes:
Header file:
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
- (id)initWithStyle:(UITableViewStyle)style;
#end
Implementation file:
#interface YourViewController ()
#property (nonatomic, retain) UITableView *tableView;
#property (nonatomic, retain) MBProgressHUD *hud;
#end
#implementation YourViewController
#pragma mark -
#pragma mark Initialization & Memory Management
- (id)initWithStyle:(UITableViewStyle)style;
{
self = [super init];
if (self) {
// create and configure the table view
_tableView = [[UITableView alloc] initWithFrame:CGRectNull style:style];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return self;
}
- (void)dealloc
{
self.tableView = nil;
self.hud = nil;
[super dealloc];
}
#pragma mark -
#pragma mark View lifecycle
- (void)loadView {
CGRect frame = [self boundsFittingAvailableScreenSpace];
self.view = [[[UIView alloc] initWithFrame:frame] autorelease];
// add UI elements
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
}
- (void)viewWillDisappear:(BOOL)animated
{
// optionally
[self cancelWhateverYouWereWaitingFor];
[self.hud hide:animated];
}
The method -(CGRect)boundsFittingAvailableScreenSpace is part of my UIViewController+FittingBounds category. You can find its implementation here: https://gist.github.com/Tafkadasoh/5206130.
In .h
#import "AppDelegate.h"
#interface ViewController : UITableViewController
{
MBProgressHUD *progressHUD;
ASAppDelegate *appDelegate;
}
In .m
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
appDelegate = (ASAppDelegate *) [UIApplication sharedApplication].delegate;
progressHUD = [MBProgressHUD showHUDAddedTo:appDelegate.window animated:YES];
progressHUD.labelText = #"Syncing To Sever";
[appDelegate.window addSubview:progressHUD];
This should work.
[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
And to remove you can try
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];