In my Iphone application I am trying to navigate from one table view controller to next table view controller. Problem I am facing is that I have to fetch data using http request and then parse this data when the user select a cell. I am able to fetch and parse the data but the view controller is not waiting for the data to parsed and the next view controller is shown (which is empty). How to over come this problem.
indexSelected = [NSString stringWithFormat: #"%d",[indexPath row] ];
[[MySingletonClass sharedMySingleton] doAnAuthenticatedAPIFetch_Subscriber_Detail : indexSelected];
SubscribersDetailViews2 *viewController = [[SubscribersDetailViews2 alloc] initWithNibName:#"SubscribersDetailViews2" bundle:nil];
[[self navigationController] pushViewController:viewController animated:YES];
[viewController release];
This is what you do:
indexSelected = [NSString stringWithFormat: #"%d",[indexPath row] ];
SubscribersDetailViews2 *viewController = [[SubscribersDetailViews2 alloc] initWithNibName:#"SubscribersDetailViews2" bundle:nil];
[[MySingletonClass sharedMySingleton] doAnAuthenticatedAPIFetch_Subscriber_Detail:indexSelected delegate:self];
[[self navigationController] pushViewController:viewController animated:YES];
[viewController release];
You define a protocol that your view controller conforms to and when the fetching and parsing of data is done you call a method on the delegate to let the view controller know that the data is ready to be displayed.
If you need more information on how to do this, leave a comment.
EDIT: So here's how to declare and use a protocol. I'm going to try to keep it as simple as possible. I'm not sure if I like your naming convention, but I'll still use it for this example.
So let's get down to the code. This is how you declare a protocol:
#protocol MySingletonClassDelegate <NSObject>
#optional
- (void)didDoAnAuthenticatedAPIFetch_Subscriber_Detail_WithData:(NSArray *)data;
- (void)failedToDoAnAuthenticatedAPIFetch_Subscriber_Detail_WithError:(NSError *)error;
#end
Again, I'm not too fond of the naming convention. You shouldn't have underscores in objective-c method names.
The protocol should be defined in MySingletonClass.h before the declaration of MySingletonClass.
I declared two methods in the protocol, one for delivering the data and one for delivering an error if it fails, so that you can notify the user that it failed.
To use the protocol you need the following:
#interface SubscribersDetailViews2 : UITableViewController <MySingletonClassDelegate>
You also need to implement the methods declared in the protocol, but I'll leave that implementation to you.
Since the fetching of data already seems to be happening in the background I don't think I'll need to explain how to do that. One important thing to remember is that you want to execute the delegate methods on the main thread. Here's the code to do that:
- (void)doAnAuthenticatedAPIFetch_Subscriber_Detail:(NSUInteger)index delegate:id<MySingletonClassDelegate>delegate {
// Fetching data in background
if (successful) {
[self performSelectorOnMainThread:#selector(didDoAnAuthenticatedAPIFetch_Subscriber_Detail_WithData:) withObject:data waitUntilDone:NO];
} else {
[self performSelectorOnMainThread:#selector(failedToDoAnAuthenticatedAPIFetch_Subscriber_Detail_WithError:) withObject:error waitUntilDone:NO];
}
}
Just to be clear the // Fetching data in background is supposed to be replaced by your code. I assume that your code produces the variables (NSArray *data, NSError *error, BOOL successful) that I use.
That's about it, if you need clarification on anything let me know.
There are a number of options:
Cache the data, i.e., take a full copy of it on the iOS device (may not be practical of course)
Display an interstitial screen saying "loading" and then move to the "real" screen when the data has downloaded
Have, effectively, two different data sources for your table. The first is your current one. The second would be a single cell saying "Loading..."
In short, there's no point and click way of doing this but there's no problem downloading the data on the fly as long as you tell your users what's happening.
Related
I'm uploading to Amazon S3 using the iOS SDK which is working great but I want to be able to trigger a method when the load is completed.
Here is my code:
AmazonS3Client *s3 = [[[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY] autorelease];
// Create the picture bucket.
[s3 createBucket:[[[S3CreateBucketRequest alloc] initWithName:[Constants pictureBucket]] autorelease]];
NSString *picName = [NSString stringWithFormat:#"%#%d", PICTURE_NAME, counter];
// Upload image data. Remember to set the content type.
S3PutObjectRequest *por = [[[S3PutObjectRequest alloc] initWithKey:picName inBucket:[Constants pictureBucket]] autorelease];
NSLog(#"------------ SUBMITTING img :%#", picName);
por.contentType = #"image/jpeg";
por.data = imageData;
counter++;
// Put the image data into the specified s3 bucket and object.
[s3 putObject:por];
Any help much appreciated thanks!
From the Amazon SDK Docs it seems that you get an S3PutObjectResponse
so
S3PutObjectResponse *response = [s3 putObject:por];
if ([response isFinishedLoading]) {
//do something
}
or maybe you are searching for connectionDidFinishLoading: which is a delegate method from NSURLConnection which it seem they use accordingly to AmazonServiceResponse Class Reference
in you .h file declare that you conform to the delegate protocol of NSURLConnection
#interface MyClass : NSObject <NSURLConnectionDelegate>
in your .m file implement the delegate methods you want
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//do your stuff here
}
and tell the NSURLConnection that you handle the delegate methods in your .m file
S3PutObjectRequest *por = [[[S3PutObjectRequest alloc] initWithKey:picName inBucket:[Constants pictureBucket]] autorelease];
por.urlRequest.delegate = self; // this is important !!!
In general you should get used to work with delegates since they are often used throug the hole iOS SDK !!
You can find additional docu here: Delegates and Data Sources
I just have one more thing to add to the comments ( I know I am stepping out of conduct here but rep prevents me from commenting). I run these two lines just to be safe, as I have found the first does not retain its value consistently:
por.delegate = self;
[por setDelegate:self];
As you are a newbie like me, delegates are essentially handlers where the object looks when it calls obligatory methods that are sometimes required or not. If you set a delegate to self, it means that the putObjectRequest will reference obligatory methods on self when they are called, such as the method in Pfitz's answer. For UITableView, an example of a delegate method is (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath where the object, UITableView will reference self to look for a method cellForRowAtIndexPath in order to populate its object's cell queue.
REVISED...
The crux of the app is communicating with a database server. Responses from the server to the app are all in XML. There are several screens. Example, screen 1 lists the user's information, screen 2 lists the user's past trades, allows new trades, and so on.
Here is some code from my AppDelegate:
StartViewController *svc = [[StartViewController alloc] init];
TradeViewController *tvc = [[TradeViewController alloc] init];
CashViewController *cvc = [[CashViewController alloc] init];
ComViewController *covc = [[ComViewController alloc] init];
PrefsViewController *pvc = [[PrefsViewController alloc] init];
NSMutableArray *tabBarViewControllers = [[NSMutableArray alloc] initWithCapacity:5];
UITabBarController *tabBarController = [[UITabBarController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:svc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:tvc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:cvc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:covc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:pvc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
[tabBarController setViewControllers:tabBarViewControllers];
[[self window] setRootViewController:tabBarController];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
Trying to stick with the MVC style, I have a singleton class which does all of the "processing".
Now an example on how I run into a wall… the user can change their email address on screen 5. Enter new email address into text field and click the save button. The button then calls a method from the singleton class which sends the new email address to the server and (via the URL) and receives a XML response confirming the change.
Here are my problems:
1. I start the spinner from the view controller before I make the singleton class method call - but not knowing when the app to server send/receive is finished, how do I make the spinner stop at the right time? I can't of it from the singleton class, I tried that. From what I know, it has to be from within the VC or is there a way to change VC output from my singleton class?
The singleton class NSURLConnection is handling ALL of my communication. Everything from a simple, email change all the way to updating transaction tables. This just seems wrong to me and makes it very difficult to keep track on who is calling what. Again, I am going by my interpretation of MVC. I think it would be much easier to have a NSURLConnection for every VC and do some processing in those classes. However that would not be MVC(ish).
I have close to a 100 variables, arrays, etc… in my singleton class which I use to assign values to all my VC. This also seems wrong to me but I can't think of any other way.
how can I distinguish in the NSURLConnection delegate
(connectionDidFinishLoading) which URL call is being made?
Each of the delegate methods (such as -connectionDidFinishLoading:) has a connection parameter that tells you which connection sent the message. A given connection can only load one URL at a time, so there's a one to one correspondence between URLs and connections.
How can I tell outside of "connectionDidFinishLoading" when the download is completed?
That method tells you when the connection is finished. It's up to you to store that information somewhere where it's useful to your app.
Update: Based on what you've added, your "processing" class is your app's model. The rest of the app shouldn't care that each transaction involves a message to the server -- that's the model's business alone. Also, there's no reason that the model has to be a single object (let alone a singleton) -- it can be a group of objects that work together.
So, you might have a class (let's call it Processor) that represents the application's interface to the model (some might even call this a "model controller"). An instance of Processor might create a local database for storing the current local state of the app.You might also have a Transaction class that represents a single transaction with the server. A transaction could create a request, send it to the server, get the response, update the database, and tell the Processor that the transaction is done. Or, maybe when some other part of the app (like one of your view controllers) asks the Processor to process a new transaction, the Processor passes the requesting object along to the transaction that it creates so that the transaction can update the requestor directly.
It's hard to say what the best plan for your app is without knowing where you're planning on taking it, but the usual guidelines hold:
break your problem into parts that are easier to solve
limit the scope of each class's responsibilities
if something seems to complicated, it probably is
Breaking your model up into several classes will make it easier to test, as well. You can imagine how easy it would be to write a set of unit tests for the Transaction class. The same goes for Processor -- if the server transaction stuff is in a different class, it's easier to test that the Processor is doing the right thing.
If you have multiple NSURLConnections for the same delegate, consider using a global (well, let's say rather an instance variable) NSMutableDictionary instance, in which you store the data depending on which NSURLConnection is being called. You can use, for example, the in-memory address of the connections converted to an NSString (something like
[NSString stringWithFormat:#"%p", connection]
should do the trick).
Also, in the connectionDidFinishLoading: and connection:didFailLoadWithError: methods, remove the keys corresponding to the NSURLConnections. Thus, you can tell it from 'outside' if a connection is finished: just check if it is in the dictionary or not.
If you're downloading any data over a network connection, I would suggest using ASIHttpRequest. This will allow you to download files asynchronously, meaning your interface doesn't freeze during the download process.
If you use ASIHttpRequest, you can also set the didFinishSelector. By doing this, you can control which method is called when a specific URL has finished loading.
Have a look at this:
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
[request setDidFinishSelector:#selector(requestDone:)];
Then:
- (void)requestDone:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
// If you want, you can get the url of the request like this
NSURL *url = [request url];
}
As for the second part of your question, if the requestDone: method has not been called, you know the download has not completed.
If you want to do something more complicated with multiple downloads, ASIHttpRequest offers queue functionality too. Take a look here.
Hope this will help you.
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
NSString *urlString = [[[connection originalRequest] URL] absoluteString];
if ([urlString caseInsensitiveCompare:#"http://www.apple.com"] == NSOrderedSame) {
//Do Task#1
}
else if ([urlString caseInsensitiveCompare:#"http://www.google.com"] == NSOrderedSame)
{
//Do Task#2
}
}
I would recommend subclassing NSURLConnection. Simply add two properties: an NSInteger, tag, and a BOOL, isFinished. This way, you can #define tags for each different request and then identify them by tag in your delegate methods. In connectionDidFinishLoading, you can set the isFinished BOOL to YES, and then you can check in other methods if then connection is finished.
Here's my own NSURLConnection subclass, TTURLConnection:
TTURLConnection.h:
#import <Foundation/Foundation.h>
#interface TTURLConnection : NSURLConnection <NSURLConnectionDelegate>
#property (nonatomic) NSInteger tag;
#property (nonatomic) BOOL isLocked;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:
(BOOL)startImmediately tag:(NSInteger)tagParam;
#end
TTURLConnection.m:
#import "TTURLConnection.h"
#implementation TTURLConnection
#synthesize tag;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:
(BOOL)startImmediately tag:(NSInteger)tagParam {
self = [super initWithRequest:request delegate:delegate
startImmediately:startImmediately];
if(self) {
self.tag = tagParam;
}
return self;
}
#end
This is my first app, and actually isn't even fully mine but rather involves re-working an existing app to add functionality.
It involves a JSON feed which I'm successfully reading in and then trying to pass the value of a URL to a view. Here's the code from my app delegate that is successfully fired once the feed is read in:
- (void)JSONFetch:(MYJSONFetch *)fetch gotTheCollection:(id)collection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.testViewController.feedURL = [NSURL URLWithString:[collection objectForKey:#"Listings"]];
[JSONFetch release];
JSONFetch = nil;
}
Then in my testViewController I have this viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
if(self.feedURL)
{
[self startDownload];
}
}
Eventhough, when I debug, the gotTheCollection method passes a value to the feedURL of the view, it then fails on the if(self feedURL) check within the view and thus the view never gets populated.
As I'm so new to this code I've no idea if the sequence is wrong, or maybe it's how I'm passing the variable.
I know the description is relatively vague but even on a basic level I don't know if this functionality works in objective C, it doesn't cause any errors though, just sits there not loading because it can't get the data.
UPDATE: Definition of FeedURL follows, in the H file:
NSURL *feedURL;
then
#property (nonatomic, retain) NSURL *feedURL;
then in the M file:
#synthesize feedURL;
Thanks for the help guys, I finally decided to just restart the entire upgrade as the project had become a mess of reworked code and I couldn't be sure what worked and what didn't. As a result there's no clear answer to this but I imagine Franks was probably the closest so I'll mark that as the answer.
The NSURL is being autoreleased, you will need to retain it yourself
Assign the NSURL to feedURL, like so
self.testViewController.feedURL = [[NSURL URLWithString:[collection objectForKey:#"Listings"]] retain];
This will also mean you will have to release it yourself.
We are using a UInavigationController with one of the views being one that plays soundtracks.
We are using the AVfountation framework.
We navigate to audiolist(where music is played) on button click
-(IBAction)audioBtnClicked
{
audiolist *audio=[[audiolist alloc] initWithNibName:#"audiolist" bundle:nil];
[self.navigationController pushViewContrller:audio animated:YES];
[audiolist release];
}
When the user plays music and navigates away, the music continues playing
Problem: when the user navigates back to the list of songs and plays another track,
2 songs are playing at the same time.
We think that a new instance of audiolist is created everytime the user navigates back.
We would like to only have one instance of audiolist. How do we make the first instance of audiolist persistent and how do we refer back to it?
You have to do
[audio release];
and not
[audiolist release];
audiolist seems to be your class name. The naming conventions says that classes should be capital and camel-cased, so it should be AudioList. It'll make your code more readable.
To have one single shared instance of your AudioList, you could do this:
Add a class method to your header:
+ (AudioList *) sharedInstance;
then add this to your implementation file:
#implementation AudioList
static AudioList *gSharedInstance = nil;
+ (AudioList *) sharedInstance {
if (gSharedInstance == nil) {
gSharedInstance = [[AudioList alloc] init];
}
return gSharedInstance;
}
Now you can always access that instance with
[AudioList sharedInstance];
Cheers.
Create a singleton AudioManager class that handles all your music playback. When you navigate to that view controller, grab the shared instance of that AudioManager to do whatever you do in that view.
You don't want the view to persist as that would be bad separation of MVC.
Apple has some discussion of different design patterns and their objective-c implementations, including the singleton:
http://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaDesignPatterns/CocoaDesignPatterns.html#//apple_ref/doc/uid/TP40002974-CH6-SW6
After several hours/days of searching and diving into example projects I've concluded that I need to just ask. If I bind the assetsView (IKImageBrowserView) directly to an IB instance of NSArrayController everything works just fine.
- (void) awakeFromNib
{
library = [[NSArrayController alloc] init];
[library setManagedObjectContext:[[NSApp delegate] managedObjectContext]];
[library setEntityName:#"Asset"];
NSLog(#"%#", [library arrangedObjects]);
NSLog(#"%#", [library content]);
[assetsView setDataSource:library];
[assetsView reloadData];
}
Both NSLogs are empty. I know I'm missing something... I just don't know what. The goal is to eventually allow multiple instances of this view's "library" filtered programmatically with a predicate. For now I'm just trying to have it display all of the rows for the "Asset" entity.
Addition: If I create the NSArrayController in IB and then try to log [library arrangedObjects] or manually set the data source for assetsView I get the same empty results. Like I said earlier, if I bind library.arrangedObjects to assetsView.content (IKImageBrowserView) in IB - with same managed object context and same entity name set by IB - everything works as expected.
- (void) awakeFromNib
{
// library = [[NSArrayController alloc] init];
// [library setManagedObjectContext:[[NSApp delegate] managedObjectContext]];
// [library setEntityName:#"Asset"];
NSLog(#"%#", [library arrangedObjects]);
NSLog(#"%#", [library content]);
[assetsView setDataSource:library];
[assetsView reloadData];
}
I was running into a similar situation where the (IKImageBrowserView) was not initializing even though the ArrayController would ultimately sync up with the NSManagedObjectContext.
Ultimately found this passage in the core data programming guide
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdBindings.html#//apple_ref/doc/uid/TP40004194-SW3
if the "automatically prepares content" flag (see, for example,
setAutomaticallyPreparesContent:) is set for a controller, the controller's initial content
is fetched from its managed object context using the controller's current fetch predicate. It
is important to note that the controller's fetch is executed as a delayed operation performed
after its managed object context is set (by nib loading)—this therefore happens after
awakeFromNib and windowControllerDidLoadNib:. This can create a problem if you want to
perform an operation with the contents of an object controller in either of these methods,
since the controller's content is nil. You can work around this by executing the fetch
"manually" with fetchWithRequest:merge:error:.
- (void)windowControllerDidLoadNib:(NSWindowController *) windowController
{
[super windowControllerDidLoadNib:windowController];
NSError *error = nil;
BOOL ok = [arrayController fetchWithRequest:nil merge:NO error:&error];
// ...
It looks like the problem is that you have not actually told the NSArrayController to fetch anything. NSArrayControllers are empty until you add objects either through bindings or manually.
After setting up library try to call its fetch method:
[library fetch:self];
Also, you probably know this already but it is possible to set bindings in code with the following method:
- (void)bind:(NSString *)binding toObject:(id)observableController withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
Can also be added in awakeFromNib if subclassing the NSArrayCotroller or via your view controller
-(void)awakeFromNib
{
[self fetchWithRequest:nil merge:NO error:nil];
...
}