Swift: CFArray : get values as UTF Strings - string

I call some functions that return a CFArray of CFStringRef values. I need to get utf strings from them. As I didn't want to make my code too complicated, I did the following:
let initString = "\(TISCreateInputSourceList(nil, false).takeUnretainedValue())"
And then I just split the resulting string by \ns to get an array of Swift strings. However, when the function started returning non-ascii strings, trouble started. I started getting strings like "\U2345\U2344".
Then i tried to take the CFArray and iterate over it getting the values and possibly converting them to Strings, but I can't get the values from it:
let ar = TISCreateInputSourceList(nil, true).takeUnretainedValue()
for i in 0...CFArrayGetCount(ar) - 1 {
print(">> ( CFArrayGetValueAtIndex(ar, i).memory )")
}
The values are always empty.
How can i get the actual values?

There are some issues here. First, TISCreateInputSourceList()
has "Create" in its name which means that it returns a (+1) retained
object and you have to take the value with takeRetainedValue(),
not takeUnretainedValue(), otherwise the code will leak memory:
let srcs = TISCreateInputSourceList(nil, true).takeRetainedValue()
You could now use the CFArray... methods to get values from the array,
but it is much easier to convert it to a NSArray (which is "toll-free bridged"):
let srcs = TISCreateInputSourceList(nil, true).takeRetainedValue() as NSArray
This is not an array of CFStringRef values but an array of
TISInputSource objects. You can convert the NSArray to a Swift array:
let srcs = TISCreateInputSourceList(nil, true).takeRetainedValue()
as NSArray as! [TISInputSource]
The forced cast as! is acceptable here because the function is
documented to return an array of input sources.
Now you can simply iterate over the elements of the array:
for src in srcs {
// do something with `src` (which is a `TISInputSource`)
}
The properties of an input source are retrieved with the TISGetInputSourceProperty() function, for example:
let ptr = TISGetInputSourceProperty(src, kTISPropertyInputSourceID)
This returns a "void pointer" (UnsafeMutablePointer<Void>) which has to be converted to an object
pointer of the appropriate type (which is CFStringRef for the
kTISPropertyInputSourceID property). Unfortunately, this is a bit
complicated (compare How to cast self to UnsafeMutablePointer<Void> type in swift):
let val = Unmanaged<CFString>.fromOpaque(COpaquePointer(ptr)).takeUnretainedValue()
Again we can take advantage of toll-free bridging, now from
CFStringRef to NSString and String:
let val = Unmanaged<CFString>.fromOpaque(COpaquePointer(ptr)).takeUnretainedValue()
as String
Putting it all together:
let srcs = TISCreateInputSourceList(nil, true).takeRetainedValue()
as NSArray as! [TISInputSource]
for src in srcs {
let ptr = TISGetInputSourceProperty(src, kTISPropertyInputSourceID)
let val = Unmanaged<CFString>.fromOpaque(COpaquePointer(ptr)).takeUnretainedValue()
as String
print(val)
}

Related

Swift - best practice to find the longest string at [String] array

I'm trying to find what is the most effective way to get the longest string in a string array. For example :
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
and the outcome will be - "Game Of Thrones is just good"
I've tried using the maxElement func, tho it's give the max string in a alphabetic ideas(maxElement()).
Any suggestions? Thanks!
Instead of sorting which is O(n log(n)) for a good sort, use max(by:) which is O(n) on Array providing it a closure to compare string lengths:
Swift 4:
For Swift 4 you can get the string length with the count property on String:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
if let max = array.max(by: {$1.count > $0.count}) {
print(max)
}
Swift 3:
Use .characters.count on String to get the string lengths:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
if let max = array.max(by: {$1.characters.count > $0.characters.count}) {
print(max)
}
Swift 2:
Use maxElement on Array providing it a closure to compare string lengths:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
if let max = array.maxElement({$1.characters.count > $0.characters.count}) {
print(max)
}
Note: maxElement is O(n). A good sort is O(n log(n)), so for large arrays, this will be much faster than sorting.
You can use reduce to do this. It will iterate through your array, keeping track of the current longest string, and then return it when finished.
For example:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
if let longestString = array.reduce(Optional<String>.None, combine:{$0?.characters.count > $1.characters.count ? $0:$1}) {
print(longestString) // "Game Of Thrones is just good"
}
(Note that Optional.None is now Optional.none in Swift 3)
This uses an nil starting value to account for the fact that the array could be empty, as pointed out by #JHZ (it will return nil in that case). If you know your array has at least one element, you can simplify it to:
let longestString = array.reduce("") {$0.characters.count > $1.characters.count ? $0:$1}
Because it only iterates through each element once, it will quicker than using sort(). I did a quick benchmark and sort() appears around 20x slower (although no point in premature optimisation, I feel it is worth mentioning).
Edit: I recommend you go with #vacawama's solution as it's even cleaner than reduce!
Here you go:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
var sortedArr = array.sort() { $0.characters.count > $1.characters.count }
let longestEelement = sortedArr[0]
You can also practice with the use of Generics by creating this function:
func longestString<T:Sequence>(from stringsArray: T) -> String where T.Iterator.Element == String{
return (stringsArray.max {$0.count < $1.count}) ?? ""
}
Explanation: Create a function named longestString. Declar that there is a generic type T that implements the Sequence protocol (Sequence is defined here: https://developer.apple.com/documentation/swift/sequence). The function will return a single String (of course, the longest). The where clause explains that the generic type T should be limited to having elements of type String.
Inside the function, call the max function of the stringsArray by comparing the longest string of the elements inside. What will be returned is the longest String (an optional as it can be nil if the array is empty). If the longest string is nil then (use of ??) returns an empty string as the longest string instead.
Now call it:
let longestA = longestString(from:["Shekinah", "Chesedh", "Agape Sophia"])
If you get the hang of using generics, even if the strings are hidden inside objects, you can make use of the pattern of coding above. You can change the element to objects of the same class (Person for example).
Thus:
class Person {
let name: String
init(name: String){
self.name = name
}
}
func longestName<T:Sequence>(from stringsArray: T) -> String where T.Iterator.Element == Person{
return (stringsArray.max {$0.name.count < $1.name.count})?.name ?? ""
}
Then call the function like these:
let longestB = longestName(from:[Person(name: "Shekinah"), Person(name: "Chesedh"), Person(name: "Agape Sophia")])
You also get to rename your function based on the appropriateness of its use. You can tweak the pattern to return something else, like the object itself, or the length (count) of the String. And finally, becoming familiar with generics may improve your coding ability.
Now, with a little tweak again, you may extend further so that you can compare strings owned by many different types as long as they implement a common protocol.
protocol Nameable {
var name: String {get}
}
This defines a protocol named Nameable that requires those who implement to have a name variable of type String. Next, we define two different things that both implement the protocol.
class Person: Nameable {
let name: String
init(name: String){
self.name = name
}
}
struct Pet: Nameable {
let name: String
}
Then we tweak our generic function so that it requires that the elements must conform to Nameable, vastly different though they are.
func longestName<T:Sequence>(from stringsArray: T) -> String where T.Iterator.Element == Nameable{
return (stringsArray.max {$0.name.count < $1.name.count})?.name ?? ""
}
Let's collect the different objects into an array. Then call our function.
let myFriends: [Nameable] = [Pet(name: "Bailey"), Person(name: "Agape Sophia")]
let longestC = longestName(from: myFriends)
Lastly, after knowing "where" above and "Sequence" above, you may simply extend Sequence:
extension Sequence where Iterator.Element == String {
func topString() -> String {
self.max(by: { $0.count < $1.count }) ?? ""
}
}
Or the protocol type:
extension Sequence where Iterator.Element == Nameable {
func theLongestName() -> Nameable? {
self.max(by: { $0.name.count < $1.name.count })
}
}

Cannot convert value of type 'Range<Int>' to expected argument type 'Range<Index>' (aka 'Range<String.CharacterView.Index>')

I have a string here, which I am trying to substring.
let desc = "Hello world. Hello World."
var stringRange = 1..<5
desc.substringWithRange(stringRange)
However Swift gives me an error with this. What have I done wrong? I am using the new notation of the stringRange because it doesn't let me use the old one.
The Range you have created does not have the correct type, it is inferred to be an Int. You need to create the range from the string itself:
let desc = "Hello world. Hello World."
let stringRange = desc.startIndex..<desc.startIndex.advancedBy(5)
let sub = desc[stringRange]
It's slightly more complex with String. Alternatively, go back to NSString and NSRange:
let range = NSMakeRange(0, 5)
let sub2 = (desc as NSString).substringWithRange(range)
Your 1..<5 is from type Range<Int>, while the method substringWithRange expects a value from type Range<Index>
let desc = "Hello world. Hello World."
var dd = desc.substringWithRange(desc.startIndex..<desc.startIndex.advancedBy(5))
You may apply advanceBy to the desc.startIndex as well

Convert String.Index to Int or Range<String.Index> to NSRange

So I've found issues relating to the case of converting NSRange to Range<String.Index>, but I've actually run into the opposite problem.
Quite simply, I have a String and a Range<String.Index> and need to convert the latter into an NSRange for use with an older function.
So far my only workaround has been to grab a substring instead like so:
func foo(theString: String, inRange: Range<String.Index>?) -> Bool {
let theSubString = (nil == inRange) ? theString : theString.substringWithRange(inRange!)
return olderFunction(theSubString, NSMakeRange(0, countElements(theSubString)))
}
This works of course, but it isn't very pretty, I'd much rather avoid having to grab a sub-string and just use the range itself somehow, is this possible?
If you look into the definition of String.Index you find:
struct Index : BidirectionalIndexType, Comparable, Reflectable {
/// Returns the next consecutive value after `self`.
///
/// Requires: the next value is representable.
func successor() -> String.Index
/// Returns the previous consecutive value before `self`.
///
/// Requires: the previous value is representable.
func predecessor() -> String.Index
/// Returns a mirror that reflects `self`.
func getMirror() -> MirrorType
}
So actually there is no way to convert it to Int and that for good reason. Depending on the encoding of the string the single characters occupy a different number of bytes. The only way would be to count how many successor operations are needed to reach the desired String.Index.
Edit The definition of String has changed over the various Swift versions but it's basically the same answer. To see the very current definition just CMD-click on a String definition in XCode to get to the root (works for other types as well).
The distanceTo is an extension which goes to a variety of protocols. Just look for it in the String source after the CMD-click.
let index: Int = string.startIndex.distanceTo(range.startIndex)
I don't know which version introduced it, but in Swift 4.2 you can easily convert between the two.
To convert Range<String.Index> to NSRange:
let range = s[s.startIndex..<s.endIndex]
let nsRange = NSRange(range, in: s)
To convert NSRange to Range<String.Index>:
let nsRange = NSMakeRange(0, 4)
let range = Range(nsRange, in: s)
Keep in mind that NSRange is UTF-16 based, while Range<String.Index> is Character based.
Hence you can't just use counts and positions to convert between the two!
In Swift 4, distanceTo() is deprecated. You may have to convert String to NSString to take advantage of its -[NSString rangeOfString:] method, which returns an NSRange.
Swift 4 Complete Solution:
OffsetIndexableCollection (String using Int Index)
https://github.com/frogcjn/OffsetIndexableCollection-String-Int-Indexable-
let a = "01234"
print(a[0]) // 0
print(a[0...4]) // 01234
print(a[...]) // 01234
print(a[..<2]) // 01
print(a[...2]) // 012
print(a[2...]) // 234
print(a[2...3]) // 23
print(a[2...2]) // 2
if let number = a.index(of: "1") {
print(number) // 1
print(a[number...]) // 1234
}
if let number = a.index(where: { $0 > "1" }) {
print(number) // 2
}
You can use this function and call it when ever you need convertion
extension String
{
func CnvIdxTooIntFnc(IdxPsgVal: Index) -> Int
{
return startIndex.distanceTo(IdxPsgVal)
}
}

Array of NSManagedObjects and reduce function compile but fail at runtime

Using playground to simulate a problem with a core data based app turned up an issue I can't seem to understand.
class Pole {
var length: NSNumber!
}
var poles: [Pole] = []
let pole1 = Pole()
pole1.length = 1
poles.append(pole1)
let pole2 = Pole()
pole2.length = 2
poles.append(pole2)
var sum = poles.reduce(0) { $0 + $1.length } // error Could not find member 'length'
The property (attribute) named length is NSNumber as it is in a NSManagedObject class (entity).
Changing the Type from NSNumber! to Int! allows the line to compile and run correctly in playground.
Leaving the the Type as NSNumber! and changing the the offending line as follows:
var sum = poles.reduce(0) { $0 + Int($1.length) }
also compiles and run correctly in playground. The next step, taking this to the app, using actual NSManagedObject entity and attribute compiles but fails at runtime. The failure is 'unwrapping a nil'.
So bottom line I can't figure out how to use the reduce function when the attribute is a NSNumber and casting it to an Int doesn't seem to be acceptable.
NSNumber is a Foundation class that is used to wrap numbers that would otherwise be represented in a value type, so they can be used wherever a reference type is expected. Swift bridges integer literals to NSNumber, but not the other way around, so the original reduce closure was trying to add an integer literal to an NSNumber, getting confused, and giving you a weird error message.
Creating a new Int is one way to handle the problem:
var sum = poles.reduce(0) { $0 + Int($1.length) }
Or you can use NSNumber's integerValue property, which returns the instance's value as an Int:
var sum = poles.reduce(0) { $0 + $1.length.integerValue }

How to convert NSString to Character in Swift?

I want to convert NSString to Character in Swift.
I am getting a String from NSTextField, where I input a single character (Example "#"), I need this in Character type.
Use Character class
var chars = [Character](str)
this should be as simple as let characterFromString = Character(textField.text).
NSString is automatically bridged to Swift's String, and the Character class has an init which accepts a single character String; see the documentation here.
This requires that the input string contain a single grapheme cluster (i.e. a single character) and so you might want to validate your input before casting. A slightly more verbose version of the above would be:
let text = textField.text as String
if countElements(text) == 1 {
let character = Character(text)
// do what you want
} else {
// bad input :-(
}
The stringValue of NSTextField actually returns a Swift string and not NSString:
let str = myTextField.stringValue // str is a String
You get the first element with
let ch = str[str.startIndex] // ch is a Character
A NSString would have to be converted to a Swift string first:
let nsString : NSString = "#"
let str = String(nsString)
let ch = str[str.startIndex]

Resources