UIWebView resource loading with Basic Authentication - uiwebview

For a (web)application I am creating, I need to use Basic Authentication to load pages in my UIWebView.
Now to set the authorization header I use:
NSString *result = [NSString stringWithFormat:#"%#:%#", username, password];
NSData *resultData = [result dataUsingEncoding:NSASCIIStringEncoding];
NSString *result64 = [NSString stringWithFormat:#"Basic %#", [resultData base64Encoding]];
[request setValue:result64 forHTTPHeaderField:#"Authorization"];
In my apache access logs I can see the login is a success. But then the UIWebView wants to load the resources like style.css and jquery.min.js but requests to these resources fail because they have no Authorization header set. How can I fix this?

The solution was to subclass NSURLProtocol to authenticate the resource loading:
#import <Foundation/Foundation.h>
#import "AuthenticationUtils.h"
#interface CustomURLProtocol : NSURLProtocol <NSURLConnectionDelegate>
{
NSMutableURLRequest *_customRequest;
NSURLConnection *_connection;
}
#end
#implementation CustomURLProtocol
static NSString *AUTHORIZED_REQUEST_HEADER = #"X-AUTHORIZED";
+(BOOL) canInitWithRequest:(NSMutableURLRequest *)request
{
// check if the request is one you want to authorize
BOOL canInit = (![request.URL.scheme isEqualToString:#"file"] && [request valueForHTTPHeaderField:[AUTHORIZED_REQUEST_HEADER stringByAppendingString:[request.URL absoluteString]]] == nil);
return canInit;
}
-(id) initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
{
_customRequest = [request mutableCopy];
[_customRequest setValue:#"" forHTTPHeaderField:[AUTHORIZED_REQUEST_HEADER stringByAppendingString:[request.URL absoluteString]]];
self = [super initWithRequest:_customRequest cachedResponse:cachedResponse client:client];
return self;
}
+(NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
NSMutableURLRequest *customRequest = [request mutableCopy];
[customRequest setValue:#"" forHTTPHeaderField:[AUTHORIZED_REQUEST_HEADER stringByAppendingString:[request.URL absoluteString]]];
NSString *basicAuthentication = [AuthenticationUtils getBasicAuthentication];
[customRequest setValue:basicAuthentication forHTTPHeaderField:#"Authorization"];
return customRequest;
}
- (void) startLoading
{
NSString *basicAuthentication = [AuthenticationUtils getBasicAuthentication];
[_customRequest setValue:basicAuthentication forHTTPHeaderField:#"Authorization"];
_connection = [NSURLConnection connectionWithRequest:_customRequest delegate:self];
}
- (void) stopLoading
{
[_connection cancel];
}
#pragma mark - NSURLConnectionDelegate
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This protocol forgets to store cookies, so do it manually
if([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
{
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:[(NSHTTPURLResponse*)redirectResponse allHeaderFields] forURL:[redirectResponse URL]] forURL:[redirectResponse URL] mainDocumentURL:[request mainDocumentURL]];
}
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[[self client] URLProtocol:self didLoadData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[[self client] URLProtocol:self didFailWithError:error];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[[self client] URLProtocolDidFinishLoading:self];
}
-(NSURLRequest *) connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
{
// This protocol forgets to store cookies, so do it manually
if([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
{
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:[(NSHTTPURLResponse*)redirectResponse allHeaderFields] forURL:[redirectResponse URL]] forURL:[redirectResponse URL] mainDocumentURL:[request mainDocumentURL]];
}
// copy all headers to the new request
NSMutableURLRequest *redirect = [request mutableCopy];
for (NSString *header in [request allHTTPHeaderFields])
{
[redirect setValue:[[request allHTTPHeaderFields] objectForKey:header] forHTTPHeaderField:header];
}
return redirect;
}
#end
And in your AppDelegate add this:
[NSURLProtocol registerClass:[CustomURLProtocol class]];

Related

CoreDataTableViewController does not reload on return from segue

I have several CoreDataTableViewControllers that utilize the helper class from Paul Hegarty's course. Everyone of them works except this one, and I cannot see a difference.
When the table first comes up, it is correctly populated and the segue executes properly when a cell is selected. However when I hit the back button, the table displays (null), (null) everywhere.
I have tried every variant of calling [self useDocument] that I can think of, still to no avail. Any thoughts? Thanks in advance.
//
// TeamTableViewController.m
//
#import "TeamTableViewController.h"
#import "iTrackAppDelegate.h"
#import "CoreDataTableViewController.h"
#import "SchoolRecords.h"
#import "ScheduleViewController.h"
#interface TeamTableViewController ()
#property NSInteger toggle;
#end
#implementation TeamTableViewController
#synthesize iTrackContext = _iTrackContext;
#synthesize schoolSelected = _schoolSelected;
-(void) setSchoolSelected:(SchoolRecords *)schoolSelected
{
_schoolSelected = schoolSelected;
}
-(void) setITrackContext:(NSManagedObjectContext *)iTrackContext
{
if(_iTrackContext != iTrackContext){
if (!iTrackContext) {
MyCoreDataHandler* cdh =
[(iTrackAppDelegate *) [[UIApplication sharedApplication] delegate] cdh];
_iTrackContext = cdh.context;
} else {
_iTrackContext = iTrackContext;
}
}
[self useDocument];
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)setupFetchedResultsController // attaches an NSFetchRequest to this UITableViewController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"SchoolRecords"];
// no predicate because we want ALL the Athletes
request.sortDescriptors = [NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"schoolName" ascending:YES],
nil];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.iTrackContext
sectionNameKeyPath:nil
cacheName:nil];
__block NSInteger myCount;
int64_t delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.iTrackContext performBlock:^(void){NSError* requestError = nil;
myCount = [self.iTrackContext countForFetchRequest:request error:&requestError];
NSLog(#"In %# and count of iTrackContext = %lu", NSStringFromClass([self class]),(unsigned long)myCount);
}];
if (!myCount || myCount == 0) {
[self displayAlertBoxWithTitle:#"No Teams" message:#"Have you added athletes yet? \nPlease go to Add Athletes" cancelButton:#"Okay"];
}
});
}
- (void)useDocument
{
if (self.iTrackContext) {
[self setupFetchedResultsController];
} else {
NSString* errorText = #"A problem arose opening the search results database of Athletes.";
[self displayAlertBoxWithTitle:#"File Error" message:errorText cancelButton:#"Okay"];
}
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.iTrackContext) {
MyCoreDataHandler* cdh =
[(iTrackAppDelegate *) [[UIApplication sharedApplication] delegate] cdh];
[self setITrackContext:cdh.context];
} else {
NSLog(#"In %# of %#. Getting ready to call useDocument",NSStringFromSelector(_cmd), self.class);
[self useDocument];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// If divide into sections use line below otherwise return 1.
// return [[self.fetchedResultsController sections] count];
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Do not really need this with only one section, but makes code usable if add sections later.
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"teamInformation";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
SchoolRecords *schoolResults = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString* titleText = schoolResults.schoolName;
cell.textLabel.text = titleText;
cell.detailTextLabel.text = [NSMutableString stringWithFormat:#"%#, %#", schoolResults.schoolCity, schoolResults.schoolState];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
}
# pragma navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
[self setSchoolSelected:[self.fetchedResultsController objectAtIndexPath:indexPath]];
// be somewhat generic here (slightly advanced usage)
// we'll segue to ANY view controller that has a photographer #property
if ([segue.identifier isEqualToString:#"scheduleDetailSegue"]) {
// use performSelector:withObject: to send without compiler checking
// (which is acceptable here because we used introspection to be sure this is okay)
NSLog(#"Preparing to passing school with schoolID = %#", self.schoolSelected.schoolID);
[segue.destinationViewController convenienceMethodForSettingSchool:self.schoolSelected];
}
}
- (void) displayAlertBoxWithTitle:(NSString*)title message:(NSString*) myMessage cancelButton:(NSString*) cancelText
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:myMessage
delegate:nil
cancelButtonTitle:cancelText
otherButtonTitles:nil];
[alert show];
}
#end
Well, I am not certain what the problem was. I ended up deleting the "offending" TableViewControllers in StoryBoard and recreated them. That did the trick. In retrospect, I wonder if I did not specify the wrong type of segue from my tabViewController. But I deleted it before I thought of that possibility.

How to insert NSManagedObject in other NSManagedObjectContext?

I am getting data from the server and converts them to an array of NSManagedObject objects.
The array is used to display the table.
How to insert first element array peoples in persistent store?
- (void)viewDidLoad
{
[self loadData];
[self insertFirstPeople];
}
- (NSManagedObjectContext *)managedObjectContext
{
if(!_managedObjectContext) _managedObjectContext = [NSManagedObjectContext MR_context];
return _managedObjectContext;
}
- (void)loadData
{
...
Network Request
...
peoples = [NSMutableArray array];
for (NSDictionary *item in items)
{
People *people = [Podcast MR_createInContext:self.managedObjectContext];
people.name = [item valueForKeyPath:#"im:name.label"];
[peoples addObject:people];
}
}
-(void)insertFirstPeople
{
People *people = peoples[0];
NSManagedObjectContext *moc = [NSManagedObjectContext MR_defaultContext];
[moc insertObject:people]
[moc MR_saveToPersistentStoreAndWait];
}
Error:
An NSManagedObject may only be in (or observed by) a single NSManagedObjectContext.
I myself have found a solution to the problem.
-(void)insertFirstPeople
{
People *people = peoples[0];
CoreDataHelper *helper = [[CoreDataHelper alloc] init];
NSManagerObjectContext *context = [NSManagedObjectContext MR_defaultContext];
[helper saveObject:people toContext:context];
[context MR_saveOnlySelfAndWait];
}
CoreDataHelper.h
#import <Foundation/Foundation.h>
#interface CoreDataHelper : NSObject
{
NSMutableDictionary* _lookup;
}
#property(nonatomic, retain) NSMutableDictionary *lookup;
-(void)saveFrom:(NSManagedObjectContext *)current to:(NSManagedObjectContext *)parent;
- (NSManagedObject *)saveObject:(NSManagedObject *)object toContext:(NSManagedObjectContext *)moc;
- (NSManagedObject*)copyObject:(NSManagedObject*)object
toContext:(NSManagedObjectContext*)moc
parent:(NSString*)parentEntity;
#end
CoreDataHelper.m
#import "CoreDataHelper.h"
#implementation CoreDataHelper
#synthesize lookup = _lookup;
-(void)saveFrom:(NSManagedObjectContext *)current to:(NSManagedObjectContext *)parent
{
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserverForName:NSManagedObjectContextDidSaveNotification
object:current queue:nil
usingBlock:^(NSNotification *notification)
{
[parent mergeChangesFromContextDidSaveNotification:notification];
}];
NSError *error;
if (![current save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[dnc removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:current];
}
- (NSManagedObject *)saveObject:(NSManagedObject *)object toContext:(NSManagedObjectContext *)moc {
NSUndoManager *docUndoMgr = [moc undoManager];
[docUndoMgr beginUndoGrouping];
NSManagedObject *object2 = [self copyObject:object toContext:moc parent:nil];
[moc processPendingChanges];
[docUndoMgr endUndoGrouping];
return object2;
}
- (NSManagedObject *)copyObject:(NSManagedObject *)object
toContext:(NSManagedObjectContext *)moc
parent:(NSString *)parentEntity; {
NSError *error = nil;
NSString *entityName = [[object entity] name];
NSManagedObject *newObject = nil;
if ([moc objectRegisteredForID:object.objectID])
newObject = [moc objectWithID:object.objectID];
else
newObject = [NSEntityDescription
insertNewObjectForEntityForName:entityName
inManagedObjectContext:moc];
if (![moc save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[[self lookup] setObject:newObject forKey:[object objectID]];
NSArray *attKeys = [[[object entity] attributesByName] allKeys];
NSDictionary *attributes = [object dictionaryWithValuesForKeys:attKeys];
[newObject setValuesForKeysWithDictionary:attributes];
id oldDestObject = nil;
id temp = nil;
NSDictionary *relationships = [[object entity] relationshipsByName];
for (NSString *key in [relationships allKeys]) {
NSRelationshipDescription *desc = [relationships valueForKey:key];
NSString *destEntityName = [[desc destinationEntity] name];
if ([destEntityName isEqualToString:parentEntity]) continue;
if ([desc isToMany]) {
NSMutableSet *newDestSet = [NSMutableSet set];
for (oldDestObject in [object valueForKey:key]) {
temp = [[self lookup] objectForKey:[oldDestObject objectID]];
if (!temp) {
temp = [self copyObject:oldDestObject
toContext:moc
parent:entityName];
}
[newDestSet addObject:temp];
}
[newObject setValue:newDestSet forKey:key];
} else {
oldDestObject = [object valueForKey:key];
if (!oldDestObject) continue;
temp = [[self lookup] objectForKey:[oldDestObject objectID]];
if (!temp && ![destEntityName isEqualToString:parentEntity]) {
temp = [self copyObject:oldDestObject
toContext:moc
parent:entityName];
}
[newObject setValue:temp forKey:key];
}
}
return newObject;
}
#end

Mail composer didn't display under my UIWebView

I have a MainController and detailedcontroller. When I popover the Book selection and select a Book, the detailcontroller display an UIWebView with the book articles :
#interface IpadBooksViewController : UITableViewController {
SearchResult *searchResult;
IpadArticleViewController *detailController;
IpadMainViewController *mainController;
UIPopoverController *popover;
}
Into the UIWebView, I display an Email icon and catch the scheme :
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
NSURL *url = [request URL];
NSString *scheme = [url scheme];
NSString *host = [url host];
if ([[url description] hasSuffix:#"next"]) {
NSLog(#"next Show");
}
BOOL isShareLinks = [host isEqualToString:#"displayShareLinks"];
BOOL isFavoriteLinks = [host isEqualToString:#"displayFavoriteLinks"];
if ([#"myappurl" isEqualToString:scheme] && (isShareLinks || isFavoriteLinks)) {
self.selectedArticleNumber = [url.path lastPathComponent];
if (isShareLinks) {
[self sendMailArticleNumber:selectedArticleNumber];
} else if (isFavoriteLinks) {
NSLog(#"ipad favorite clicked");
[self toggleFavorite:selectedArticleNumber];
Broker *broker = [[Broker alloc] init];
[broker loadProjects:self];
[broker release];
}
}
return [super webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
}
Action is supposed to display the MFMailController under my UIWebView, but nothing is displayed without error message :
- (void) sendMailArticleNumber:(NSString *)articleNumber {
MFMailComposeViewController* composer = [[MFMailComposeViewController alloc] init];
composer.mailComposeDelegate = self;
[composer setSubject:#"Article"];
NSString *messageBody = [Article fetchBody:articleNumber bookId:bookId];
[composer setMessageBody:messageBody isHTML:YES];
[self presentModalViewController:composer animated:YES];
[composer release];
}
Any help will be welcomed. I did try creating a popover, addView atIndex without success ... Let me know if you need more code.
David
I did solve the issue by loading the controller into the delegate as following :
[((PublilexAppDelegate*)[[UIApplication sharedApplication] delegate]) sendMailArticleNumber:selectedArticleNumber bookId:self.bookId];
And then into the Delegate :
- (void) sendMailArticleNumber:(NSString *)articleNumber bookId:(NSString*)bookId {
MFMailComposeViewController* composer = [[MFMailComposeViewController alloc] init];
composer.mailComposeDelegate = self;
[composer setSubject:#"Article"];
NSString *messageBody = [Article fetchBody:articleNumber bookId:bookId];
[composer setMessageBody:messageBody isHTML:YES];
[self->navigationController presentModalViewController:composer animated:YES];
[composer release];
}
And to dismiss and comeback to the previous view :
-(void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
[self->navigationController dismissModalViewControllerAnimated:YES];
// [self dismissViewControllerAnimated:YES completion:nil];
// [((PublilexAppDelegate*)[[UIApplication sharedApplication] delegate]) navigateToIpadMain];
}

Singleton class for displaying iAds for iPhone

I have written a Singleton Class for managing iAds.The iAds pop up after 5 seconds of the user inactivity. The idleTimerExceeded call generate a notification to show the iAd. This code works fine for my requirements but since I am new to iOS development, my application sometimes hangs unexpectedly after integrating this code. This code results in lots of warnings etc. I would like to optimize my code in terms of memory and performance.
I would be very thankful for your kind suggestions and reviews.
Below is my code:
iAdSingleton.h
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#import "iAd/iAd.h"
#interface iAdSingleton : UIViewController<ADBannerViewDelegate> {
ADBannerView *adView;
UIViewController *displayVC;
NSTimer *idleTimer;
BOOL isItFirstTime;
}
#property (nonatomic, retain) ADBannerView *adView;
#property (nonatomic, retain) UIViewController *displayVC;
#property (nonatomic) BOOL isItFirstTime;
+ (id) shareAdSingleton;
- (void) resetIdleTimer;
- (void) idleTimerExceeded;
#end
iAdSingleton.m
#import "iAdSingleton.h"
#implementation iAdSingleton
static iAdSingleton* _sharedAdSingleton = nil;
BOOL bannerVisible = NO;
BOOL controlAccessBannerVisibility = NO;
#synthesize adView, displayVC;
#synthesize isItFirstTime;
#define kMaxIdleTimeSeconds 5.0
+(id)sharedAdSingleton
{
#synchronized(self)
{
if(!_sharedAdSingleton)
_sharedAdSingleton = [[self alloc] init];
return _sharedAdSingleton;
}
return nil;
}
+(id)alloc
{
#synchronized([iAdSingleton class])
{
NSAssert(_sharedAdSingleton == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedAdSingleton = [super alloc];
return _sharedAdSingleton;
}
return nil;
}
-(id)init
{
self = [super init];
if (self != nil) {
/* Initialize The Parameters Over Here */
//adView = [[ADBannerView alloc] initWithFrame:CGRectMake(0, 480, 0, 0)];
adView = [[ADBannerView alloc] init];
adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
self.adView.delegate=self;
[self resetIdleTimer];
}
return self;
}
-(void)dealloc
{
displayVC = nil;
if (adView) {
[adView removeFromSuperview]; //Remove ad view from superview
[adView setDelegate:nil];
adView = nil;
}
[super dealloc];
}
-(UIViewController *)viewControllerForPresentingModalView
{
return displayVC;
}
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
banner.hidden = NO;
if(!bannerVisible){
NSLog(#"Banner Changes 1 - Purpose: Visibility");
// [UIView beginAnimations:#"bannerAppear" context:NULL];
// banner.frame = CGRectOffset(banner.frame, 0, -100);
// [UIView commitAnimations];
bannerVisible = YES;
controlAccessBannerVisibility = YES;
}
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
//NSLog(#"Unable to receive Ad.");
NSLog(#"Banner Changes 2 - Purpose: Unable to Receive Ad.");
banner.hidden = YES;
if(bannerVisible){
[UIView beginAnimations:#"bannerDisappear" context:NULL];
banner.frame = CGRectOffset(banner.frame, 0, 100);
[UIView commitAnimations];
bannerVisible = NO;
}
}
- (BOOL) bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
{
NSLog(#"Pause anything necessary");
return YES;
}
- (void) bannerViewActionDidFinish:(ADBannerView *)banner
{
NSLog(#"We now resume to normal operations");
}
- (void)resetIdleTimer {
if (!idleTimer) {
idleTimer = [[NSTimer scheduledTimerWithTimeInterval:kMaxIdleTimeSeconds
target:self
selector:#selector(idleTimerExceeded)
userInfo:nil
repeats:NO] retain];
}
else {
if (fabs([idleTimer.fireDate timeIntervalSinceNow]) < kMaxIdleTimeSeconds-1.0) {
[idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:kMaxIdleTimeSeconds]];
/*
Notification: HideAd
*/
NSLog(#"Notification Generated For HideAd");
[[NSNotificationCenter defaultCenter] postNotificationName:#"HideAdBanner" object:nil userInfo:nil];
}
}
}
- (void)idleTimerExceeded {
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if (appDel.adVisible == NO) {
NSLog(#"Notification Generated For ShowAd");
/*
Notification: ShowAd
*/
if (controlAccessBannerVisibility == YES) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"ShowAdBanner" object:nil userInfo:nil];
}
}
}
#end
This is what you need. This code is thread safe as well as will not have any memory issue and warnings.
+ (iAdSingleton *) sharedInstance
{
static dispatch_once_t onceToken;
static iAdSingleton * __sharedInstance = nil;
dispatch_once(&onceToken, ^{
__sharedInstance = [[self alloc] init];
});
return __sharedInstance;
}

Background-queue changes to parent NSManagedObjectContext in UIManagedDocument cause duplicate in NSFetchedresultsController on merge

Ok guys. This one is driving me up the wall. I have
UIManagedDocument and its 2 MOContexts (regular and parent.)
A UITableViewController (subclassed to CoreDataTableViewController by Paul Hegarty) that runs off of an
NSFetchedResultsController
A background GCD Queue for syncing with the server that the parent cue accesses
I've tried this so many different ways and I run into problems each time.
When I add a new "animal" entity, it is no problem and immediately shows up on the table. But when I upload it to the server (on the upload queue) and changed its "status" (with the parent context) so that it should be in the uploaded section, it appears there but doesn't disappear from the un-uploaded section.
I END UP WITH TWINS I DIDN'T WANT! or it doesn't even make the correct one sometimes and just keeps the wrong one.
***BUT, the extra one will disappear when the app is shut down and reloaded. So it's just in memory somewhere. I can verify in the store that everything is correct. But the NSFetchedResultsController isn't firing the controllerDidChange... stuff.
Here is the superclass of my view controller
CoreDataTableViewController.m
#pragma mark - Fetching
- (void)performFetch
{
self.debug = 1;
if (self.fetchedResultsController) {
if (self.fetchedResultsController.fetchRequest.predicate) {
if (self.debug) NSLog(#"[%# %#] fetching %# with predicate: %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName, self.fetchedResultsController.fetchRequest.predicate);
} else {
if (self.debug) NSLog(#"[%# %#] fetching all %# (i.e., no predicate)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName);
}
NSError *error;
[self.fetchedResultsController performFetch:&error];
if (error) NSLog(#"[%# %#] %# (%#)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]);
} else {
if (self.debug) NSLog(#"[%# %#] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
}
[self.tableView reloadData];
}
- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc
{
NSFetchedResultsController *oldfrc = _fetchedResultsController;
if (newfrc != oldfrc) {
_fetchedResultsController = newfrc;
newfrc.delegate = self;
if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
self.title = newfrc.fetchRequest.entity.name;
}
if (newfrc) {
if (self.debug) NSLog(#"[%# %#] %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? #"updated" : #"set");
[self performFetch];
} else {
if (self.debug) NSLog(#"[%# %#] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
[self.tableView reloadData];
}
}
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if (self.debug) NSLog(#"fetchedResultsController returns %d sections", [[self.fetchedResultsController sections] count]);
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex: (NSInteger)index
{
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [self.fetchedResultsController sectionIndexTitles];
}
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
[self.tableView beginUpdates];
self.beganUpdates = YES;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
if(self.debug) NSLog(#"controller didChangeObject: %#", anObject);
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
NSLog(#"#########Controller did change type: %d", type);
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if (self.beganUpdates) [self.tableView endUpdates];
if (self.debug) NSLog(#"controller Did Change Content");
}
- (void)endSuspensionOfUpdatesDueToContextChanges
{
_suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
}
- (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend
{
if (suspend) {
_suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;
} else {
[self performSelector:#selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0];
}
}
#end
And here's my specific view controller I subclassed from it:
- (NSArray *)sectionHeaderTitles
{
if (_sectionHeaderTitles == nil) _sectionHeaderTitles = [NSArray arrayWithObjects:#"Not Yet Uploaded", #"Uploaded But Not Featured", #"Previously Featured", nil];
return _sectionHeaderTitles;
}
- (NSDictionary *)selectedEntry
{
if (_selectedEntry == nil) _selectedEntry = [[NSDictionary alloc] init];
return _selectedEntry;
}
- (void)setupFetchedResultsController
{
[self.photoDatabase.managedObjectContext setStalenessInterval:0.0];
[self.photoDatabase.managedObjectContext.parentContext setStalenessInterval:0.0];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Animal"];
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"status" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:#"unique" ascending:NO], nil];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.photoDatabase.managedObjectContext sectionNameKeyPath:#"status" cacheName:nil];
NSError *error;
BOOL success = [self.fetchedResultsController performFetch:&error];
if (!success) NSLog(#"error: %#", error);
else [self.tableView reloadData];
self.fetchedResultsController.delegate = self;
}
- (void)useDocument
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.photoDatabase.fileURL path]]) {
[self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.photoDatabase.documentState == UIDocumentStateClosed) {
[self.photoDatabase openWithCompletionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.photoDatabase.documentState == UIDocumentStateNormal) {
[self setupFetchedResultsController];
}
}
- (void)setPhotoDatabase:(WLManagedDocument *)photoDatabase
{
if (_photoDatabase != photoDatabase) {
_photoDatabase = photoDatabase;
[self useDocument];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont fontWithName:#"AmericanTypewriter" size:20];
label.shadowColor = [UIColor colorWithWhite:0.0 alpha:0.5];
label.textAlignment = UITextAlignmentCenter;
label.textColor = [UIColor whiteColor];
self.navigationItem.titleView = label;
label.text = self.navigationItem.title;
[label sizeToFit];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Get CoreData database made if necessary
if (!self.photoDatabase) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Default Photo Database"];
self.photoDatabase = [[WLManagedDocument alloc] initWithFileURL:url];
NSLog(#"No existing photoDatabase so a new one was created from default photo database file.");
}
self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"DarkWoodBackGround.png"]];
}
- (void)syncWithServer
{
// This is done on the syncQ
// Start the activity indicator on the nav bar
dispatch_async(dispatch_get_main_queue(), ^{
[self.spinner startAnimating];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.spinner];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:self.photoDatabase.managedObjectContext.parentContext];
});
// Find new animals (status == 0)
NSFetchRequest *newAnimalsRequest = [NSFetchRequest fetchRequestWithEntityName:#"Animal"];
newAnimalsRequest.predicate = [NSPredicate predicateWithFormat:#"status == 0"];
NSError *error;
NSArray *newAnimalsArray = [self.photoDatabase.managedObjectContext.parentContext executeFetchRequest:newAnimalsRequest error:&error];
if ([newAnimalsArray count]) NSLog(#"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
if (error) NSLog(#"fetchError: %#", error);
// Get the existing animals from the server
NSArray *parsedDownloadedAnimalsByPhoto = [self downloadedAllAnimalsFromWeb];
// In the parent context, insert downloaded animals into core data
for (NSDictionary *downloadedPhoto in parsedDownloadedAnimalsByPhoto) {
[Photo photoWithWebDataInfo:downloadedPhoto inManagedObjectContext:self.photoDatabase.managedObjectContext.parentContext];
// table will automatically update due to NSFetchedResultsController's observing of the NSMOC
}
// Upload the new animals if there are any
if ([newAnimalsArray count] > 0) {
NSLog(#"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
for (Animal *animal in newAnimalsArray) {
// uploadAnimal returns a number that lets us know if it was accepted by the server
NSNumber *unique = [self uploadAnimal:animal];
if ([unique intValue] != 0) {
animal.unique = unique;
// uploadThePhotosOf returns a success BOOL if all 3 uploaded successfully
if ([self uploadThePhotosOf:animal]){
[self.photoDatabase.managedObjectContext performBlock:^{
animal.status = [NSNumber numberWithInt:1];
}];
}
}
}
}
[self.photoDatabase.managedObjectContext.parentContext save:&error];
if (error) NSLog(#"Saving parent context error: %#", error);
[self performUpdate];
// Turn the activity indicator off and replace the sync button
dispatch_async(dispatch_get_main_queue(), ^{
// Save the context
[self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
if (success)
{
NSLog(#"Document was saved");
[self.photoDatabase.managedObjectContext processPendingChanges];
} else {
NSLog(#"Document was not saved");
}
}];
[self.spinner stopAnimating];
self.navigationItem.leftBarButtonItem = self.syncButton;
});
// Here it skips to the notification I got from saving the context so I can MERGE them
}
- (NSNumber *)uploadAnimal:(Animal *)animal
{
NSURL *uploadURL = [NSURL URLWithString:#"index.php" relativeToURL:self.remoteBaseURL];
NSString *jsonStringFromAnimalMetaDictionary = [animal.metaDictionary JSONRepresentation];
NSLog(#"JSONRepresentation of %#: %#", animal.namestring, jsonStringFromAnimalMetaDictionary);
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
[request setPostValue:jsonStringFromAnimalMetaDictionary forKey:#"newmeta"];
[request startSynchronous];
NSError *error = [request error];
NSString *response;
if (!error) {
response = [request responseString];
NSNumber *animalUnique = [(NSArray *)[response JSONValue]objectAtIndex:0];
return animalUnique;
} else {
response = [error description];
NSLog(#"%# got an error: %#", animal.namestring, response);
return [NSNumber numberWithInt:0];
}
}
- (BOOL)uploadThePhotosOf:(Animal *)animal
{
NSURL *uploadURL = [NSURL URLWithString:#"index.php" relativeToURL:self.remoteBaseURL];
int index = [animal.photos count];
for (Photo *photo in animal.photos) {
// Name the jpeg file
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
NSString *imageServerPath = [NSString stringWithFormat:#"%lf-Photo.jpeg",timeInterval];
// Update the imageServerPath
photo.imageURL = imageServerPath;
NSData *photoData = [[NSData alloc] initWithData:photo.image];
NSString *photoMeta = [photo.metaDictionary JSONRepresentation];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
[request addPostValue:photoMeta forKey:#"newphoto"];
[request addData:photoData withFileName:imageServerPath andContentType:#"image/jpeg" forKey:#"filename"];
[request setUploadProgressDelegate:self.progressView];
[request startSynchronous];
NSLog(#"%# progress: %#", animal.namestring, self.progressView.progress);
NSString *responseString = [request responseString];
NSLog(#"uploadThePhotosOf:%# photo at placement: %d has responseString: %#", animal.namestring, [photo.placement intValue], responseString);
SBJsonParser *parser= [[SBJsonParser alloc] init];
NSError *error = nil;
id jsonObject = [parser objectWithString:responseString error:&error];
NSNumber *parsedPhotoUploadResponse = [(NSArray *)jsonObject objectAtIndex:0];
// A proper response is not 0
if ([parsedPhotoUploadResponse intValue] != 0) {
photo.imageid = parsedPhotoUploadResponse;
--index;
}
}
// If the index spun down to 0 then it was successful
int success = (index == 0) ? 1 : 0;
return success;
}
- (NSArray *)downloadedAllAnimalsFromWeb
{
NSURL *downloadURL = [NSURL URLWithString:#"index.php" relativeToURL:self.remoteBaseURL];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:downloadURL];
[request setPostValue:#"yes" forKey:#"all"];
request.tag = kGetHistoryRequest;
[request startSynchronous];
NSString *responseString = [request responseString];
NSLog(#"downloadedAllAnimalsFromWeb responseString: %#", responseString);
SBJsonParser *parser= [[SBJsonParser alloc] init];
NSError *error = nil;
id jsonObject = [parser objectWithString:responseString error:&error];
NSArray *parsedDownloadedResponseStringArray = [NSArray arrayWithArray:jsonObject];
return parsedDownloadedResponseStringArray;
}
- (void)performUpdate
{
NSManagedObjectContext * context = self.photoDatabase.managedObjectContext.parentContext;
NSSet * inserts = [context updatedObjects];
if ([inserts count])
{
NSError * error = nil;
NSLog(#"There were inserts");
if ([context obtainPermanentIDsForObjects:[inserts allObjects]
error:&error] == NO)
{
NSLog(#"BAM! %#", error);
}
}
[self.photoDatabase updateChangeCount:UIDocumentChangeDone];
}
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.photoDatabase.managedObjectContext.parentContext];
NSLog(#"userInfo from the notification: %#", [notification userInfo]);
// Main thread context
NSManagedObjectContext *context = self.fetchedResultsController.managedObjectContext;
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[context performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
NSLog(#"ContextDidSaveNotification was sent. MERGED");
}
#pragma mark - Table view data source
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"EntryCell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"EntryCell"];
}
// Configure the cell here...
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = animal.namestring;
if (([animal.numberofanimals intValue] > 0) && animal.species) {
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#s", animal.species];
} else {
cell.detailTextLabel.text = animal.species;
}
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
// be somewhat generic here (slightly advanced usage)
// we'll segue to ANY view controller that has a photographer #property
if ([segue.identifier isEqualToString:#"newAnimal"]) {
NSLog(#"self.photodatabase");
[(NewMetaEntryViewController *)[segue.destinationViewController topViewController] setPhotoDatabaseContext:self.photoDatabase.managedObjectContext];
} else if ([segue.destinationViewController respondsToSelector:#selector(setAnimal:)]) {
// use performSelector:withObject: to send without compiler checking
// (which is acceptable here because we used introspection to be sure this is okay)
[segue.destinationViewController performSelector:#selector(setAnimal:) withObject:animal];
NSLog(#"animal: %# \r\n indexPath: %#", animal, indexPath);
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30;
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return nil;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
NSLog(#"header for section called for section: %d", section);
NSLog(#"fetchedResultsController sections: %#", self.fetchedResultsController.sections);
CGRect headerRect = CGRectMake(0, 0, tableView.bounds.size.width, 30);
UIView *header = [[UIView alloc] initWithFrame:headerRect];
UILabel *headerTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, tableView.bounds.size.width - 10, 20)];
if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:0]) {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:0];
} else if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:1]) {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:1];
} else {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:2];
}
headerTitleLabel.textColor = [UIColor whiteColor];
headerTitleLabel.font = [UIFont fontWithName:#"AmericanTypewriter" size:20];
headerTitleLabel.backgroundColor = [UIColor clearColor];
headerTitleLabel.alpha = 0.8;
[header addSubview:headerTitleLabel];
return header;
}
Way too much code for anyone to want to wade through.
However, from a quick inspection, it looks like you are violating the MOC constraints. Specifically, you are accessing the parent context directly, and not from its own thread, either.
Typically, you would start a new thread, then create a MOC in that thread, make its parent be the MOC of the document. then do your stuff, and call save on the new MOC. It will then notify the parent, which should handle the updating.

Resources