iOS: How to display special characters in Swift [NSString] - ios

I am sending data via socket.io my app, which I want to embed the text of a parameter in a UILabel
Javascript server
socket.emit("app",{ac:"organización"})
iOS App
var ac = self.jsonOFsocket["ac"] as String
//ac === "organización"
self.label.text = ac
//self.label.text === "organizaci\U00f3n"
The socket.io the app sends unencrypted characters.
The app successfully receives all characters
The app shows different symbols
I try to do this:
func utf(txt:String) -> NSString {
var newTxt = NSString(format:txt, NSUTF8StringEncoding)
newTxt.precomposedStringWithCanonicalMapping
return newTxt
}
var ac = self.jsonOFsocket["ac"] as String
//ac === "organización"
self.label.text = uft(ac)
//self.label.text === "organizaciââ¥n"
This is important:
organizaciââ¥n

This is the Objective-C way:
NSData *data = [MyString dataUsingEncoding:NSUTF8StringEncoding];
NSString *stringWithSpecialCharacters = [[NSString alloc] initWithData:data encoding:NSNonLossyASCIIStringEncoding];
You'll have to store the proper string format on your server too and make sure it returns the right string via curl.

Related

Automatically save a HTML <img> from a WKWebView to app's tmp directory

I have a WKWebView that can visit arbitrary websites. When a user clicks on any HTML <img> element, I'd like it to transparently save that image (as a file) into the app tmp directory, ideally with standard title (img.png), and ideally such that it would overwrite each time.
Given that client-side JavaScript has no access to the filesystem, I expect that a fully automatic solution would involve FileManager; however, I don't know how I'd transmit the <img> data from the WKWebView to a FileManager instance. I wonder whether JavaScriptCore might need to be involved, to bridge the data between the two.
I see that semi-automatic solutions exist, through the use of a HTML download attribute, wherein the user is prompted with a 'Save As..." dialogue. This is not ideal, as I would like the action to be transparent and free of user error. However, it may prove to be the only option.
I am implementing this on both macOS and iOS, so I can accept a solution for either platform; I expect there will be little difference between the two.
May I propose the following :
Prepare your view controller to receive messages from the javascript side of the WKWebView. I usually do that in the view controller's viewDidLoad.
Load and execute a javascript in the web page that will add an onClick event to each img tag.
In that event, you send a message back from javascript to Objective-C / Swift side with a Base64 encoded string of the image data as parameter
In the Objective-C / Swift handler of the message, you transform that string in data and save it.
Step 1 & 2 :
- (void) viewDidLoad
{
[super viewDidLoad] ;
WKUserContentController *controller = self.webView.configuration.userContentController ;
// Add self as scriptMessageHandler of the webView to receive messages from the scripts
[controller addScriptMessageHandler:self
name:#"imageHasBeenClicked"] ;
// Load script
NSURL *scriptURL = <<... URL of your javascript (can be bundled in your app) ...>> ;
NSString *scriptString = [NSString stringWithContentsOfURL:scriptURL
encoding:NSUTF8StringEncoding
error:NULL] ;
WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptString
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:YES] ;
[controller addUserScript:script] ;
}
Step 3 :
The javascript :
// This function takes an image tab and encodes the image as BAse64
function getBase64Image(img)
{
// Create an empty canvas element
var canvas = document.createElement("canvas") ;
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d") ;
ctx.drawImage(img,0,0) ;
// Get the data-URL formatted image, use PNG as JPG re-encode the image
var dataURL = canvas.toDataURL("image/png");
// Remove the initial marker so that we directly have NSData compatibility
return dataURL.replace(/^data:image\/(png|jpg);base64,/,"");
}
// Search for all img tags and add an onclick event that will encode the image
// then, send it to the objective-c side
var imgList = document.getElementsByTagName("img") ;
for (var i = 0; i < imgList.length; i++)
{
imgList[i].onclick = function()
{
var txt = getBase64Image(this) ;
window.webkit.messageHandlers["imageHasBeenClicked"].postMessage(txt) ;
} ;
}
Step 4 :
When the "imageHasBeenCLicked" message is received by the view controller, then convert the Base64 string into data and save it as an image file.
- (void) userContentController:(WKUserContentController*)userContentController
didReceiveScriptMessage:(WKScriptMessage*)message
{
if ([message.name isEqualToString:#"imageHasBeenClicked"])
{
NSData *data = [[NSData alloc] initWithBase64EncodedString:message.body
options:0] ;
[data writeToFile:#"/toto.png"
atomically:YES] ;
}
}

Cannot access protected member 'Foundation.NSDictionary.NSDictionary(System.IntPtr)'

We are trying to deploy our Xamarin built application to the Apple appstore and so we needed to migrate our application to the Unified Api to get 64-bit support. After upgrading to the latest version of Xamarin.iOS (Version: 8.8.2.4) and migrating the app to the Unified Api, we now get the following compile error:
Cannot access protected member 'Foundation.NSDictionary.NSDictionary(System.IntPtr)' via a qualifier of type 'Foundation.NSDictionary'.
The error occurs on this line:
NSDictionary prefSpecification = new NSDictionary(preferences.ValueAt(i));
of this method call:
public static void RegisterDefaultsFromSettingsBundle()
{
string settingsBundle = NSBundle.MainBundle.PathForResource("Settings", #"bundle");
if(settingsBundle == null) {
System.Console.WriteLine(#"Could not find Settings.bundle");
return;
}
NSString keyString = new NSString(#"Key");
NSString defaultString = new NSString(#"DefaultValue");
NSDictionary settings = NSDictionary.FromFile(Path.Combine(settingsBundle,#"Root.plist"));
NSArray preferences = (NSArray) settings.ValueForKey(new NSString(#"PreferenceSpecifiers"));
NSMutableDictionary defaultsToRegister = new NSMutableDictionary();
for (uint i=0; i<preferences.Count; i++) {
NSDictionary prefSpecification = new NSDictionary(preferences.ValueAt(i));
NSString key = (NSString) prefSpecification.ValueForKey(keyString);
if(key != null) {
NSObject def = prefSpecification.ValueForKey(defaultString);
if (def != null) {
defaultsToRegister.SetValueForKey(def, key);
}
}
}
NSUserDefaults.StandardUserDefaults.RegisterDefaults(defaultsToRegister);
}
Can anyone explain why this is happening and how I can work around this issue?
According to Apple docs, PreferenceSpecifiers is an Array of dictionaries, so instead of doing this:
NSDictionary prefSpecification = new NSDictionary(preferences.ValueAt(i));
You should be able to do this:
NSDictionary prefSpecification = (NSDictionary)preferences.ValueAt(i);
Udated
If you are using the new unified API, you will have to subclass the desired class (NSDictionary in this case) to have access to the NSDictionary(IntPtr) constructor as described on this Xamarin docs.
Hope it helps

How to get the public IP address of the device

I found this sample code to get all local IP addresses, but I don't find an easy solution to get the public IP.
A legacy class from Apple allowed to do that
... but it's legacy ...
It's as simple as this:
NSString *publicIP = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"https://icanhazip.com/"] encoding:NSUTF8StringEncoding error:nil];
publicIP = [publicIP stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; // IP comes with a newline for some reason
I have used ALSystemUtilities in the past. You basically have to make a call externally to find this out.
+ (NSString *)externalIPAddress {
// Check if we have an internet connection then try to get the External IP Address
if (![self connectedViaWiFi] && ![self connectedVia3G]) {
// Not connected to anything, return nil
return nil;
}
// Get the external IP Address based on dynsns.org
NSError *error = nil;
NSString *theIpHtml = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.dyndns.org/cgi-bin/check_ip.cgi"]
encoding:NSUTF8StringEncoding
error:&error];
if (!error) {
NSUInteger an_Integer;
NSArray *ipItemsArray;
NSString *externalIP;
NSScanner *theScanner;
NSString *text = nil;
theScanner = [NSScanner scannerWithString:theIpHtml];
while ([theScanner isAtEnd] == NO) {
// find start of tag
[theScanner scanUpToString:#"<" intoString:NULL] ;
// find end of tag
[theScanner scanUpToString:#">" intoString:&text] ;
// replace the found tag with a space
//(you can filter multi-spaces out later if you wish)
theIpHtml = [theIpHtml stringByReplacingOccurrencesOfString:
[ NSString stringWithFormat:#"%#>", text]
withString:#" "] ;
ipItemsArray = [theIpHtml componentsSeparatedByString:#" "];
an_Integer = [ipItemsArray indexOfObject:#"Address:"];
externalIP =[ipItemsArray objectAtIndex:++an_Integer];
}
// Check that you get something back
if (externalIP == nil || externalIP.length <= 0) {
// Error, no address found
return nil;
}
// Return External IP
return externalIP;
} else {
// Error, no address found
return nil;
}
}
Source from ALSystemUtilities
Thanks #Tarek for his answer
Here, code in Swift 4 version
func getPublicIPAddress() -> String {
var publicIP = ""
do {
try publicIP = String(contentsOf: URL(string: "https://www.bluewindsolution.com/tools/getpublicip.php")!, encoding: String.Encoding.utf8)
publicIP = publicIP.trimmingCharacters(in: CharacterSet.whitespaces)
}
catch {
print("Error: \(error)")
}
return publicIP
}
NOTE1: To get public IP address, we must have external site to return public IP. The website I use is business company website, so, it will be their until the business gone.
NOTE2: You can made some site by yourself, however, Apple require HTTPS site to be able to use this function.
For those of us using Swift, here's my translation of Andrei's answer with the addition of NSURLSession to run it in the background. To check the network, I use Reachability.swift. Also, remember to add dyndns.org to NSExceptionDomains for NSAppTransportSecurity in your info.plist.
var ipAddress:String?
func getIPAddress() {
if reachability!.isReachable() == false {
return
}
guard let ipServiceURL = NSURL(string: "http://www.dyndns.org/cgi-bin/check_ip.cgi") else {
return
}
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(ipServiceURL, completionHandler: {(data, response, error) -> Void in
if error != nil {
print(error)
return
}
let ipHTML = NSString(data: data!, encoding: NSUTF8StringEncoding) as? String
self.ipAddress = self.scanForIPAddress(ipHTML)
})
task.resume()
}
func scanForIPAddress(var ipHTML:String?) -> String? {
if ipHTML == nil {
return nil
}
var externalIPAddress:String?
var index:Int?
var ipItems:[String]?
var text:NSString?
let scanner = NSScanner(string: ipHTML!)
while scanner.atEnd == false {
scanner.scanUpToString("<", intoString: nil)
scanner.scanUpToString(">", intoString: &text)
ipHTML = ipHTML!.stringByReplacingOccurrencesOfString(String(text!) + ">", withString: " ")
ipItems = ipHTML!.componentsSeparatedByString(" ")
index = ipItems!.indexOf("Address:")
externalIPAddress = ipItems![++index!]
}
if let ip = externalIPAddress {
print("External IP Address: \(ip)")
}
return externalIPAddress
}
I use ipify and with no complaints.
NSURL *url = [NSURL URLWithString:#"https://api.ipify.org/"];
NSString *ipAddress = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
NSLog(#"My public IP address is: %#", ipAddress);
In case you want to retrieve IP asynchronously, there is a way to do that using ipify.org and Alamofire in 1 line of code:
Alamofire.request("https://api.ipify.org").responseString { (response) in
print(response.result.value ?? "Unable to get IP")
}
You can call a public IP address lookup services to get this? I have set up http://ipof.in as a service that returns the device IP address as JSON / XML or plain text. You can find them here
For JSON with GeoIP data
http://ipof.in/json
https://ipof.in/json
For XML response
http://ipof.in/xml
https://ipof.in/xml
For plain text IP address
http://ipof.in/txt
https://ipof.in/txt
You have to query an external server to find out the public IP. Either set up your own server (1 line of php code) or use one of the many available ones which return the IP as plain text or json upon http query. Like for example http://myipdoc.com/ip.php .
I find both Andrei and Tarek's answers helpful.
Both are relying a web URL to query the "public IP" of the iOS/OS X device.
However, there is an issue with this approach in some part of the world where URL such as "http://www.dyndns.org/cgi-bin/check_ip.cgi" is censored as in andrei's answer:
NSString *theIpHtml = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.dyndns.org/cgi-bin/check_ip.cgi"]
encoding:NSUTF8StringEncoding
error:&error];
In this case, we will need to use an "uncensored" URL within the region such as http://1212.ip138.com/ic.asp
Note that the web URL could use a different HTML and encoding than what Andrei's answer could parse - in the URL above, some very gentle changes can fix it by using kCFStringEncodingGB_18030_2000 for http://1212.ip138.com/ic.asp
NSURL* externalIPCheckURL = [NSURL URLWithString: #"http://1212.ip138.com/ic.asp"];
encoding:NSUTF8StringEncoding error:nil];
NSStringEncoding encoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
NSString *theIpHtml = [NSString stringWithContentsOfURL: externalIPCheckURL
encoding: encoding
error: &error];

How can i render a list of all iPhone contacts with Cordova (phone gap)

I am trying to create a Application which lists all contacts from the iPhone address book with the following code (coffeescript)
listContacts: ->
options = new ContactFindOptions()
options.filter = '';
options.multiple = true
fields = ["id", "photos", "name", "phoneNumbers"]
navigator.contacts.find(fields, #onSuccess, #onError, options)
onSuccess: (contacts) ->
console.log contacts.length
onError: (error) ->
console.log error
this seems to work nice for a bunch of contacts. but with 3000 the contacts will never return. the funny thing though this works perfectly on the iOsSimulator.
are there any limitations to the number of contacts which can be retrieved?
I had the same problem with 300 contacts, it took around 5 minutes. After I patched it only takes 10 seconds.
Here is my pull request : https://github.com/phonegap/phonegap/pull/19
They have to generate a temp file for each picture and they are using a crazy loop to find a free file path. Something like :
do {
filePath = [NSString stringWithFormat:#"%#/photo_%03d.jpg", docsPath, i++];
} while ([fileMgr fileExistsAtPath:filePath]);
Now I use mktemp and everything is faster.
If you don't need full res pictures, you can also replace :
CFDataRef photoData = ABPersonCopyImageData(self.record);
by :
CFDataRef photoData = ABPersonCopyImageDataWithFormat(self.record, kABPersonImageFormatThumbnail);
I hope that'll help you !
Edit :
IOS'll flush the temp directory each time you start the application:
You are responsible for deleting any temporary files that you created.
The system will clean them up at startup, but that could be a very
long time away.
From: http://cocoadev.com/wiki/NSTemporaryDirectory
If you don't want to slow down the bootstrap of your application, you should use always the same filepath based on the contact id. You'll save cleanup and write time if the file already exists :
- (NSObject*)extractPhotos
{
NSMutableArray* photos = nil;
if (ABPersonHasImageData(self.record)) {
//CFDataRef photoData = ABPersonCopyImageDataWithFormat(self.record, kABPersonImageFormatThumbnail);
CFDataRef photoData = ABPersonCopyImageData(self.record);
NSData* data = (__bridge NSData*)photoData;
// write to temp directory and store URI in photos array
// get the temp directory path
NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath];
NSError* err = nil;
int recordId = ABRecordGetRecordID(self.record);
NSFileManager* fileMgr = [[NSFileManager alloc] init];
NSString* filePath = [NSString stringWithFormat:#"%#/photo_%03d.jpg", docsPath, recordId];
BOOL hasImage = NO;
if ([fileMgr fileExistsAtPath:filePath]) {
hasImage = YES;
} else if ([data writeToFile:filePath options:NSAtomicWrite error:&err]) {
hasImage = YES;
}
if (hasImage) {
photos = [NSMutableArray arrayWithCapacity:1];
NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithCapacity:2];
[newDict setObject:filePath forKey:kW3ContactFieldValue];
[newDict setObject:#"url" forKey:kW3ContactFieldType];
[newDict setObject:#"false" forKey:kW3ContactFieldPrimary];
[photos addObject:newDict];
}
CFRelease(photoData);
}
return photos;
}
Edit (08/01/2013):
FYI : merged in cordova : http://git-wip-us.apache.org/repos/asf/cordova-ios/commit/c6a1dbe3
First you have to add plugin from terminal command line
$ cordova plugin add org.apache.cordova.contacts
onDeviceReady you can call a method to open contact list
function chooseContact() {
var options = new ContactFindOptions();
options.fields = ["displayName", "name", "emails", "phoneNumbers"];
navigator.contacts.chooseContact(onSuccess, options);
}
function onSuccess(id, contact) {
console.log(JSON.stringify(contact));
}

How to add a image in email body using MFMailComposeViewController

I am trying to find out the best way to add an image inside the body of the email and not as attachment in ios.
1) Apple has provided a function "addAttachment" and the doc says, to add any image in the content, we should use this function, but I tried that function, and sent an mail, I checked on my browser, it is recieved as an attachment.
2) Secondly, many blogs say to use base64 encoding, but that also wont work, image is sent as a broken one.
So friends, please help me out to find the best available solution to do this.
Regards
Ranjit
Set email format as HTML. This code is woking fine in my app.
MFMailComposeViewController *emailDialog = [[MFMailComposeViewController alloc] init];
NSString *htmlMsg = #"<html><body><p>This is your message</p></body></html>";
NSData *jpegData = UIImageJPEGRepresentation(emailImage, 1.0);
NSString *fileName = #"test";
fileName = [fileName stringByAppendingPathExtension:#"jpeg"];
[emailDialog addAttachmentData:jpegData mimeType:#"image/jpeg" fileName:fileName];
emailDialog setSubject:#"email subject"];
[emailDialog setMessageBody:htmlMsg isHTML:YES];
[self presentModalViewController:emailDialog animated:YES];
[emailDialog release];
Swift 5
import MessageUI
func composeMail() {
let mailComposeVC = MFMailComposeViewController()
mailComposeVC.addAttachmentData(UIImage(named: "emailImage")!.jpegData(compressionQuality: CGFloat(1.0))!, mimeType: "image/jpeg", fileName: "test.jpeg")
mailComposeVC.setSubject("Email Subject")
mailComposeVC.setMessageBody("<html><body><p>This is your message</p></body></html>", isHTML: true)
self.present(mailComposeVC, animated: true, completion: nil)
}
I just went through this recently for Swift.
Function to add photo to email in Swift:
func postEmail() {
var mail:MFMailComposeViewController = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setSubject("your subject here")
var image = // your image here
var imageString = returnEmailStringBase64EncodedImage(image)
var emailBody = "<img src='data:image/png;base64,\(imageString)' width='\(image.size.width)' height='\(image.size.height)'>"
mail.setMessageBody(emailBody, isHTML:true)
self.presentViewController(mail, animated: true, completion:nil)
}
Function to return the formatted image:
func returnEmailStringBase64EncodedImage(image:UIImage) -> String {
let imgData:NSData = UIImagePNGRepresentation(image)!;
let dataString = imgData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
return dataString
}
I've found that (at least in my case) a PNG will work in the message composer but NOT when the message is opened / received by the user.
Composer Dandily showing logo PNG image!
Viewer Not so much logo images over here.
(Occasionally there will be a light blue outline where the image should be.)
Using the HTML body string below and the conversion below that seems to do the trick.
Message Body HTML String using JPEG
NSString *body = [NSString stringWithFormat:
#"\
<html>\
<body>\
Check out the App!\
<br>\
Isn't this a terriffic logo?!.\
<br>\
<img src = \"data:image/jpeg;base64,%#\" width = 100 height= 100>\
<br>\
<a href = \"%#\" > CLICK ITTTTTTT! </a>\
</body>\
</html>",
imageString, #"http://www.LOLamazingappLOL.com"];
Convert Image to string with JPEG Data
+ (NSString *)dataStringFromImage:(UIImage *)image
{
NSData *imgData = UIImageJPEGRepresentation(image, 1);
return [imgData base64EncodedStringWithOptions:kNilOptions];
}
Additional Info:
iOS Target = 8.0
iOS Device = 9.1
I am awful with HTML!
Thank you #Richard for the CORRECT answer to this question.
Few things to note:
- Use addAttachmentData
- use setMessageBody and set isHTML:true
you dont have to add manually in your email body. the api will take care of that.
func postEmail() {
var mail:MFMailComposeViewController = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setSubject("your subject here")
var image = // your image here
var imageData = UIImageJPEGRepresentation(image, 1)
mail.addAttachmentData(imageData, mimeType:"image/jpeg", fileName:"Your Filename"
var emailBody = "<html><body><p>This is your message</p></body></html>"
mail.setMessageBody(emailBody, isHTML:true)
self.presentViewController(mail, animated: true, completion:nil)}

Resources