How to read variables from txt file? - swift

For example i've got a build archive and i want it to read some variables from txt file. So the question is there any way to upload a txt file to iPhone device and how to read that variables?
In my project i've got this constants:
baseURL: String = "someUrl"
client_id: String
client_secret: String
authorize_uri: String
token_uri: String
scope: String
redirect_uris: [String]
secret_in_body: Bool
and for example i created a txt file with this strings and somehow uploaded it to my device
i want the builded project to read this file

You could create a new plist file in your project, then parse it into a dictionary at startup. Once there you can just refer to each value by its key
If you create an extension to Bundle()
extension Bundle {
func parsePlist(ofName name: String) -> [String: String]? {
// check if plist data available
guard let plistURL = Bundle.main.url(forResource: name, withExtension: "plist"),
let data = try? Data(contentsOf: plistURL)
else {
return nil
}
// parse plist into [String: Anyobject]
guard let plistDictionary = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: String] else {
return nil
}
return plistDictionary
}
}
then you can load it into a dictionary like this
var dict = Bundle().parsePlist(ofName: "plistName")!
Xcode does a nice job of allowing you to edit the plist, and it'll be automatically included in your build

Related

Send Core Data object to post api Alamofire multipartFormData

I have a screen on my app that I get some fields and save on my object Order. This object is my Core Data Object. After saving it, I need to send it to my backend through Alamofire POST multipartFormData.
The problem is that this is a Core Data Object (not Codable) and I need to send Data type on multipartFormData. How can I convert my object to Data? Is there another way of doing it?
What I've done:
let order = Order(context: DatabaseController.getContext())
order.orderItem = orderItem
order.product = product
order.value = value
order.date = date
Alamofire part:
Alamofire.upload (
multipartFormData: { multipartFormData in
multipartFormData.append(order, withName: "order")
},
to: url,
headers: headers,
encodingCompletion: { encodingResult in
The problem is how to put my object Order inside multipartFormData?
Could anyone help me please?
Updated:
Ok, sending the whole object didn't work, my api didn't accept, so I made a specific json only with the fields needed and turned it a Data type:
(PS: files are Data type from images user choose, even from camera or gallery)
var files = Dictionary<Data, String>()
var jsonFiles = [[String: String]]()
var jsonOrder = [String: Any]()
for file in files {
let dict : [String: String] = [ "orderImageIdLocal": uuidOrderImageIdLocal,
"orderItemAnalysisIdLocal": uuidAnalysisIdLocal,
"urlImageLocal": "\(imageId).jpg"]
jsonFiles.append(dict)
}
jsonOrder = [ "reason": "\(textViewReason)",
"orderImagess": jsonFiles,
"orderAnalysisId": "",
"orderIdLocal": "\(uuidAnaliseIdLocal)",
"orderId": "\(orderId ?? "")",
"typeSolicitation": "\(typeSolicitation)"]
Then I convert it to Data type like you said and send to Alamofire like above:
let orderData = try? JSONSerialization.data(withJSONObject: jsonOrder, options: .prettyPrinted) {
My problem now is that my api expect a zip file with those images user took from camera or gallery. So I am trying to use ZIPFoundation. I still don't know how to zip it and send. Should I zip each picture as Data type? Then transform zip file into Data type so I can send through multipartFormData.append?
I have tried: here and here
Here the code as an extension of NSManagedObject which creates dictionary from the attributes name.
extension NSManagedObject {
func toData() -> Data? {
let keys = Array(self.entity.attributesByName.keys)
let dict = self.dictionaryWithValues(forKeys: keys)
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
return jsonData
}
catch{}
return nil
}
}
Usage:
let jsonData = order.toData()
multipartFormData.append(jsonData, withName: "order")

How to get the realy fixed Device-ID in swift?

I use the below code since long time. I have thought it is unique
But I have deleted my app and reinstalled it, I get new different Device-ID.
if let uuid = UIDevice.current.identifierForVendor?.uuidString {
print(uuid)
}
every new reinstall, I get a new ID.
How do I get something which stays the same?
Since the value returned from identifierForVendor can be cleared when deleting the app or reset if the user resets it in the Settings app, you have to manage persisting it yourself.
There are a few ways to accomplish this. You can setup a server that assigns a uuid which is then persisted and fetched server side via user login, or you can create and store it locally in the keychain.
Items stored in the keychain will not be deleted when the app is deleted. This allows you to check if a uuid was previously stored, if so you can retrieve it, if not you can generate a new uuid and persist it.
Here's a way you could do it locally:
/// Creates a new unique user identifier or retrieves the last one created
func getUUID() -> String? {
// create a keychain helper instance
let keychain = KeychainAccess()
// this is the key we'll use to store the uuid in the keychain
let uuidKey = "com.myorg.myappid.unique_uuid"
// check if we already have a uuid stored, if so return it
if let uuid = try? keychain.queryKeychainData(itemKey: uuidKey), uuid != nil {
return uuid
}
// generate a new id
guard let newId = UIDevice.current.identifierForVendor?.uuidString else {
return nil
}
// store new identifier in keychain
try? keychain.addKeychainData(itemKey: uuidKey, itemValue: newId)
// return new id
return newId
}
And here's the class for storing/retrieving from the keychain:
import Foundation
class KeychainAccess {
func addKeychainData(itemKey: String, itemValue: String) throws {
guard let valueData = itemValue.data(using: .utf8) else {
print("Keychain: Unable to store data, invalid input - key: \(itemKey), value: \(itemValue)")
return
}
//delete old value if stored first
do {
try deleteKeychainData(itemKey: itemKey)
} catch {
print("Keychain: nothing to delete...")
}
let queryAdd: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecValueData as String: valueData as AnyObject,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
let resultCode: OSStatus = SecItemAdd(queryAdd as CFDictionary, nil)
if resultCode != 0 {
print("Keychain: value not added - Error: \(resultCode)")
} else {
print("Keychain: value added successfully")
}
}
func deleteKeychainData(itemKey: String) throws {
let queryDelete: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject
]
let resultCodeDelete = SecItemDelete(queryDelete as CFDictionary)
if resultCodeDelete != 0 {
print("Keychain: unable to delete from keychain: \(resultCodeDelete)")
} else {
print("Keychain: successfully deleted item")
}
}
func queryKeychainData (itemKey: String) throws -> String? {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if resultCodeLoad != 0 {
print("Keychain: unable to load data - \(resultCodeLoad)")
return nil
}
guard let resultVal = result as? NSData, let keyValue = NSString(data: resultVal as Data, encoding: String.Encoding.utf8.rawValue) as String? else {
print("Keychain: error parsing keychain result - \(resultCodeLoad)")
return nil
}
return keyValue
}
}
Then you can just have a user class where you get the identifier:
let uuid = getUUID()
print("UUID: \(uuid)")
If you put this in a test app in viewDidLoad, launch the app and note the uuid printed in the console, delete the app and relaunch and you'll have the same uuid.
You can also create your own completely custom uuid in the app if you like by doing something like this:
// convenience extension for creating an MD5 hash from a string
extension String {
func MD5() -> Data? {
guard let messageData = data(using: .utf8) else { return nil }
var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
_ = digestData.withUnsafeMutableBytes { digestBytes in
messageData.withUnsafeBytes { messageBytes in
CC_MD5(messageBytes, CC_LONG(messageData.count), digestBytes)
}
}
return digestData
}
}
// extension on UUID to generate your own custom UUID
extension UUID {
static func custom() -> String? {
guard let bundleID = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String else {
return nil
}
let unique = bundleID + NSUUID().uuidString
let hashData = unique.MD5()
let md5String = hashData?.map { String(format: "%02hhx", $0) }.joined()
return md5String
}
}
Note that to use the MD5 function you will have to add the following import to an Objective-C bridging header in your app: (if you're building with Xcode < 10. In Xcode 10+ CommonCrypto is included so you can skip this step)
#import <CommonCrypto/CommonCrypto.h>
If your app does not have a bridging header, add one to your project and make sure to set it in build settings:
Once it's setup you can generate your own custom uuid like this:
let otherUuid = UUID.custom()
print("Other: \(otherUuid)")
Running the app and logging both outputs generates uuids something like this:
// uuid from first example
UUID: Optional("8A2496F0-EFD0-4723-8C6D-8E18431A49D2")
// uuid from second custom example
Other: Optional("63674d91f08ec3aaa710f3448dd87818")
Unique Id in iPhone is UDID, which is not accessible in current version of OS, because it can be misused. So Apple has given the other option for the unique key but it change every time you install the app.
--Cannot access UDID
But there is another way to implement this feature.
First you have to generate the Unique ID :
func createUniqueID() -> String {
let uuid: CFUUID = CFUUIDCreate(nil)
let cfStr: CFString = CFUUIDCreateString(nil, uuid)
let swiftString: String = cfStr as String
return swiftString
}
After getting the this which is unique but changes after the app install and reinstall.
Save this id to the Key-Chain on any key let say "uniqueID".
To save the key in keyChain :
func getDataFromKeyChainFunction() {
let uniqueID = KeyChain.createUniqueID()
let data = uniqueID.data(using: String.Encoding.utf8)
let status = KeyChain.save(key: "uniqueID", data: data!)
if let udid = KeyChain.load(key: "uniqueID") {
let uniqueID = String(data: udid, encoding: String.Encoding.utf8)
print(uniqueID!)
}
}
func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
Next when you required to perform any task based on uniqueID ,first check if any data is saved in Key-Chain on key "uniqueID" .
Even if you uninstall the app , the key-chain data is still persisted, it will delete by OS.
func checkUniqueID() {
if let udid = KeyChain.load(key: "uniqueID") {
let uniqueID = String(data: udid, encoding: String.Encoding.utf8)
print(uniqueID!)
} else {
let uniqueID = KeyChain.createUniqueID()
let data = uniqueID.data(using: String.Encoding.utf8)
let status = KeyChain.save(key: "uniqueID", data: data!)
print("status: ", status)
}
}
In this way you can generate the uniqueID once and use this id every time.
NOTE: But when you upload next version of your app, upload it with the same Provisioning Profile otherwise you cannot access the key-chain store of your last installed app.
Key-Chain Store is associated with the Provisioning profile.
Check Here
Access to the unique device id (UDID) has been disallowed for ages now. identifierForVendor is its replacement, and its behaviour has always been documented.

Swift: unzipping file

I’m trying to get String from txt file inside the zip file using native libcompression library. Actually I use the code from
https://github.com/mw99/DataCompression/blob/master/Sources/DataCompression.swift.
At first, I was doing:
let zip = try? Data(contentsOf: "/.../test.zip")
let tmp: Data? = zip?.unzip()
let txt: String? = String(data: tmp!, encoding: .utf8)
But how do I get the contents of zip file and how do I get data from certain txt file?
ZIP Foundation supports accessing individual entries in ZIP archives.
You have to initialize an archive by passing a file URL to the Archive initializer.
Afterwards you can access a specific entry via subscripting:
let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("test.zip")
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
return
}
guard let entry = archive["file.txt"] else {
return
}
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("out.txt")
do {
try archive.extract(entry, to: destinationURL)
} catch {
print("Extracting entry from archive failed with error:\(error)")
}
You can also directly access the contents of entry by using the closure based API. This allows you to process the entry without writing it to the file system first:
try archive.extract(entry, consumer: { (data) in
print(data.count)
})

NSArrayURL , userDefault filePath and URL

I have one pdf source code and I want to add the Url in Array and use UserDefault
let defaults = UserDefaults.standard
struct Constants {
static let myKeyURL = "myKeyUrl"
}
I download the Pdf Like This
let documentsPath =NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let fileName = urlString as NSString;
let filePath="\(documentsPath)/\(fileName.lastPathComponent)";
After I save the Path like This
var arrayUrl = [String]()
arrayUrl.append(filePath)
self.defaults.set(arrayUrl, forKey: Constants.myKeyURL)
Now I want to Read
var arrayUrl = [String]()
defaults.stringArray(forKey: Constants.myKeyURL)
arrayUrl = defaults.stringArray(forKey: Constants.myKeyURL)!
I need to Read in This Model
documents = arrayUrl.flatMap { PDFDocument(url: $0 ) }
But I received
Cannot convert value of type 'String' to expected argument type 'URL'
I need this URL (arrayUrl) File in this format file:///private/var/mobile/Containers/Data/Application/----/Documents/Sample.pdf
The error is clear:
PDFDocument(url: expects URL, you pass String which is a classic type mismatch.
You have to create URL instances from the strings
documents = arrayUrl.flatMap { PDFDocument(url: URL(fileURLWithPath: $0) ) }
However you are discouraged from saving the full path because the path to the Documents folder changes. Save only the file name or relative path and get the actual path to the Documents folder on each application launch.

Content Blocker extension with a String instead of a file

I'm using the function NSItemProvider(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json") in a content blocker extension.
The thing is that all my rules are stored in a few dictionaries and, when I'm using this function, it's always because the rules have changed. I'm currently creating a String from these dictionaries that looks like "[{\"trigger\": {\"url-filter\": \"webiste.com\"},\"action\": {"\type\": \"css-display-none\",\"selector\":\".testContentBlocker\"}}]"and I have to transform it in a JSON file to finally be able to use it in the function written previously described.
Instead of having to put the String in a JSON file to be able to use it, could I do something simpler to use NSItemProvider()?
By loading the extension up in the debugger (and by using Hopper), you can see that NSItemProvider(contentsOfURL:) is simply registering to provide data from the file's contents with type public.json.
(lldb) po attachment
<NSItemProvider: 0x7fd4c250f2a0> {types = (
"public.file-url",
"public.json"
)}
It's roughly equivalent to this:
// possible implementation of NSItemProvider.init(contentsOfURL:)
convenience init?(contentsOfURL fileURL: NSURL!)
{
self.init(item: fileURL, typeIdentifier: (fileURL.fileURL ? kUTTypeFileURL : kUTTypeURL) as String)
let type = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, fileURL.pathExtension!, nil)?.takeRetainedValue() as! String
registerItemForTypeIdentifier(type) { completionHandler, expectedValueClass, options in
let data = try! NSData(contentsOfURL: fileURL, options: .DataReadingMappedAlways)
completionHandler(data, nil)
}
}
So you can do this yourself, in memory:
// get the data
let data = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json")!)
// put the data in an item provider
let attachment = NSItemProvider(item: data, typeIdentifier: kUTTypeJSON as String)
// send the item to Safari
let item = NSExtensionItem()
item.attachments = [attachment]
context.completeRequestReturningItems([item], completionHandler: nil);
If you want to provide content dynamically, you can use NSJSONSerialization to transform a dictionary into NSData at runtime.