I have a string "John+20", I would like to extract out "John", so, I tried following based on this answer:
// data contains value "John+20"
static func getName(fromString data: String?) {
guard let myData = data else {
return
}
let idx = myData.index(of: "+")
//Compiler ERROR: Generic parameter 'Self' could not be inferred
let name = String(myData[..<idx])
}
But I get the error I mentioned in code comment, why is that?
I am using Swift 4.1 in my iOS project.
I guess the index is an optional as well. Try :
// data contains value "John+20"
static func getName(fromString data: String?) {
guard let myData = data else, let idx = myData.index(of: "+") {
return
}
let name = String(myData[..<idx])
}
I keep trying to fix this error and trying to make the character contain in the row which will be an int.
func isRightTileAt(location:CGPoint) ->Bool {
//as shape node so we can get fill
var currentRect = self.atPoint(location) as! SKShapeNode
//get the 10th character which will contain the row and make it an int
// let rowOfNode = Int(currentRect.name![10]) //error(tried both of these)
var rowOfNode = Int(currentRect(name[10])) //error
//flip position is used for the row index below the screen to flip it to the top.
var currentRow = self.flipPosition + 1
var currentRowOfClick = self.flipPosition
//we reuse the flip position because it hasn't flipped yet but it normally contains the right row.
//because flip position happens after this check so it won't be sent back around yet
if self.flipPosition == 5 {
currentRowOfClick = 0
}
//if they are at least on the right row
if rowOfNode == currentRowOfClick && currentRect.fillColor.hash == 65536{
return true
}
return false
}
There are several challenges accessing the characters of the name property of SKNode or an SKNode subclass (such as SKShapeNode).
First, since name is a String?, it needs to be unwrapped.
guard let string = self.name else {
return
}
Second, you can't access the characters of a String with an Int subscript; you'll need to use a String.Index.
// Since Swift is zero based, the 10th element is at index 9; use 10 if you want the 11th character.
let index = string.index(string.startIndex, offsetBy: 9)
// The 10th character of the name
let char = string[index]
Third, you can't directly convert a Character to an Int. You'll need to convert the character to a String and then convert the string to an Int.
let rowString = String(char)
// Unwrap since Int(string:String) returns nil if the string is not an integer
guard let row = Int(rowString) else {
return
}
At this point, row is the 10th character of name converted to an Int.
Alternatively, you can implement the above as an extension
extension String {
func int(at index:Int) -> Int? {
let index = self.index(self.startIndex, offsetBy: index)
let string = String(self[index])
return Int(string)
}
}
and use it with
guard let name = self.name, let row = name.int(at:9) else {
return
}
First, I initialize the variables to hold the stock data
var applePrice: String?
var googlePrice: String?
var twitterPrice: String?
var teslaPrice: String?
var samsungPrice: String?
var stockPrices = [String]()
I fetch current stock prices from YQL, and put those values into an array
func stockFetcher() {
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
if let applePrice = json["query"]["results"]["quote"][0]["Ask"].string {
print(applePrice)
self.applePrice = applePrice
self.tableView.reloadData()
}
if let googlePrice = json["query"]["results"]["quote"][1]["Ask"].string {
print(googlePrice)
self.googlePrice = googlePrice
self.tableView.reloadData()
}
if let twitterPrice = json["query"]["results"]["quote"][2]["Ask"].string {
print(twitterPrice)
self.twitterPrice = twitterPrice
self.tableView.reloadData()
}
if let teslaPrice = json["query"]["results"]["quote"][3]["Ask"].string {
print(teslaPrice)
self.teslaPrice = teslaPrice
self.tableView.reloadData()
}
if let samsungPrice = json["query"]["results"]["quote"][4]["Ask"].string {
print(samsungPrice)
self.samsungPrice = samsungPrice
self.tableView.reloadData()
}
let stockPrices = ["\(self.applePrice)", "\(self.googlePrice)", "\(self.twitterPrice)", "\(self.teslaPrice)", "\(self.samsungPrice)"]
self.stockPrices = stockPrices
print(json)
}
}
}
in cellForRowAt indexPath function I print to the label
if self.stockPrices.count > indexPath.row + 1 {
cell.detailTextLabel?.text = "Current Stock Price: \(self.stockPrices[indexPath.row])" ?? "Fetching stock prices..."
} else {
cell.detailTextLabel?.text = "No data found"
}
I'm running into the issue of printing Current Stock Price: Optional("stock price"), with the word optional. I gather that this is because I'm giving it an array of optional values, but I sort of have to since I actually don't know if there will be data coming from YQL, one of the 5 stocks might be nil while the others have data. From reading other similar questions I can see that the solution would be to unwrap the value with !, but I'm not so sure how to implement that solution when it's an array with data that might be nil, and not just an Int or something.
How can I safely unwrap here and get rid of the word Optional?
First off:
Any time you repeat the same block of code multiple times and only increase a value from 0 to some max, it is a code smell. You should think about a different way to handle it.
You should use an array to do this processing.
How about a set of enums for indexes:
enum companyIndexes: Int {
case apple
case google
case twitter
case tesla
//etc...
}
Now you can run through your array with a loop and install your values more cleanly:
var stockPrices = [String?]()
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
let pricesArray = json["query"]["results"]["quote"]
for aPriceEntry in pricesArray {
let priceString = aPriceEntry["ask"].string
stockPrices.append(priceString)
}
}
}
And to fetch a price from the array:
let applePrice = stockPrices[companyIndexes.apple.rawValue]
That will result in an optional.
You could use the nil coalescing operator (??) to replace a nil value with a string like "No price available.":
let applePrice = stockPrices[companyIndexes.apple.rawValue] ?? "No price available"
or as shown in the other answer:
if let applePrice = stockPrices[companyIndexes.apple.rawValue] {
//we got a valid price
} else
//We don't have a price for that entry
}
I'm writing this outside of Xcode (so there might be typos), but this kind of logic should work.
if self.stockPrices.count > indexPath.row + 1 {
var txt = "Fetching stock prices..."
if let price = self.stockPrices[indexPath.row] {
txt = price
}
cell.detailTextLabel?.text = txt
} else {
cell.detailTextLabel?.text = "No data found"
}
For safe unwrap use that code:
if let currentStockPrice = self.stockPrices[indexPath.row]
{
// currentStockPrice available there
}
// currentStockPrice unavailable
If you need to unwrap multiple variables in one if after another it may lead to unreadable code. In such case use this pattern
guard let currentStockPrice = self.stockPrices[indexPath.row]
else
{
// currentStockPrice is unavailable there
// must escape via return, continue, break etc.
}
// currentStockPrice is available
I have buttons that when pressed, will call/message a number from an array. i.e. button1 will call the number at index 0 of the array, button2 at index 1, etc.. For some reason whenever the number from the array contains a format other than xxx-xxx-xxx it crashes (i.e. (xxx) xxx-xxx). And yet, the log gives me the following error even though the array isn't nil:
Anyone know why this is happening?
Here is the code for everything:
import UIKit
import AddressBook
var contactInfo: [String] = []
[...]
override func viewDidLoad() {
super.viewDidLoad()
//this is the function that grabs the array from an app group
setUpCallMessageButtons()
[...]
callButton1.addTarget(self, action: "call:", forControlEvents: UIControlEvents.TouchUpInside)
}
func call(sender:UIButton!)
{
if (sender == callButton1) {
println("\(contactInfo)")
var url:NSURL? = NSURL(string: "tel:\(contactInfo[0])")
self.extensionContext?.openURL(url!, completionHandler:{(success: Bool) -> Void in
})
}
}
func setUpCallMessageButtons(){
let appGroupID = "**redacted**"
let defaults = NSUserDefaults(suiteName: appGroupID)
contactInfo = (defaults!.objectForKey("contactInfo") as! [String])
println("\(contactInfo)")
//This is gives the log down below. As you can see, none are nil.
}
Buttons 1,2 and 5 work while 3 and 4 always crash.
My guess is that if the phone number isn't formatted correctly, the call to convert it to an NSURL is failing and returning nil.
You probably need to wrap your call to openURL in an optional binding ("if let") block:
var url:NSURL? = NSURL(string: "tel:\(contactInfo[0])")
if let url = url
{
self.extensionContext?.openURL(url!,
completionHandler:
{
(success: Bool) -> Void in
}
}
else
{
println("Phone number \(contactInfo[0]) is not in a valid format")
}
You might want to strip away parenthesis from your phone number before trying to create your URL. A simple way would be to use the NSString method stringByReplacingOccurrencesOfString:withString:.
Here's a little storyboard - which shows you where the nil is coming from
Unexpectedly found nil means there is a variable which is expected to be non-nil but at run time was nil
This is the line of code that is causing the issue
self.extensionContext?.openURL(url!, completionHandler:{(success: Bool)
It expects url to be non-nil (i.e. the !) but it is definitely nil (see image)
If this data comes from the user or from the internet, you might want a method to strip away all non-numeric characters. Something like this (from a working playground I just banged out) :
import UIKit
func digitsOnly(#fromString: String) -> String
{
var workString = NSMutableString(string: fromString)
let digitsSet = NSCharacterSet.decimalDigitCharacterSet()
var index: Int
for index = count(fromString)-1; index>=0; index--
{
if !digitsSet.characterIsMember(workString.characterAtIndex(index))
{
workString.deleteCharactersInRange(NSRange(location:index, length:1))
}
}
return workString as String
}
let testString = "(555) 111-2222"
let result = digitsOnly(fromString:testString)
println("digitsOnly(\"\(testString)\") = \"\(result)\" ")
This displays:
digitsOnly("(555) 111-2222") = "5551112222"
Edit:
Or alternately a more Swift-like version of the same function:
func digitsOnly(#fromString: String) -> String
{
var result = String()
let digitsSet = NSCharacterSet.decimalDigitCharacterSet()
for char in fromString
{
if digitsSet.characterIsMember(char as unichar)
result += char
}
}
EDIT #2:
You can increase the set of characters that is left in place by changing the character set you use. Replace the line
let digitsSet = NSCharacterSet.decimalDigitCharacterSet()
With
let digitsSet = NSCharacterSet(charactersInString: "0123456789+-")
To preserve "+" signs and dashes. (Edit the string to include the characters you need.)
Given the name of a file in the bundle, I want load the file into my Swift app. So I need to use this method:
let soundURL = NSBundle.mainBundle().URLForResource(fname, withExtension: ext)
For whatever reason, the method needs the filename separated from the file extension. Fine, it's easy enough to separate the two in most languages. But so far I'm not finding it to be so in Swift.
So here is what I have:
var rt: String.Index = fileName.rangeOfString(".", options:NSStringCompareOptions.BackwardsSearch)
var fname: String = fileName .substringToIndex(rt)
var ext = fileName.substringFromIndex(rt)
If I don't include the typing on the first line, I get errors on the two subsequent lines. With it, I'm getting an error on the first line:
Cannot convert the expression's type '(UnicodeScalarLiteralConvertible, options: NSStringCompareOptions)' to type 'UnicodeScalarLiteralConvertible'
How can I split the filename from the extension? Is there some elegant way to do this?
I was all excited about Swift because it seemed like a much more elegant language than Objective C. But now I'm finding that it has its own cumbersomeness.
Second attempt: I decided to make my own string-search method:
func rfind(haystack: String, needle: Character) -> Int {
var a = Array(haystack)
for var i = a.count - 1; i >= 0; i-- {
println(a[i])
if a[i] == needle {
println(i)
return i;
}
}
return -1
}
But now I get an error on the line var rt: String.Index = rfind(fileName, needle: "."):
'Int' is not convertible to 'String.Index'
Without the cast, I get an error on the two subsequent lines.
Can anyone help me to split this filename and extension?
Swift 5.0 update:
As pointed out in the comment, you can use this.
let filename: NSString = "bottom_bar.png"
let pathExtention = filename.pathExtension
let pathPrefix = filename.deletingPathExtension
This is with Swift 2, Xcode 7: If you have the filename with the extension already on it, then you can pass the full filename in as the first parameter and a blank string as the second parameter:
let soundURL = NSBundle.mainBundle()
.URLForResource("soundfile.ext", withExtension: "")
Alternatively nil as the extension parameter also works.
If you have a URL, and you want to get the name of the file itself for some reason, then you can do this:
soundURL.URLByDeletingPathExtension?.lastPathComponent
Swift 4
let soundURL = NSBundle.mainBundle().URLForResource("soundfile.ext", withExtension: "")
soundURL.deletingPathExtension().lastPathComponent
Works in Swift 5. Adding these behaviors to String class:
extension String {
func fileName() -> String {
return URL(fileURLWithPath: self).deletingPathExtension().lastPathComponent
}
func fileExtension() -> String {
return URL(fileURLWithPath: self).pathExtension
}
}
Example:
let file = "image.png"
let fileNameWithoutExtension = file.fileName()
let fileExtension = file.fileExtension()
Solution Swift 4
This solution will work for all instances and does not depend on manually parsing the string.
let path = "/Some/Random/Path/To/This.Strange.File.txt"
let fileName = URL(fileURLWithPath: path).deletingPathExtension().lastPathComponent
Swift.print(fileName)
The resulting output will be
This.Strange.File
In Swift 2.1 String.pathExtension is not available anymore. Instead you need to determine it through NSURL conversion:
NSURL(fileURLWithPath: filePath).pathExtension
In Swift you can change to NSString to get extension faster:
extension String {
func getPathExtension() -> String {
return (self as NSString).pathExtension
}
}
Latest Swift 4.2 works like this:
extension String {
func fileName() -> String {
return URL(fileURLWithPath: self).deletingPathExtension().lastPathComponent
}
func fileExtension() -> String {
return URL(fileURLWithPath: self).pathExtension
}
}
In Swift 2.1, it seems that the current way to do this is:
let filename = fileURL.URLByDeletingPathExtension?.lastPathComponent
let extension = fileURL.pathExtension
Swift 5 with code sugar
extension String {
var fileName: String {
URL(fileURLWithPath: self).deletingPathExtension().lastPathComponent
}
var fileExtension: String{
URL(fileURLWithPath: self).pathExtension
}
}
SWIFT 3.x Shortest Native Solution
let fileName:NSString = "the_file_name.mp3"
let onlyName = fileName.deletingPathExtension
let onlyExt = fileName.pathExtension
No extension or any extra stuff
(I've tested. based on #gabbler solution for Swift 2)
Swift 5
URL.deletingPathExtension().lastPathComponent
Strings in Swift can definitely by tricky. If you want a pure Swift method, here's how I would do it:
Use find to find the last occurrence of a "." in the reverse of the string
Use advance to get the correct index of the "." in the original string
Use String's subscript function that takes an IntervalType to get the strings
Package this all up in a function that returns an optional tuple of the name and extension
Something like this:
func splitFilename(str: String) -> (name: String, ext: String)? {
if let rDotIdx = find(reverse(str), ".") {
let dotIdx = advance(str.endIndex, -rDotIdx)
let fname = str[str.startIndex..<advance(dotIdx, -1)]
let ext = str[dotIdx..<str.endIndex]
return (fname, ext)
}
return nil
}
Which would be used like:
let str = "/Users/me/Documents/Something.something/text.txt"
if let split = splitFilename(str) {
println(split.name)
println(split.ext)
}
Which outputs:
/Users/me/Documents/Something.something/text
txt
Or, just use the already available NSString methods like pathExtension and stringByDeletingPathExtension.
Swift 5
URL(string: filePath)?.pathExtension
Try this for a simple Swift 4 solution
extension String {
func stripExtension(_ extensionSeperator: Character = ".") -> String {
let selfReversed = self.reversed()
guard let extensionPosition = selfReversed.index(of: extensionSeperator) else { return self }
return String(self[..<self.index(before: (extensionPosition.base.samePosition(in: self)!))])
}
}
print("hello.there.world".stripExtension())
// prints "hello.there"
Swift 3.0
let sourcePath = NSURL(string: fnName)?.pathExtension
let pathPrefix = fnName.replacingOccurrences(of: "." + sourcePath!, with: "")
Swift 3.x extended solution:
extension String {
func lastPathComponent(withExtension: Bool = true) -> String {
let lpc = self.nsString.lastPathComponent
return withExtension ? lpc : lpc.nsString.deletingPathExtension
}
var nsString: NSString {
return NSString(string: self)
}
}
let path = "/very/long/path/to/filename_v123.456.plist"
let filename = path.lastPathComponent(withExtension: false)
filename constant now contains "filename_v123.456"
A better way (or at least an alternative in Swift 2.0) is to use the String pathComponents property. This splits the pathname into an array of strings. e.g
if let pathComponents = filePath.pathComponents {
if let last = pathComponents.last {
print(" The last component is \(last)") // This would be the extension
// Getting the last but one component is a bit harder
// Note the edge case of a string with no delimiters!
}
}
// Otherwise you're out of luck, this wasn't a path name!
They got rid of pathExtension for whatever reason.
let str = "Hello/this/is/a/filepath/file.ext"
let l = str.componentsSeparatedByString("/")
let file = l.last?.componentsSeparatedByString(".")[0]
let ext = l.last?.componentsSeparatedByString(".")[1]
A cleaned up answer for Swift 4 with an extension off of PHAsset:
import Photos
extension PHAsset {
var originalFilename: String? {
if #available(iOS 9.0, *),
let resource = PHAssetResource.assetResources(for: self).first {
return resource.originalFilename
}
return value(forKey: "filename") as? String
}
}
As noted in XCode, the originalFilename is the name of the asset at the time it was created or imported.
Maybe I'm getting too late for this but a solution that worked for me and consider quite simple is using the #file compiler directive. Here is an example where I have a class FixtureManager, defined in FixtureManager.swift inside the /Tests/MyProjectTests/Fixturesdirectory. This works both in Xcode and withswift test`
import Foundation
final class FixtureManager {
static let fixturesDirectory = URL(fileURLWithPath: #file).deletingLastPathComponent()
func loadFixture(in fixturePath: String) throws -> Data {
return try Data(contentsOf: fixtureUrl(for: fixturePath))
}
func fixtureUrl(for fixturePath: String) -> URL {
return FixtureManager.fixturesDirectory.appendingPathComponent(fixturePath)
}
func save<T: Encodable>(object: T, in fixturePath: String) throws {
let data = try JSONEncoder().encode(object)
try data.write(to: fixtureUrl(for: fixturePath))
}
func loadFixture<T: Decodable>(in fixturePath: String, as decodableType: T.Type) throws -> T {
let data = try loadFixture(in: fixturePath)
return try JSONDecoder().decode(decodableType, from: data)
}
}
Creates unique "file name" form url including two previous folders
func createFileNameFromURL (colorUrl: URL) -> String {
var arrayFolders = colorUrl.pathComponents
// -3 because last element from url is "file name" and 2 previous are folders on server
let indx = arrayFolders.count - 3
var fileName = ""
switch indx{
case 0...:
fileName = arrayFolders[indx] + arrayFolders[indx+1] + arrayFolders[indx+2]
case -1:
fileName = arrayFolders[indx+1] + arrayFolders[indx+2]
case -2:
fileName = arrayFolders[indx+2]
default:
break
}
return fileName
}