I am trying to integrate Hyperpay payment into React Native project and I have problems with objective-c, I followed an article and found many issues and with searching, I solve them, but still two issues I can't solve because I am not familiar with objective-c
Issue 1,
No known class method for selector 'presentCheckoutForSubmittingTransactionCompletionHandler:cancelHandler:'
Issue 2,
No known class method for selector 'dismissCheckoutAnimated:completion:'
I am sorry if my code is long but I don't to miss something
// RCTCalendarModule.m
#import "HyperPay.h"
#import "UIKit/UIKit.h"
#import <OPPWAMobile/OPPWAMobile.h>
#implementation HyperPay{
RCTResponseSenderBlock onDoneClick;
RCTResponseSenderBlock onCancelClick;
UIViewController *rootViewController;
NSString *isRedirect;
OPPPaymentProvider *provider;
}
// To export a module named RCTCalendarModule
RCT_EXPORT_METHOD(openHyperPay:(NSDictionary *)indic createDialog:(RCTResponseSenderBlock)doneCallback createDialog:(RCTResponseSenderBlock)cancelCallback) {
onDoneClick = doneCallback;
onCancelClick = cancelCallback;
NSArray *events = #[];
if ([indic[#"is_sandbox"] isEqualToString:#"1"]) {
provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeTest];
} else {
provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeLive];
}
OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
// Set available payment brands for your shop
checkoutSettings.paymentBrands = #[#"VISA", #"MASTER"];
// Set shopper result URL
checkoutSettings.shopperResultURL = #"com.simicart.enterprise.payments://result";
OPPCheckoutProvider *checkoutProvider = [OPPCheckoutProvider checkoutProviderWithPaymentProvider:provider checkoutID:indic[#"checkoutId"]
settings:checkoutSettings];
dispatch_async(dispatch_get_main_queue(), ^{
[OPPCheckoutProvider presentCheckoutForSubmittingTransactionCompletionHandler:^(OPPTransaction * _Nullable transaction, NSError * _Nullable error) {
if (error) {
// Executed in case of failure of the transaction for any reason
if (isRedirect && ![isRedirect isEqualToString:#"1"]) {
onCancelClick(#[#"cancel", events]);
}
} else if (transaction.type == OPPTransactionTypeSynchronous) {
// Send request to your server to obtain the status of the synchronous transaction
// You can use transaction.resourcePath or just checkout id to do it
NSDictionary *responeDic = #{#"resourcePath" : transaction.resourcePath};
onDoneClick(#[responeDic, events]);
NSLog(#"%#", transaction.resourcePath);
} else {
// The SDK opens transaction.redirectUrl in a browser
// See 'Asynchronous Payments' guide for more details
}
} cancelHandler:^{
onCancelClick(#[#"cancel", events]);
// Executed if the shopper closes the payment page prematurely
}];
});
}
- (instancetype)init{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(getStatusOder:) name:#"getStatusOrder" object:nil];
}
return self;
}
- (void)getStatusOder:(NSNotification*)noti{
[OPPCheckoutProvider dismissCheckoutAnimated:YES completion:^{
isRedirect = #"1";
NSURL *url = noti.object;
NSString *urlString = [url absoluteString];
NSLog(#"%#", urlString);
if (![urlString isEqualToString:#"com.simicart.enterprise.payments://result"]) {
NSArray *events = #[];
NSDictionary *responeDic = #{#"url" : urlString};
onDoneClick(#[responeDic, events]);
}
}];
}
#end
Related
I am wondering how I can make the form show in my iOS App https://developers.google.com/admob/ump/ios/quick-start.
I am using Xcode vs 11.6 and exported the game from Godot game engine.
I have changed the GADApplicationIdentifier in my info.plist and I don't get any errors in my code it just doesn't show the form when I run the game. I live in Europe. Any help is appreciated.
This is my current code:
#include <UserMessagingPlatform/UserMessagingPlatform.h>
#import "ViewController1.h"
#include <UserMessagingPlatform/UserMessagingPlatform.h>
#interface ViewController1 ()
#end
#implementation ViewController1
- (void)start {
// Create a UMPRequestParameters object.
UMPRequestParameters *parameters = [[UMPRequestParameters alloc] init];
// Set tag for under age of consent. Here #NO means users are not under age.
parameters.tagForUnderAgeOfConsent = #NO;
// Request an update to the consent information.
[UMPConsentInformation.sharedInstance
requestConsentInfoUpdateWithParameters:parameters
completionHandler:^(NSError *_Nullable error) {
if (error) {
// Handle the error.
} else {
// The consent information state was updated.
// You are now ready to check if a form is
// available.
UMPFormStatus formStatus =
UMPConsentInformation.sharedInstance
.formStatus;
if (formStatus == UMPFormStatusAvailable) {
[self loadForm];
}
}
}];
}
- (void)viewDidLoad {
// Create a UMPRequestParameters object.
UMPRequestParameters *parameters = [[UMPRequestParameters alloc] init];
// Set tag for under age of consent. Here #NO means users are not under age.
parameters.tagForUnderAgeOfConsent = #NO;
// Request an update to the consent information.
[UMPConsentInformation.sharedInstance
requestConsentInfoUpdateWithParameters:parameters
completionHandler:^(NSError *_Nullable error) {
if (error) {
// Handle the error.
} else {
// The consent information state was updated.
// You are now ready to check if a form is
// available.
UMPFormStatus formStatus =
UMPConsentInformation.sharedInstance
.formStatus;
if (formStatus == UMPFormStatusAvailable) {
[self loadForm];
}
}
}];
[super viewDidLoad];
}
- (void)loadForm {
[UMPConsentForm loadWithCompletionHandler:^(UMPConsentForm *form,
NSError *loadError) {
if (loadError) {
// Handle the error.
} else {
// Present the form. You can also hold on to the reference to present
// later.
if (UMPConsentInformation.sharedInstance.consentStatus ==
UMPConsentStatusRequired) {
[form
presentFromViewController:self
completionHandler:^(NSError *_Nullable dismissError) {
if (UMPConsentInformation.sharedInstance.consentStatus ==
UMPConsentStatusObtained) {
// App can start requesting ads.
}
}];
} else {
// Keep the form available for changes to user consent.
}
}
}];
}
#end
EDIT
Add a few NSLog to see what gets called.
try the following - just a stone into the bush, maybe it hits something ...
#include <UserMessagingPlatform/UserMessagingPlatform.h>
#import "ViewController1.h"
#include <UserMessagingPlatform/UserMessagingPlatform.h>
#interface ViewController1 ()
#end
#implementation ViewController1
- (void)start {
// Create a UMPRequestParameters object.
UMPRequestParameters *parameters = [[UMPRequestParameters alloc] init];
// Set tag for under age of consent. Here #NO means users are not under age.
parameters.tagForUnderAgeOfConsent = #NO;
// Request an update to the consent information.
[UMPConsentInformation.sharedInstance
requestConsentInfoUpdateWithParameters:parameters
completionHandler:^(NSError *_Nullable error) {
if (error) {
// Handle the error.
} else {
// The consent information state was updated.
// You are now ready to check if a form is
// available.
UMPFormStatus formStatus =
UMPConsentInformation.sharedInstance
.formStatus;
if (formStatus == UMPFormStatusAvailable) {
[self loadForm];
}
}
}];
}
// Change this one
- (void)viewDidLoad {
[super viewDidLoad];
}
// Add this one
- ( void ) viewDidAppear:( BOOL ) animated {
[super viewDidAppear:animated];
// View controller is now visible and on screen, so request permission
self.addMobStuff;
}
// Change / add this one
- ( void ) addMobStuff {
NSLog( #"addMobStuff" );
// Create a UMPRequestParameters object.
UMPRequestParameters *parameters = [[UMPRequestParameters alloc] init];
// Set tag for under age of consent. Here #NO means users are not under age.
parameters.tagForUnderAgeOfConsent = #NO;
// Request an update to the consent information.
if ( ! UMPConsentInformation.sharedInstance )
{
NSLog(#"No shared instance");
}
[UMPConsentInformation.sharedInstance
requestConsentInfoUpdateWithParameters:parameters
completionHandler:^(NSError *_Nullable error) {
if (error) {
// Handle the error.
NSLog(#"Some error %#", error);
} else {
NSLog(#"Proceed to form ...");
// The consent information state was updated.
// You are now ready to check if a form is
// available.
UMPFormStatus formStatus =
UMPConsentInformation.sharedInstance
.formStatus;
if (formStatus == UMPFormStatusAvailable) {
NSLog(#"Loading form ...");
[self loadForm];
}
else {
NSLog(#"Form status is not available");
}
}
}];
}
- (void)loadForm {
[UMPConsentForm loadWithCompletionHandler:^(UMPConsentForm *form,
NSError *loadError) {
if (loadError) {
// Handle the error.
NSLog(#"Form error %#", error);
} else {
// Present the form. You can also hold on to the reference to present
// later.
if (UMPConsentInformation.sharedInstance.consentStatus ==
UMPConsentStatusRequired) {
NSLog(#"Presenting form");
[form
presentFromViewController:self
completionHandler:^(NSError *_Nullable dismissError) {
if (UMPConsentInformation.sharedInstance.consentStatus ==
UMPConsentStatusObtained) {
// App can start requesting ads.
}
}];
} else {
// Keep the form available for changes to user consent.
NSLog(#"Changes");
}
}
}];
}
#end
note if this works it probably needs a bit more polish before it can be released into the world but hopefully at least the request form will be displayed ...
Background
In my app, I have class called FavoritesController that manages objects that the user's marked as favorites, and this favorite status is then used throughout the app. The FavoritesController is designed as a singleton class as there are a number of UI elements throughout the app that needs to know the 'favorite status' for objects in different places, also network requests need to be able to signal that a favorite needs to be invalidated if the server says so.
This invalidation part happens when the server responds with a 404 error, indicating that the favorite object must be removed from the user's favorites. The network fetch function throws an error, which triggers the FavoritesController to remove the object and then send a notification to interested parties that they need to refresh.
The problem
When using a unit test to check the quality of the 404 implementation, all methods are triggered as intended – the error is thrown and caught, the FavoritesController deletes the object and sends the notification. In some instances though, the deleted favorite is still there – but it depends on from where the query is done!
If I query inside the singleton the deletion went OK, but if I query from a class that makes use of the singleton, the deletion didn't happen.
Design details
The FavoritesController property favorites uses an ivar with all accesses #synchronized(), and the value of the ivar is backed by a NSUserDefaults property.
A favorite object is an NSDictionary with two keys: id and name.
Other info
One weird thing which I fail to understand why it happens: in some deletion attempts, the name value for the favorite object gets set to "" but the id key retains its value.
I've written unit tests that add an invalid favorite and checks that it gets removed on first server query. This test passes when starting with empty set of favorites, but fails when there is an instance of 'semi-deleted' object as above (that retains its id value)
The unit test now consistently passes, but in live usage the failure to delete remains. I suspect that this is due to NSUserDefaults not saving to disk immediately.
Steps I've tried
Making sure that the singleton implementation is a 'true' singleton, i.e. sharedController always returns the same instance.
I thought there was some sort of 'capture' problem, where a closure would keep its own copy with outdated favorites, but I think not. When NSLogging the object ID it returns the same.
Code
FavoritesController main methods
- (void) serverCanNotFindFavorite:(NSInteger)siteID {
NSLog(#"Server can't find favorite");
NSDictionary * removedFavorite = [NSDictionary dictionaryWithDictionary:[self favoriteWithID:siteID]];
NSUInteger index = [self indexOfFavoriteWithID:siteID];
[self debugLogFavorites];
dispatch_async(dispatch_get_main_queue(), ^{
[self removeFromFavorites:siteID completion:^(BOOL success) {
if (success) {
NSNotification * note = [NSNotification notificationWithName:didRemoveFavoriteNotification object:nil userInfo:#{#"site" : removedFavorite, #"index" : [NSNumber numberWithUnsignedInteger:index]}];
NSLog(#"Will post notification");
[self debugLogFavorites];
[self debugLogUserDefaultsFavorites];
[[NSNotificationCenter defaultCenter] postNotification:note];
NSLog(#"Posted notification with name: %#", didRemoveFavoriteNotification);
}
}];
});
}
- (void) removeFromFavorites:(NSInteger)siteID completion:(completionBlock) completion {
if ([self isFavorite:siteID]) {
NSMutableArray * newFavorites = [NSMutableArray arrayWithArray:self.favorites];
NSIndexSet * indices = [newFavorites indexesOfObjectsPassingTest:^BOOL(NSDictionary * entryUnderTest, NSUInteger idx, BOOL * _Nonnull stop) {
NSNumber * value = (NSNumber *)[entryUnderTest objectForKey:#"id"];
if ([value isEqualToNumber:[NSNumber numberWithInteger:siteID]]) {
return YES;
}
return NO;
}];
__block NSDictionary* objectToRemove = [[newFavorites objectAtIndex:indices.firstIndex] copy];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Will remove %#", objectToRemove);
[newFavorites removeObject:objectToRemove];
[self setFavorites:[NSArray arrayWithArray:newFavorites]];
if ([self isFavorite:siteID]) {
NSLog(#"Failed to remove!");
if (completion) {
completion(NO);
}
} else {
NSLog(#"Removed OK");
if (completion) {
completion(YES);
}
}
});
} else {
NSLog(#"Tried removing site %li which is not a favorite", (long)siteID);
if (completion) {
completion(NO);
}
}
}
- (NSArray *) favorites
{
#synchronized(self) {
if (!internalFavorites) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self->internalFavorites = [self.defaults objectForKey:k_key_favorites];
});
if (!internalFavorites) {
internalFavorites = [NSArray array];
}
}
return internalFavorites;
}
}
- (void) setFavorites:(NSArray *)someFavorites {
#synchronized(self) {
internalFavorites = someFavorites;
[self.defaults setObject:internalFavorites forKey:k_key_favorites];
}
}
- (void) addToFavorites:(NSInteger)siteID withName:(NSString *)siteName {
if (![self isFavorite:siteID]) {
NSDictionary * newFavorite = #{
#"name" : siteName,
#"id" : [NSNumber numberWithInteger:siteID]
};
dispatch_async(dispatch_get_main_queue(), ^{
NSArray * newFavorites = [self.favorites arrayByAddingObject:newFavorite];
[self setFavorites:newFavorites];
});
NSLog(#"Added site %# with id %ld to favorites", siteName, (long)siteID);
} else {
NSLog(#"Tried adding site as favorite a second time");
}
}
- (BOOL) isFavorite:(NSInteger)siteID
{
#synchronized(self) {
NSNumber * siteNumber = [NSNumber numberWithInteger:siteID];
NSArray * favs = [NSArray arrayWithArray:self.favorites];
if (favs.count == 0) {
NSLog(#"No favorites");
return NO;
}
NSIndexSet * indices = [favs indexesOfObjectsPassingTest:^BOOL(NSDictionary * entryUnderTest, NSUInteger idx, BOOL * _Nonnull stop) {
if ([[entryUnderTest objectForKey:#"id"] isEqualToNumber:siteNumber]) {
return YES;
}
return NO;
}];
if (indices.count > 0) {
return YES;
}
}
return NO;
}
Singleton implementation of FavoritesController
- (instancetype) init {
static PKEFavoritesController *initedObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
initedObject = [super init];
self.defaults = [NSUserDefaults standardUserDefaults];
});
return initedObject;
}
+ (instancetype) sharedController
{
return [self new];
}
Unit testing code
func testObsoleteFavoriteRemoval() {
let addToFavorites = self.expectation(description: "addToFavorites")
let networkRequest = self.expectation(description: "network request")
unowned let favs = PKEFavoritesController.shared()
favs.clearFavorites()
XCTAssertFalse(favs.isFavorite(313), "Should not be favorite initially")
if !favs.isFavorite(313) {
NSLog("Adding 313 to favorites")
favs.add(toFavorites: 313, withName: "Skatås")
}
let notification = self.expectation(forNotification: NSNotification.Name("didRemoveFavoriteNotification"), object: nil) { (notification) -> Bool in
NSLog("Received notification: \(notification.name.rawValue)")
return true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
NSLog("Verifying 313 is favorite")
XCTAssertTrue(favs.isFavorite(313))
addToFavorites.fulfill()
}
self.wait(for: [addToFavorites], timeout: 5)
NSLog("Will trigger removal for 313")
let _ = SkidsparAPI.fetchRecentReports(forSite: 313, session: SkidsparAPI.session()) { (reports) in
NSLog("Network request completed")
networkRequest.fulfill()
}
self.wait(for: [networkRequest, notification], timeout: 10)
XCTAssertFalse(favs.isFavorite(313), "Favorite should be removed after a 404 error from server")
}
To give context around my answers, this is what the code in question looked like when suggesting the change:
- (NSArray *)favorites {
#synchronized(internalFavorites) {
if (!internalFavorites) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
internalFavorites = [self.defaults objectForKey:k_key_favorites];
});
if (!internalFavorites) {
internalFavorites = [NSArray array];
}
}
}
return internalFavorites;
}
I was suspicious of the check if (!internalFavorites) { that followed #synchronized(internalFavorites) because that meant that there was an expectation of #synchronized being passed nil, which results in a noop.
This meant multiple calls to to favorites or setFavorites could happen in funny ways since they wouldn't actually be synchronized. Giving #sychronized an actual object to synchronize on was crucial for thread safety. Synchronizing on self is fine, but for a particular class, you have to be careful not to synchronize too many things on self or you'll be bound to create needless blocking. Providing simple NSObjects to #sychronized is a good way to narrow the scope of what you're protecting.
Here's how you can avoid using self as your lock.
- (instancetype)init {
static PKEFavoritesController *initedObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
initedObject = [super init];
self.lock = [NSObject new];
self.defaults = [NSUserDefaults standardUserDefaults];
});
return initedObject;
}
+ (instancetype)sharedController {
return [self new];
}
- (NSArray *)favorites {
#synchronized(_lock) {
if (!internalFavorites) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self->internalFavorites = [self.defaults objectForKey:k_key_favorites];
});
if (!internalFavorites) {
internalFavorites = [NSArray array];
}
}
}
return internalFavorites;
}
Regarding the abnormalities between test runs, definitely calling synchronize on the NSUserDefaults will help because calls to change the defaults are asynchronous, which means other threads are involved. There are like 3 layers of caching as well, and specifically for the purposes of running tests synchronize should ensure that things are completely and cleanly committed before Xcode pulls the plug on the test run. The documentation very abruptly insists that it's not a necessary call, but if it truly weren't necessary it wouldn't exist :-). On my first iOS projects, we always called synchronize after every defaults change... so, I think that documentation is more aspirational on the Apple engineers' parts. I'm glad this intuition helped you.
I am trying to make a Facebook/9gag like comment page in an iOS app. Briefly speaking, there will be a list of images in a list view, and there is a comment button, wheneven users press it, a comment list view will be shown just like those in Facebook and 9gag.
In my project, I created several classes to achieve this, which are:
APIOperator, which defines the path of the api to call, and called whenever a request is made to the server
MyPhotoCell, which is a custom UITableViewCell class forming the photo list view, consisting an image and a comment button
CommentViewController, which is the controller for the comment list displayed
CommentCell, which is a custom UITableViewCell forming the comment list displayed
And below are the code segments regarding the issue:
Firstly, when the user presses the comment button in one of the MyPhotoCell, it will invoke the creation of CommentListViewController:
//MyPhotoCell.m
- (void)click_btn_comment:(id)sender
{
CommentViewController *comment_view = [[CommentViewController alloc] initWithNibName:#"CommentViewController" bundle:nil];
comment_view.targetPhotoid = self.photoItem.photoid;
[[MyAppCoreData coreData].navController pushViewController:comment_view animated:YES];
}
Following with:
//CommentViewController.h
#property (assign, nonatomic) NSInteger targetPhotoid;
#property (strong, nonatomic) NSMutableArray *arr_commentList;
//CommentViewController.m
#interface SecondCreation_CommentViewController ()<UITableViewDataSource, UITableViewDelegate>
{
ASIHTTPRequest *request_comment;
}
#property (strong, nonatomic) UITableView *tbl_comment;
#property (strong, nonatomic) NSNumber *page;
#end
- (void)viewWillAppear:(BOOL)animated
{
if(!self.tbl_comment){
self.tbl_comment = [[UITableView alloc] init];
self.tbl_comment.delegate = self;
self.tbl_comment.dataSource = self;
self.tbl_comment.frame = CGRectMake(0, 55, PHOTOWIDTH, SCREEN_HEIGHT - 100);
[self.view addSubview:self.tbl_comment];
}
if([NSArray checkEmptyArray:self.arr_commentList]){
self.page = 0;
[self sendRequest:1];
}
}
- (void)addDisplayData:(NSArray *)arr_addData withReset:(BOOL)reset
{
//NSLog here shows arr_addData has a count of 6 (6 comments inside, which is normal)
//NSLog here shows arr_commentList has a count of 0 (which is also expected)
if (reset) {
[self.arr_commentList removeAllObjects];
}else {
if (!self.arr_commentList) {
self.arr_commentList = [NSMutableArray array];
}
}
[self.arr_commentList addObjectsFromArray:arr_addData];
//NSLog here shows arr_addData still has a count of 6
//NSLog here shows arr_commentList has a count of 6
}
- (void)sendRequest:(NSInteger)page
{
__block typeof(self)blockSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
blockSelf -> request_comment = [APIOperator getCommentListForPhoto:#(self.targetPhotoid) andPage:page WithCompletionHandler:^(APISTATUS apiStatus, CommentListItem *commentListItem, ASIHTTPRequest *request) {
if (request != blockSelf -> request_comment) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^(void) {
if (apiStatus == APISTATUS_SUCCESS) {
//I tried to access and print out the content of the returned commentList here, and it gives EXC_BAD_ACCESS (code = 1 ......)
//e.g. NSLog(((CommentItem *)[commentListItem.commentList objectAtIndex:0]).message);
//I tried to assign the returned commentList to my controller through various ways like:
//[blockSelf.arr_commentList addObjectsFromArray:commentListItem.commentList];
//[blockSelf.arrcommentList = commentListItem.commentList;
//[blockSelf addDisplayData:commentListItem.commentList withReset:YES];
//I tried to access the content of controller's arr_commentList, it will give EXC_BAD_ACCESS (code = 1, .....) too
} else {
}
});
}];
});
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"commentcell";
CommentCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell)
cell = [[CommentCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
//acessing the local arr_commentList here will also gives EXC_BAD_ACCESS (code = 1.....)
return cell;
}
And finally, the APIOperator class where the api request is made(ASIHTTPREQUEST is a wrapper class that used to send HTTP request to server and interacts with RESTFUL api, link: link here):
//APIOperator.m
+ (ASIHTTPRequest *)getCommentListForPhoto:(NSNumber *)photoid andPage:(NSInteger)page WithCompletionHandler:(void(^)(APISTATUS apiStatus, CommentListItem *apiItem, ASIHTTPRequest *request))completionBlock
{
NSString *str_page = [NSString stringWithFormat:#"%#", #(page)];
//API_URL here is the url to call the api in the server
NSString *str_API = [NSString stringWithFormat:#"%#",API_URL];
ASIHTTPRequest *_request = [ASIHTTPRequest createAPIRequest:str_API];
__block ASIHTTPRequest *request = _request;
__block void(^tempBlock)(APISTATUS apiStatus, CommentListItem *apiItem, ASIHTTPRequest *request) = completionBlock;
[_request setCompletionBlock:^{
if ([request responseStatusCode] == 200 || [request responseStatusCode] == 302 || [request responseStatusCode] == 304) {
DEBUGMSG(#"%d %#",[request responseStatusCode], request.url)
JSONParse *jsonParse = [[JSONParse alloc] initWithData:[request responseData]];
CommentListItem *obj = (CommentListItem *)[jsonParse parser2OneObject:#"CommentListItem" withPath:nil];
//I tried to print out the contents of the comments here, and everything are as expected
NSLog(((CommentItem *)[obj.commentList objectAtIndex:0]).message);
jsonParse = nil;
if (tempBlock) {
tempBlock(APISTATUS_SUCCESS,obj,request);
}
tempBlock = nil;
request = nil;
} else {
DEBUGMSG(#"%d %#",[request responseStatusCode], request.url)
if (tempBlock) {
tempBlock(apiStatus,nil,request);
}
tempBlock = nil;
request = nil;
}
}];
[_request setFailedBlock:^{
DEBUGMSG(#"%d %#",[request responseStatusCode], request.url)
if (tempBlock) {
tempBlock(apiStatus,nil,request);
}
tempBlock = nil;
request = nil;
}];
[_request performSelector:#selector(startAsynchronous)];
return _request;
}
I searched on the internet and found out that EXC_BAD_ACCESS code = 1 is about accessing/releasing a variable that is already released/deallocated before, but I still can't figure out the problem here. I can't find where it is released/deallocated, and why it still give me a count of 6 if it is released/deallocated. Hope I can get some help here about what is causing this problem and how to deal with it, as I've already dealing with this problem for a long time, but still have no idea about what the problem is and what to do. Thanks a lot!
We need to create a shared link for a file and then retrieve that link so
that we can display it inside our application.
We are able to create a shared link for a specific file (we can see it
inside Box Account on the Web) but we are not able to retrive sharedLink
via the API. It is always nil, although isShared method returns YES.
From the header file of BoxObject.h we find that these two methods provide
required information about shared state of the item.
#protocol BoxObject
// ...
// Information about the shared state of the item
#property (readonly, getter = isShared) BOOL shared;
#property (readonly) NSString *sharedLink;
//...
#end
This is how we create shared link.
Find BoxFile that we would like to share, lets call that object photo
Prior calling method shareWithPassword:message:emails:callbacks:, [photo
isShared] returns NO.
we call [photo shareWithPassword:#"" message:#"" emails:[NSArray
arrayWithObject:#""] callbacks:^(id<BoxOperationCallbacks>
on1){...}];
inside on1.after we check if response == BoxCallbackResponseSuccessful
and then we call [photo updateWithCallbacks:^(id
on2){..}]
inside on2.after we check if response == BoxCallbackResponseSuccessful
on successful response [photo isShared] returns YES but [photo
sharedLink] returns nil
And if we check on the Web, we can see that file is actually shared but we
cannot retrive sharedLink from the Box SDK.
Anyone has the same problem?
This is working for me, based off of the code already posted and the information found on github here
- (void) getShareableLinkForFileId:(NSString *)fileId
{
BoxFileBlock fileSuccess = ^(BoxFile *file) {
NSDictionary *fileInfo = file.rawResponseJSON;
if (![fileInfo[#"shared_link"] isEqual:[NSNull null]]) {
NSDictionary *linkData = fileInfo[#"shared_link"];
//Do something with the link
} else {
// failure
}
};
BoxAPIJSONFailureBlock failure = ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSDictionary *JSONDictionary) {
//Handle the failure
};
BoxFilesRequestBuilder *builder = [[BoxFilesRequestBuilder alloc] init];
BoxSharedObjectBuilder *sharedBuilder = [[BoxSharedObjectBuilder alloc] init];
sharedBuilder.access = BoxAPISharedObjectAccessOpen;
builder.sharedLink = sharedBuilder;
[[BoxSDK sharedSDK].filesManager editFileWithID:fileId requestBuilder:builder success:fileSuccess failure:failure];
}
I was able to get the share link by refreshing the folder itself. This is the code I came up with:
[boxFile shareWithPassword:#"" message:#"" emails:#[ #"" ] callbacks:^(id<BoxOperationCallbacks> on) {
on.after(^(BoxCallbackResponse response) {
if (response == BoxCallbackResponseSuccessful) {
[self.rootFolder updateWithCallbacks:^(id<BoxOperationCallbacks> on) {
on.after(^(BoxCallbackResponse response) {
BoxFile *updatedBoxFile = (BoxFile*)[self.rootFolder.children objectAtIndex:self.selectedIndexPath.row];
NSString *fileName = updatedBoxFile.name;
NSString *shareLink = updatedBoxFile.sharedLink;
NSLog(#"%# [%#]: %#", fileName, updatedBoxFile.isShared ? #"YES" : #"NO", shareLink);
});
}];
} else {
[BoxErrorHandler presentErrorAlertViewForResponse:response];
}
});
}];
This is with the old v1 API. Not sure if it has changed with the newer v2.
You can create a shared link by edit its info with Box V2:
Box2FolderBlock folderSuccess = ^(Box2Folder *folder) {
if (![[folder sharedLink] isEqual:[NSNull null]]) {
NSString *sharedUrl = [[folder sharedLink] objectForKey:Box2APIObjectKeyURL];
} else {
// failure
}
};
Box2FileBlock fileSuccess = ^(Box2File *file) {
if (![[file sharedLink] isEqual:[NSNull null]]) {
NSString *sharedUrl = [[file sharedLink] objectForKey:Box2APIObjectKeyURL];
} else {
// failure
}
};
Box2APIJSONFailureBlock failure = ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSDictionary *JSONDictionary) {
};
BoxSharedObjectBuilder *sharedLinkObject = [[BoxSharedObjectBuilder alloc] init];
sharedLinkObject.access = BoxAPISharedObjectAccessOpen;
BoxAPIJSONOperation *operation;
if (isFile == NO) {
sharedLinkObject.canPreview = BoxAPISharedObjectPermissionStateEnabled;
BoxFoldersRequestBuilder *requestBuilder = [[BoxFoldersRequestBuilder alloc] init];
requestBuilder.sharedLink = sharedLinkObject;
operation = [boxSDK.foldersManager editFolderWithID:fileOrFolderId requestBuilder:requestBuilder success:folderSuccess failure:failure];
} else {
sharedLinkObject.canDownload = BoxAPISharedObjectPermissionStateEnabled;
BoxFilesRequestBuilder *requestBuilder = [[BoxFilesRequestBuilder alloc] init];
requestBuilder.sharedLink = sharedLinkObject;
operation = [boxSDK.filesManager editFileWithID:fileOrFolderId requestBuilder:requestBuilder success:fileSuccess failure:failure];
}
i want to migrate my existing app to iCloud.
in my attempt to learn what to do, i have tried a sample app that has worked as described in the demo (as seen in lecture 17 of the iTunes stanford university course).
can someone who has had this problem help me diagnose what step i messed up to cause the following to fail? is this just telling me basically that there's no data to query? or is there something else going on that i haven't properly configured?
i am getting the following in my console (which occurs in the code below immediately after i call [self.iCloudQuery startQuery];
item update error: 0, Error Domain=LibrarianErrorDomain Code=10 "The operation couldn’t be completed.
(LibrarianErrorDomain error 10 - Unable to configure the collection.)" UserInfo=0x11ee00 {NSDescription=Unable to
configure the collection.}
i created a development provisioning profile that has iCloud turned on on iTunes connect.
i turned on entitlements. here are the contents:
the code is straight from the Stanford University iTunes lectures:
#import "DocumentViewController.h"
#interface DocumentViewController ()
#property (strong, nonatomic) NSArray* documents; // of NSURLs
#property (strong, nonatomic) NSMetadataQuery* iCloudQuery;
#end
#implementation DocumentViewController
#synthesize documents = _documents;
#synthesize iCloudQuery = _iCloudQuery;
- (void)setDocuments:(NSArray*)documents {
// always sort documents alphabetically
documents
= [documents sortedArrayUsingComparator:^NSComparisonResult(NSURL* url1, NSURL* url2) {
return [[url1 lastPathComponent] caseInsensitiveCompare:[url2 lastPathComponent]];
}];
if (![documents isEqualToArray:_documents])
{
_documents = documents;
[self.tableView reloadData];
}
}
- (NSMetadataQuery*)iCloudQuery {
if (!_iCloudQuery)
{
_iCloudQuery = [[NSMetadataQuery alloc] init];
_iCloudQuery.searchScopes
= [NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope];
_iCloudQuery.predicate
= [NSPredicate predicateWithFormat:#"%K like '*'", NSMetadataItemFSNameKey];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processCloudQueryResults:)
name:NSMetadataQueryDidFinishGatheringNotification
object:_iCloudQuery];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processCloudQueryResults:)
name:NSMetadataQueryDidUpdateNotification
object:_iCloudQuery];
}
return _iCloudQuery;
}
#pragma mark - private implementation
- (NSURL*)iCloudURL {
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
}
- (NSURL*)iCloudDocumentsURL {
return [self.iCloudURL URLByAppendingPathComponent:#"Documents"];
}
- (NSURL*)filePackageURLForCloudURL:(NSURL*)url
{
if ([url.path hasPrefix:self.iCloudDocumentsURL.path])
{
NSArray* iCloudDocumentsURLComponents = self.iCloudDocumentsURL.pathComponents;
NSArray* urlComponents = url.pathComponents;
if (iCloudDocumentsURLComponents.count < urlComponents.count)
{
NSRange newRange = NSMakeRange(0, iCloudDocumentsURLComponents.count+1);
urlComponents = [urlComponents subarrayWithRange:newRange];
url = [NSURL fileURLWithPathComponents:urlComponents];
}
}
return url;
}
- (void)processCloudQueryResults:(NSNotification*)notification
{
[self.iCloudQuery disableUpdates];
NSMutableArray* documents = [NSMutableArray array];
NSUInteger resultCount = self.iCloudQuery.resultCount;
for (NSUInteger i = 0; i < resultCount ; ++i)
{
NSMetadataItem* item = [self.iCloudQuery resultAtIndex:i];
NSURL* url = [item valueForAttribute:NSMetadataItemURLKey];
url = [self filePackageURLForCloudURL:url];
[documents addObject:url];
}
self.documents = documents;
[self.iCloudQuery enableUpdates];
}
#pragma mark - overrides
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (![self.iCloudQuery isStarted])
[self.iCloudQuery startQuery];
[self.iCloudQuery enableUpdates];
}
- (void)viewWillDisappear:(BOOL)animated {
[self.iCloudQuery disableUpdates];
[super viewWillDisappear:animated];
}
for anyone else running into this … apparently, this is the error that occurs when you haven't enabled iCloud on the device on which you are testing. once i enabled iCloud on the device, the error stopped occurring in my log.
and so now i know what error to check for in order to present the user with a dialog saying "connecting to iCloud won't work until you sign up for iCloud in the system preferences".