i am developing an application where i am loading a urlrequest in the UIWebView and it happened successfully.
But now i am trying to display a UIProgressView when the loading is in progress( starting from 0.0 to 1.0), which is changed dynamically with the progress of loading.
How can i do that?
UIWebView doesn't give you any progress information in the normal mode. What you need to do is first fetch your data asynchronously using an NSURLConnection.
When the NSURLConnection delegate method connection:didReceiveResponse, you're going to take the number you get from expectedContentLength and use that as your max value. Then, inside the delegate method connection:didReceiveData, you're going to use the length property of the NSData instance to tell you how far along you are, so your progress fraction will be length / maxLength , normalized to between 0.0 and 1.0.
Finally, you're going to init the webview with data instead of a URL (in your connection:didFinishLoading delegate method).
Two caveats:
It is possible that the expectedContentLength property of the NSURLResponse is going to be -1 (NSURLReponseUnknownLength constant). In that case, I would suggest throwing up a standard UIActivityIndicator that you shut off inside connection:didFinishLoading.
Make sure that any time you manipulate a visible control from one of the NSURLConnection delegate methods, you do so by calling performSelectorOnMainThread: - otherwise you'll start getting the dreaded EXC_BAD_ACCESS errors.
Using this technique, you can show a progress bar when you know how much data you're supposed to get, and a spinner when you don't know.
You can try to use this subclass of UIWebView which uses the private UIWebView methods - therefore, this solution is not 100% AppStore safe (though some apps do almost 100% use it: Facebook, Google app, ...).
https://github.com/petr-inmite/imtwebview
Using NSURLConnection you are fetching the same data twice,waste of time because it can slow down the user interaction it loads the data twice ,consumes internet data.It is better to make a uiprogress based on timer and when the webview successfuly loaded the webpage it will show the uiprogress loading.in that case you can show a dynamic uiprogress everytime the webpage is loaded.. dont forget to create a uiprogress view and named it myProgressview and set it in file owner's.
HERE IS THE CODE HOPE IT HELPS
#synthesize myProgressView;
- (void)updateProgress:(NSTimer *)sender
{ //if the progress view is = 100% the progress stop
if(myProgressView.progress==1.0)
{
[timer invalidate];
}
else
//if the progress view is< 100% the progress increases
myProgressView.progress+=0.5;
}
- (void)viewDidLoad
{ //this is the code used in order to load the site
[super viewDidLoad];
NSString *urlAddress = #"http://www.playbuzz.org/";
myWebview.delegate = self;
[myWebview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlAddress]]];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{ ///timer for the progress view
timer=[[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(updateProgress:)
userInfo:myProgressView
repeats:YES]retain];
}
- (void)dealloc {
[myProgressView release];
[super dealloc];
}
#end
this code and idea really helps me in solving my problem.
Related
In my iOS app, I am uploading an image with the AFURLConnectionOperation class (View A), and then I allow the user to edit part of the image (View B). Later, in View C, I have a progress bar that needs to show the progress of the uploading that started back in View A.
I cannot figure out how to gain access to the progress of the operation that started in View A from within View C with AFNetworking. It may not be possible as far as I know.
Thanks in advance,
Will
Of course it's possible Will and it has very little to do with AFNetworking but much more to do with common programming patterns.
You're going to need to store the AFURLConnectionOperation object outside of your view controllers where they can both access it. The best practice here would be creating a singleton class that encapsulates the AFNetworking properties and methods to handle uploading your image(s). Whenever you need information about or to interact with that upload you can simply access that singleton via a class method like a sharedInstance.
+ (id)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
If you are interacting with a web service (and not a raw FTP server), then subclassing AFHTTPClient would probably be your best bet for the 'upload manager' type of class solution.
Whatever you choose, once you have a simple class put together you can then register for KVO notifications in your ViewControllers' viewWillAppear & unregister in viewWillDisappear to cleanly handle your UI updates (e.g. progress bar). If you don't understand Key-Value Observing, read the Introduction to Key-Value Observing Programming Guide. You'll be much more able to cope in iOS after having that knowledge under your belt.
So, in view A's upload code, use your magic new class to create and enqueue the upload using a URL (multiple methods could be made to use images in-memory, NSFileURLs or an NSString as shown here)
[[MyImageUploadManager sharedInstance] uploadImageFromPath:#"/wherever/1.png" toURL:[NSURL URLWithString:#"ftp://someserver.com/images/"]];
…in View C's controller's viewWillAppear
- (void) viewWillAppear:(BOOL)animated
{
...
[[MyImageUploadManager sharedInstance] addObserver:self forKeyPath:#"progress" options:NSKeyValueObservingOptionNew context:nil];
...
}
… and in View C's viewWillDisappear
- (void)viewWillDisappear:(BOOL)animated
{
...
[[MyImageUploadManager sharedInstance] removeObserver:self forKeyPath:#"progress" context:nil];
...
}
Whenever that 'progress' property changes in your upload manager class, iOS will call the function observerValueForKeyPath:ofObject:change:context:. Here's what a very simple version of that looks like:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ( [keyPath isEqualToString:#"progress"] )
{
// since you only upload a single file, and you've only added yourself as
// an observer to your upload, there's no mystery as to who has sent you progress
float progress=[change valueForKey:NSKeyValueChangeNewKey];
NSLog(#"operation:%# progress:%0.2f", object, progress );
// now you'd update the progress control via a property bound in the nib viewer
[[_view progressIndicator] setProgress:progress];
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
That should get you well on the way, hope that was helpful.
I am trying to call a web service on page load. Currently I call it on a button click and it works fine. But when I try to do the same on viewDidAppear, it doesn't happen. What i want to achieve is if username and password are saved then it should automatically load the next page. It is filling in the text boxes but not loading the next page.
Here is my code for submit button and ViewDidAppear:
-(IBAction)submitButton{
[apd showCoverView:YES WithActivityIndicator:YES];
PlaceWebService *handler = [[PlaceWebService alloc]init];
[handler setRequestType:Loginparser];
NSString *url = [NSString stringWithFormat:#"http://www.mywebsite.com/api.php?command=auth&cardno=%#&password=%#",username.text,password.text];
[handler sendingLoginRequest:url Respond:self At:#selector(showParsed:)];
}
and for viewDidAppear
-(void)viewDidAppear:(BOOL)animated
{
NSLog(#"Appeared");
[self loginArea];
apd=[[UIApplication sharedApplication]delegate];
NSString *filepath=[self pathOfFile];
if([[NSFileManager defaultManager]fileExistsAtPath:filepath])
{
NSArray *array=[[NSArray alloc]initWithContentsOfFile:filepath];
username.text=[array objectAtIndex:0];
password.text=[array objectAtIndex:1];
[self submitButton];
}
}
What should I do? Please help...
If you want to call the method after loading view and without any event, then you need to that as normal instance method instead of IBAction method.
-(Void)submitButton{
// implementation
}
and then call this method from viewDidAppear.
A couple things could be the problem here.
1)
IBActions usually take a parameter. Declare it as:
- (IBAction) submitButton: (id) sender;
And then call it from your viewDidAppear method as:
[self submitButton: self];
2)
Also make sure UI stuff is happening on the main thread (you didn't specify if the app is multi threaded or not), so maybe:
[self performSelectorOnMainThread: #selector(submitButton:) withObject: self];
And
3)
Set breakpoints to see if your submitButton method (and the lines before it) are actually even called when viewFromAppear: is called.
And Rishi's suggestion is good, too!
long time listener, first time caller.
I have a basic memory issue I don't understand, that I'm sure just about any one of you will see in a second. I'm playing around, trying to learn various ways of using UIWebViews, getting strings from URLs, and so on. Specifically, I'm trying to obtain one URL from another. In other words, I have uploaded an html page to the web, containing a URL. The address for that page is coded into the app, giving me a "hook" into the app - I can change the contents of that page and send the app a new URL any time I want. Make sense?
So...retrieving the URL? No problem. Passing it into a string for later use - no problem. But when I set up a tap gesture recognizer, which should take that string, convert it back to an NSURL, and open it in Safari, I get a runtime crash. An NSLog call tells me that the string in question keeps being assigned to all sorts of random things.
The relevant bits of my code follow. I'm sure some of you will tell me there are much better ways to do what I want - and that's certainly welcome. But I'd also really love to know what I'm doing wrong for this particular implementation, as I'm sure it's a basic misunderstanding that I'd like to correct.
Thanks in advance. (And sorry about the formatting of the code block - haven't quite got the hang of posting on here!)
#import "Messing_With_Web_ViewsViewController.h"
#implementation Messing_With_Web_ViewsViewController
#synthesize tapView;
NSString *finalURL;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *firstString = #"http://www.my_web_address.html";
//Of course, I have the correct address here.
NSURL *firstUrl = [NSURL URLWithString:firstString];
NSError * error;
finalURL = [NSString stringWithContentsOfURL:firstUrl
encoding:NSASCIIStringEncoding error:&error];
if ( finalURL )
{
NSLog(#"Text=%#", finalURL);
//everything fine up to here; console prints the correct
contents of "my web address"
}
else
{
NSLog(#"Error = %#", error);
}
//Taps
UITapGestureRecognizer *tapRecognizer;
tapRecognizer=[[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(foundTap:)];
tapRecognizer.numberOfTapsRequired=1;
tapRecognizer.numberOfTouchesRequired=1;
[tapView addGestureRecognizer:tapRecognizer];
[tapRecognizer release];
}
- (void)foundTap:(UITapGestureRecognizer *)recognizer {
NSLog(#"Trying to load %#", finalURL);
//at this point the app either crashes, or the console shows a random memory object
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: finalURL]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
}
- (void)dealloc {
[finalURL release];
[super dealloc];
}
#end
finalURL = [NSString stringWithContentsOfURL:firstUrl
encoding:NSASCIIStringEncoding error:&error];
The line above creates an instance of NSString which you do not own (because you did not call a method whose name includes 'new', 'alloc' 'retain', or 'copy' on it). That finalURL with therefore eventually be destroyed when it is no longer needed. By the time your -foundTap: method runs finalURL has been deallocated and you are just referencing the memory location where it used to be and which now may contain some other object or random data.
Read the memory management guidelines again and also learn to run the static analyzer which should point out mistakes like this.
I'm pretty new to the objective-c language (less than three months) but it is something that i really need to understand.
Suppose there is a controller (in a iOS environment) that manages a table view for input data from the user. The table must have editable cells and some features to make the value selection easier, for example a button that shows a popover with the possible values for a field.
Suppose there is a field to store country names. The popover first shows a list of continents; when the user selects a continent, the controller of the popover must show the countries of the previews selected continent.
Now, this popover appears in many places in the app so it will be nice if I can encapsulate it for later use. What i will expect for this popover is something like this:
...
#protocol MyPopoverDelegate<NSObject> {
-(void)didSelectCountry:(NSString *)countryName;
{
...
MyPopoverController *dataSelector = [[MyPopoverController] alloc] init];
dataSelector.dataType = CountryDataType;
dataSelector.delegate = self;
[dataSelector show];
[dataSelector release];
...
The problem here is the line [dataSelector release] because the code for managing the popover must stay alive until the country is selected. That's means the dataSelector variable must be a property of the caller class and that sucks.
The question then is:
How can i organize situations like this to have a reusable controller?
Thanks
Edited after vodkhang answer:
Ok, that's a good one, but dataSelector still is a property.
What if i do:
#implementation MyPopoverController
- (id)init {
...
[self retain];
...
}
- (void)popoverControllerDidDismissPopover: (UIPopoverController *)popoverController {
...
[delegate didFinishSelectingCountry:countryName];
[self release];
}
#end
I never see this behavior in objective-c, i feel that this is not the idea.
Why is it wrong?.
One of the way you can do for delegate method is to have:
MyPopOverDelegate
- (void)didFinishSelectingCountry:(NSString *)countryName popOver:(MyPopOver *)popOver;
Caller.m
// the caller
- (void)viewDidLoad {
MyPopoverController *dataSelector = [[MyPopoverController] alloc] init];
dataSelector.dataType = CountryDataType;
dataSelector.delegate = self;
[dataSelector show];
}
- (void)didFinishSelectingCountry:(NSString *)countryName popOver:(MyPopOver *)popOver {
// finish stuff
[popOver release];
}
This way is used a lot like NSUrlConnection, UIImagePickerController
If you want some unique object reusable across an entire app from anywhere in the view hierarchy, you can make it a property of the app delegate, and let the app delegate own it (retain it when live, release it during memory warnings, etc.).
A self retained object may eventually run into problems if you ever port your code to a garbage collected environment.
I'm just getting into iPhone development after many years doing Java development. I'm looking for the Objective-C equivalent to Java's BlockingQueue. Is there something like that?
In case I'm going about things the wrong way, here's what I'm trying to achieve:
I want to display, one at a time, chunks of data pulled from a network server. To keep the user from noticing network lag, I want to always have a few chunks of data pre-fetched. In Java-land, I'd use a thread-safe queue between my fetching thread and my display thread.
Here's an implementation of a blocking queue with a queue and dequeue method. The expectation would be that one thread goes into a loop calling dequeueUnitOfWorkWaitingUntilDate: and processes units of work while a second thread is calling queueUnitOfWork:.
#interface MyBlockingQueue : NSObject {
NSMutableArray *queue;
NSConditionLock *queueLock;
}
- (id)dequeueUnitOfWorkWaitingUntilDate:(NSDate *)timeoutData;
- (void)queueUnitOfWork:(id)unitOfWork;
#end
enum {
kNoWorkQueued = 0,
kWorkQueued = 1
}
#implementation MyBlockingQueue
- (id)init {
if ((self = [super init])) {
queueLock = [[NSConditionLock alloc] initWithCondition:kNoWorkQueued];
workItems = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
[queueLock release];
[workItems release];
[super dealloc];
}
- (id)dequeueUnitOfWorkWaitingUntilDate:(NSDate *)timeoutDate {
id unitOfWork = nil;
if ([queueLock lockWhenCondition:kWorkQueued beforeDate:timeoutDate]) {
unitOfWork = [[[queue objectAtIndex:0] retain] autorelease];
[queue removeObjectAtIndex:0];
[queueLock unlockWithCondition:([workItems count] ? kWorkQueued : kNoWorkQueued)];
}
return unitOfWork;
}
- (void)queueUnitOfWork:(id)unitOfWork {
[queueLock lock];
[queue addObject:unitOfWork];
[queueLock unlockWithCondition:kWorkQueued];
}
#end
You can simply spin off an NSOperation and post a notification when the data has come back (finished loading). Take a look at Dave Dribin's blog post on concurrency with NSOperation that shows how to encapsulate an NSURLConnection session:
http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
If you are not talking about accessing a web service or site where NSURLConnection is appropriate, you can instead use Cocoa Async Socket if it's straight TCP/IP or UDP:
http://code.google.com/p/cocoaasyncsocket/
Best Regards,
I don't think such a thing exists natively - you're probably going to have to write your own class that maintains a queue of network objects. Your header might look something like:
#interface ObjcBlockingQueue : NSObject {
// The objects that you're holding onto
NSArray *objects;
}
#property(nonatomic,retain) NSArray *objects;
- (ServerData *)getNextChunk;
Then you can implement getNextChunk to pop and return the top object off your objects array, and if [objects count] is less than a certain value, launch a thread to fetch some more objects (probably using NSURLConnection with ObjcBlockingQueue being the delegate). You can also have that thread/connection launched inside an overridden init method to prefill the queue.
You might also want to think about adding a
- (BOOL)isChunkAvailable;
method that will let your display thread know whether it can display something new right away or if it has to display a loading message. Depending on where you're displaying the data and how your app is structured, it may also be worth your while to make ObjcBlockingQueue a singleton class.