Swift - Which Types to Use? Nsstring or String

Swift - which types to use? NSString or String

You should use the Swift native types whenever possible. The language is optimized to use them, and most of the functionality is bridged between the native types and the Foundation types.

While String and NSString are mostly interchangeable, i.e, you can pass String variables into methods that take NSString parameters and vice versa, some methods seem to not be automatically bridged as of this moment. See this answer for a discussion on how to get the a String's length and this answer for a discussion on using containsString() to check for substrings. (Disclaimer: I'm the author for both of these answers)

I haven't fully explored other data types, but I assume some version of what was stated above will also hold true for Array/NSArray, Dictionary/NSDictionary, and the various number types in Swift and NSNumber

Whenever you need to use one of the Foundation types, you can either use them to type variables/constants explicitly, as in var str: NSString = "An NSString" or use bridgeToObjectiveC() on an existing variable/constant of a Swift type, as in str.bridgeToObjectiveC().length for example. You can also cast a String to an NSString by using str as NSString.

However, the necessity for these techniques to explicitly use the Foundation types, or at least some of them, may be obsolete in the future, since from what is stated in the language reference, the String/NSString bridge, for example, should be completely seamless.

For a thorough discussion on the subject, refer to Using Swift with Cocoa and Objective-C: Working with Cocoa Data Types

Difference between String and NSString in Struct in Swift

I think the issue is when you initialize the NSString, Swift is automatically changing it to a String. Try initializing the NSString like this and seeing if that gives you compile error:

    var testingstring1: NSString = "testingstring1" as NSString

Here is an explanation of the bridging between the two languages in terms of Objective-C's reference types and Swift's value types: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html

Calling NSString method on a String in Swift

After doing some research, it looks like containsString is not a String function, but can be accessed by bridging to an NSString.

Under Apple's Documentation on Using Swift with Cocoa and Objective-C, it says that

Swift automatically bridges between the String type and the NSString
class. This means that anywhere you use an NSString object, you can
use a Swift String type instead and gain the benefits of both types

But it appears that only some of NSString's functions are accessible without explicitly bridging. To bridge to an NSString and use any of its functions, the following methods work:

 //Example Swift String var
var newString:String = "this is a string"

//Bridging to NSString
//1
(newString as NSString).containsString("string")
//2
newString.bridgeToObjectiveC().containsString("string")
//3
NSString(string: newString).containsString("string")

All three of these work.
It's interesting to see that only some NSString methods are available to Strings and others need explicit bridging. This may be something that is built upon as Swift develops.

Difference between String and NSString in Struct in Swift

I think the issue is when you initialize the NSString, Swift is automatically changing it to a String. Try initializing the NSString like this and seeing if that gives you compile error:

    var testingstring1: NSString = "testingstring1" as NSString

Here is an explanation of the bridging between the two languages in terms of Objective-C's reference types and Swift's value types: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html

Which Swift character count should I use when interacting with NSString APIs?


TL;DR

The documentation for NSString.length specifies:

The number of UTF-16 code units in the receiver.

Thus, if you want to interop between String and NSString:

  • You should use string.utf16.count, and it will match up perfectly with (string as NSString).length.

If you want to count the number of visible characters:

  • You should use string.count, and it will match up to the same number of times you need the (right) key on your keyboard until you get to the end of the string (assuming you start at the beginning).

    Note: This is not always 100% accurate, but it appears Apple is constantly improving the implementation to make it more and more accurate.


Here's a Swift 4.0 playground to test a bunch of strings and functions:

let header = "NSString   .utf16❔   encodedOffset❔   NSRange❔   .count❔   .characters❔   distance❔   .unicodeScalars❔   .utf8❔   Description"
var format = " %3d %3d ❓ %3d ❓ %3d ❓ %3d ❓ %3d ❓ %3d ❓ %3d ❓ %3d ❓ %@"
format = format.replacingOccurrences(of: "❓", with: "%@") // "❓" acts as a placeholder for "%@" to align the text perfectly

print(header)

test("")
test("abc")
test("❌")
test(")
test("☾test")
test("‍‍‍)
test("\u{200d}\u{200d}\u{200d})
test(")
test("\u{1F468}")
test("‍♀️‍♂️)
test("你好吗")
test("مرحبا", "Arabic word")
test("م", "Arabic letter")
test("שלום", "Hebrew word")
test("ם", "Hebrew letter")

func test(_ s: String, _ description: String? = nil) {
func icon(for length: Int) -> String {
return length == (s as NSString).length ? "✅" : "❌"
}

let description = description ?? "'" + s + "'"
let string = String(
format: format,
(s as NSString).length,
s.utf16.count, icon(for: s.utf16.count),
s.endIndex.encodedOffset, icon(for: s.endIndex.encodedOffset),
NSRange(s.startIndex..<s.endIndex, in: s).upperBound, icon(for: NSRange(s.startIndex..<s.endIndex, in: s).upperBound),
s.count, icon(for: s.count),
s.characters.count, icon(for: s.characters.count),
s.distance(from: s.startIndex, to: s.endIndex), icon(for: s.distance(from: s.startIndex, to: s.endIndex)),
s.unicodeScalars.count, icon(for: s.unicodeScalars.count),
s.utf8.count, icon(for: s.utf8.count),
description)
print(string)
}

And here is the output:

NSString   .utf16❔   encodedOffset❔   NSRange❔   .count❔   .characters❔   distance❔   .unicodeScalars❔   .utf8❔   Description
0 0 ✅ 0 ✅ 0 ✅ 0 ✅ 0 ✅ 0 ✅ 0 ✅ 0 ✅ ''
3 3 ✅ 3 ✅ 3 ✅ 3 ✅ 3 ✅ 3 ✅ 3 ✅ 3 ✅ 'abc'
1 1 ✅ 1 ✅ 1 ✅ 1 ✅ 1 ✅ 1 ✅ 1 ✅ 3 ❌ '❌'
4 4 ✅ 4 ✅ 4 ✅ 1 ❌ 1 ❌ 1 ❌ 2 ❌ 8 ❌ ''
5 5 ✅ 5 ✅ 5 ✅ 5 ✅ 5 ✅ 5 ✅ 5 ✅ 7 ❌ '☾test'
11 11 ✅ 11 ✅ 11 ✅ 1 ❌ 1 ❌ 1 ❌ 7 ❌ 25 ❌ '‍‍‍'
11 11 ✅ 11 ✅ 11 ✅ 1 ❌ 1 ❌ 1 ❌ 7 ❌ 25 ❌ '‍‍‍'
8 8 ✅ 8 ✅ 8 ✅ 4 ❌ 4 ❌ 4 ❌ 4 ❌ 16 ❌ ''
2 2 ✅ 2 ✅ 2 ✅ 1 ❌ 1 ❌ 1 ❌ 1 ❌ 4 ❌ ''
58 58 ✅ 58 ✅ 58 ✅ 13 ❌ 13 ❌ 13 ❌ 32 ❌ 122 ❌ '‍♀️‍♂️'
3 3 ✅ 3 ✅ 3 ✅ 3 ✅ 3 ✅ 3 ✅ 3 ✅ 9 ❌ '你好吗'
5 5 ✅ 5 ✅ 5 ✅ 5 ✅ 5 ✅ 5 ✅ 5 ✅ 10 ❌ Arabic word
1 1 ✅ 1 ✅ 1 ✅ 1 ✅ 1 ✅ 1 ✅ 1 ✅ 2 ❌ Arabic letter
4 4 ✅ 4 ✅ 4 ✅ 4 ✅ 4 ✅ 4 ✅ 4 ✅ 8 ❌ Hebrew word
1 1 ✅ 1 ✅ 1 ✅ 1 ✅ 1 ✅ 1 ✅ 1 ✅ 2 ❌ Hebrew letter

Conclusions:

  • To get a length that is compatible with NSString/NSRange, use either (s as NSString).length, s.utf16.count (preferred), s.endIndex.encodedOffset, or NSRange(s.startIndex..<s.endIndex, in: s).
  • To get the number of visible characters, use either s.count (preferred), s.characters.count (deprecated), or s.distance(from: s.startIndex, to: s.endIndex)

A helpful extension to get the full range of a String:

public extension String {

var nsrange: NSRange {
return NSRange(startIndex..<endIndex, in: self)
}
}

Thus, you can call the original method like so:

replace("‍‍‍, characterAtIndex: "‍‍‍.utf16.count - 1) // ‍‍‍�!

how can i convert NSString to String


let myNSString: NSString = "I'm iOSDev"
let myString: String = myNSString as String
print(myString)
print(myString.isEmpty)

Casting Refer to myNSString as String

Also, You don't need to cast textField text as NSString it's by default String
So, Just use

var myString: String = myTextField.text ?? ""

I Couldn't comment so Posting as Answer

How do I bridge objc typedef NSString to Swift as String?


The answer is to use NS_SWIFT_BRIDGED_TYPEDEF

As the title of the question:

How do I bridge objc typedef NSString to Swift as String?

I found NS_SWIFT_BRIDGED_TYPEDEF, use that for an ObjC typedef of NSString, then the type will be bridged as String to Swift.

Example

Without NS_SWIFT_BRIDGED_TYPEDEF

typedef NSString * ObjcNSString;

@interface ObjC : NSObject
+ (void)doThingsWithString:(nullable ObjcNSString)mid;
@end

The corresponding Swift interface is:

public typealias ObjcNSString = NSString

open class ObjC : NSObject {

open class func doThings(with mid: String?)
}

And that will give me error when I do this:

let str: ObjcNSString? = nil
ObjC.doThings(with: str) // Cannot convert value of type 'ObjcNSString?' (aka 'Optional<NSString>') to expected argument type 'String?'

With NS_SWIFT_BRIDGED_TYPEDEF

Add NS_SWIFT_BRIDGED_TYPEDEF for the ObjC typedef:

typedef NSString * ObjcNSString NS_SWIFT_BRIDGED_TYPEDEF;

@interface ObjC : NSObject
+ (void)doThingsWithString:(nullable ObjcNSString)mid;
@end

Then the Swift interface becomes:

public typealias ObjcNSString = String

open class ObjC : NSObject {

open class func doThings(with mid: ObjcNSString?)
}

And the same code compiles:

let str: ObjcNSString? = nil
ObjC.doThings(with: str)

So I achieved my goal:

But I really want to use the ObjcNSString type in Swift


If you look close to the difference between the Swift interfaces with or without NS_SWIFT_BRIDGED_TYPEDEF, you'll see by adding NS_SWIFT_BRIDGED_TYPEDEF we get a more desired Swift interface.



Related Topics



Leave a reply



Submit