I have a view in which I am fetchind data from the web - calling a php script and get a JSON response. The way that I do it is by performing an NSUrlRequest.
The way I do it is like this:
-(void) viewDidLoad
{
.....
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:url]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"didReceiveResponse");
[self.responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"didFailWithError");
NSLog(#"Connection failed: %#", [error description]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"connectionDidFinishLoading");
NSLog(#"Succeeded! Received %d bytes of data",[self.responseData length]);
...
}
Now I have another UIView, that wants to be displayed when that screen opens - like a splash screen only for this screen (which is not the starting screen) - it would be a how to use it screen. I want to be visible only for 5 secs for example.
If I place the [self openCustomView] inside the ViewDidLoad then I get a warning:
is not in the root view hierarchy
and the view does not open.
If I put in the connectionDidFinishLoading then I am waiting for the data to download which can take time.
Is there a way to preview that splash screen and download the data in the background?
My openCustomView is this:
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
CustomView *vc = [sb instantiateViewControllerWithIdentifier:#"CustomViewID"];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:vc animated:YES completion:NULL];
This is a bit of guessing but, from the message, I suspect you're calling openCustomView from the wrong spot. I don't believe self (and its views) are considered part of the hierarchy during loading. I'd try viewDidAppear.
Related
Hai here Am downloading a video and storing it to documents folder in iphone... but when i click on download button video will start downloading and progress will show nicely... but when i go back to previous view controller and came back to the downloading controller during downloading the progress view status will not show the current status... can anybody help me to solve this? thanks in advance...
Here is my code...
- (void)downLoad:(UIButton *)sender {
_downloadBtn.hidden = YES;
_videoData = [[NSMutableData alloc] init];
_resourceName = [_resourceNameArray objectAtIndex:sender.tag];
_resourceType = [_contentTypesArray objectAtIndex:sender.tag];
NSString *urlStr = [NSString stringWithFormat:#"%#",[_videoUrlArray objectAtIndex:sender.tag]];
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]] delegate:self ];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"Downloading Started");
_length = [response expectedContentLength];
NSLog(#"Size:%0.2f",_length);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_videoData appendData:data];
float progress = (float)[_videoData length]/(float)_length;
NSLog(#"Progress:%0.2f",progress);
//_timeLabel.text = [NSString stringWithFormat:#"%0.2F%%",progress*100];
[_progressView setProgress:progress animated:YES];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"didFailWithError");
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self saveLocally:_videoData res:_resourceName type:_resourceType];
NSLog(#"File Saved !");
}
Download screen -> previous screen -> Download screen. Let's try:
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_videoData appendData:data];
float progress = (float)[_videoData length]/(float)_length;
NSLog(#"Progress:%0.2f",progress);
NSNumer *n = [NSNumber numberWithFloatValue:progress];
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_DOWNLOAD_PROGRESS object:n];
//_timeLabel.text = [NSString stringWithFormat:#"%0.2F%%",progress*100];
[_progressView setProgress:progress animated:YES];
}
When you go back to download screen, you should update progress by receive notification
#define NOTIFICATION_DOWNLOAD_PROGRESS #"NOTIFICATION_DOWNLOAD_PROGRESS"
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(processProgressBar:) name:NOTIFICATION_DOWNLOAD_PROGRESS object:nil];
}
- (void) processProgressBar:(NSNumber *) progress
{
[_progressView setProgress: progress.floatValue];
}
I'm trying to create an single UIWebView-based application. Its main purpose is to provide authentication to a few websites using NTLM or Basic authentication, without the user having to input the username/password for every website when he/she changes his password (the websites share the user/password database).
In order to do that, I'm "overriding" the loading of page data for the UIWebView, so that I can intercept the HTTP response codes and ask the user for a password every time I detect a 401 Unauthorized response.
I've set up a NSURLConnectionDataDelegate to handle the requests as such:
#interface LPURLConnectionDelegate : NSObject<NSURLConnectionDataDelegate>
{
UIWebView* _webView;
NSMutableURLRequest* _request;
NSURLConnection* _connection;
NSString* _encoding;
NSString* _mimeType;
NSMutableData* _data;
}
- (LPURLConnectionDelegate*) initWithWebView:(UIWebView *)webView;
- (void) loadPageWithRequest: (NSURLRequest*)request;
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
#end
#implementation LPURLConnectionDelegate
- (void) loadPageWithRequest: (NSURLRequest*)request
{
_request = [request mutableCopy];
if(!_data)
{
_data = [NSMutableData dataWithCapacity:0];
[_data retain];
}
[_data setLength:0];
if(_connection)
{
[_connection cancel];
[_connection release];
_connection = nil;
}
_connection = [[NSURLConnection alloc] initWithRequest:_request delegate:self startImmediately:YES];
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
LPAppDelegate* delegate = [LPAppDelegate getInstance];
NSURLCredential* credential = [NSURLCredential credentialWithUser:[delegate getUsername] password:[delegate getPassword] persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//handle connection failed
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"Connection did receive response");
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
_mimeType = [httpResponse MIMEType];
_encoding = [httpResponse textEncodingName];
//handle status - if the status is 401, the connection is canceled and the user is asked for his new password
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[_webView loadData:_data MIMEType:_mimeType textEncodingName:_encoding baseURL:[_request URL]];
}
#end
My UIWebViewDelegate looks like this:
#interface LPWebViewDelegate : NSObject<UIWebViewDelegate>
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
#end
#implementation LPWebViewDelegate
- (void) webViewDidStartLoad:(UIWebView *)webView
{
}
- (void) webViewDidFinishLoad:(UIWebView *)webView
{
//handle success
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
LPURLConnectionDelegate* connectionDelegate = [[LPURLConnectionDelegate alloc] initWithWebView:webView];
[connectionDelegate loadPageWithRequest:request];
return NO;
}
- (void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
//handle fail
}
#end
Basically, what I'm trying to do is tell the UIWebView not to load any page by itself, letting me handle the request to load the page. When the request completes and the user is authenticated, I'm trying to set the data using the UIWebView::loadData:MIMEType:textEncodingName:baseUrl method.
The problem here is that after setting the UIWebView's data from the NSURLConnectionDataDelegate, the UIWebView reloads the page set as baseURL (at least in the simulator - I haven't tried the code on an actual device just yet).
Does anyone know a way to set data to UIWebViews in such way that it doesn't reload the page(*)?
*) I know that I could just let the UIWebView load the page after I make the NSURLConnection to check for authentication (or in parallel), but I'm trying to avoid requesting the page header twice for each page.
Note: the code is not complete, I've extracted what I consider to be the most relevant parts for this issue.
Implement NSURLProtocol class. It essentially a abstract class that allows subclasses to define the URL loading behavior of HTTP schemes
Check this tutorial out here
In this application when user presses a button a url request will be sent to a server and some corresponding data will be received by the application.
My problem is I want to receive the data completely from the server and when the connection did finished receiving the data, change the view and pass the received data to the other view.
After the conversion of data I receive error and app crashes.
I tried this approach but don't know what should I do exactly.
Problem Solved and Code is Edited
#import "ViewController.h"
#interface ViewController ()
//#property (nonatomic, strong) NSMutableData *responseData;
#end
#implementation ViewController
#synthesize responseData;
#synthesize myTableView;
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"viewdidload");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"Response received From the server.");
[self.responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"appending data to the response object.");
[self.responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"didFailWithError");
NSLog([NSString stringWithFormat:#"Connection failed: %#", [error description]]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"Loading data succeeded! Received %d bytes of data",
[self.responseData length]);
//call the converter to convert the received
//Data to Json Packet and save to variable
[self convertDataToJson];
[self changePage];
}
-(void)changePage {
resultsTableViewController *myTableView = [[resultsTableViewController alloc]init];
myTableView.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
myTableView=[myTableView init];
[self presentViewController:myTableView animated:YES completion:NULL];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
resultsTableViewController *mytable = [segue destinationViewController];
NSMutableArray * response = self.responseArray;
mytable.responseArray = response;
}
- (IBAction)get:(id)sender {
//making an instance of data query object and passing the word and dictionary selection to it.
if ( ![[searchField text]isEqualToString:#""] )
{
word = #”test”
[self makeRequestForWord:word withDictionaryName:#""];
// NSData *query = [word dataUsingEncoding:NSUTF8StringEncoding];
}
}
-(void)makeRequestForWord:(NSString*)word withDictionaryName:(NSString*)dicSelect;
{
//creating URL Request Object using given URL object.
//It requests data using nsConnection
}
- (void)convertDataToJson
{
// some conversion and save into responseDATA
}
- (void)viewDidUnload {
[super viewDidUnload];
}
#end
And then in function related to segue I'll send the object to the next view.
You shouldn't be presenting the view controller in connectionDidFinishLoading: since it appears that you have a segue set up for the controller -- it's crashing there because you haven't created myTable yet (declaring it in the .h file doesn't create it). The segue will create the instance of your controller, so you should call performSegueWithIdentifier:sender: in connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"Loading data succeeded! Received %d bytes of data",
[self.responseData length]);
//call the converter to convert the received
//Data to Json Packet and save to variable
[self convertDataToJson];
[self performSegueWithIdentifier:#"MyIdentifier" sender:self];
}
Im am trying to properly integrate MBProgressHUD in a project while i am downloading and processing some data. Forgive me my ignorance if i am asking stupid things, but i'm a complete noob...
I have a "Data" class that is handling my HTTPRequests, so i thing here is the proper place to put some stuff in:
-(void)resolve
{
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[[NSURL alloc] initWithString:[self url]]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSLog(#"Resolving content from: %#",[self url]);
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [[NSMutableData data] retain];
} else {
NSLog(#"Content - resolve: connection failed");
}
// Here is the part that it makes me crazy...
HUD = [[MBProgressHUD showHUDAddedTo:self.view animated:YES] retain];
return;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
expectedLength = [response expectedContentLength];
currentLength = 0;
HUD.mode = MBProgressHUDModeDeterminate;
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
currentLength += [data length];
HUD.progress = currentLength / (float)expectedLength;
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
return;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[HUD hide:YES];
// release the connection, and the data object
[connection release];
// receivedData is declared as a method instance elsewhere
[receivedData release];
// inform the user
NSLog(#"Content - Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
return;
}
Now i know that putting the "showHUDaddedTo" in the -(void)resolve is not the right way...
I am calling this data function from a view controller with an IBaction like this:
-(IBAction) btnClicked:(id) sender {
if ([[issue status] intValue] == 1 ) // issue is not downloaded
{
[(Content *)[issue content] resolve];
//HUD = [[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES] retain];
}
else // issue is downloaded - needs to be archived
{
NSError * error = nil;
[[NSFileManager defaultManager] removeItemAtPath:[(Content *)[issue content] path] error:&error];
if (error) {
// implement error handling
}
else {
Content * c = (Content *)[issue content];
[c setPath:#""];
[issue setStatus:[NSNumber numberWithInt:1]];
[buttonView setTitle:#"Download" forState:UIControlStateNormal];
[gotoIssue setTitle:#"..." forState:UIControlStateNormal];
// notify all interested parties of the archived content
[[NSNotificationCenter defaultCenter] postNotificationName:#"contentArchived" object:self]; // make sure its persisted!
}
}
}
Simply said: i would like to call all the MBProgressHUD stuff from my Conten.m file in the moment i push the download button in my IssueController.m file. I think you see now where my problem is: i am not a coder ;-) Any help is appreciated.
Cheers,
sandor
I would try and use the HUD method showWhileExecuting. Wrap your calls up in a new method and call that to download your information. When your method finishes, so will your HUD.
Also, is there a reason why you are retaining your calls for the HUD? I have not seen that before and I'm not sure if you need to retain those as they are autoreleased.
I'm building an iPhone app that aggregates data from several different data sources and displays them together in a single table. For each data source, I've created a class (WeatherProvider, TwitterProvider) that handles connecting to the datasource, download the data and storing it in an object.
I have another class ConnectionManager, that instantiates and calls each of the two provider classes, and merges the results into a single NSArray which is displayed in the table.
When I run the program calling just the WeatherProvider or just the TwitterProvider, it works perfectly. I can call each of those objects again and again to keep them up-to-date without a problem. I can also call TwitterProvider and then WeatherProvider without a problem.
However, if I call WeatherProvider then TwitterProvider, the TwitterProvider hangs just as I call: [[NSURLConnection alloc] initWithRequest:request delegate:self];.
Other points:
- it doesn't seem to matter how long between when I call WeatherProvider and TwitterProvider, TwitterProvider still hangs.
- in WeatherProvider, I'm using NSXMLParser with NSAutoreleasePool to parse the output from the WeatherProvider.
- ConnectionManager creates an instance of WeatherProvider and TwitterProvider when the application is started, and reuses those instances when the user requests a data refresh.
- I ran the app with the activity monitor connected and it verifies that the app is basically just hanging. No CPU usage, or additional memory allocations or network activity seems to be happening.
There's a bunch of code spread across a couple files, so I've tried to include the relevant bits (as far as I can tell!) here. I very much appreciate any help you can provide, even if it is just additional approaches to debugging.
WeatherProvider
-(void)getCurrentWeather: (NSString*)lat lon:(NSString*)lon lastUpdate:(double)lastUpdate
{
double now = [[NSDate date] timeIntervalSince1970];
NSString *noaaApiUrl;
// don't update if current forecast is < than 1 hour old
if(now - lastUpdate < 3600)
{
[[self delegate] weatherUpdaterComplete:1];
return;
}
// if we already have a forecast, delete and refill it.
if(forecast)
{
[forecast release];
forecast = [[WeatherForecast alloc] init];
}
forecast.clickThroughUrl = [NSString stringWithFormat:#"http://forecast.weather.gov/MapClick.php?lat=%#&lon=%#",
lat, lon];
noaaApiUrl = [NSString stringWithFormat:#"http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php?lat=%#&lon=%#&format=24+hourly",
lat, lon];
NSURLRequest *noaaUrlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:noaaApiUrl]];
[[NSURLConnection alloc] initWithRequest:noaaUrlRequest delegate:self];
}
#pragma mark -
#pragma mark NSURLConnection delegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.noaaData = [NSMutableData data];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[noaaData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.noaaConnection = nil;
[[self delegate] weatherUpdaterError:error];
[connection release];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Spawn a thread to fetch the data so UI isn't blocked while we parse
// the data. IMPORTANT - don't access UIKit objects on 2ndary threads
[NSThread detachNewThreadSelector:#selector(parseNoaaData:) toTarget:self withObject:noaaData];
// the noaaData will be retailed by the thread until parseNoaaData: has finished executing
// so, we'll no longer need a reference to it in the main thread.
self.noaaData = nil;
[connection release];
}
#pragma mark -
#pragma mark NSXMLParser delegate methods
- (void)parseNoaaData:(NSData *)data
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser parse];
[parser release];
[pool release];
}
TwitterProvider
-(void)getLocationTimeline:(NSString*)lat lon:(NSString*)lon lastUpdate:(double)lastUpdate refreshUrl:(NSString*)newUrl
{
NSString *updateURL;
if(tweets.count > 1)
[tweets removeAllObjects];
updateURL = [NSString stringWithFormat:#"http://search.twitter.com/search.json?geocode=%#,%#,1mi&rpp=%#&page=1", lat, lon, TWITTER_LOCAL_UPDATES_URL_RESULTS];
_serviceResponse = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:updateURL]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
#pragma mark -
#pragma mark NSURLConnection delegate methods
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *responseString = [[NSString alloc] initWithData:_serviceResponse encoding:NSUTF8StringEncoding];
// parse tweets
[responseString release];
[_serviceResponse release];
[connection release];
// tell our delegate that we're done!
[[self delegate] twitterUpdaterComplete:tweets.count];
}
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
[_serviceResponse setLength:0];
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
[_serviceResponse appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
[connection release];
[[self delegate] twitterUpdaterError:error];
}
I was able to fix this by removing the threading in WeatherUpdater that was being used for the NSXMLParser. I had 'barrowed' that code from Apple's SiesmicXML sample app.