This is fairly straightforward.
I have an app that publishes a URL scheme, like so.
In the spirit of DRY, I'd like to avoid referencing it, using constant strings. Instead, I'd like to fetch it from the bundle.
How do I do that?
This snippet prints the URL schemes defined in an app's Info.plist:
if let types = Bundle.main.infoDictionary?["CFBundleURLTypes"] as? [[String: Any]] {
var result = [String]()
for type in types {
guard let schemes = type["CFBundleURLSchemes"] as? [String] else { continue }
guard let scheme = schemes.first else { continue }
result.append(scheme)
}
print(result)
}
This was solved by leveraging Gereon's answer. Here's how I did it:
/* ###################################################################################################################################### */
// MARK: - Fleshing out the Addressable (General) -
/* ###################################################################################################################################### */
public extension LGV_MeetingSDK_AddressableEntity_Protocol {
/* ################################################################## */
/**
This fetches the first URL scheme from the bundle, renders it as a String, and returns it.
*/
var urlScheme: String {
guard let bundleTypes = Bundle.main.infoDictionary?["CFBundleURLTypes"] as? [[String: Any]],
let myURLScheme = (bundleTypes.first?["CFBundleURLSchemes"] as? [String])?.first
else { return "" }
return myURLScheme
}
}
/* ###################################################################################################################################### */
// MARK: - Fleshing out the Addressable (User) -
/* ###################################################################################################################################### */
public extension HeartOfRecovrr_Member {
/* ################################################################## */
/**
This returns an addressable URL for this member record.
*/
var urlString: String { "\(urlScheme)://user/\(id)" }
}
And here it is, in my general-purpose Swift extensions package.
Hey guys in my app the user clicks a button called the showfunfact() that moves them through an array of strings. When the user removes the app from multitasking or turns off the phone I want the users place to be saved and then when the reload the can pick up where they left off
var TechfactIndex = 0
let TechnologyfactBook = TechFactBook()
override func viewDidLoad() {
super.viewDidLoad()
let defaults = NSUserDefaults.standardUserDefaults()
TechfactIndex = NSUserDefaults.standardUserDefaults().integerForKey("ByteLocation")
defaults.setObject(TechfactIndex, forKey: "ByteLocation")
}
#IBAction func showFunFact() {
if ( UIApplication.sharedApplication().applicationIconBadgeNumber != 0){
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
if (TechfactIndex >= TechnologyfactBook.TechfactsArray.count) {
self.TechfactIndex = 0
}
TechByteLabel.text = TechnologyfactBook.TechfactsArray[TechfactIndex]
TechfactIndex++
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
NSUserDefaults.standardUserDefaults().setInteger(TechfactIndex, forKey: "ByteLocation")
NSUserDefaults.standardUserDefaults().synchronize()
}
The thing is that viewDidDisappear: is unrelated to your spec. It is not called "When the user removes the app from multitasking or turns off the phone". It is not called when the phone rings. It is not called when the user hits the home button. It's irrelevant. You've put your code in the wrong place.
What you want to do is register to hear when the app is deactivated. That is the moment to write information into the user defaults.
It's not that expensive of an operation to just write to the User Defaults every time the next fun fact is viewed. So for the sake of simplicity, you could just write the new index to the NSUserDefaults every time they view a new fun fact.
#IBAction func showFunFact() {
if (UIApplication.sharedApplication().applicationIconBadgeNumber != 0){
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
TechfactIndex = NSUserDefaults.standardUserDefaults().integerForKey("ByteLocation")
if (TechfactIndex >= TechnologyfactBook.TechfactsArray.count) {
self.TechfactIndex = 0
}
TechByteLabel.text = TechnologyfactBook.TechfactsArray[TechfactIndex]
TechfactIndex++
NSUserDefaults.standardUserDefaults().setInteger(TechfactIndex, forKey: "ByteLocation")
NSUserDefaults.standardUserDefaults().synchronize()
}
However, the user defaults is usually reserved for preferences... so you may be better off implementing a plist (or similar simple saving option) here to store the current index.
EDIT: Here is a simple example on how you could use a plist to achieve this
First, create a Property List file in your directory, call it Data.plist. Make the root object a dictionary and add an NSNumber object with the key FunFactIndex. This will be a template for your plist on the first time a save occurs.
func showFunFact() {
if (UIApplication.sharedApplication().applicationIconBadgeNumber != 0){
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
// Load the next index
var factIndex = getCurrentFunFactIndex() as Int
if factIndex < 0 {
println("error")
return
}
if (factIndex >= TechnologyfactBook.TechfactsArray.count) {
factIndex = 0
}
TechByteLabel.text = TechnologyfactBook.TechfactsArray[factIndex]
factIndex++
// Save the index
let saveSuccess = saveFunFactIndex(factIndex);
let successString = (saveSuccess) ? "success" : "failure"
println("Save was a \(successString)")
}
func getCurrentFunFactIndex() -> Int {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as? NSString
let path = documentsDirectory!.stringByAppendingPathComponent("Data.plist")
let fileManager = NSFileManager.defaultManager()
// Check if file exists, copy it over from the bundle if it doesn't
if !fileManager.fileExistsAtPath(path) {
let bundle = NSBundle.mainBundle().pathForResource("Data", ofType: "plist")
fileManager.copyItemAtPath(bundle!, toPath: path, error:nil)
}
if let dataDict = NSDictionary(contentsOfFile: path) {
if let indexNum: AnyObject = dataDict.objectForKey("FunFactIndex") {
return indexNum.integerValue
}
}
return -1 // Something went wrong
}
func saveFunFactIndex(index: Int) -> Bool {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path = documentsDirectory.stringByAppendingPathComponent("Data.plist")
let fileManager = NSFileManager.defaultManager()
// Check if file exists, copy it over from the bundle if it doesn't
if !fileManager.fileExistsAtPath(path) {
let bundle = NSBundle.mainBundle().pathForResource("Data", ofType: "plist")
fileManager.copyItemAtPath(bundle!, toPath: path, error:nil)
}
if let dataDict = NSMutableDictionary(contentsOfFile: path) {
let indexNum: NSNumber = index
dataDict.setObject(indexNum, forKey: "FunFactIndex")
return dataDict.writeToFile(path, atomically: true)
}
return false
}
There are two methods I added for you; getCurrentFunFactIndex and saveFunFactIndex:. Both will first check in the Documents directory of the sandbox forData.plist. If that file does not exist, it will copy over the template plist that we created in the bundle. All future uses of these methods will use theData.plist` file that is in the Documents directory. This will allow the value to persist on subsequent app launches (removing the app from the background or turning off the device).
Trying to get the SSID of current device. I have found plenty of examples on how to do it however I am struggling with getting the CNCopySupportedInterfaces to autocomplete. I have 'import SystemConfiguration' at the top of my swift file but no success. Can't seem to figure out what I am doing wrong.
iOS 12
You must enable Access WiFi Information from capabilities.
Important
To use this function in iOS 12 and later, enable the Access WiFi Information capability for your app in Xcode. When you enable this capability, Xcode automatically adds the Access WiFi Information entitlement to your entitlements file and App ID. Documentation link
You need: import SystemConfiguration.CaptiveNetwork
Underneath the covers, CaptiveNetwork is a C header file (.h) that is within the SystemConfiguration framework:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SystemConfiguration.framework/Headers/CaptiveNetwork.h
If you know Objective-C, this goes into more depth:
iPhone get SSID without private library
You have to use the awkward syntax to bridge from any pure C API, so the following is required:
for interface in CNCopySupportedInterfaces().takeRetainedValue() as! [String] {
println("Looking up SSID info for \(interface)") // en0
let SSIDDict = CNCopyCurrentNetworkInfo(interface).takeRetainedValue() as! [String : AnyObject]
for d in SSIDDict.keys {
println("\(d): \(SSIDDict[d]!)")
}
}
ADDENDUM FOR SWIFT 2.2 and 3.0
The CFxxx datatypes are now bridged to native Objective-C runtime, eliminating the head-scratching retain calls. However, nullable pointers give rise to Optionals, so things don't get any shorter. At least, it's fairly clear what's going on, plus the nil helps us identify the simulator. The other answer uses an awful lot of bit-casting and unsafe operations which seems non-Swiftian, so I offer this.
func getInterfaces() -> Bool {
guard let unwrappedCFArrayInterfaces = CNCopySupportedInterfaces() else {
print("this must be a simulator, no interfaces found")
return false
}
guard let swiftInterfaces = (unwrappedCFArrayInterfaces as NSArray) as? [String] else {
print("System error: did not come back as array of Strings")
return false
}
for interface in swiftInterfaces {
print("Looking up SSID info for \(interface)") // en0
guard let unwrappedCFDictionaryForInterface = CNCopyCurrentNetworkInfo(interface) else {
print("System error: \(interface) has no information")
return false
}
guard let SSIDDict = (unwrappedCFDictionaryForInterface as NSDictionary) as? [String: AnyObject] else {
print("System error: interface information is not a string-keyed dictionary")
return false
}
for d in SSIDDict.keys {
print("\(d): \(SSIDDict[d]!)")
}
}
return true
}
Output on success:
SSIDDATA: <57696c6d 79>
BSSID: 12:34:56:78:9a:bc
SSID: YourSSIDHere
In Swift 2.0 / iOS 9 the API CaptiveNetwork is (nearly) gone or depreciated. I contacted Apple regarding this problem and I thought we could (or should) use the NEHotspotHelper instead. I got a respond from Apple today: One should continue to use CaptiveNetwork and the two relevant APIs (even tough there marked depreciated):
CNCopySupportedInterfaces
CNCopyCurrentNetworkInfo
The user braime posted an updated code-snippet for this problem on Ray Wenderlich forums:
let interfaces:CFArray! = CNCopySupportedInterfaces()
for i in 0..<CFArrayGetCount(interfaces){
let interfaceName: UnsafePointer<Void>
= CFArrayGetValueAtIndex(interfaces, i)
let rec = unsafeBitCast(interfaceName, AnyObject.self)
let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)")
if unsafeInterfaceData != nil {
let interfaceData = unsafeInterfaceData! as Dictionary!
currentSSID = interfaceData["SSID"] as! String
} else {
currentSSID = ""
}
}
Works perfect for me.
Swift:
import SystemConfiguration.CaptiveNetwork
func currentSSIDs() -> [String] {
guard let interfaceNames = CNCopySupportedInterfaces() as? [String] else {
return []
}
return interfaceNames.flatMap { name in
guard let info = CNCopyCurrentNetworkInfo(name as CFString) as? [String:AnyObject] else {
return nil
}
guard let ssid = info[kCNNetworkInfoKeySSID as String] as? String else {
return nil
}
return ssid
}
}
Then print(currentSSIDs()), not working on simulator, only real devices.
Taken from https://forums.developer.apple.com/thread/50302
func getInterfaces() -> String? {
var ssid: String?
if let interfaces = CNCopySupportedInterfaces() as NSArray? {
for interface in interfaces {
if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? {
ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String
break
}
}
}
return ssid
}
In iOS 12 and up you will need to enable the Access WiFi Information capability for your app in order to get the ssid
Hello I'm trying to publish a iOS (SWIFT) personal project in GitHub but I'm afraid of sharing my private API keys and secrets with everybody.
I'm using parse so I have in my AppDelegate something like this:
let applicationId = "mySecretApplicationId"
let clientKey = "mySecretClientKey"
Parse.setApplicationId(applicationId!, clientKey: clientKey!)
I would like to hide "mySecretApplicationId" and "mySecretClientKey", is there private place or directory in my project where I can put this variables?
Thanks!
You can use a .plist file where you store all your important keys. It is very important to put this file into your .gitignore file.
In your case, you need to set your keys.plist file like this:
And use it inside your AppDelegate as follows:
var keys: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("Keys", ofType: "plist") {
keys = NSDictionary(contentsOfFile: path)
}
if let dict = keys {
let applicationId = dict["parseApplicationId"] as? String
let clientKey = dict["parseClientKey"] as? String
// Initialize Parse.
Parse.setApplicationId(applicationId!, clientKey: clientKey!)
}
SWIFT 3 Update:
if let path = Bundle.main.path(forResource: "Keys", ofType: "plist") {
keys = NSDictionary(contentsOfFile: path)
}
Put them in a configuration file that you add to the .gitignore file. Check in a sample configuration file that every developer can use to create their own configuration.
If you want to share your project without keys then:
Add Keys( as you prefer - enum, struct, or even object/singleton)
struct Keys {
static let sandboxToken = "Tpk_hh43nneu3jwsu3u"
static let productionToken = "pk_b5h4uend8ejwnw8"
}
In your code add follow code:
extension APIManager {
enum Environment {
case sandbox, production
var apiKey: String {
switch self {
case .sandbox:
return Keys.iexSandboxToken // <- Here
case .production:
return Keys.iexProductionToken // <- Here
}
}
}
}
or if you want to deal with optionals then you can add something similar to:
struct Keys {
static let sandboxToken: String? = "Tpk_hh43nneu3jwsu3u"
static let productionToken: String?
}
and on use add assert
var apiKey: String {
switch self {
case .sandbox:
guard let token = Keys.iexSandboxToken else {
assertionFailure("Please fill the tokent in Keys.swift")
return "anything you want"
}
return token
case .production:
guard let token = Keys.iexProductionToken else {
assertionFailure("Please fill the tokent in Keys.swift")
return "anything you want"
}
return token
}
}
So, in production, it will fail.
Add it on .gitignore. So, your keys are hidden.
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
}