How to hide API keys in GitHub for iOS (SWIFT) projects? - ios

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.

Related

Alamofire - IOS SSL Pinning with multiple certificates

I am using Alamofire in my IOS Application and has implemented SSL Pinning. I have kept the certificate locally in .der format. The issue is the certificate when getting expired, I have to do an App release with new certificate.
Code Snippet used:
struct Certificates {
fileprivate static let sslCertificate: SecCertificate? = Certificates.certificate(filename: (Bundle.main.infoDictionary?["SSL_CERTIFICATE"] as? String))
private static func certificate(filename: String?) -> SecCertificate? {
guard let filePath = Bundle.main.path(forResource: filename, ofType: "der"), let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)), let certificate = SecCertificateCreateWithData(nil, data as CFData) else{
return nil
}
return certificate
}
final class APIClient {
// MARK: - Host To Evaluate
private static var hostToEvaluate: String {
guard let urlString = Bundle.main.infoDictionary?["API_BASE_URL_ENDPOINT"] as? String, let hostURL = URL(string: urlString), let host = hostURL.host else{
return ""
}
return host
}
// MARK: - Evaluators
private static var evaluators: Dictionary<String,ServerTrustEvaluating> {
guard let sslCertificate = Certificates.sslCertificate else{
return [hostToEvaluate : DisabledTrustEvaluator()]
}
return [hostToEvaluate : PinnedCertificatesTrustEvaluator(certificates: [sslCertificate])]
}
// MARK: - Session
private static let session = Session(
serverTrustManager: ServerTrustManager(evaluators: evaluators)
)
}
Can I keep 2 certificates and do a check to evaluate existing certificate and if it fails pick up the new certificate.
Have checked few documentations and forums, but did not find anything relevant.
Thanks,
Abin
Yes. By default PublicKeysTrustEvaluator and PinnedCertificatesTrustEvaluator will pick up all certificates that are in the application bundle, if you don't pass anything for keys and certificates parameters in the initializer respectively. The evaluation will pass if one of the bundled certificates are matching.
Your code will look something like this:
// MARK: - Evaluators
private static var evaluators: Dictionary<String, ServerTrustEvaluating> = [
hostToEvaluate: PinnedCertificatesTrustEvaluator()
]
// MARK: - Session
private static let session = Session(
serverTrustManager: ServerTrustManager(evaluators: evaluators)
)
Just make sure that both certificates are in the application bundle. (add them to your target)

Localization request permission base on the application language not device language

Localization is working fine in my application. I want localization the permission dialog text messages. It is working fine with the device language changes but I want to change message according to my application language.
I have tried the following code
import UIKit
class LocalizeHelper: NSObject {
private var myBundle: Bundle? = nil
static let shared: LocalizeHelper = {
let instance = LocalizeHelper()
return instance
}()
override init() {
super.init()
// use systems main bundle as default bundle
myBundle = Bundle.main
}
func localizedString(forKey key: String) -> String {
return myBundle!.localizedString(forKey: key, value: "", table: nil)
}
// Converted with Swiftify v1.0.6331 - https://objectivec2swift.com/
func setLanguage(_ lang: String) {
// path to this languages bundle
let path: String? = Bundle.main.path(forResource: lang, ofType: "lproj")
if path == nil {
// there is no bundle for that language
// use main bundle instead
myBundle = Bundle.main
}
else {
// use this bundle as my bundle from now on:
myBundle = Bundle(path: path!)
// to be absolutely shure (this is probably unnecessary):
if myBundle == nil {
myBundle = Bundle.main
}
}
}
func getLanguage() -> String {
print("\(String(describing: myBundle?.bundlePath.last))")
//return myBundle!.bundlePath.last >> Error
return myBundle!.bundlePath.lastCharacter!
}
}
extension String {
public var lastCharacter: String? {
guard let aLast = self.last else {
return nil
}
return String(aLast)
}
}
I have surfed in StackOverflow but didn't find any solution. Any help shell we appreciated.
Your best bet is to save the application's language in the user defaults as a string for example and then when you take it out of user defaults parse it as a custom app language enum. If you don't have any app language save you can always fall through to the device language but prefer or override it with the app language in user defaults not being nil. Also preferably you should wrap your user defaults interactions with a user defaults manager that you access in your view controller.
So the suggested steps are:
create userdefualts manager
define language enum
implement setLanguage func language parameter
implement getLanguage func language output with device language or
english as guard return

How do I interrogate the current app's URL scheme programmatically?

My iOS app has 50+ targets, each with their own custom URL scheme. I need to detect if a request from a webview matches the scheme of the currently running app. In order to do this, I need to be able to interrogate the current app's URL scheme(s) from code.
Similar questions deal with attempting to interrogate other apps to discover their URL schemes, which seems like it is not possible. I need to find the scheme out for my own app, from within my app.
I would like to avoid having to set another plist value to the URL scheme and including that with the target.
Is there any way to get the URL scheme of the current app?
This is the function I came up with to accomplish this.
- (BOOL)doesMatchURLScheme:(NSString *)scheme {
if([[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleURLTypes"]) {
NSArray *urlTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleURLTypes"];
for(NSDictionary *urlType in urlTypes)
{
if(urlType[#"CFBundleURLSchemes"])
{
NSArray *urlSchemes = urlType[#"CFBundleURLSchemes"];
for(NSString *urlScheme in urlSchemes)
if([urlScheme caseInsensitiveCompare:scheme] == NSOrderedSame)
return YES;
}
}
}
return NO;
}
In Swift 4 : Working version of getting the custom url scheme from Info.plist programmatically
func externalURLScheme() -> String? {
guard let urlTypes = Bundle.main.infoDictionary?["CFBundleURLTypes"] as? [AnyObject],
let urlTypeDictionary = urlTypes.first as? [String: AnyObject],
let urlSchemes = urlTypeDictionary["CFBundleURLSchemes"] as? [AnyObject],
let externalURLScheme = urlSchemes.first as? String else { return nil }
return externalURLScheme
}
(with the assumption that there is only 1 element in the urlTypes and urlSchemes array, hence taking the first element). Here is my plist entry of URL Scheme
Extension for Bundle swift 4 syntax
extension Bundle {
static let externalURLSchemes: [String] = {
guard let urlTypes = main.infoDictionary?["CFBundleURLTypes"] as? [[String: Any]] else {
return []
}
var result: [String] = []
for urlTypeDictionary in urlTypes {
guard let urlSchemes = urlTypeDictionary["CFBundleURLSchemes"] as? [String] else { continue }
guard let externalURLScheme = urlSchemes.first else { continue }
result.append(externalURLScheme)
}
return result
}()
}

Why is my relation-object reinitialized each time it is accessed?

I use Realm Swift 0.95.3. In my project I'm working with a contact (not with contacts AddressBook), which contain information about the name, last name, middle name, email, … and photograph.
I have created a subclass of Object called Contact. I understand that the image, or any other binary data, should not be stored in the Realm. Especially as my images could be large enough ~5 Mb.
In any case, I wanted to store the image in the file system, and Realm stores only a link to this image. I thought that in the future I may need to attach other files (audio, PDF, …) to subclasses of Object. So I decided to create a separate class File, which is inherited from Object. The whole structure looks like.
class File: Object {
dynamic var objectID = NSUUID().UUIDString
private var filePath: String {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory = paths[0] as NSString
return documentsDirectory.stringByAppendingPathComponent(objectID)
}
lazy var fileData: NSData? = {
return self.readFile()
}()
override static func primaryKey() -> String? {
return "objectID"
}
override static func ignoredProperties() -> [String] {
return ["fileData"]
}
override func copy() -> AnyObject {
let result = File(value: self)
result.objectID = NSUUID().UUIDString
result.fileData = fileData?.copy() as? NSData
return result
}
}
extension File {
//MARK:-
private func readFile() -> NSData? {
let lFilePath = filePath
print("Try to read file \(lFilePath)")
return NSData(contentsOfFile: lFilePath)
}
private func writeFileWithFileData() {
if let lFileData = fileData {
lFileData.writeToFile(filePath, atomically: true)
print("File \(filePath) was created")
} else {
removeFile()
}
}
private func removeFile() {
do {
try NSFileManager.defaultManager().removeItemAtPath(filePath)
print("File \(filePath) was removed")
} catch {
print(error)
}
}
}
As you can see, when trying to get the property fileData, data is received from the file system. Moreover fileData is declared with the keyword lazy, that is, I wish that the data is requested from disk to cache in this property. If this property is changed, the File object before saving to the database, I call writeFileWithFileData (), and data on the disk is overwritten. This system works as I need to, I to experiment. Then I created a Contact.
class Contact: Object {
dynamic var objectID = NSUUID().UUIDString
dynamic var firstName = ""
dynamic var lastName = ""
...
private dynamic var avatarFile: File?
var avatar: UIImage? {
get {
guard let imageData = avatarFile?.fileData else { return nil }
return UIImage(data: imageData)
}
set {
avatarFile = File()
guard let imageData = newValue else {
avatarFile?.fileData = nil
return
}
avatarFile?.fileData = UIImagePNGRepresentation(imageData)
}
}
override static func primaryKey() -> String? {
return "objectID"
}
override static func ignoredProperties() -> [String] {
return ["image"]
}
}
The problem is that when I choose a contact from the database, and am trying to get the avatar, then access to the file system occurs every time you access this property. That is, property fileData does not operate as lazy - as I thought at first. But then I looked at the memory address properties avatarFile, each time it is received, the address has changed. From this I can conclude that the object avatarFile is constantly requested from the database again, with any reference to this property. As a consequence, all its ignoredProperties are reset.
Why is the relation-object reinitialized each time it is accessed?
Share your opinion about my arch
It should be fine. Realm Objects are live links to their parent Realm object, not static copies, so their addresses do periodically change. This is normal, and the objects aren't getting re-allocated so you shouldn't see any memory issues here. As far as I'm aware, NSData itself is lazy, so the data won't actually be read until you explicitly request it.
Your architecture looks good! You're right in that it's usually not the best form to store file data directly inside a Realm file, but you've done a good job at making the Realm Object manage the external file data as if it was. :)

How to split filename from file extension in Swift?

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
}

Resources