46

I want to change a sentence, for example:

Être ou ne pas être. C'était là-bas.

Would become:

Etre ou ne pas etre. C'etait la-bas.

Is there any easy way to do this with NSString? Or do I have to develop this on my own by checking each char?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Rob
  • 15,732
  • 22
  • 69
  • 107

8 Answers8

60
NSString *str = @"Être ou ne pas être. C'était là-bas.";
NSData *data = [str dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *newStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(@"%@", newStr);

... or try using NSUTF8StringEncoding instead.

List of encoding types here:

https://developer.apple.com/documentation/foundation/nsstringencoding


Just FTR here's a one line way to write this great answer:

yourString = [[NSString alloc]
  initWithData:
    [yourString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]
  encoding:NSASCIIStringEncoding];
Cœur
  • 37,241
  • 25
  • 195
  • 267
Luke
  • 11,426
  • 43
  • 60
  • 69
  • 2
    Thank you but when I transform UTF-8 characters, for example **à** doesn't become **a** but **?**. – Rob Jun 07 '12 at 13:17
  • Edited post - sorry I used the wrong encoding type. My version using ASCII encoding works in Xcode - maybe using the correct encoding will work for you. – Luke Jun 07 '12 at 13:20
  • 3
    This seems to assume that strings can be converted to ASCII without losing information (other than accents). It might work for the example but is far from guaranteed to work when using languages in other character sets. – Rupert May 06 '14 at 14:11
  • dataUsingEncoding throws an exception when used in iOS 8. One could use try/catch but if the exception breakpoint is enabled, the program will always stop there. Is there another solution? – John Feb 23 '15 at 21:14
  • 1
    @vomako The string you're converting to data is probably nil - the code works fine under iOS 8. – Luke Feb 23 '15 at 21:43
  • @Luke: Thank you. I now check for nil but still get the following exception: Exception reason: *** -[__NSCFString dataUsingEncoding:allowLossyConversion:]: didn't convert all characters – John Feb 24 '15 at 11:47
  • 1
    @vomako If you cannot solve the issue (use Google and this site!), then I'd recommend making a new question on here, and link to this one and your comments for reference. – Luke Feb 24 '15 at 11:56
  • I found out that the character § causes the exception in my case. Now I replace § by a space and it seems to work. However, I cannot be sure that this is the only problematic character. – John Feb 25 '15 at 11:00
49

Mattt Thompson covered this in NSHipster and again at WWDC 2013 session 228

TL;DR

NSMutableString *str = [@"Être ou ne pas être. C'était là-bas." mutableCopy];
CFStringTransform((__bridge CFMutableStringRef)string, NULL, kCFStringTransformStripCombiningMarks, NO);

Should do the trick, it worked great for me.

Caveat Since a lot of people in the comments say this should be the accepted answer I want to give a caveat for this method. This method is pretty damn slow and should be used with care if huge amounts of string/data needs to be transformed

hashier
  • 4,670
  • 1
  • 28
  • 41
tapi
  • 1,726
  • 1
  • 16
  • 16
  • 2
    This question is the second I found which has the 'trick' to convert to NSData and back as the accepted answer. Your answer should be the accepted one, it even beats `[input stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:[NSLocale currentLocale]];` in that it does not require a locale. – xorgate Nov 05 '13 at 06:56
  • Thanks, turns out theres a lot of cool stuff in Core Foundation that gets over looked – tapi Mar 26 '14 at 15:19
  • Should be the approved answer, and thanks for the link to Mattt Thompson's page. – lucasart Apr 09 '14 at 01:58
  • I agree - this is a much more reliable answer. – Rupert May 06 '14 at 14:11
  • Note also that the string has to be a `NSMutableString`, not a `NSString*`. – Matthieu Riegler May 31 '14 at 13:50
  • I get a EXC_BAD_ACCESS on the CFStringTransform line. The console reads: CLTilesManagerClient: initialize, sSharedTilesManagerClient CLTilesManagerClient: init CLTilesManagerClient: reconnecting, 0x1165f380 Why is that? – fatuhoku Jun 21 '14 at 12:33
  • That almost sounds like a dangling pointer reference. Are you sure the first object is a reference to a mutable string? The "CL" sounds like core location and header dumps (https://github.com/EthanArbuckle/IOS-7-Headers/blob/master/Frameworks/CoreLocation.framework/CLTilesManagerClient.h) seem to back that up. – tapi Jul 02 '14 at 19:38
28

Have you tried

[string stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:[NSLocale currentLocale]]

or

Boolean CFStringTransform (
   CFMutableStringRef string,
   CFRange *range,
   CFStringRef transform,
   Boolean reverse
);

?

CFStringTransform & Transform Identifiers

NSMutableString *string = ...;
CFMutableStringRef stringRef = (__bridge CFMutableStringRef)string;
CFStringTransform(stringRef, NULL, kCFStringTransformToLatin, NO);
NSLog(@"%@", string);
Regexident
  • 29,441
  • 10
  • 93
  • 100
  • See changed answer for solution without the need for intermediate NSData conversion and UTF-8 encoding loss. – Regexident Jun 07 '12 at 13:26
  • @Regexident I tried this but it didn't work i'm getting error on CFStringTransform(stringRef, NULL, kCFStringTransformToLatin, NO); this line... – Karthik Apr 17 '13 at 06:06
16

Just an update to say that it can be done like that in swift:

"Être ou ne pas être. C'était là-bas.".stringByFoldingWithOptions(NSStringCompareOptions.DiacriticInsensitiveSearch, locale: NSLocale.currentLocale())

--> "Etre ou ne pas etre. C'etait la-bas."

valR
  • 829
  • 7
  • 13
9

Here a Performance Test using Swift 2.0 on iPhone 6 iOS 9.0 Simulator between solutions using:

  • CFStringTransform (Task 1)
  • stringByFoldingWithOptions (Task 2)

Task 2 is consistently faster, e.g.:

Task 1 took 9.49736100435257 seconds.
Task 2 took 1.96649599075317 seconds.

Here the test:

    let timer = ParkBenchTimer()
    for _ in 1...1000000 {
        let mStringRef = NSMutableString(string: "Être ou ne pas être. C'était là-bas.") as CFMutableStringRef
        CFStringTransform(mStringRef, nil, kCFStringTransformStripCombiningMarks, false)
        String(mStringRef)
    }
    print("Task 1 took \(timer.stop()) seconds.")

    let timer2 = ParkBenchTimer()
    for _ in 1...1000000 {
        "Être ou ne pas être. C'était là-bas.".stringByFoldingWithOptions(NSStringCompareOptions.DiacriticInsensitiveSearch, locale: NSLocale.currentLocale())
    }
    print("Task 2 took \(timer2.stop()) seconds.")

ParkBenchTimer by Klaas: https://stackoverflow.com/a/26578191/1097106

Community
  • 1
  • 1
schmittsfn
  • 1,412
  • 18
  • 40
6

Swift 3 (tested in playground)

//String+StripCombiningMarks.swift

extension String {
    /// strip combining marks (accents or diacritics)
    var stripCombiningMarks: String {
        let mStringRef = NSMutableString(string: self) as CFMutableString
        CFStringTransform(mStringRef, nil, kCFStringTransformStripCombiningMarks, false)
        return mStringRef as String
    }
}

Usage:

let umlaut = "äöüÄÖÜ"
let stripped = umlaut.stripCombiningMarks //aouAOU
Peter Kreinz
  • 7,979
  • 1
  • 64
  • 49
3

here is complete code. use function stringbyfoldignWithOptions.

NSString *str1=@"Être ou ne pas être C'était là-bas"; NSString *str2=[str1 stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:[NSLocale systemLocale]]; NSLog(@"%@",str2);

Hitesh Agarwal
  • 1,943
  • 17
  • 21
1

For those who want a Swift version of CFStringTransform solution:

let stripAccentAndDiacritics: (String) -> String = {
    var mStringRef = NSMutableString(string: $0) as CFMutableStringRef
    CFStringTransform(mStringRef, nil, kCFStringTransformStripCombiningMarks, Boolean(0))
    return String(mStringRef)
}
clmntcrl
  • 273
  • 2
  • 7