I have a TableViewController with a button that triggers an [NSURLConnection sendAsynchronousRequest...] event, and also loads a modal segue through performSegueWithIdentifier:sender: which targets a small view controller. The purpose of this overlay view controller is to show a loading graphic and to prevent user interaction while the data is sent through the NSURLConnection.
In the completion block of the NSURLConnection, I call a method which removes the data in the TableViewController (its just a batch listing), and then calls dismissViewControllerAnimated:completion: on the overlay view controller.
Everything works except for dismissing the overlay view controller, which throws a warning in the debugger which says:
"Warning: Attempt to dismiss from view controller while a presentation or dismiss is in progress!"
I have found various questions and answers about this error, particularly about using the performSelector:object:withDelay methods, but so far nothing has worked.
This is particularly annoying because I use a similar process in another area of the app, except that the dismissViewController is called from selecting a UITableViewCell, and this works fine...
The relevant bits of my code are shown below:
#import "ViewBatch.h"
#interface ViewBatch ()
#property (strong, nonatomic) LoadingOverlayViewController *loadingOverlay;
#end
#implementation ViewBatch
#synthesize loadingOverlay;
....
- (IBAction)exportBatch:(id)sender
{
if ([productArray count] > 0) {
[self performSegueWithIdentifier:#"loadingSegue" sender:self];
[self processData];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"loadingSegue"]) {
loadingOverlay = segue.destinationViewController;
}
}
- (void)processData
{
// Code to create a file and NSURLRequest...
// ....
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
if ([responseData length] > 0 && error == nil)
{
// Not used for this request yet...
}
else if ([responseData length] == 0 && error == nil)
{
// Success...
[self didSendData];
}
else if (error != nil)
{
// Connection error...
NSLog(#"Error: %#", error);
}
}];
}
- (void)didSendData
{
// Reset the batch...
[productArray removeAllObjects];
[self.tableView reloadData];
[loadingOverlay dismissViewControllerAnimated:YES completion:NULL];
}
And the loading view controller is just:
#import <UIKit/UIKit.h>
#interface LoadingOverlayViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *statusLabel;
#property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
#end
....
....
#import "LoadingOverlayViewController.h"
#interface LoadingOverlayViewController ()
#end
#implementation LoadingOverlayViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.activityIndicator startAnimating];
}
#end
I'm not sure about the exact cause of the problem, but here are some observations about your code.
You do not need an overlay in order to prevent user interaction. Simply turn off user interaction with -NSApplication beginIgnoringInteractionEvents.
You do not need an entire view controller in order to show an overlay view. Just show the view. For example I often place a large UIActivityIndicatorView in the middle of my view while an NSURLConnection is happening.
You do not need an NSOperationQueue in order to use NSURLConnection asynchronously. It is already asynchronous. Just create the NSURLConnection and wait for the delegate messages to arrive. As it is, you are bolluxing yourself because you are setting up a secondary queue, the message arrives on that queue, and you call didSendData which calls reloadData on the table - in the background, which is illegal. If you did this the normal way, your delegate messages would arrive on the main thread which is exactly what you want.
Never mind, I got it worked out.
Amazing what half an hours break can do to organise one's thoughts...
The process was finishing and calling dismissViewController before the actual view was even finished loading. A simple delegate call from the viewDidLoad on the overlay sorted things out.
I had this problem as well. I noticed it was a result from copying and pasting a button in my nib file. The result of this was that I had to IBActions tied two 1 UIButton.
Related
This question already has answers here:
how to reload tableview of another uiviewcontroller in current viewcontroller
(4 answers)
Closed 7 years ago.
I have two view controllers. First one has a table view and using the second view controller I'm calling a method on first view controller. In that method I'm trying to adding an item to the objects array and refresh the table view.
[postsArr insertObject:post atIndex:postsArr.count];
dispatch_async(dispatch_get_main_queue(), ^{
[self.newsTableView reloadData];
});
I'm calling this method in 2nd view controller,
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
[self.appContext.creator createWithText:userText completion:^(UserData *userData,NSError *error) {
if (error == nil){
if (userData != nil) {
[self.parent addNew:((UserData*)[userData.list objectAtIndex:0]) withImage:nil];
}
}else{
[self showAlertWith:#"Error" with:#"Error occurred!"];
}
}];
});
How may I refresh the table view from another view controller?
Add this on the top of the interface of your second viewcontroller
#protocol SecondViewControllerDelegate <NSObject>
- (void)addNewItem:(id)item;
#end
#interface SecondViewController : UIViewController
#property (nonatomic, weak) id <SecondViewControllerDelegate> delegate;
#end
The point in your firstViewController from where you are instantiating your secondViewController for navigation add secondviewController.delegate as self.
self.secondViewController.delegate = self;
From the point where you get response in your secondViewController and you want to addItem in your firstViewController call delegate method from here and pass that item to firstViewController in that delegate method.
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
[self.appContext.creator createWithText:userText completion:^(UserData *userData,NSError *error) {
if (error == nil){
if (userData != nil) {
[self.delegate addNewItem:((UserData*)[userData.list objectAtIndex:0]) withImage:nil];
}
}else{
[self showAlertWith:#"Error" with:#"Error occurred!"];
}
}];
});
Add the implementation of addNewItem in your firstViewController
- (void)addNewItem:(id)item{
[postsArr insertObject:post atIndex:postsArr.count];
dispatch_async(dispatch_get_main_queue(), ^{
[self.newsTableView reloadData];
});
}
You can use NSNotificationCenter
Fire notification when you need to reload the table
Just follow the link to know how implement notifications
Send and receive messages through NSNotificationCenter in Objective-C?
There Are Many methods through you can achieve your Goal
NSNotification
Delegates
KeyValueObserver
But I found most reliable is
#property (copy) void (^valueTypeChangedBlock) (NSarray arrayTypeObject);
Add This Property In .h file
And In .m File Add This
self.valueTypeChangedBlock = ^(NSarray NewArr) {
postsArr = NewArr
[self.newsTableView reloadData];
};
Where Ever U want to change the array from which table view Reload
Add the new array here
self.valueTypeChangedBlock (self.NewArray);
I have two ViewControllers. Let's call them no1 and no2. In no2 I have PopoverViewController with some options and instance of NSTimer. Timer function is calling popToViewController 3 seconds after popup is presented if nothing is clicked in the popover, which is returning user to no1 ViewController. Problem is when this function is triggered, screen is changed to the no1, but application crashes without error message below.
PopoverViewController doesn't have delegate and it is registered as property of second VC as:
#property (nonatomic)UIPopoverController *optionsPopover;
Does anyone have any idea why there is no crash report available? And if there is no reference to the popover why it is crashing?
Implementation in viewWillDisappear looks like this:
if([_optionsPopover isPopoverVisible]){
[_optionsPopover dismissPopoverAnimated:NO];
_optionsPopover = nil;
}
I tried forcing UI to update on main thread (below code), but the result is the same. Crash still exists.
dispatch_async(dispatch_get_main_queue(), ^{
if([_optionsPopover isPopoverVisible]){
[_optionsPopover dismissPopoverAnimated:NO];
_optionsPopover = nil;
}
});
Please try to make strong reference of UIPopoverController
#property (nonatomic,retain)UIPopoverController *optionsPopover;
call below method in - (void)viewDidDisappear:(BOOL)animated instead of viewwilldisappear -
- (void)viewDidDisappear:(BOOL)animated {
if([_optionsPopover isPopoverVisible]){
[_optionsPopover dismissPopoverAnimated:NO];
_optionsPopover = nil;
}
}
Set the property as strong :
#property (strong, nonatomic) UIPopoverController *_optionsPopover;
Remove this line of code :
_optionsPopover = nil;
You are setting _optionsPopover to nil after dismiss..
Edit
Possible problem: timer not invalidated after viewController is dismissed,
if([_optionsPopover isPopoverVisible])
{
[yourTime invalidate]; // added on edit
[_optionsPopover dismissPopoverAnimated:NO];
}
Try: [_optionsPopover dismissPopoverAnimated:NO]; alone, because dismissing it will basically makes it nil..
In my app, I am trying to make a splash image appear as my UIWebView loads so it is not just a blank screen. However my webViewDidFinishLoad method will not work. This means that the splash image appears but does not disappear from the screen once the UIWebView has loaded.
My code for the method is:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
[loadingImageView removeFromSuperview];
}
Any help on why the method will not work would be appreciated greatly.
My .h:
#interface ViewController : UIViewController
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
My ViewDidLoad and webViewDidFinishLoading:
- (void)viewDidLoad {
UIWebView *mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
[super viewDidLoad];
}
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
// Remove loading image from view
[loadingImageView removeFromSuperview];
}
Hi probably you do not set proper delegate.
This is small code tip for you.
-(void)viewDidLoad {
mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[loadingImageView removeFromSuperview];
NSLog(#"finish");
}
In you're .h file add.
#interface MyView: UIViewController <UIWebViewDelegate> {
UIWebView *webView;
}
Code fixes.
For .h file
#interface ViewController : UIViewController<UIWebViewDelegate>
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
For .m file
- (void)viewDidLoad
{
[super viewDidLoad];
webView.delegate = self;
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
At certain times, this delegate method actually never gets fired. I have had severe problems with the same thing in some of my projects.
At one occasion, I actually had to solve it with a timer, checking the state of the web view every second or so to see if I could proceed.
In that particular case, I just needed a certain element to be present. Still, the view did not trigger the finish loading event, due to external script errors being injected.
So, I just started a trigger when the web view begun loading, then called a method every now and then to see if the web view contained the element in question.
- (void)methodCalledByTimer {
if (<I still do not have what I need>) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
You could also check if the web view is actually loading, if that is absolutely necessary:
- (void)methodCalledByTimer {
if (self.webView.isLoading) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
Then, naturally, I'd check for the finishedLoading event as well, just to be sure. Remember to also implement the webView:didFailLoadWithError: method as well.
When waiting for a web page to finish loading, there are some things to keep in mind.
For instance, do you really need it to stop loading, or is there anything else you can do? In my case, I needed an element. Being able to properly execute a script is another thing that may be required.
Second, is the loading page using any external resources? I once had external script errors causing the webViewDidFinishLoad: method to not being called at all. If I removed the external scripts, it worked.
Third, if the page is using external resources, you are exposed not only to the loading capacity of your own resources, but that of the external resources as well. Tracking scripts, ads etc...if one resource provider is delivering content sloooowly (or not at all), you could page could be stuck in loading state forever.
So, I'd go with checking for something else. :)
I see you aren't handling errors. If there is an error, all subsequent delegate calls will not happen. I was surprised to find that this is true when the webview uses a plugin too. It calls this error method telling you that the webview handed off to the delegate, in my case the movie player.
implement this and see if that is it.
-(void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.code == 204) {
//request was handled by a plugin instead of by the webview directly
...
}
else
{
NSLog(#"didFailLoadWithError. ERROR: %#", error);
}
}
I was able to do all the remaining loading work in this method instead of the webviewdidfinishLoad
Here is my .h file
#import <UIKit/UIKit.h>
#interface PersonViewController : UIViewController
#property(strong,nonatomic) NSString *personTitle;
And here is my .m file
#interface PersonViewController ()
#property (weak, nonatomic) IBOutlet UILabel *titleView;
#end
#implementation PersonViewController
//stuff …
-(void)setPersonTitle:(NSString *)personTitle
{
[self.titleView setText:personTitle];// also self.titleView.text=personTitle
[self.titleView setNeedsDisplay];
NSLog(#"The title shoud match as %# :: %#",personTitle,self.titleView.text);
}
-(NSString *)personTitle
{
return self.titleView.text;
}
//… more stuff
#end
The logging shows that the value is (null) for self.titleView.text whereas personTitle prints the appropriate value.
I remember doing this same thing a number of times and it worked. Any ideas why it’s failing this time?
update I use storyboard to set my scenes. And I am using xcode-5 and iOS-7
update: how I call
The user clicks a button, leading to a push segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"enter prepare for segue.");
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
if ([segue.identifier isEqualToString:the_identifier_for_person]) {
NSLog(#"segue to person is progressing“);
if ([segue.destinationViewController isKindOfClass:[PersonViewController class]]) {
NSLog(#"segue to person destination is a match");
PersonViewController *aPerson = (PersonViewController *)segue.destinationViewController;
aPerson.personTitle=((MyItem*)self.allItems[indexPath.row]).title;
NSLog(#"segue to person is done");
}
}
}
This sounds like you forgot to wire up your UILabel in the storyboard. Can you confirm that self.titleView is not null?
View controllers create their views on demand, but can spot that only via a call to view. When the view is loaded, your outlets will be populated.
Either call view to force loading or keep the string in abeyance until you get viewDidLoad.
(aside: prior to iOS 6, views would also be released in low-memory situations so the idiomatic thing is to store the string and populate on viewDidLoad)
Having accepted another answer, I wanted to show the pattern that I actually used to solve the problem, in case someone else comes looking. This pattern is best practice (yes, I forgot it for a long moment there).
#pragma mark - update UI
-(void)setPersonTitle:(NSString *)personTitle
{
_personTitle=personTitle;
if (self.view.window) [self updateUI];//only if I am on screen; or defer to viewWillAppear
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateUI];
}
-(void)updateUI
{
self.titleView.text=self.personTitle;
}
It is always important to update the ui when the data has changed, which is why I must make the call inside setPersonTitle. But because the IBOutlets are not yet set when I set personTitle in prepareForSegue, then I must also make the call inside viewWillAppear.
Do you actually call the -(void)setPersonTitle:(NSString *)personTitle method?
It seems that you aren't calling it correctly which would result in the title being null.
After reviewing the prepareForSeque it is clear that you are not calling the method. You are actually just changing the #property named personTitle.
In the viewDidLoad you should have it so that self.titleView.text = self.personTitle;
I am currently designing the structure for my first iPhone game and ran into a problem. Currently, I have a 'MenuViewController' that allows you to pick the level to play and a 'LevelViewController' where the level is played.
A UIButton on the 'MenuViewController' triggers a modal segue to the 'LevelViewController'.
A UIButton on the 'LevelViewController' triggers the following method to return to the 'MenuViewController':
-(IBAction)back:(id)sender //complete
{
[self dismissModalViewControllerAnimated:YES];
}
The problem is, I have a UILabel on the menu page that prints the number of total points a player has. Whenever I go back to the menu from the level, I want this label to automatically update. Currently, the label is defined programmatically in the 'MenuViewController':
-(void)viewDidLoad {
[super viewDidLoad];
CGRect pointsFrame = CGRectMake(100,45,120,20);
UILabel *pointsLabel = [[UILabel alloc] initWithFrame:pointsFrame];
[pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]];
[self.pointsLabel setTag:-100]; //pointsLabel tag is -100 for id purposes
}
self.playerPoints is an integer property of MenuViewController
Is there a way I could update the label? Thanks ahead of time!
This is a perfect case for delegation. When the LevelViewController is done, it needs to fire off a delegate method which is handled in the MenuViewController. This delegate method should dismiss the modal VC and then do whatever else you need it to do. The presenting VC should normally handled the dismissal of modal views it presents.
Here is a basic example of how to implement this:
LevelViewController.h (Above the Interface declaration):
#protocol LevelViewControllerDelegate
-(void)finishedDoingMyThing:(NSString *)labelString;
#end
Same file inside ivar section:
__unsafe_unretained id <LevelViewControllerDelegate> _delegate;
Same File below ivar section:
#property (nonatomic, assign) id <LevelViewControllerDelegate> delegate;
In LevelViewController.m file:
#synthesize delegate = _delegate;
Now in the MenuViewController.h, #import "LevelViewController.h" and declare yourself as a delegate for the LevelViewControllerDelegate:
#interface MenuViewController : UIViewController <LevelViewControllerDelegate>
Now inside MenuViewController.m implement the delegate method:
-(void)finishedDoingMyThing:(NSString *)labelString {
[self dismissModalViewControllerAnimated:YES];
self.pointsLabel.text = labelString;
}
And then make sure to set yourself as the delegate for the LevelViewController before presenting the modal VC:
lvc.delegate = self; // Or whatever you have called your instance of LevelViewController
Lastly, when you are done with what you need to do inside the LevelViewController just call this:
[_delegate finishedDoingMyThing:#"MyStringToPassBack"];
If this doesn't make sense, holler and I can try to help you understand.
Make a property self.pointsLabel that points to the UILabel, then you can just call something like [self.pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]]; to update the label with the new score
In your modal view header file, add the property:
#property (nonatomic,assign) BOOL updated;
Then in your main view controller, use didViewAppear with something like:
-(void)viewDidAppear:(BOOL)animated{
if (modalView.updated == YES) {
// Do stuff
modalView.updated = NO;
}
}
Where "modalView" is the name of that UIViewController that you probably alloc/init there.
Add more properties if you want to pass more info, like what level the user picked.