Prevent warning sheet after NSPersistentDocument rename - objective-c

Whenever my document is renamed , autosaving is blocked and the first save after the rename presents the pictured message.
Technically its not an issue as either button takes the user back to a autosavable state but it is confusing for my users.
I have tried hooking the method
-(void)moveToURL:(NSURL *)url completionHandler:(void (^)(NSError *))completionHandler
{
void(^takeoverblock)(NSError *error) = ^(NSError *error){
if (completionHandler) {
completionHandler(error);
}
if (!error) {
[self updateChangeCountWithToken:[self changeCountTokenForSaveOperation:NSAutosaveInPlaceOperation] forSaveOperation:NSAutosaveInPlaceOperation];
}
};
[super moveToURL:url completionHandler:takeoverblock];
}
and using various flavours of updateChangeCount: and updateChangeCountWithToken: but the warning consistently appears.
How do I put the document in a state where it resumes standard autosave behaviour after a rename/move.?

The answer via a friendly Apple engineer is this appears when the modificationDate on the underlying sqlite file is different to the fileModificationDate property on the NSPersistentDocument instance so to resolve reset the fileModificationDate after the move
override moveToUrl: like this
-(NSDate *)modDateForURL:(NSURL *)url
{
NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:[url path] error:NULL];
return dict[NSFileModificationDate];
}
-(void)moveToURL:(NSURL *)url completionHandler:(void (^)(NSError *))completionHandler
{
void(^takeoverblock)(NSError *error) = ^(NSError *error){
if (completionHandler) {
completionHandler(error);
}
if (!error) {
self.fileModificationDate = [self modDateForURL:self.fileURL];
}
};
[super moveToURL:url completionHandler:takeoverblock];
}

Related

Snapchat's SnapKit addLoginStatusObserver not listening to events

I'm currently developing a React Native plugin for Snapchat's SnapKit SDK.
I can't seem to get the addLoginStatusObserver method to work (detailed here: https://snapkit.com/docs/api/ios/) and I suspect it's my lack of experience with Objective C's protocol/interface/implementation features.
Here's a trimmed down version of the code:
...
#interface RNSnapSDKListener : NSObject<SCSDKLoginStatusObserver> {
...
}
- (void)scsdkLoginLinkDidSucceed;
- (void)scsdkLoginLinkDidFail;
- (void)scsdkLoginDidUnlink;
...
#end
#implementation RNSnapSDKListener
- (void)scsdkLoginLinkDidSucceed{
NSLog(#"[RNSnapSDKListener] Snapchat Did Login!");
}
- (void)scsdkLoginLinkDidFail{
NSLog(#"[RNSnapSDKListener] Snapchat Did Fail!");
}
- (void)scsdkLoginDidUnlink{
NSLog(#"[RNSnapSDKListener] Snapchat Did Unlink!");
}
- (void)setDelegate: (RCTEventEmitter*) eventEmitter{
NSLog(#"[RNSnapSDKListener] Delegate Set!");
}
#end
#implementation RNSnapSDK
...
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(initialize){
RNSnapSDKListener *listener = [[RNSnapSDKListener alloc] init];
[listener setDelegate:self];
[SCSDKLoginClient addLoginStatusObserver:listener];
}
RCT_EXPORT_METHOD(login)
{
[SCSDKLoginClient loginFromViewController:[UIApplication sharedApplication].delegate.window.rootViewController completion:^(BOOL success, NSError * _Nullable error) {
}];
}
RCT_EXPORT_METHOD(logout: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
[SCSDKLoginClient unlinkAllSessionsWithCompletion:^(BOOL success) {
NSLog(#"Logout %s", success ? "true" : "false");
resolve(NULL);
}];
}
RCT_EXPORT_METHOD(getUserData: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *graphQLQuery = #"{me{externalId, displayName, bitmoji{avatar}}}";
NSDictionary *variables = #{#"page": #"bitmoji"};
[SCSDKLoginClient fetchUserDataWithQuery:graphQLQuery
variables:variables
success:^(NSDictionary *resources) {
NSDictionary *data = resources[#"data"];
resolve(data);
} failure:^(NSError * error, BOOL isUserLoggedOut) {
NSLog(#"%#",[error localizedDescription]);
NSLog(#" %s", isUserLoggedOut ? "true" : "false");
if(isUserLoggedOut){
[SCSDKLoginClient loginFromViewController:[UIApplication sharedApplication].delegate.window.rootViewController completion:^(BOOL success, NSError * _Nullable error) {
}];
}else{
reject(#"error", [error localizedDescription], error);
}
}];
}
NSURL *saved;
RCT_EXPORT_METHOD(authenticateDeepLink: (NSString *)url)
{
NSURL *finalUrl = [NSURL URLWithString:url];
saved = finalUrl;
[SCSDKLoginClient application:[UIApplication sharedApplication] openURL:finalUrl options:[NSMutableDictionary dictionary]];
}
...
#end
.initialize() is called inside the React Native module, and the setDelegate() method is called successfully (printing out "Delegate set" - this is for the react-native event bridge), but the other [RNSnapSDKListener]s dont print when they should (after logging in or logging out)
Is this something I'm doing wrong with objective-c or some other misuse of Snapchat's SDK?
Thanks!
The problem ended up being that the RNSnapSDKListener *listener needed to be declared as a global variable and initialized inside initialize() - not entirely sure why though - something with garbage collection maybe?

dispatch_group_t issue, dispatch_group_notify is calling back before leaving the group

I have the following snippet of code below that fetches data from Parse using PFQueues in the background and returns data and a status. This structure is based off of waiting for the dispatch_group_t to notify that's it's completed all entered groups. Unfortunately dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
is called before the completion blocks call dispatch_group_leave. By the time the dispatch_group_leave() is called on any of the completion blocks, an EXC_BAD_INSTRUCTION is thrown. I've attached an image below for the instruction error. Does anyone know if I'm doing something wrong or if Parse has some annoyances that prevent me from using this method?
- (void)downloadAndCacheObjectsWithCompletion:(void (^)(NSError *))callback
{
__block NSError *downloadError1;
__block NSError *downloadError2;
__block NSError *downloadError3;
__block NSError *downloadError4;
NSLog(#"%#", NSStringFromSelector(_cmd));
dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *artwork, NSError *error) {
downloadError1 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *artworkPhotos, NSError *error) {
downloadError2 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *artists, NSError *error) {
downloadError3 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *badges, NSError *error) {
downloadError4 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSError *returnError;
if (downloadError1 || downloadError2 || downloadError3 || downloadError4) {
returnError = [[NSError alloc] initWithDomain:#"ParseFactory" code:-1 userInfo:#{NSLocalizedDescriptionKey: #"There was an error retrieving the content"}];
}
if (callback) {
callback(returnError);
}
});
}
- (void)fetchDataWithCompletion:(void(^)(NSArray *data, NSError *error))callback
{
NSLog(#"Fetching Data");
if ([self.cachedData objectForKey:kDataClassName]) {
if (callback) {
callback([self.cachedData objectForKey:kDataClassName], nil);
}
return;
}
PFQuery *dataQueue = [PFQuery queryWithClassName:kDataClassName];
dataQueue.cachePolicy = kPFCachePolicyCacheThenNetwork;
[dataQueue findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
[self.cachedData setObject:objects forKey:kDataClassName];
} else {
NSLog(#"Fetching Data Error: %#", error);
}
if (callback) {
callback(objects, error);
}
}];
}
The download process listed above is called from AppDelegate as such
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Register PFObject subclasses
[Data registerSubclass];
[Parse setApplicationId:#"appkey" clientKey:#"clientkey"];
[[ParseFactory sharedInstance] downloadAndCacheObjectsWithCompletion:^(NSError *error) {
}];
return YES;
}
Stack trace:
The error you're seeing indicates that your program calls dispatch_group_leave too many times. It's trivial to reproduce. I reproduced it with this program:
int main(int argc, const char * argv[])
{
#autoreleasepool {
dispatch_group_t group = dispatch_group_create();
dispatch_group_leave(group);
}
return 0;
}
Therefore I deduce that your fetchDataWithCompletion: method calls its completion block more than once. If you can't figure out why, edit your question to include the source code of that method (and any related methods or declarations).
I come in late, but it seems clear your issue comes from the kPFCachePolicyCacheThenNetwork.
Parse will call the completion block twice, one with cached data (even the 1st time), one with downloaded data... so your dispatch_group_leave will be called twice as much as your dispatch_group_enter.

presentRequestsDialogModallyWithSession does not work, but gives good result

When I use the webdialog for a friendrequest, everything is going fine, except no request or anything is made.
The code:
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
facebookFriend.id, #"to",
nil];
[FBWebDialogs presentRequestsDialogModallyWithSession:FBSession.activeSession
message:NSLocalizedString(#"FB_FRIEND_INVITE_MESSAGE", #"Facebook friend invite message")
title:NSLocalizedString(#"FB_FRIEND_INVITE_TITLE", #"Facebook friend invite title")
parameters:params
handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error) {
}];
This is the result I get:
fbconnect://success?request=xxxxxxxxxxxx&to%5B0%5D=xxxxxxxx
How can I debug what is going wrong?
Thanks in advance.
Ruud
For SDK 3.2 or above we have a facility to use FBWebDialogs class that will help us to show a popup along with the friend list and pick one or more from list to send invitation.
Lets do it step by step:
1) Download and setup SDK 3.2 or above.
2) First setup your application on facebook by following this url.
3) Then use the attached code.
Sample Code: (It generates invite friend request)
-(void)inviteFriends
{
if ([[FBSession activeSession] isOpen])
{
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:nil];
[FBWebDialogs presentRequestsDialogModallyWithSession:nil
message:[self getInviteFriendMessage]
title:nil
parameters:params
handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error)
{
if (error)
{
[self requestFailedWithError:error];
}
else
{
if (result == FBWebDialogResultDialogNotCompleted)
{
[self requestFailedWithError:nil];
}
else if([[resultURL description] hasPrefix:#"fbconnect://success?request="])
{
// Facebook returns FBWebDialogResultDialogCompleted even user
// presses "Cancel" button, so we differentiate it on the basis of
// url value, since it returns "Request" when we ACTUALLY
// completes Dialog
[self requestSucceeded];
}
else
{
// User Cancelled the dialog
[self requestFailedWithError:nil];
}
}
}
];
}
else
{
/*
* open a new session with publish permission
*/
[FBSession openActiveSessionWithPublishPermissions:[NSArray arrayWithObject:#"publish_stream"]
defaultAudience:FBSessionDefaultAudienceFriends
allowLoginUI:YES
completionHandler:^(FBSession *session, FBSessionState status, NSError *error)
{
if (!error && status == FBSessionStateOpen)
{
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:nil];
[FBWebDialogs presentRequestsDialogModallyWithSession:nil
message:[self getInviteFriendMessage]
title:nil
parameters:params
handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error)
{
if (error)
{
[self requestFailedWithError:error];
}
else
{
if (result == FBWebDialogResultDialogNotCompleted)
{
[self requestFailedWithError:nil];
}
else if([[resultURL description] hasPrefix:#"fbconnect://success?request="])
{
// Facebook returns FBWebDialogResultDialogCompleted even user
// presses "Cancel" button, so we differentiate it on the basis of
// url value, since it returns "Request" when we ACTUALLY
// completes Dialog
[self requestSucceeded];
}
else
{
// User Cancelled the dialog
[self requestFailedWithError:nil];
}
}
}];
}
else
{
[self requestFailedWithError:error];
}
}];
}
}
and here are the helper functions that calls delegates function OnFBSuccess and OnFBFailed.
- (void)requestSucceeded
{
NSLog(#"requestSucceeded");
id owner = [fbDelegate class];
SEL selector = NSSelectorFromString(#"OnFBSuccess");
NSMethodSignature *sig = [owner instanceMethodSignatureForSelector:selector];
_callback = [NSInvocation invocationWithMethodSignature:sig];
[_callback setTarget:owner];
[_callback setSelector:selector];
[_callback retain];
[_callback invokeWithTarget:fbDelegate];
}
- (void)requestFailedWithError:(NSError *)error
{
NSLog(#"requestFailed");
id owner = [fbDelegate class];
SEL selector = NSSelectorFromString(#"OnFBFailed:");
NSMethodSignature *sig = [owner instanceMethodSignatureForSelector:selector];
_callback = [NSInvocation invocationWithMethodSignature:sig];
[_callback setTarget:owner];
[_callback setSelector:selector];
[_callback setArgument:&error atIndex:2];
[_callback retain];
[_callback invokeWithTarget:fbDelegate];
}
So the class taht calls method InviteFriend MUST have these functions:
-(void)OnFBSuccess
{
CCLOG(#"successful");
// do stuff here
[login release];
}
-(void)OnFBFailed:(NSError *)error
{
if(error == nil)
CCLOG(#"user cancelled");
else
CCLOG(#"failed");
// do stuff here
[login release];
}
Recommended Reads:
Send Invitation via Facebook
API Permissions
An Example
NOTE:
1) Don't forget to setup Facebook application ID in plist.
2) Don't forget to adjust AppDelegate to handle urls.
Partial snippet taken from above link in point 2:
/*
* If we have a valid session at the time of openURL call, we handle
* Facebook transitions by passing the url argument to handleOpenURL
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
return [FBSession.activeSession handleOpenURL:url];
}
Hope it helps!
EDIT
Here:
declaration for fbDelegate is:
#property (nonatomic, assign) id <FBLoginDelegate> fbDelegate;
#protocol FBLoginDelegate <NSObject>
#required
-(void) OnFBSuccess;
-(void) OnFBFailed : (NSError *)error;
#end
and this is how you can consume this code:
FBLoginHandler *login = [[FBLoginHandler alloc] initWithDelegate:self]; // here 'self' is the fbDelegate you have asked about
[login inviteFriends];
I think your application is not enable for Android and for web . And you are trying to get notification on web or on Android device.
Points : For getting notification on Android or on web you have to enable your app for Android and web too .
To Enable Android and Web on your App : GoTo your App > Setting > Click on + Add platform add enter necessary information and Save .
Lets Enjoy Notification . :-)

openActiveSessionWithReadPermissions does not log in automatically

I have recently started using Facebook SDK 3.1, and am encountering some problems with logging in using openActiveSessionWithReadPermissions.
Actually, loggin in works perfectly, if there is a cached token available, it logs in without presenting Facebook UI, and if not, it presents the UI.
The problem occurs after I make a call to reauthorizeWithPublishPermissions. If I call reauthorizeWithPublishPermissions, then close and reopen the application, and make a call to openActiveSessionWithReadPermissions, it presents the Facebook UI and requires the user to say "yes I'm OK with read permissions", even though there is a cached token available.
It only presents the Facebook UI erroneously if I make a call to reauthorizeWithPublishPermissions, otherwise everything works fine.
Open for read code:
[FBSession openActiveSessionWithReadPermissions:readpermissions allowLoginUI:YES
completionHandler:^(FBSession *aSession, FBSessionState status, NSError *error) {
[self sessionStateChanged:[FBSession activeSession] state:status error:error];
if (status != FBSessionStateOpenTokenExtended) {
// and here we make sure to update our UX according to the new session state
FBRequest *me = [[FBRequest alloc] initWithSession:aSession
graphPath:#"me"];
[me startWithCompletionHandler:^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *aUser,
NSError *error) {
self.user = aUser;
aCompletionBlock(aSession, status, error);
}];
}
}];
the sessionStateChanged function:
- (void)sessionStateChanged:(FBSession *)aSession state:(FBSessionState)state error:(NSError *)error {
if (aSession.isOpen) {
// Initiate a Facebook instance and properties
if (nil == self.facebook || state == FBSessionStateOpenTokenExtended) {
self.facebook = [[Facebook alloc]
initWithAppId:FBSession.activeSession.appID
andDelegate:nil];
// Store the Facebook session information
self.facebook.accessToken = FBSession.activeSession.accessToken;
self.facebook.expirationDate = FBSession.activeSession.expirationDate;
}
} else {
// Clear out the Facebook instance
if (state == FBSessionStateClosedLoginFailed) {
[FBSession.activeSession closeAndClearTokenInformation];
}
self.facebook = nil;
}
}
the Publish call, with an empty aPublishAction for testing:
- (void)doPublishAction:(void(^)(FBSession *aSession, NSError *error))aPublishAction {
if ([FBSession.activeSession.permissions
indexOfObject:#"publish_actions"] == NSNotFound) {
NSArray *writepermissions = [[NSArray alloc] initWithObjects:
#"publish_stream",
#"publish_actions",
nil];
[[FBSession activeSession]reauthorizeWithPublishPermissions:writepermissions defaultAudience:FBSessionDefaultAudienceFriends completionHandler:^(FBSession *aSession, NSError *error){
if (error) {
NSLog(#"Error on public permissions: %#", error);
}
else {
aPublishAction(aSession, error);
}
}];
}
else {
// If permissions present, publish the story
aPublishAction(FBSession.activeSession, nil);
}
}
Thanks for everything in advance, I would be grateful for any and all help!!
You need to add
[FBSession.activeSession handleDidBecomeActive];
to your -(void) applicationDidBecomeActive:(UIApplication *)application method in your app's delegate as stated in the migration guide.
What if you use openActiveSessionWithPermissions instead of openActiveSessionWithReadPermissions?

AFNetworking Asynchronous Data Fetching

I'm using the AFNetworking library to pull a JSON feed from a server to populate a UIPickerView, but I'm having a little trouble wrapping my head around the asynchronous way of doing things. The #property classChoices is an NSArray that's being used to populate the UIPickerView, so that the web call is only performed once. However, since the block isn't finished by the time the instance variable is returned, the getter returns nil, and it eventually causes my program to crash later on. Any help in fixing this would be greatly appreciated. Let me know if you need any additional information.
PickerViewController.m classChoices Getter
- (NSArray *)classChoices {
if (!_classChoices) {
// self.brain here refers to code for the SignUpPickerBrain below
[self.brain classChoicesForSignUpWithBlock:^(NSArray *classChoices) {
_classChoices = classChoices;
}];
}
return _classChoices;
}
SignUpPickerBrain.m
- (NSArray *)classChoicesForSignUpWithBlock:(void (^)(NSArray *classChoices))block {
[[UloopAPIClient sharedClient] getPath:#"mobClass.php" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseJSON) {
NSLog(responseJSON);
if (block) {
block(responseJSON);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
if (block) {
block(nil);
}
}];
}
You need a method like the following in your PickerViewController which returns the array once it has been downloaded. Once the callback has been returned, you can then continue on with your code:
- (void)classChoices:(void (^) (NSArray * classChoices)) _callback {
if (!self.classChoices) {
// self.brain here refers to code for the SignUpPickerBrain below
[self.brain classChoicesForSignUpWithBlock:^(NSArray *classChoices) {
_callback(classChoices);
}];
}
}
// call the method
- (void) viewDidLoad {
[super viewDidLoad];
[self classChoices:^(NSArray * updatedChoices) {
self.classChoices = updatedChoices;
[self.pickerView reloadAllComponents];
}];
}