I need the user's device token as a hexadecimal string to send push notifications. I used to do this by using the deviceToken 'description', but this doesn't work anymore in iOS13. I found several solutions for this in Objective-C and Swift.
How can I convert my device token (NSData) into an NSString?
The problem however, is that deviceToken.bytes returns a Pointer in RubyMotion. And calling bytes[index] will give a different result compared to doing it in Objective-C. I've tried a lot of different things, but I can't seem to get it to work. The RubyMotion docs about Pointers are also really barebones, so that doesn't help. I found the following regarding the Pointers on SO:
Pointer handling with RubyMotion
It says that I have to do bytes + index instead of bytes[index]. I currently have the following code:
def application(application, didRegisterForRemoteNotificationsWithDeviceToken: device_token)
string = token_to_string(device_token)
end
def token_to_string(device_token)
data_length = device_token.length
if data_length == 0
nil
else
data_buffer = device_token.bytes
token = NSMutableString.stringWithCapacity(data_length * 2)
index = 0
begin
buffer = data_buffer + index
token.appendFormat('%02.2hhx', buffer)
index += 1
end while index < data_length
token.copy
end
end
It's not giving any errors, but the generated device token doesn't seem to be correct. Any advice would be greatly appreciated!
I've been trying to think of a pure RubyMotion solution, but I just don't understand 'Pointers' enough to get it to work. So I've gone a different way: extending the NSData class.
1) Create the folder vendor/NSData+Hex inside your project.
2) Create the file NSData+Hex.h inside the folder and put this inside:
#import <Foundation/Foundation.h>
#interface NSData (Hex)
#property (nonatomic, copy, readonly) NSString *hexString;
#end
3) Now create NSData+Hex.m in the same folder and put this in the file:
#import "NSData+Hex.h"
#implementation NSData (Hex)
- (NSString *) hexString
{
NSUInteger len = self.length;
if (len == 0) {
return nil;
}
const unsigned char *buffer = self.bytes;
NSMutableString *hexString = [NSMutableString stringWithCapacity:(len * 2)];
for (int i = 0; i < len; ++i) {
[hexString appendFormat:#"%02x", buffer[i]];
}
return [hexString copy];
}
#end
4) Add this to your Rakefile:
app.vendor_project('vendor/NSData+Hex', :static)
5) Now you can simply call deviceToken.hexString within def application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken).
If anyone can think of a pure RubyMotion solution, feel free to answer. For now, it's at least working. :)
Hi I use the following to convert NSData to an NSString that can then be manipulated in ruby.
class NSData
def to_s
NSString.alloc.initWithBytes(bytes, length: length, encoding: NSUTF8StringEncoding)
end
end
I do the following:
token = deviceToken.description.gsub(/[<> ]/, '')
Related
I have problems with special characters when using JSON in xcode 6 with swift
I found these codes in Cocoa/objective C to solve some problems converting accent but could not make it work in Swift. Any suggestions for how to use it? ... best alternative suggestions would also be cool ...
Thanks
NSString *input = #"\\u5404\\u500b\\u90fd";
NSString *convertedString = [input mutableCopy];
CFStringRef transform = CFSTR("Any-Hex/Java");
CFStringTransform((__bridge CFMutableStringRef)convertedString, NULL, transform, YES);
NSLog(#"convertedString: %#", convertedString);
// prints: 各個都, tada!
It's fairly similar in Swift, though you still need to use the Foundation string classes:
let transform = "Any-Hex/Java"
let input = "\\u5404\\u500b\\u90fd" as NSString
var convertedString = input.mutableCopy() as NSMutableString
CFStringTransform(convertedString, nil, transform as NSString, 1)
println("convertedString: \(convertedString)")
// convertedString: 各個都
(The last parameter threw me for a loop until I realized that Boolean in Swift is a type alias for UInt - YES in Objective-C becomes 1 in Swift for these types of methods.)
Swift String extension:
extension String {
var unescapingUnicodeCharacters: String {
let mutableString = NSMutableString(string: self)
CFStringTransform(mutableString, nil, "Any-Hex/Java" as NSString, true)
return mutableString as String
}
}
Swift 3
let transform = "Any-Hex/Java"
let input = "\\u5404\\u500b\\u90fd" as NSString
var convertedString = input.mutableCopy() as! NSMutableString
CFStringTransform(convertedString, nil, transform as NSString, true)
print("convertedString: \(convertedString)")
// convertedString: 各個都
In my Function below if I remove the tempString release statement it works just fine but with it, there is ALWAYS a runtime error. It is a simple function that displays an array in an NSTextField either _stackDisp1 or _stackDisp2 but for some reason releasing the string creates a runtime error Any help?
- (void) displayArr:(NSMutableArray*)stack{
NSTextField *myObj;
if([stack count] <= 10) myObj = _stackDisp1;
else myObj = _stackDisp2;
NSString *tempString = [[NSString alloc]initWithString:#""];
for(NSString *i in stack){
tempString = [NSString stringWithFormat:#"%#\n%#",tempString,i];
}
[myObj setStringValue:tempString];
[tempString release];
}
That's because
tempString = [NSString stringWithFormat:#"%#\n%#",tempString,i];
creates a new autoreleased object assigning it to your variable tempString. The pointer to the first object gets lost and you end up over-releasing an autoreleased object. Just change the initial assignment to
NSString *tempString = #"";
and remove the [tempString release] line.
In the for loop you're assigning tempString to an autoreleased string:
tempString = [NSString stringWithFormat:#"%#\n%#",tempString,i];
releasing it manually results in a BAD_ACCESS.
Also you are probably looking for this:
- (void) displayArr:(NSMutableArray*)stack{
NSTextField *myObj = ([stack count] <= 10) ? _stackDisp1 : _stackDisp2;
[myObj setStringValue:[stack componentsJoinedByString:#"\n"]];
}
The declaration/assignment of myObj was a bit too verbose for my taste,
so I used a ternary operator instead (it's use is not essiential though. Just a matter of style.).
I have an array with a bunch of strings and I want to check if a certain string is contained in the array. If I use the containsObject: message on the array, I'm getting correct results. Do all NSString objects with the same string point to the same object? Or why is the containsObject: working?
NSArray *stringArray = [NSArray arrayWithObjects:#"1",#"2",#"3",anotherStringValue, nil];
if([stringArray containsObject:#"2"]){
//DO SOMETHING
}
Yes, hard-coded NSStrings (string literals) (that is any #"..." in your source code) are turned into strings that exist indefinitely while your process is running.
However NSArray's containsObject: methods calls isEqual: on its objects, hence even a dynamically created string such as [NSString stringWithFormat:#"%d", 2] would return YES in your sample snippet.
This is because NSString's isEqual: (or more precisely its isEqualToString:) method is implemented to be content aware (vs. comparing pointer identities) and thus returns YES for any pair of strings containing the very same sequence of characters (at time of comparison), no matter how and when they were created.
To check for equal (pointer-)identity you'd have to enumerate your array and compare via
NSString *yourString = #"foo";
BOOL identicalStringFound = NO;
for (NSString *someString in stringArray) {
if (someString == yourString) {
identicalStringFound = YES;
break;
}
}
(which you most likely wouldn't want, though).
Or in a more convenient fashion:
BOOL identicalStringFound = [stringArray indexOfObjectIdenticalTo:someString] != NSNotFound;
(you most likely wouldn't want this one either).
Summing up:
So the reason you're getting a positive reply from containsObject: is NOT because literal strings share the same constant instance, BUT because containsObject: by convention calls isEqual:, which is content aware.
You might want to read the (short) documentation for isEqual: from the NSObject protocol.
containsObject: performs a value check, not a pointer check. It uses the isEqual: method defined by NSObject and overridden by other objects for testing. Therefore, if two strings contain the same sequence of characters, they will be considered the same.
The distinction between pointer testing and value testing is very important in some cases. Constant strings defined in source code are combined by the compiler so that they are the same object. However, strings created dynamically are not the same object. Here is an example program which will demonstrate this:
int main(int argc, char **argv) {
NSAutoreleasePool *p = [NSAutoreleasePool new];
NSString *constantString = #"1";
NSString *constantString2 = #"1";
NSString *dynamicString = [NSString stringWithFormat:#"%i",1];
NSArray *theArray = [NSArray arrayWithObject:constantString];
if(constantString == constantString2) NSLog(#"constantString == constantString2");
else NSLog(#"constantString != constantString2");
if(constantString == dynamicString) NSLog(#"constantString == dynamicString");
else NSLog(#"constantString != dynamicString");
if([constantString isEqual:dynamicString]) NSLog(#"[constantString isEqual:dynamicString] == YES");
else NSLog(#"[constantString isEqual:dynamicString] == NO");
NSLog(#"theArray contains:\n\tconstantString: %i\n\tconstantString2: %i\n\tdynamicString: %i",
[theArray containsObject:constantString],
[theArray containsObject:constantString2],
[theArray containsObject:dynamicString]);
}
The output of this program is:
2011-04-27 17:10:54.686 a.out[41699:903] constantString == constantString2
2011-04-27 17:10:54.705 a.out[41699:903] constantString != dynamicString
2011-04-27 17:10:54.706 a.out[41699:903] [constantString isEqual:dynamicString] == YES
2011-04-27 17:10:54.706 a.out[41699:903] theArray contains:
constantString: 1
constantString2: 1
dynamicString: 1
You can use containsObject to findout if certain string is exist,
NSArray *stringArray = [NSArray arrayWithObjects:#"1",#"2",#"3",anotherStringValue, nil];
if ( [stringArray containsObject: stringToFind] ) {
// if found
} else {
// if not found
}
I have an XCode project, with the XIB interface files built using Interface Builder. I'm building localized XIB files by using ibtool to extract strings, translating them, and using ibtool again to build localized XIB files.
However, doing this means I have to translate all items in the application menus, including those that are completely standard (File, Save, Open, Minimize, etc.). Is there a way to avoid that?
i've developed a solution to this problem.
https://www.corecode.io/index_opensource.html
look for "Translator", it will translate your MainMenu.strings file into a dozen languages with the standard Apple translations for the standard menu item strings.
if you find some strings or languages missing that Aapple has included in their base apps, please send a patch over.
So, apparently no way around this.
I have been looking for a similar solution for a while and I found this resource
http://www.bdunagan.com/2009/03/15/ibtool-localization-made-easy/
It quotes toward the end of the article:
ibtool will look through MainMenu.xib for every user-visible string and insert that string with an associated ObjectID into MainMenu.strings, essentially a dictionary of strings keyed by ObjectID. Even better, the tool sorts them by ObjectID, so versioned .strings files are nicely diff’able. I can easily see what strings are added, removed, or just changed. Let me repeat this because it’s so incredibly handy: .strings files diff well! Of course, these .strings files are unicode, so they are not grep’able. Below is an example of the output for a string:
Go ahead and take a look I really hope it helps you as much as it helped me!
Translator by https://github.com/core-code/MiscApps/blob/master/Translator/Translator/MainMenuTranslations.plist
is cool but if you do not want to deal with 30 MainMenu.string files in your build (I personally don't) - you can just add MainMenuTranslations.plist to your resources (230KB uncompressed is tiny) and do it on the fly like this:
- (void) processMenu: (NSString*) app {
NSDictionary* d = [self loadMenuTranslations: app];
NSMenu* mm = NSApplication.sharedApplication.mainMenu;
for (int i = 0; i < mm.numberOfItems; i++) {
NSMenuItem* mi = [mm itemAtIndex: i];
mi.title = [self translateMenu: mi.title withDictionary: d];
NSMenu* sm = [[mm itemAtIndex: i] submenu];
sm.title = [self translateMenu: sm.title withDictionary: d];
for (int j = 0; j < sm.numberOfItems; j++) {
NSMenuItem* mi = [sm itemAtIndex: j];
mi.title = [self translateMenu: mi.title withDictionary: d];
}
}
}
- (NSString*) translateMenu: (NSString*) key withDictionary: (NSDictionary*) dictionary {
for (NSString* lang in dictionary) {
NSDictionary* translation = dictionary[lang];
NSString* t = translation[key];
if (t != null) {
return t;
}
}
return key;
}
- (NSDictionary*) loadMenuTranslations: (NSString*) app {
NSArray* langs = [NSUserDefaults.standardUserDefaults objectForKey: #"AppleLanguages"];
NSURL* url = [NSBundle.mainBundle URLForResource:#"MainMenuTranslations.plist" withExtension: null];
NSMutableDictionary* r = NSMutableDictionary.new;
NSDictionary* translations = [NSDictionary dictionaryWithContentsOfURL: url];
for (NSString* lang in langs) {
NSString* locale = [NSString stringWithFormat:#"%#.lproj", lang];
NSDictionary* translation = translations[locale];
NSMutableDictionary* d = [NSMutableDictionary.alloc initWithCapacity: translations.count * 3 / 2];
for (NSString* k in translation) {
NSString* v = translation[k];
NSString* key = k;
if ([k indexOf: #"APPLICATIONNAME"] >= 0) {
key = [k stringByReplacingOccurrencesOfString: #"APPLICATIONNAME" withString: app];
}
if ([v indexOf: #"APPLICATIONNAME"] >= 0) {
v = [v stringByReplacingOccurrencesOfString: #"APPLICATIONNAME" withString: app];
}
d[key] = v;
}
if (d.count > 0) {
r[lang] = d;
}
}
return r;
}
just call it from
- (void) applicationDidFinishLaunching: (NSNotification*) n {
// ...
[self processMenu: #"<your app name>"];
}
I wish there is a UniversalTranslation.plist somewhere (which could be probably collected automatically via creative use of translate.google.com)
When i try and compile I come up with a warning that reads initialization makes pointer from integer without a cast. No clue why. I am just trying to get the size of a website.
#import "Lockerz_RedemptionViewController.h"
#implementation Lockerz_RedemptionViewController
-(IBAction)startLoop:(id) sender {
NSData *dataNew = [NSData dataWithData:[NSData dataWithContentsOfURL:[NSURL
URLWithString:#"http://www.google.com/"]]];
NSUInteger *len = [dataNew length]; //error is here
NSLog(#"%#", len);
}
NSUInteger is just a wrapper for an unsigned int, alter your code to this (i.e. remove the * as it's not a pointer to an object)
NSUInteger len = [dataNew length];
Also I think you're going a bit overboard with your initialisation, why not just do
NSData *dataNew = [NSData dataWithContentsOfURL:[NSURL
URLWithString:#"http://www.google.com/"]];
That should return you an autoreleased object containing the data you need