IOS values passed to a view are lost or forgotten - objective-c

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.

Related

NSFilePresenter methods never get called

I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.
The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.
Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:
Interface:
#interface FileManager : NSObject <NSFilePresenter>
#property (unsafe_unretained) IBOutlet NSTextView *textView;
- (void) saveFile;
- (void) reloadFile;
#end
Implementation:
#implementation FileManager
{
NSOperationQueue* queue;
NSURL* fileURL;
}
- (id) init {
self = [super init];
if (self) {
self->queue = [NSOperationQueue new];
self->fileURL = [NSURL URLWithString:#"/Users/Jonathan/file.txt"];
[NSFileCoordinator addFilePresenter:self];
}
return self;
}
- (NSURL*) presentedItemURL {
NSLog(#"presentedItemURL");
return self->fileURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
NSLog(#"presentedItemOperationQueue");
return self->queue;
}
- (void) saveFile {
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
[coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
NSString* content = [self.textView string];
[content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}];
}
- (void) reloadFile {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
__block NSData* content;
[coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
if ([fileManager fileExistsAtPath:[url path]]) {
content = [fileManager contentsAtPath:[url path]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
});
}
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
#end
Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).
The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.
Can anyone tell me what I'm doing wrong here?
I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.
I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.
(I realize that this is an old question, but... :) )
First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).
Secondly, you wrote:
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.
- (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
{
// implement your save routine here, but only if you need to!
if ( dataHasChanged ) [self save]; // <-- meta code
//
NSError * err = nil; // <-- = no error, in this simple implementation
completionHandler(err); // <-- essential!
}
I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)

How to Change SQL Database Stored in a Singleton?

I have an app which pretty much follows the method described here. The key code is as follows:
#import <Foundation/Foundation.h>
#import <sqlite3.h>
#interface FailedBankDatabase : NSObject {
sqlite3 *_database;
}
+ (FailedBankDatabase*)database;
- (NSArray *)failedBankInfos;
#end
#import "FailedBankDatabase.h"
#import "FailedBankInfo.h"
#implementation FailedBankDatabase
static FailedBankDatabase *_database;
+ (FailedBankDatabase*)database {
if (_database == nil) {
_database = [[FailedBankDatabase alloc] init];
}
return _database;
}
- (id)init {
if ((self = [super init])) {
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"banklist"
ofType:#"sqlite3"];
if (sqlite3_open([sqLiteDb UTF8String], &_database) != SQLITE_OK) {
NSLog(#"Failed to open database!");
}
}
return self;
}
- (void)dealloc {
sqlite3_close(_database);
[super dealloc];
}
Now, the app works with one database as expected. But, I want to be able to switch to a different database when the user touches a button. I have the button handler and logic OK, and I store the name of the database to be used and can retrieve it. But, no matter what I do, I always get the same (original) database being called. I fear that the handle associated with _database, a object of type sqlite3, in the example is not being changed properly, so I don't open the database properly. How should I go about changing this? You can't re-init a singleton, but I need to change what's stored in it, in this case _database. Thanks.
EDIT: I would add that if I ask for _database is a pointer. So I need to open a new database (and close the first I guess) and give the new database a new address in the process.
I had the same problem, but couldn't modify the database (they were used in other projects).
So, I created a method called useDatabase:, that close the previous connection, and open a new one.
The steps :
Your - (id)init remains the same
In FailedBankDatabase, you create a method that close and open the database with the name of the new database
-(void)useDatabase:(NSString*)database {
sqlite3_close(_database);
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:database
ofType:#"sqlite3"];
if (sqlite3_open([sqLiteDb UTF8String], &_database) != SQLITE_OK) {
NSLog(#"Failed to open database!");
}
}
At the very beggining (for example in appDidFinishLaunching), you call the singleton once
[FailedBankDatabase database];
, so that it is first initialised.
Then, when you want to change the .sqlite used, you can call :
[FailedBankDatabase useDatabase:#"anOtherDatabase"]
I think you can do this when you don't have to change the database very often. In my case, I use this once at the very first screen, with 3 buttons, where I will choose wich database will be used.
For more complicated cases, for exemple involving multithreading, you should not do that since it closes the connection for a little time, while it is used elsewhere.
Hope it helps,
Jery
After some additional study, I was unsuccessful in answering the question as asked. However, it looks like FMDB can probably handle the task, I just didn't want to add a large framework to my project. I solved my problem an entirely different way: I modified each database to give it an identifying column and then combined them, and modified the query I used to select only the original database chunk that was wanted. This approach will only work when the databases have the same structure of course.

Navigating between View controllers?

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.

Using NSManagedObject manually - something wrong with the NSManagedContext I get?

I'm new to Cocoa programming, and decided for my first project to create a small application to monitor and remember certain battery stats for my laptop. (I have it plugged in most of the time, and apple recommend you discharge it now and again, so why not try to make a small program to help you remember to do this? :))
Anyway, I have a standard Objective-C project, with a DataModel file.
It contains an Entity, BatteryEvent, with properties, charge and event.
I then have PowerListener.m (and .h).
PowerListener.m is implemented as follows:
#implementation PowerListener
void myPowerChanged(void * context) {
printf("Is charging: %d\n", [PowerFunctions isCharging]);
printf("Is on ac: %d\n", [PowerFunctions isOnAC]);
printf("Charge left: %d\n", [PowerFunctions currentCapacity]);
printf("Powerchanged\n");
NSManagedObject *newBatteryEvent = [NSEntityDescription
insertNewObjectForEntityForName:#"BatteryEvent"
inManagedObjectContext:context];
}
- (PowerListener*) init {
self = [super init];
if(self) {
CFRunLoopSourceRef loop = IOPSNotificationCreateRunLoopSource(myPowerChanged, [[NSApp delegate] managedObjectContext]);
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, kCFRunLoopDefaultMode);
CFRelease(loop);
} else {
printf("Error\n");
}
return self;
}
#end
My problem is that once I run this (inited through main.m's main-method) and the power actually DOES change, I get thrown an error where I try to create the new BatteryEvent object:
2009-08-19 17:59:46.078 BatteryApp[5851:813] +entityForName: could not locate an NSManagedObjectModel for entity name 'BatteryEvent'
So it looks to me like I have the wrong ManagedContext? How do I get the right one?
Am I even on the right track here?
I've tried passing another kind of NSManagedObjectContext to the callback function as well.
I followed this guide: Core Data Guide, but, again same error...
I'm at my wits end!
Any help appreciated!
It looks like your app isn't loading the managed object model as a part of the launch and/or Core Data stack initialization.
Where is your model loaded?
Also, make sure you spelled the entity name correctly in the model.

How can I troubleshoot my custom URL scheme?

I set up a simple event handler as mentioned here, but it appears that the selector isn't called. I put the code in my AppDelegate class and wired up the delegate in IB. Tried putting in some NSLog()s and breakpoints in the selector I expect to be called, but none of it is hit. The URL scheme works inasmuch as it launches my app, but it doesn't do anything after that. Can anyone advise how to troubleshoot this? Thanks!
Well, I can't help but notice that you're -init method is mis-declared. If should have return type id and have a return self; at the end.
- (id)init
{
self = [super init];
if (self) {
[[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:#selector(getUrl:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
}
return self;
}
With those fixes, I was able to paste those two routines into a test AppController class and have it print out the URLs (with a custom scheme) that I typed into Safari. I'd put a breakpoint on that -init method and step through it to make absolutely sure that -setEventHandler: method is getting called.
The big question is: Where are you calling NSAppleEventManager's -setEventHandler:...? You need to call this before your app finishes launching if you want to catch a URL that started your app. If your app delegate is created in your MainMenu.nib, then either its -init or -awakeFromNib methods will work, but, for example, -applicationDidFinishLaunching: won't.
Also, make sure that the selector you provide to -setEventHandler: is exactly the same as your method name, paying particular attention to capitalization and the proper number of colons.
Obviously, if you posted your app delegate's relevant code, it would be quite helpful.
Thanks for the suggestions. I double-checked those things. I'm sure it's some newbie mistake, but I'd appreciate anyone looking at the code. (The URL bits are stored in info.plist.) Right now I'm just trying to confirm that it's working before I try to do anything with the URL.
- (void)init{
self = [super init];
if(self){
[[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:#selector(getUrl:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
}
}
- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
NSLog(url);
// now you can create an NSURL and grab the necessary parts
}
Newbie mistake #2: Didn't set the class of my app delegate in IB. Fixing this and the init method as above got me going. Grrr...