Swift + Objective C - Extra Argument 'withCompletionBlock' in call - objective-c

I'm a little new to Swift and Objective-C (and by that I mean I started last week) so if this is a duplicate question I'm sorry in advance.
In short I seem to be having trouble with calling a method. One part of the code is in Objective-C and the other part is in Swift. I imported the Objective-C file in to the Swift file through a bridging header file if that helps.
Declaration of method (in Objective-C):
- (NSUInteger)fetchTeamsForEventKey:(NSString *)eventKey withCompletionBlock:(void (^)(NSArray *teams, NSInteger totalCount, NSError *error))completionBlock {
NSString *apiMethod = [NSString stringWithFormat:#"event/%#/teams", eventKey];
NSUInteger taskId = [[TBAKit sharedKit] callArrayMethod:apiMethod modelClass:[TBATeam class] andCompletionBlock:^(NSArray *modelObjects, NSInteger totalCount, NSError *error) {
completionBlock(modelObjects, totalCount, error);
}];
return taskId;
}
Implementation of method (Swift):
TBAKit.fetchTeamsForEventKey("2015cacg", withCompletionBlock: {(teams:NSArray, totalCount:NSInteger, error:NSError) in //The error appears in this line.
if error {
NSLog("Unable to fetch event - %#", error.localizedDescription)
return
}
teamList = teams
})
The source code for the declaration method is in this GitHub Repo: https://github.com/ZachOrr/TBAKit. It's in the file TBAKit/TBAKit+EventMethods.m
Edit: I also tried the code with each of the following:
-> UInt
and
-> Int
Edit 2: So the solution mentioned by t4nhpt did work as it was intended to, but in turn another problem presented itself. I had to replace
teamList = teams
with
self.teamList = teams
Now I'm getting the error: "cannot assign a value of type 'anyobject' to a value of type '[TBATeam]'" on the line
self.teamList = teams
My declaration of the variable is
var teamList = [TBATeam]()
Edit 3: I solved the problem specified in EDIT 2 by converting each object inside teams to TBATeam. Sorry for the stupid mistake.

Try this:
TBAKit.sharedKit().fetchTeamsForEventKey("2015cacg") { (teams, totalCount, error) -> Void in
// your code
}

Related

How to call two React Native native module methods at the same time?

Ok, this is a bit complicated. I can't link all of my code because it's huge, but I'll do my best to give some representative pseduo code in it's place.
I have an ios React Native project and in xcode I have embdeded a custom objective C framework in my project. To access that objective C framework I've created a Native Module that acts as a wrapper/bridge.
So far this has all worked great - essentially my user clicks on a button, which triggers the objective c framework via a native module, the objective c framework takes about 30 seconds to a minute to finish processing the user's selected data and then spits out the uri to a processed file.
The problem I've run into is that my users are confused as to whether or not my app is frozen. As it happens the main function of my framework uses a for loop, so I decided to add two properies to the framework's main class and add a function that returns the properties so we can see the progress. It looks like this:
#implementation MyFramework {
int currentBlock;
int allBlocks;
}
- (NSArray<NSString*>*)processFile:(NSString *)file
{
int totalBlocks = examineFile(file);
for( int iBlock= 0; iBlock<totalBlocks; iBlock++) {
currentBlock = iBlock;
//other code that actually does stuff to the file goes here
}
//code that stitches the blocks together and generates files goes here
NSArray* files = #[filePath1, filePath2, filepath3];
return files;
}
- (NSArray<NSString*>*)getProgress:(NSString *)msg
{
int* pCurrentBlock = &currentBlock;
int* pAllBlocks = &allBlocks;
NSString* currentBlockString = [NSString stringWithFormat:#"%d", *pCurrentBlock];
NSString* allBlocksString = [NSString stringWithFormat:#"%d", *pAllBlocks];
NSArray* progressArray = #[currentBlockString, allBlocksString];
return progressArray;
}
Back in Javascript I call a Native Module wrapper for the above code. So when the user clicks a button I trigger a Native Module method called getFiles() which in turn calls the processFile() method in Objective C code above.
Now, while the processFile() method is running I simultaneously kick off a setInterval() every 2 seconds that calls a second Native Module method called status() which calls the getProgress() function shown above. The big problem I have is that getProgress() will not return a result while processFile() is running. Instead it waits until processFile() method has finished and then it returns around 20 results all at once.
So instead of getting updates like this:
['1','37']
['2','37']
['3','37']
['4','37']
['5','37']
I get something like this all at the end
['36','37']
['36','37']
['36','37']
['36','37']
['36','37']
Here's my Native Module code
const MyFramework *myLocalFramework = [[MyFramework alloc]init];
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(getFiles:(NSString *)path
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *modelFilePath = [[NSBundle mainBundle] pathForResource:#"converted_model_float16" ofType:#"tflite"];
NSArray * files = [myLocalFramework localFile:path tfModel:modelFilePath];
if (files) {
resolve(files);
} else {
reject(#"event_failure", #"no event id returned", nil);
}
}
RCT_EXPORT_METHOD(status:(NSString *)path
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSString* hi = #"hi";
NSArray* percentages = [myLocalFramework getProgress:hi];
if (percentages) {
resolve(percentages);
} else {
reject(#"event_failure", #"no event id returned", nil);
}
}
So with all that said, if anyone has any ideas for how I can get the Native Module to run the status() method without waiting for getFiles() to finish first, I would love your input!

Implementing a request URL into an imported cocapod that uses WordNik API

I'm making a swift app in Xcode that makes use of a cocapod called HNKWordLookup (originally written in objective c). This pod uses the WordNik API to return a random word. My only issue is that a lot of the words that are returned are quite obscure.
I figured that I could go to the http://developer.wordnik.com/docs page and set parameters there, and then be given a Request URL that caters to these parameters . I assume I need to put this into my code somewhere in place of another URL that is present within the pre written pod, but I have no clue where to put the request URL. At first I put it in place in the following line of code which was located in the pod's .m file ("HNKLookup.m:):
static NSString *const kHNKLookupBaseUrl = #"http://api.wordnik.com:80/v4";
changing it to
static NSString *const kHNKLookupBaseUrl = #"http://api.wordnik.com:80/v4/words.json/randomWord?hasDictionaryDef=true&excludePartOfSpeech=definite-article&minCorpusCount=1&maxCorpusCount=-1&minDictionaryCount=30&maxDictionaryCount=-1&minLength=1&maxLength=-1&api_key=a2a73e7b926c924fad7001ca3111acd55af2ffabf50eb4ae5";
but this broke my code. Is there a certain phrase or area that I should be looking out for within the pod where I can put my new request URL in and thus run my program with my desired parameters? As you can tell I'm pretty new to programming.
You should not change kHNKLookupBaseUrl in pod. kHNKLookupBaseUrl is used to connect to the service.
Use this to get a random word:
[[HNKLookup sharedInstance] randomWordWithCompletion:^(NSString *randomWord, NSError *error) {
if (error) {
NSLog(#"ERROR: %#", error);
} else {
NSLog(#"%#", randomWord);
}
}];
You have the parameters initialised in HNKHttpSessionManager.m
+ (NSUInteger)randomWordWithCompletion:(void (^)(NSURLSessionDataTask *, id,
NSError *))completion
{
return
[self startRequestWithPath:kHNKPathRandomWord
parameters:#{
#"hasDictionaryDef" :
#(kHNKRandomWordShouldHaveDictionaryDefinition),
#"minCorpusCount" : #(kHNKRandomWordMinimumCorpusCount),
#"maxCorpusCount" : #(kHNKRandomWordMaximumCorpusCount),
#"minDictionaryCount" :
#(kHNKRandomWordMinimumDictionaryCount),
#"maxDictionaryCount" :
#(kHNKRandomWordMaximumDictionaryCount),
#"minLength" : #(kHNKRandomWordMinimumLength),
#"maxLength" : #(kHNKRandomWordMaximumLength)
}
completion:completion];
}
You can tweak this to get desired result.

Type Error in closure (translated code from obj c to swift)

I am trying to use a library which is written in objective c in my swift application.
I tried to translate a snippet from the readme to swift code - But I get a type error I don't understand.
Obj.C code from readme:
[self.client logonWithUsername:self.username.text password:self.password.text responseCallback:^(NSDictionary *response, NSError *error) {
if (error) {
[self handleFailedAuth:error];
return;
}
[self handleSuccessfulAuth];
}];
My translation to swift:
var username = NSUserDefaults.standardUserDefaults().stringForKey("username")
var password = NSUserDefaults.standardUserDefaults().stringForKey("password")
client.logonWithUsername(username, password: password, responseCallback: {
(response: NSDictionary, error: NSError) in
if(error){
handleFailedAuth(error)
return;
}
handleSuccessfulAuth()
}
)
I Get [NSObject: AnyObject]! is not a subtype of NSDictionary on the line where the parameters of the closure are defined. How is that possible? I am using the same types as in the example.
Your Swift should probably read the following:
var username = NSUserDefaults.standardUserDefaults().stringForKey("username")
var password = NSUserDefaults.standardUserDefaults().stringForKey("password")
client.logonWithUsername(username, password: password, responseCallback: {
(response: NSDictionary?, error: NSError?) in
if(error){
handleFailedAuth(error!)
return;
}
handleSuccessfulAuth()
}
)
This is because Swift optionals in some ways replace the way you used to pass nil in objective-c. So because the NSDictionary might be nil and the NSError might be nil, you put a ?-mark after them, then conditionally unwrap w/ a !-mark inside the block when you need to access the value of that
You're explicitly specifying the block/closure parameter types in Swift, and the Swift compiler does not have enough information about the NSDictionary. This is because the Swift Dictionary is more strongly typed than the Objective-C NSDictionary.
The error message says (admittedly, pretty cryptically) that the exact type Swift is expecting is a Dictionary<NSObject, AnyObject>!.
There are a couple of ways to solve this. One is to be more explicit about your NSDictionary parameter in the Swift closure definition:-
client.logonWithUsername(username, password: password, responseCallback: {
(response: Dictionary<NSObject, AnyObject>!, error: NSError) in
// ... handler body
}
)
A somewhat easier way is to not try to tell Swift about the types at all and let the compiler infer what it needs:-
client.logonWithUsername(username, password: password, responseCallback: {
response, error in
// ... handler body
}
)

Completion Blocks Syntax in Swift

Slowly getting into Swift but still struggling with the completion blocks. How would the following code look like in Swift?
[self.eventStore requestAccessToEntityType:type completion:^(BOOL granted, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self alertViewWithDataClass:((type == EKEntityTypeEvent) ? Calendars : Reminders) status:(granted) ? NSLocalizedString(#"GRANTED", #"") : NSLocalizedString(#"DENIED", #"")];
});
}];
self.eventStore.requestAccessToEntityType(type) {
(granted: Bool, err: NSError!) in
dispatch_async(dispatch_get_main_queue()) {
...
}
}
for an example of working code, I was experimenting with this exact API in swift :)
Your Objective-C 'completion blocks' in Swift (now called 'closures' in this context) will contain all of the same information:
parameter labels and types (at the start of the block in parentheses)
return type (preceded by '->')
the keyword 'in' separating the signature from the code
Note that the signature of the method specifies the type for the parameters, so all you really need to do there is supply names for them :) (type inference FTW!) Additionally, your block returns 'Void' so we don't need to include the return type here, either.
That would give us:
self.eventStore.requestAccessToEntityType(type) { (granted, err) in
dispatch_async(dispatch_get_main_queue()) {
...other stuff...
}
}

Displaying file copy progress using FSCopyObjectAsync

It appears after much searching that there seems to be a common problem when trying to do a file copy and show a progress indicator relative to the amount of the file that has been copied. After spending some considerable time trying to resolve this issue, I find myself at the mercy of the StackOverflow Gods once again :-) - Hopefully one day I'll be among those that can help out the rookies too!
I am trying to get a progress bar to show the status of a copy process and once the copy process has finished, call a Cocoa method. The challenge - I need to make use of File Manager Carbon calls because NSFileManager does not give me the full ability I need.
I started out by trying to utilize the code on Matt Long's site Cocoa Is My Girlfriend. The code got me some good distance. I managed to get the file copy progress working. The bar updates and (with some additional searching within Apple docs) I found out how to tell if the file copy process has finished...
if (stage == kFSOperationStageComplete)
However, I have one last hurdle that is a little larger than my leap right now. I don't know how to pass an object reference into the callback and I don't know how to call a Cocoa method from the callback once finished. This is a limit of my Carbon -> Cocoa -> Carbon understanding. One of the comments on the blog said
"Instead of accessing the progress indicator via a static pointer, you can just use the void *info field of the FSFileOperationClientContext struct, and passing either the AppDelegate or the progress indicator itself."
Sounds like a great idea. Not sure how to do this. For the sake of everyone else that appears to bump into this issue and is coming from a non-Carbon background, based mostly upon the code from Matt's example, here is some simplified code as an example of the problem...
In a normal cocoa method:
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
FSFileOperationRef fileOp = FSFileOperationCreate(kCFAllocatorDefault);
OSStatus status = FSFileOperationScheduleWithRunLoop(fileOp,
runLoop, kCFRunLoopDefaultMode);
if (status) {
NSLog(#"Failed to schedule operation with run loop: %#", status);
return NO;
}
// Create a filesystem ref structure for the source and destination and
// populate them with their respective paths from our NSTextFields.
FSRef source;
FSRef destination;
// Used FSPathMakeRefWithOptions instead of FSPathMakeRef which is in the
// original example because I needed to use the kFSPathMakeRefDefaultOptions
// to deal with file paths to remote folders via a /Volume reference
FSPathMakeRefWithOptions((const UInt8 *)[aSource fileSystemRepresentation],
kFSPathMakeRefDefaultOptions,
&source,
NULL);
Boolean isDir = true;
FSPathMakeRefWithOptions((const UInt8 *)[aDestDir fileSystemRepresentation],
kFSPathMakeRefDefaultOptions,
&destination,
&isDir);
// Needed to change from the original to use CFStringRef so I could convert
// from an NSString (aDestFile) to a CFStringRef (targetFilename)
CFStringRef targetFilename = (CFStringRef)aDestFile;
// Start the async copy.
status = FSCopyObjectAsync (fileOp,
&source,
&destination, // Full path to destination dir
targetFilename,
kFSFileOperationDefaultOptions,
statusCallback,
1.0,
NULL);
CFRelease(fileOp);
if (status) {
NSString * errMsg = [NSString stringWithFormat:#"%# - %#",
[self class], status];
NSLog(#"Failed to begin asynchronous object copy: %#", status);
}
Then the callback (in the same file)
static void statusCallback (FSFileOperationRef fileOp,
const FSRef *currentItem,
FSFileOperationStage stage,
OSStatus error,
CFDictionaryRef statusDictionary,
void *info )
{
NSLog(#"Callback got called.");
// If the status dictionary is valid, we can grab the current values to
// display status changes, or in our case to update the progress indicator.
if (statusDictionary)
{
CFNumberRef bytesCompleted;
bytesCompleted = (CFNumberRef) CFDictionaryGetValue(statusDictionary,
kFSOperationBytesCompleteKey);
CGFloat floatBytesCompleted;
CFNumberGetValue (bytesCompleted, kCFNumberMaxType,
&floatBytesCompleted);
NSLog(#"Copied %d bytes so far.",
(unsigned long long)floatBytesCompleted);
// fileProgressIndicator is currently declared as a pointer to a
// static progress bar - but this needs to change so that it is a
// pointer passed in via the controller. Would like to have a
// pointer to an instance of a progress bar
[fileProgressIndicator setDoubleValue:(double)floatBytesCompleted];
[fileProgressIndicator displayIfNeeded];
}
if (stage == kFSOperationStageComplete) {
NSLog(#"Finished copying the file");
// Would like to call a Cocoa Method here...
}
}
So the bottom line is how can I:
Pass a pointer to an instance of a progress bar from the calling method to the callback
Upon completion, call back out to a normal Cocoa method
And as always, help is much appreciated (and hopefully the answer will solve many of the issues and complaints I have seen in many threads!!)
You can do this by using the last parameter to FSCopyObjectAsync(), which is a struct of type FSFileOperationClientContext. One of the fields of that struct is info, which is a void* parameter that you can basically use as you see fit. Whatever you assign to that field of the struct you pass into FSCopyObjectAsync() will be passed in turn to your callback function as the last info function parameter there. A void* can be anything, including a pointer to an object, so you can use that to pass the instance of your object that you want to handle the callback.
The setup code would look like this:
FSFileOperationClientContext clientContext = {0}; //zero out the struct to begin with
clientContext.info = myProgressIndicator;
//All the other setup code
status = FSCopyObjectAsync (fileOp,
&source,
&destination, // Full path to destination dir
targetFilename,
kFSFileOperationDefaultOptions,
statusCallback,
1.0,
&clientContext);
Then, in your callback function:
static void statusCallback (FSFileOperationRef fileOp,
const FSRef *currentItem,
FSFileOperationStage stage,
OSStatus error,
CFDictionaryRef statusDictionary,
void *info )
{
NSProgressIndicator* fileProgressIndicator = (NSProgressIndicator*)info;
[fileProgressIndicator setDoubleValue:(double)floatBytesCompleted];
[fileProgressIndicator displayIfNeeded];
}