Block from Objective-C to Swift - objective-c

I used a framework Objective-C in my Project (Swift). But in the code have a Block, i cannot convert to swift (i'm newbie in swift)
So the code is
[self.datePicker setDateHasItemsCallback:^BOOL(NSDate *date) {
int tmp = (arc4random() % 30)+1;
return (tmp % 5 == 0);
}];
Please help me.
Thank you ,

Where you would use a block in Objective-C, you use a function in Swift. In Objective-C, the argument is a block that takes an NSDate and returns a BOOL:
[self.datePicker setDateHasItemsCallback:^BOOL(NSDate *date) {
So, in Swift the argument is a function that takes an NSDate and returns a Bool:
self.datePicker.setDateHasItemsCallback {
(date:NSDate) -> Bool in
return true // fix this up as desired
}

Related

Convert Swift convenience init with Switch statement to Objective-C

I am trying to convert this swift code to Objective-C
convenience init(fromString string: String, format:DateFormat)
{
if string.isEmpty {
self.init()
return
}
let string = string as NSString
switch format {
case .DotNet:
let startIndex = string.rangeOfString("(").location + 1
let endIndex = string.rangeOfString(")").location
let range = NSRange(location: startIndex, length: endIndex-startIndex)
let milliseconds = (string.substringWithRange(range) as NSString).longLongValue
let interval = NSTimeInterval(milliseconds / 1000)
self.init(timeIntervalSince1970: interval)
So far, I am doing this:
-(id) initFromString: (NSString *) string format: (DateFormat *) format{
if (string == nil) {
self = [self init];
return self;
}
switch (format) {
case .DotNet:
NSRange *startIndex = [[string rangeOfString:#"("] location]+1;
}
}
and have already run into the following errors:
for the switch(format): statement requires expression of integer type (DateFormat * __strong' invalid)
and for the 2 following lines: Expected expression
Any ideas ?
In Objective-C, the string is impliedly optional. Testing for nil merely tests whether a string was supplied. It doesn't check whether an empty string was supplied. You probably want to switch to string.length == 0 as, by the magic of nil-messaging, that'll work to check for either an empty string or no string at all.
Objective-C uses C's switch statement. So you can switch on integral types only. If this were Objective-C code originally, DateFormat would probably be an NS_ENUM — an integral type rather than an object type. It looks like the original was an enumeration from your use of dot syntax? If you can make it an Objective-C enumeration then do so and simply use:
- (id)initFromString:(NSString *)string format:(DateFormat)format {
....
switch(format)
{
case DateFormatDotNet: {
....
} break;
}
(with the curly brackets within the case being because you want to declare variables in there).
Otherwise, if it must be an object format then you're looking at a painful construction like:
if([format isEqual:[DateFormat dotNetFormat]]) {
}
else if([format isEqual:[DateFormat otherFormat]]) {
}
... etc ...
Also Objective-C has a syntactic distinction between structs, which are exactly what they are in C — named fields but no built-in behaviour — and object types, which is again because it's a strict superset of C. NSRange is a struct. So square bracket messaging syntax doesn't work on it. Instead of:
[[string rangeOfString:#"("] location]
Use:
[string rangeOfString:#"("].location
Square brackets around the rangeOfString call because it's a message dispatch to an object, then a dot for location because you get back a C struct as a value, and that's how one accesses a field in a C struct.
(dot syntax also works for properties on Objective-C objects, but explicitly to alias to getter and setter calls, and only for about the most recent of Objective-C's three decades)
Assuming this code is related to How to convert a Swift enum: String into an Objective-C enum: NSString?
Since your DateFormat variable is an object with a dateFormatType that is an NSString, you are going to have to use a chained if-else construct to select from the various possibilities:
if([format.dateFormatType compare: DotNetDateFormatType] == NSOrderedSame) {
[self handleDotNetDateFormat: format]
} else if ([format.dateFormatType compare: RSSDateFormatType] == NSOrderedSame) {
[self handleRSSDateFormat: format]
...
Objective-C has no concept of the dot-value syntax for enum values (so ".DotNet" is not a valid term in Objective-C). That's why the compiler is complaining about those either lines.

Pointer to pointer parameter in Swift function

How do you do pointers to pointers in Swift? In Objective-C I had a function which I would call recursively so that I could keep track of the number of recursions, but I'm stumped as to how to achieve this in Swift.
NSNumber *recursionCount = [NSNumber numberWithInteger:-1];
[self doRecursion:&recursionCount];
- (void)doRecursion:(NSNumber **)recursionCount {
// I'm sure this is a terrible way to increment, but I have limited Objective-C experience
// and this worked, so I moved on after failing to find a decent var++ equivalent :-(
int addThis = (int)i + 1;
*recursionCount = [NSNumber numberWithInt:[*recursionCount intValue] + addThis];
[self doRecursion:recursionCount];
}
In the process of cutting this sample down for this post, I've ended up creating a never-ending loop, but you get the idea on how I'm remembering the value with each recursion.
Does anybody know how to do this in Swift? Thanks.
Usage of pointers in swift is highly discouraged.
To change a variable passed as argument to a function, you have to pass it by reference (similar to passing its pointer) using the inout modifier. I've changed the logic of your function to stop after 10 iterations:
func doRecursion(inout recursionCount: Int) -> Int {
if (recursionCount < 10) {
++recursionCount
doRecursion(&recursionCount)
}
return recursionCount
}
Then you can call using:
var counter: Int = -1
let x = doRecursion(&counter) // Prints 10 in playground
Suggested reading: In-Out Parameters

Objective C: Why am I getting "EXC_BAD_ACCESS"?

I'm really new to Objective C and am trying to write a program to go through the collatz conjecture. When I run the program, it stops after the first scanf and comes up with "EXC_BAD_ACCESS". Here's my code:
int original,i;
NSString *PrintFull;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSLog(#"Collatz Conjecture:");
NSLog(#"Print full results?");
scanf("%s",PrintFull);
NSLog(#"What number should we go up to?");
scanf("%d", &original);
while (original <= 100) {
NSLog(#"\n\n%d", original);
i = original;
while (i != 1) {
if (i % 2) {
i = (i*3)+1;
} else {
i = (i/2);
}
if ([PrintFull isEqualToString:#"yes"]) {
NSLog(#"%d",i);
}
}
original++;
}
}
What am I doing wrong here?
scanf does not work with with object types such as NSString. Please see SO post - Using scanf with NSStrings.
scanf's arguments after the format string should point to already allocated objects. In this case you've just declared a pointer and passed it in without setting it. scanf will try to write to this location, but since the pointer contains a garbage value, the application crashes.
scanf is from the C library 'stdio.h', meaning it doesn't know about NSStrings, which are from the Objective-C 'Foundation' framework.
The following should solve these problems
int original,i;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSLog(#"Collatz Conjecture:");
NSLog(#"Print full results?");
char inputBuffer[80];
scanf("%s", inputBuffer);
NSString *printFull = [NSString stringWithCString:inputBuffer encoding:NSUTF8Encoding];
First, you have to initialize and alloc the NSString. Second, scanf can't handle NSString.
Also notice, that class names begin with a capital letter and class instances with a small one.

Converting NSArray Contents to a varargs (With ARC) For Use With NSString initWithFormat

We have some code today that takes an NSArray and passes it as a argument list to -[NSString initWithFormat:arguments] and we're trying to get this to work with ARC. Here's the code were using
NSString* format = #"Item %s and Item %s"; // Retrieved elsewhere
NSArray* args = [NSArray arrayWithObjects:#"1", #"2", nil]; // Retrieved elsewhere
// http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
char* argsList = (char*) malloc(sizeof(NSString*) * args.count);
[args getObjects:(id*) argsList];
NSString* message = [[[NSString alloc] initWithFormat:format arguments:argsList] autorelease];
free(argsList);
Any recommendations on how to make this ARC compliant? Or we're even open to a better way of doing it.
This only works for arrays with a single element
The answer by chrisco was working well, until I went to compile with 64-bit architecture. This caused an error:
EXC_BAD_ADDRESS type EXC_I386_GPFLT
The solution was to use a slightly different approach for passing the argument list to the method:
+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;
{
__unsafe_unretained id * argList = (__unsafe_unretained id *) calloc(1UL, sizeof(id) * arguments.count);
for (NSInteger i = 0; i < arguments.count; i++) {
argList[i] = arguments[i];
}
NSString* result = [[NSString alloc] initWithFormat:format, *argList] ;// arguments:(void *) argList];
free (argList);
return result;
}
Cannot find a way to do this obj-c but a swift helper class finally got this working (my whole project is obj-c except this class)
#objc class StringFormat: NSObject {
class func format(key: String, args: [AnyObject]) -> String {
let locArgs: [CVarArgType] = args.map({ (arg: AnyObject) -> CVarArgType in
if let iArg = (arg is NSNumber ? arg.intValue : nil) {
return iArg
}
return arg as! CVarArgType
});
return String(format: key, arguments: locArgs)
}
}
There is some magic going on, to do with how [CVarArgType] doesn't behave like a normal array - but this works in the flexible cross architecture way you expect it to.
Expanding on #mcfedr's answer, this Swift 3 helper does the job:
import Foundation
#objc (FTStringFormat) public class StringFormat: NSObject {
#objc public class func format(key: String, args: [AnyObject]) -> String {
let locArgs: [CVarArg] = args.flatMap({ (arg: AnyObject) -> CVarArg? in
if let arg = arg as? NSNumber {
return arg.intValue
}
if let arg = arg as? CustomStringConvertible {
return arg.description
}
return nil
});
return String(format: key, arguments: locArgs)
}
}
Calling from Objective-C:
[FTStringFormat formatWithKey:#"name: %# age: %d" args:#[#"John", #(42)]]
For the %# format specifier we're using Swift's CustomStringConvertible protocol in order to call description on all of the array members.
Supporting all number format specifiers like %d and %f is not really possible because the NSNumber object doesn't reveal if it's an integer or float. So we could only support one or the other. Here we use intValue, so %d is supported but %f and %g are not.
The only thing you need to do is remove the autorelease.
You're malloc'ing and free'ing yourself - ARC doesn't care about that.
I write solution use NSInvocation and signatures.
Answer create in this.
Also I write detailed description how it work but only on Russian ((
Maybe it help for someone.
I tried mcfedr's code. Somehow, my Xcode 11 treated CVarArgType as undeclared type, so I investigated into this for a while.
I didn't not understand the closure part of his/her code. And, I just simplified to hard casted each element to CVarArg using as! operator.
func format(key: String, args: [Any]) -> String {
return String(format: key, arguments: args.map { ($0 as! CVarArg) })
}
let doubleValue: Double = 1.25
let floatValue: Float = 2.75
let intValue: Int = 3
let numberValue: NSNumber = 4.5 as NSNumber
let hello: String = "Hello"
let world: NSString = "World" as NSString
print(format(key: "double: %f, float: %f, int: %d, number: %#, %#, %#", args: [doubleValue, floatValue, intValue, numberValue, hello, world]))
// double: 1.250000, float: 2.750000, int: 3, number: 4.5, Hello, World
It seems it's working fine under swift 5.1, but there may be some pitfalls.

pointer before declaration of object objective-c

can you tell me something : is it a mistake or can we write "result" without the " * " here :
#implementation Person (Sorting)
- (NSComparisonResult)compareByName:(Person *)person2 {
>>//here :
>>NSComparisonResult result = [self.lastName caseInsensitiveCompare:person2.lastName];
if (result == NSOrderedSame) {
return [self.firstName caseInsensitiveCompare:person2.firstName];
}
return result;
}
#end
Thanks
caseInsensitiveCompare method returns NSComparisonResult so not using * is absolutely correct.
In objective-c you must use pointers to obj-c objects, but NSComparisonResult is just an enum (i.e. plain integer) so you may freely use it without pointer.