stringByReplacingOccurrencesOfString is not working? - objective-c

I am trying to translate this Objective-C code :
if ([[self.appStoreCountry stringByReplacingOccurrencesOfString:#"[A-Za-z]{2}" withString:#"" options:NSRegularExpressionSearch range:NSMakeRange(0, 2)] length])
So I tried to wrote this code :
if !self.appStoreCountry.stringByReplacingOccurrencesOfString("[A-Za-z]{2}", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: 0, end: 2)).isEmpty
The problem is that the stringByReplacingOccurrencesOfString part does not seem to return a string. I have the error message from xcode : Cannot convert the expression's type 'Bool' to type 'String'.
I investigated a little more and tried :
let replaced = self.appStoreCountry.stringByReplacingOccurrencesOfString("[A-Za-z]{2}", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: 0, end: 2))
The error message is similar but even more weird : Cannot convert the expression's type 'String' to type 'String'.
Am I doing something wrong or is there a bug I should submit to Apple?

Keep in mind that Foundation's types are different from Swift's types. A String in Swift is not an NSString, and a Range is not an NSRange. This does work:
let country = self.appStoreCountry // if self.appStoreCountry is an NSString
let country: NSString = self.appStoreCountry // if self.appStoreCountry is a Swift string
let replaced = country.stringByReplacingOccurrencesOfString("[A-Za-z]{2}", withString: "", options: .RegularExpressionSearch, range: NSMakeRange(0, 2))
Also note the short-hand notation for .RegularExpressionSearch, which makes using enums in Swift a bit easier.

extension String{
func exclude(find:String) -> String {
return stringByReplacingOccurrencesOfString(find, withString: "", options: .LiteralSearch, range: nil)
}
func replaceAll(find:String, with:String) -> String {
return stringByReplacingOccurrencesOfString(find, withString: with, options: .LiteralSearch, range: nil)
}
}
"1-2-3-4-5-6-7-8-9-0".exclude("-") // "1234567890"
"1-2-3-4-5-6-7-8-9-0".replaceAll("-", with: "+") // "1+2+3+4+5+6+7+8+9+0"

Try This code:
var originalString="Hello World"
var needToReplaceString = "Hello"
var replaceString = " Hi"
var newString = originalString.stringByReplacingOccurrencesOfString(needToReplaceString, withString: replaceString, options: nil, range: nil)
println(newString)

Open a Playground and add this code, now it's correct:
var str = NSString(CString: "en")
str = str.stringByReplacingOccurrencesOfString("[A-Za-z]{2}", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range: NSRange(location: 0, length: 2))
if str == ""
{
var a = "true"
} else {
var b = "false"
}
Your problems were:
.isEmpty is not a method of NSString
You were using Range() and not NSRange as required by the method .stringByReplacingOccurrencesOfString of NSString
since your replace returns an empty string and not a nil object, you have to test for "" in the if statement

let maskCharSet = NSCharacterSet(charactersInString: " ()-.")
let cleanedString = phoneNumber.componentsSeparatedByCharactersInSet(maskCharSet).reduce("", combine: +)

Related

Swift Cannot convert value of type CBPeripheral to expected argument type

The code I have:
func didDiscoverBLE(_ peripheral: CBPeripheral!, address: String!, rssi: Int32) {
DispatchQueue.main.async(execute: {() -> Void in
// Handle Discovery
self.arrayPeripehral.contains(where:peripheral)
return
})
self.arrayPeripehral.append(peripheral)
let title: String = "\(peripheral.name) \(address) (RSSI:\(rssi))"
self.arrayPeripheralName.append(title)
In this line i have a problem:
self.arrayPeripehral.contains(where:peripheral)
return
})
Has anyone an idea?
Here is the code I copied from the obective c to the swift and got stuck on this error
- (void)didDiscoverBLE:(CBPeripheral *)peripheral address:(NSString *)address rssi:(int)rssi
{
dispatch_async(dispatch_get_main_queue(), ^{
// Handle Discovery
if([arrayPeripehral containsObject:peripheral])
return;
[arrayPeripehral addObject:peripheral];
NSString * title = [NSString stringWithFormat:#"%# %# (RSSI:%d)", peripheral.name, address, rssi];
[arrayPeripheralName addObject:title];
Change the type of arrayPeripehral to [CBPeripheral] from [Any] that will give compiler more idea about its type then use contains(where:) like this to check array contains object or not.
var arrayPeripehral = [CBPeripheral]()
Now use contains(where:) this way to check array contains object or not.
if self.arrayPeripehral.contains(where: { $0.name == peripheral.name }) {
return
}
Also change type declaration of arrayPeripheralName to [String] from [Any] as of you are appending only String object in it.
var arrayPeripheralName = [String]()

Swift equivalent to Objective-C FourCharCode single quote literals (e.g. 'TEXT')

I am trying replicate some Objective C cocoa in Swift. All is good until I come across the following:
// Set a new type and creator:
unsigned long type = 'TEXT';
unsigned long creator = 'pdos';
How can I create Int64s (or the correct Swift equivalent) from single quote character literals like this?
Types:
public typealias AEKeyword = FourCharCode
public typealias OSType = FourCharCode
public typealias FourCharCode = UInt32
I'm using this in my Cocoa Scripting apps, it considers characters > 0x80 correctly
func OSTypeFrom(string : String) -> UInt {
var result : UInt = 0
if let data = string.dataUsingEncoding(NSMacOSRomanStringEncoding) {
let bytes = UnsafePointer<UInt8>(data.bytes)
for i in 0..<data.length {
result = result << 8 + UInt(bytes[i])
}
}
return result
}
Edit:
Alternatively
func fourCharCodeFrom(string : String) -> FourCharCode
{
assert(string.count == 4, "String length must be 4")
var result : FourCharCode = 0
for char in string.utf16 {
result = (result << 8) + FourCharCode(char)
}
return result
}
or still swiftier
func fourCharCode(from string : String) -> FourCharCode
{
return string.utf16.reduce(0, {$0 << 8 + FourCharCode($1)})
}
I found the following typealiases from the Swift API:
typealias FourCharCode = UInt32
typealias OSType = FourCharCode
And the following functions:
func NSFileTypeForHFSTypeCode(hfsFileTypeCode: OSType) -> String!
func NSHFSTypeCodeFromFileType(fileTypeString: String!) -> OSType
This should allow me to create the equivalent code:
let type : UInt32 = UInt32(NSHFSTypeCodeFromFileType("TEXT"))
let creator : UInt32 = UInt32(NSHFSTypeCodeFromFileType("pdos"))
But those 4-character strings doesn't work and return 0.
If you wrap each string in ' single quotes ' and call the same functions, you will get the correct return values:
let type : UInt32 = UInt32(NSHFSTypeCodeFromFileType("'TEXT'"))
let creator : UInt32 = UInt32(NSHFSTypeCodeFromFileType("'pdos'"))
Adopt the ExpressibleByStringLiteral protocol to use four-character string literals directly:
extension FourCharCode: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
if let data = value.data(using: .macOSRoman), data.count == 4 {
self = data.reduce(0, {$0 << 8 + Self($1)})
} else {
self = 0
}
}
}
Now you can just pass a string literal as the FourCharCode / OSType / UInt32 parameter:
let record = NSAppleEventDescriptor.record()
record.setDescriptor(NSAppleEventDescriptor(boolean: true), forKeyword: "test")
In Swift 4 or later, I use this code - if the string is not 4 characters in size, it will return an OSType(0):
extension String {
public func osType() -> OSType {
var result:UInt = 0
if let data = self.data(using: .macOSRoman), data.count == 4
{
data.withUnsafeBytes { (ptr:UnsafePointer<UInt8>) in
for i in 0..<data.count {
result = result << 8 + UInt(ptr[i])
}
}
}
return OSType(result)
}
}
let type = "APPL".osType() // 1095782476
// check if this is OK in a playground
let hexStr = String(format: "0x%lx", type) // 0x4150504c -> "APPL" in ASCII
Swift 5 Update:
extension String {
func osType() -> OSType {
return OSType(
data(using: .macOSRoman)?
.withUnsafeBytes {
$0.reduce(into: UInt(0)) { $0 = $0 << 8 + UInt($1) }
} ?? 0
)
}
}
Here's a simple function
func mbcc(foo: String) -> Int
{
let chars = foo.utf8
var result: Int = 0
for aChar in chars
{
result = result << 8 + Int(aChar)
}
return result
}
let a = mbcc("TEXT")
print(String(format: "0x%lx", a)) // Prints 0x54455854
It will work for strings that will fit in an Int. Once they get longer it starts losing digits from the top.
If you use
result = result * 256 + Int(aChar)
you should get a crash when the string gets too big instead.
Using NSHFSTypeCodeFromFileType does work, but only for 4-character strings wrapped with single quotes, aka 6-character strings. It returns 0 for unquoted 4-character strings.
So wrap your 4-character string in ' ' before passing it to the function:
extension FourCharCode: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
switch (value.count, value.first, value.last) {
case (6, "'", "'"):
self = NSHFSTypeCodeFromFileType(value)
case (4, _, _):
self = NSHFSTypeCodeFromFileType("'\(value)'")
default:
self = 0
}
}
}
Using the above extension, you can use 4-character or single-quoted 6-character string literals:
let record = NSAppleEventDescriptor.record()
record.setDescriptor(NSAppleEventDescriptor(boolean: true), forKeyword: "4444")
record.setDescriptor(NSAppleEventDescriptor(boolean: true), forKeyword: "'6666'")
It would be even better to limit the string literal to 4-character strings at compile time. That does not seem to currently be possible, but is being discussed for Swift here:
Allow for Compile-Time Checked Intervals for Parameters Expecting Literal Values

Calculator in Swift a method [rangeOfString]

I am making calculator in Swift for IOS 8. All operand and operator works very well. Only problem is with "."
For example, 192.168.0.1 is invalid. The "." should only display once. Instead 192.16801 is valid.
See below code -
//Assume user has entered 192.168
//User cannot press "." button again now, "." should not appear again
var display.text 192.168`
if display.text.rangeOfString(".") != nil{
println("exists")
}
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.textField.delegate = self
}
//Textfield delegates
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { // return NO to not change text
switch string {
case "0","1","2","3","4","5","6","7","8","9":
return true
case ".":
let array = Array(textField.text)
var decimalCount = 0
for character in array {
if character == "." {
decimalCount++
}
}
if decimalCount == 1 {
return false
} else {
return true
}
default:
let array = Array(string)
if array.count == 0 {
return true
}
return false
}
}
}
the textField functions will return a value of true if you have entered a valid number, it will return a value of false if you input anything else than a number or if you enter a second “.”. If this functions returns false it will not change the value of the text field. Example: if a user (tries to) input 3.14a9 the a will be ignored and the textfield text will be 3.149. If a user inputs 3.45.12 the textfield will have a text value of 3.45.12
I’m not sure this is the question, but if you’re looking for a second “.” you can use the find function with a where for this:
let decimal: Character = "."
if let first = find(display.text, decimal)
where (find(display.text[first.successor()..<display.text.endIndex], decimal) != nil) {
println("second exists")
}
else {
println("none or one")
}
You can use regular expression to validate your textfield whenever its value changes.
Example of a regulator
/*
^ //begin string
([0-9]+)? //optional: has 0 or more digits
(\\.)? //optional: has a point
([0-9]+)? //optional: has 0 or more digits
$ //end string
*/
extension String {
func isValidNumber() -> Bool {
let regex = NSRegularExpression(pattern: "^([0-9]+)?(\\.)?([0-9]+)?$", options: .CaseInsensitive, error: nil)
return regex?.firstMatchInString(self, options: nil, range: NSMakeRange(0, countElements(self))) != nil
}
}
Used in the call-back of your UITextField:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return textField.text.isValidNumber()
}
var text = "192.168.2"
var isValid = true
let range = text.rangeOfString(".")
if let range = range {
let subText = text.substringFromIndex(range.endIndex)
let range2 = subText.rangeOfString(".")
if range2 != nil {
isValid = false
}
}
println("isValid: \(isValid)") // isValid: false

Saving Arrays in Swift

I have a question about saving Arrays in Apple's new programming language Swift. In Objective-C I saved data with NSFileManager... but this doesn't work anymore in Swift. So I wanted to ask how I should save an array WITHOUT using NSUserDefaults which isn't really suited for storing a big amount of data. I would really much appreciate any help :]
First (if your array is not of string type) change it to String:
var notStringArray = [1, 2, 3, 4]
var array: [String] = []
for value in notStringArray{
array.append(String(value))
}
Then reduce the array to one string:
var array = ["1", "2", "3", "4", "5"] //Ignore this line if your array wasn't of type String and you did the step above
var stringFromArray = reduce(array, "") { $0.isEmpty ? $1 : "\($0)\n\($1)" }
This create an string that looks like this:
"1
2
3
4
5"
And then to write and read a file add this class at the top of your file:
class File {
class func open (path: String, utf8: NSStringEncoding = NSUTF8StringEncoding) -> String? {
var error: NSError? //
return NSFileManager().fileExistsAtPath(path) ? String(contentsOfFile: path, encoding: utf8, error: &error)! : nil
}
class func save (path: String, fileContent: String, utf8: NSStringEncoding = NSUTF8StringEncoding) -> Bool {
var error: NSError? //
return fileContent.writeToFile(path, atomically: true, encoding: utf8, error: &error)
}
}
(Don't forget to import UIKit)
To save to a file:
let didSave = File.save("DirectoryOfFile", content: stringFromArray)
if didSave {
println("file saved")
} else {
println("error saving file")
}
To get it back:
var stringFromFile = ""
if let loadData = File.open("DirectoryOfFile") {
stringFromFile = loadData
} else {
println("error reading file")
}
To put it back in an array:
var newArray: [String] = [] //Creates empty string array
newArray = stringFromFile.componentsSeparatedByString("\n")
And there you have it

Accessing temp directory in Swift

I was trying to access temp directory in Swift. In Objective-C, I could use the following code to do so:
- (NSString *)tempDirectory {
NSString *tempDirectoryTemplate =
[NSTemporaryDirectory() stringByAppendingPathComponent:#"XXXXX"];
const char *tempDirectoryTemplateCString = [tempDirectoryTemplate fileSystemRepresentation];
char *tempDirectoryNameCString = (char *)malloc(strlen(tempDirectoryTemplateCString) + 1);
strcpy(tempDirectoryNameCString, tempDirectoryTemplateCString);
char *result = mkdtemp(tempDirectoryNameCString);
if (!result) {
return nil;
}
NSString *tempDirectoryPath = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempDirectoryNameCString length:strlen(result)];
free(tempDirectoryNameCString);
return tempDirectoryPath;
}
However, I'm a bit confuse about the type conversion and casting from Objective-C to Swift, such as const char * or CMutablePointer<CChar>. Is there any documents that I should look into?
Thanks.
How about something like :
public extension FileManager {
func createTempDirectory() throws -> String {
let tempDirectory = (NSTemporaryDirectory() as NSString).appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(atPath: tempDirectory,
withIntermediateDirectories: true,
attributes: nil)
return tempDirectory
}
}
It doesn't answer your question about char* but it's cleaner...
NSFileManager reference here.
Also check out this SO question regarding unique names.
According to Apple, use of NSTemporaryDirectory is discouraged:
See the FileManager method url(for:in:appropriateFor:create:) for the
preferred means of finding the correct temporary directory. For more
information about temporary files, see File System Programming Guide.
So instead, you should use FileManager.default.temporaryDirectory
or if you want an unique path:
let extractionPath = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
Swift 2.1 version:
func createTempDirectory() -> String? {
let tempDirURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("XXXXXX")
do {
try NSFileManager.defaultManager().createDirectoryAtURL(tempDirURL, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
return tempDirURL.absoluteString
}
Swift 3 and up
I think a good way to do this in swift is with an extension on FileManager. This should create a unique temporary folder and return the URL to you.
extension FileManager{
func createTemporaryDirectory() throws -> URL {
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
try createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
return url
}
}
Swift 3 version
func createTempDirectory() -> String? {
guard let tempDirURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("myTempFile.xxx") else {
return nil
}
do {
try FileManager.default.createDirectory(at: tempDirURL, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
return tempDirURL.absoluteString
}
A direct translation of your Objective-C code to Swift would be:
func tempDirectory()->String! {
let tempDirectoryTemplate = NSTemporaryDirectory() + "XXXXX"
var tempDirectoryTemplateCString = tempDirectoryTemplate.fileSystemRepresentation().copy()
let result : CString = reinterpretCast(mkdtemp(&tempDirectoryTemplateCString))
if !result {
return nil
}
let fm = NSFileManager.defaultManager()
let tempDirectoryPath = fm.stringWithFileSystemRepresentation(result, length: Int(strlen(result)))
return tempDirectoryPath
}
It uses the same mkdtemp() BSD method as your original code. This method creates
a directory name from the template which is guaranteed not to exist at the time where
the method is called.
Thanks to Nate Cook who figured out that reinterpretCast() can be used to treat the UnsafePointer<CChar> returned by mkdtemp() as a CString, so that it can be passed to stringWithFileSystemRepresentation(), see Working with C strings in Swift, or: How to convert UnsafePointer<CChar> to CString.
As of Xcode 6 beta 6, the reinterpretCast() is not necessary anymore and the
above code can be simplified to
func tempDirectory()->String! {
let tempDirectoryTemplate = NSTemporaryDirectory() + "XXXXX"
var tempDirectoryTemplateCString = tempDirectoryTemplate.fileSystemRepresentation()
let result = mkdtemp(&tempDirectoryTemplateCString)
if result == nil {
return nil
}
let fm = NSFileManager.defaultManager()
let tempDirectoryPath = fm.stringWithFileSystemRepresentation(result, length: Int(strlen(result)))
return tempDirectoryPath
}