Using Cocoalumberjack in Swift-project - swift

I'm asking this question because all answers I've found online are either outdated or not working for me.
I'm working with a customers framework and for some reason they require me to use CocoaLumberjack in the project so any suggestions on other Log-tools are useless for me, at least for this project, thank you in advance for understanding
The question:
How do I get the logs from users? I am not that familiar to logging so this is all new to me.
I've written some code with the help of numerous SO-answers and CocoaLumberjacks documentation on Github.
I'm pretty sure that I am actually logging because I can get the logs from Xcode when I run my app on a real device by doing: Xcode -> Window -> Devices and Simulators -> Select the device (and app) -> Download Container.
From the container I can see the logs. But how can I see the logs from user that are not physically here with their device?
AppDelegate.swift :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let formatter = LogFormatter()
DDTTYLogger.sharedInstance.logFormatter = formatter
return true
}
my logformatter is from StackOverflow https://stackoverflow.com/a/14000342/4189589
class LogFormatter: NSObject, DDLogFormatter {
let dateFormatter: DateFormatter
static let sharedInstance = LogFormatter()
override init() {
dateFormatter = DateFormatter()
dateFormatter.formatterBehavior = .behavior10_4
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss:SSS"
DDLog.add(DDOSLogger.sharedInstance) // Uses os_log
let fileLogger: DDFileLogger = DDFileLogger() // File Logger
fileLogger.rollingFrequency = 60 * 60 * 24 // 24 hours
fileLogger.logFileManager.maximumNumberOfLogFiles = 7
DDLog.add(fileLogger)
DDLogDebug("Debug")
DDLogVerbose("Verbose")
DDLogInfo("Info")
DDLogWarn("Warn")
DDLogError("Error")
super.init()
}
func format(message logMessage: DDLogMessage) -> String? {
let dateAndTime = dateFormatter.string(from: logMessage.timestamp)
return "\(dateAndTime) [\(logMessage.fileName):\(logMessage.line)]: \(logMessage.message)"
}
var ddFileLogger: DDFileLogger!
var logFileDataArray: [NSData] {
get {
let logFilePaths = ddFileLogger.logFileManager.sortedLogFilePaths
var logFileDataArray = [NSData]()
for logFilePath in logFilePaths {
let fileURL = NSURL(fileURLWithPath: logFilePath)
if let logFileData = try? NSData(contentsOf: fileURL as URL, options: NSData.ReadingOptions.mappedIfSafe) {
// Insert at front to reverse the order, so that oldest logs appear first.
logFileDataArray.insert(logFileData, at: 0)
}
}
return logFileDataArray
}
}
}
And then I want to email the logs with a button (from same SO-answer)
func emailLogsTo(email: String) {
if MFMailComposeViewController.canSendMail() {
let composeVC = MFMailComposeViewController()
composeVC.mailComposeDelegate = self
// Configure the fields of the interface.
composeVC.setToRecipients([email])
composeVC.setSubject("Feedback for app")
composeVC.setMessageBody("", isHTML: false)
let attachmentData = NSMutableData()
for logFileData in LogFormatter.sharedInstance.logFileDataArray {
attachmentData.append(logFileData as Data)
}
composeVC.addAttachmentData(attachmentData as Data, mimeType: "text/plain", fileName: "diagnostic.log")
self.present(composeVC, animated: true, completion: nil)
} else {
// Tell user about not able to send email directly.
}
}
What happens when I call the function to send the email is that I get an "unexpectedly found nil while implicitly unwrapping an Optional value"-error on
let logFilePaths = ddFileLogger.logFileManager.sortedLogFilePaths
in LogFormatter()
What am I doing wrong?

I only have it in Obj-C I'm afraid:
NSMutableData *logData = [NSMutableData data];
for (NSData *logFileData2 in [self.utilities logfileData]) {
[logData appendData:logFileData2];
}
[picker addAttachmentData:logData mimeType:#"text/plain" fileName:#"logs.txt"];
Then in my utilities class:
- (NSMutableArray *)logfileData {
NSUInteger maximumLogFilesToReturn = MIN(fileLogger.logFileManager.maximumNumberOfLogFiles, 10);
NSMutableArray *logFiles = [NSMutableArray arrayWithCapacity:maximumLogFilesToReturn];
DDFileLogger *logger = fileLogger;
NSArray *sortedLogFileInfos = [logger.logFileManager sortedLogFileInfos];
for (NSUInteger i = 0; i < MIN(sortedLogFileInfos.count, maximumLogFilesToReturn); i++) {
DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:i];
NSData *fileData = [NSData dataWithContentsOfFile:logFileInfo.filePath];
[logFiles addObject:fileData];
}
return logFiles;
}
So maybe for you:
var logFileDataArray: [NSData] {
get {
let sortedLogFileInfos = ddFileLogger.logFileManager.sortedLogFileInfos
var logFileDataArray = [NSData]()
for logFileInfo in sortedLogFileInfos {
if let logFileData = try? NSData(contentsOf: logFileInfo.filePath as String, options: NSData.ReadingOptions.mappedIfSafe) {
// Insert at front to reverse the order, so that oldest logs appear first.
logFileDataArray.insert(logFileData, at: 0)
}
}
return logFileDataArray
}
}

Related

How to Read Data from Text File iOS 15 [duplicate]

This question already has answers here:
UIDocumentPickerViewController is not working when testing with my iPad but fine with simulators
(2 answers)
Closed 12 months ago.
Update: This code works in the simulator, but not on my device. Obviously, I'm needing it to work on both.
I've followed the tutorials, yet I cannot seem to get this feature to work. When the user selects the barButtonItem, DocumentPicker opens allowing the user to select a .txt file. I then take the URL to the selected file and attempt to return a string from it; however, I'm getting the following error: "The file “Test.txt” couldn’t be opened because you don’t have permission to view it." What am I missing? Did I fail to ask for permission somewhere? I've tried cleaning the build folder - didn't work.
#IBAction func importFileBtnTapped(_ sender: Any) {
selectFiles()
}
func selectFiles() {
let types = UTType.types(tag: "txt",
tagClass: UTTagClass.filenameExtension,
conformingTo: nil)
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let myURL = urls.first else {
let alert = SCLAlertView()
alert.showError("ERROR", subTitle: "Unable to retrieve document.")
return
}
let text = createStringFromSelectedFile(fileURL: myURL)
if text == "error" {
print("ERROR creating a string from the selected file.")
return
}
let separatedStrings = decipherString(text: text)
if separatedStrings.first == "error" {
print("ERROR deciphering the string in ClaimInfoViewController")
return
}
for string in separatedStrings {
print("\(string)")
}
print("import result: \(myURL)")
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func createStringFromSelectedFile(fileURL: URL) -> String {
var text = String()
do {
text = try String(contentsOf: fileURL)
}
catch {
print("ERROR in the createStringFromSelectedFile function in ClaimInfoViewController")
print("The error: \(error.localizedDescription)")
let alert = SCLAlertView()
alert.showError("ERROR", subTitle: "Unable to read the file. Please try again.")
return "error"
}
return text
}
func decipherString(text: String) -> [String]{
let newText = text
let startIndexes = ["<Claim#",
"<File#",
"<DateOfLoss:"
]
var claimNumber = String()
var fileNumber = String()
var dateOfLoss = String()
for indexValue in startIndexes {
guard let index = newText.firstIndex(of: ">") else { return ["error"] }
let newString = String(newText[..<index])
if indexValue == "<Claim#" {
claimNumber = newString
}
else if indexValue == "<File#" {
fileNumber = newString
}
else if indexValue == "<DateOfLoss:" {
dateOfLoss = newString
}
}
let finalText = [claimNumber, fileNumber, dateOfLoss]
return finalText
}
Thanks to matt, who commented above, I was able to find out that it's a security issue. Adding this simple code resolved the issue:
let shouldStopAccessing = pickedFolderURL.startAccessingSecurityScopedResource()
defer {
if shouldStopAccessing {
pickedFolderURL.stopAccessingSecurityScopedResource()
}
}
I added it right before this line of code that can be seen above:
let text = createStringFromSelectedFile(fileURL: myURL)
I got this code from here: StackOverflow Post

Issue with file manager in swift 3.1

I have the below code that worked fine, up until the release of swift 3.1.
func loadImage() {
id = userPhotoModel.id
let fileManager = FileManager.default
let imagePath = (self.getDirectoryPath() as NSString).appendingPathComponent(photoName)
if fileManager.fileExists(atPath: imagePath){
let imageFromPath = resizeImage(named: (contentsOfFile: imagePath))
print("name of photo retrieved: \(photoName)")
self.userPhoto.image = imageFromPath
}else{
print("No Image")
}
}
Now, swift 3.1 wants to add as! String to:
let imageFromPath = resizeImage(named: (contentsOfFile: imagePath) as! String)
However, when I run the app, it crashes in this location with no error message as per the below image.
What is causing this?
EDIT: Here is the resizeImage func
fileprivate func resizeImage(named name: String) -> UIImage
{
var image = UIImage(named: name)
if image!.size.height > image!.size.width
{
self.userPhoto.contentMode = .scaleAspectFit
}
else
{
image = image!.resizeTo(self.userPhoto.bounds)
}
return image!
}
The issue is the confusing syntax in the line:
let imageFromPath = resizeImage(named: (contentsOfFile: imagePath))
That should simply be:
let imageFromPath = resizeImage(named: imagePath)
No cast required and correct in any Swift 3.x.

Custom Dimensions in Swift

I have an Objective-C app that integrates with Google Analytics. Now, I'm trying to integrate an app written in Swift.
There's my Objective-C Code:
- (void) signInGoogleAnalytics {
id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
// You only need to set User ID on a tracker once. By setting it on the tracker, the ID will be
// sent with all subsequent hits.
[tracker set:kGAIUserId
value:self.txtStoreCode.text];
NSString *dimensionUsuarioLogado = [NSString stringWithFormat:#"%#", _txtEmployee.text];
NSString *dimensionLoja = [NSString stringWithFormat:#"%#", _txtStoreCode.text];
[tracker send:[[[GAIDictionaryBuilder createScreenView] set:dimensionUsuarioLogado
forKey:[GAIFields customDimensionForIndex:1]] build]];
[tracker send:[[[GAIDictionaryBuilder createScreenView] set:dimensionLoja
forKey:[GAIFields customDimensionForIndex:2]] build]];
}
and I'm trying in swift
func signInGoogleAnalytics() {
let tracker = GAI.sharedInstance().defaultTracker
tracker.set(kGAIUserId, value: txtStore.text)
var dimensionUsuarioLogado = "\(txtUser.text)"
var dimensionLoja = "\(txtStore.text)"
tracker.send(GAIDictionaryBuilder.createScreenView().set(dimensionUsuarioLogado, forKey: GAIFields.customDimension(forIndex: 1)).build())
tracker.send(GAIDictionaryBuilder.createScreenView().set(dimensionLoja, forKey: GAIFields.customDimension(forIndex: 1)).build())
}
but I'm getting GAIFields has no member customDimension. Then, how should be the code in Swift?
It worked for me:
func signInGoogleAnalytics() {
let tracker = GAI.sharedInstance().defaultTracker
tracker.set(kGAIUserId, value: txtStore.text)
let dimensionUsuarioLogado = "\(txtUser.text)"
let dimensionLoja = "\(txtStore.text)"
tracker.send(GAIDictionaryBuilder.createScreenView().set(dimensionUsuarioLogado, forKey: GAIFields.customDimensionForIndex(1)).build() as NSDictionary as [NSObject : AnyObject])
tracker.send(GAIDictionaryBuilder.createScreenView().set(dimensionLoja, forKey: GAIFields.customDimensionForIndex(2)).build() as NSDictionary as [NSObject : AnyObject])
}
I was tracking custom exception with custom dimension:
//MARK:- CUSTOM EXCEPTION TRACKING
func doTrackCustomExceptionWithGA(message:String, customDimensionValue:String, isFatal:Bool = false) {
guard let tracker = GAI.sharedInstance()?.defaultTracker else { return }
guard let exceptionBuilder = GAIDictionaryBuilder.createException(withDescription: message, withFatal: NSNumber(value: isFatal)) else { return }
if !customDimensionValue.isEmpty {
exceptionBuilder.set(customDimensionValue, forKey: GAIFields.customDimension(for: 15))
}
guard let build = exceptionBuilder.build() as? [AnyHashable : Any] else { return }
tracker.send(build)
// ADDING DUMMY EVENT TO TRACK PREVIOUS EVENT QUICKLY, AS GA EVENTS ARE TRACKED ON NEXT EVENT CALLS
// BELOW CODE IS OPTIONAL
let event = GAIDictionaryBuilder.createScreenView()
tracker.send(event?.build() as! [NSObject: Any])
}

NSKeyedArchiver.archiveRootObject returns "false" if I want to overwrite data, why?

NSKeyedArchiver.archiveRootObject(<#rootObject: AnyObject#>, toFile: <#String#>)
Only returns true the first time. Every next time I call it, the method returns false.
I read some SO, some posts said that I can't rewrite data this way. However, I tried :
NSFileManager.defaultManager().removeItemAtPath(path, error: nil)
and it still didn't help.
What I did:
Checked all my model files for the NSCoding protocol
Checked all my required init(coder aDecoder: NSCoder) and func encodeWithCoder(aCoder: NSCoder)
I am missing something, since I have done this in my last app and it worked fla`
import Foundation
private let ON_DISK_DATA_DICTIONARY = "savedDataPathsOnDisk"
private let _WBMAccessDataOnDiskMShared = WBMAccessDataOnDiskM()
private var dataDirectories:NSArray! = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
private var dataDirectoryURL:NSURL! = NSURL(fileURLWithPath: dataDirectories.objectAtIndex(0) as! String, isDirectory: true)
private var dataDirectoryPath:String! = dataDirectoryURL.path!
let FILE_FORMAT = ".archive"
class WBMAccessDataOnDiskM: NSObject
{
class var sharedData: WBMAccessDataOnDiskM
{
return _WBMAccessDataOnDiskMShared
}
private var dataAndPathDictionary = [String:String]()
func getDataAndPathDictionary() -> [String:String]
{
return self.dataAndPathDictionary
}
func addDataAndPathToDictionary(data:String ,path:String)
{
if !checkIfDataAllreadyExists(data)
{
let fullPath = createFullDataPath(path)
dataAndPathDictionary[data] = fullPath
NSUserDefaults.standardUserDefaults().setObject(dataAndPathDictionary, forKey: ON_DISK_DATA_DICTIONARY)
}
}
func checkIfDataIsAvailable(dataPathComponent:String) -> (Bool,String)
{
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
var dataPath = paths.stringByAppendingPathComponent(dataPathComponent)
var checkValidation = NSFileManager.defaultManager()
println(dataPathComponent)
if (checkValidation.fileExistsAtPath(dataPath))
{
return (true,dataPath)
}
else
{
return (false,"")
}
}
func checkForDataOnDisk() -> Bool
{
let dataDict = NSUserDefaults.standardUserDefaults().objectForKey(ON_DISK_DATA_DICTIONARY) as? [String:String]
if dataDict == nil
{
return false
}
else
{
dataAndPathDictionary = dataDict!
return true
}
}
private func checkIfDataAllreadyExists(data:String) -> Bool
{
let keys = self.dataAndPathDictionary.keys.array
if contains(keys, data)
{
return true
}
return false
}
private func createFullDataPath(path:String) -> String
{
var fullPathURL = dataDirectoryURL.URLByAppendingPathComponent(path + FILE_FORMAT)
return fullPathURL.path!
}
func saveDataArray(data:[AnyObject], path:String)
{
NSFileManager.defaultManager().removeItemAtPath(path, error: nil)
if NSKeyedArchiver.archiveRootObject(data, toFile: path)
{
// SAVING
println(" Saving data ARRAY ")
}
else
{
println(" NOT saving data ARRAY ")
}
}
func saveDataObject(dataObject:AnyObject, path:String)
{
if NSKeyedArchiver.archiveRootObject(dataObject, toFile: path)
{
println(" Saving data OBJECT ")
}
else
{
println(" NOT saving data OBJECT ")
}
}
// dataFromDisk = NSKeyedUnarchiver.unarchiveObjectWithFile(pathForNews) as? [AnyObject]
func loadDataArray(path:String) -> [AnyObject]?
{
var dataArrayFromDisk: [AnyObject]?
dataArrayFromDisk = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [AnyObject]
return dataArrayFromDisk
}
func loadDataObject(path:String) -> AnyObject?
{
var dataObjectFromDisk: AnyObject?
dataObjectFromDisk = NSKeyedUnarchiver.unarchiveObjectWithFile(path)
return dataObjectFromDisk
}
func getNewsDataLanguagePath() -> String
{
var currentOSLanguage = LOCALIZATION.currentOsLanguage
currentOSLanguage = currentOSLanguage.substringToIndex(2)
if currentOSLanguage == "de"
{
return ON_DISK_CONTENT_DE
}
else if currentOSLanguage == "en"
{
return ON_DISK_CONTENT_ENG
}
return ON_DISK_CONTENT_ENG
}
`
I am using Xcode 6.4 and Swift 1.2.
Any help & code correction is welcome.
Because of the code you put here does't contain the call of saveDataArray or saveDataObject so I judge that you have maintain the path of a archived object manually.This is where thing went wrong. The method of NSKeyedArchiver named archiveRootObject can automatically maintain the archiver file path.
In the Apple's doucumet
Archives an object graph rooted at a given object by encoding it into a data object then atomically writes the resulting data object to a file at a given path, and returns a Boolean value that indicates whether the operation was successful.
And there is another question in SO may help you.
I followed apple instructions in this good example: Persist Data
But I had the same problem you describe with my app for AppleTV. At the end I change .Documents directory for CacheDirectory and it's working well.
static let DocumentsDirectorio = NSFileManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first!

Definition of Global Variable Resetting

I have a class designed to take the temperature data from an API for a specific date and add it to a dictionary. The URL for the API is stored in a global variable called baseURL. It is defined at the beginning as an empty string, but is later changed. My class is below:
import UIKit
import Foundation
typealias ServiceResponse = (JSON, NSError?) -> Void
class WeatherManager: NSObject {
var baseURL: String = ""
var data: String = ""
static let sharedInstance = WeatherManager()
func getRandomUser(onCompletion: (JSON) -> Void) {
println("Starting getRandomUser")
let route = self.baseURL
println(self.baseURL)
makeHTTPGetRequest(route, onCompletion: { json, err in
onCompletion(json as JSON)
})
}
func makeHTTPGetRequest(path: String, onCompletion: ServiceResponse) {
let request = NSMutableURLRequest(URL: NSURL(string: path)!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
let json:JSON = JSON(data: data)
onCompletion(json, error)
if error != nil {
println("No Error")
} else {
println("Error")
}
})
task.resume()
}
func addData() {
WeatherManager.sharedInstance.getRandomUser { json in
var jsonData = json["response"]["version"]
self.data = "\(jsonData)"
dispatch_async(dispatch_get_main_queue(),{
let alert = UIAlertView()
alert.title = "Weather Data Update"
if self.data != "null" {
println("Value:\(self.data)")
alert.message = "The weather data was updated successfully."
alert.addButtonWithTitle("OK")
alert.show()
} else {
println("Error Reading Data")
println(self.data)
alert.message = "HealthTrendFinder encountered an error while updating data."
alert.addButtonWithTitle("OK")
alert.show()
}
})
}
}
func updateWeatherHistory() {
println(self.baseURL)
let calendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
println("Weather Updating...")
// This sets the start date to midnight of the current date if no start date has been set.
if StorageManager.getValue(StorageManager.StorageKeys.WeatherStartDate) == nil {
let startDate: NSDate = calendar.startOfDayForDate(NSDate())
StorageManager.setValue(startDate, forKey: StorageManager.StorageKeys.WeatherStartDate)
}
// This adds a data array if it hasn't been created yet.
if StorageManager.getValue(StorageManager.StorageKeys.WeatherData) == nil {
StorageManager.setValue([:], forKey: StorageManager.StorageKeys.WeatherData)
}
var weatherData: [NSDate: NSObject] = StorageManager.getValue(StorageManager.StorageKeys.WeatherData)! as! [NSDate : NSObject]
let startMidnight: NSDate = StorageManager.getValue(StorageManager.StorageKeys.WeatherStartDate) as! NSDate
let currentMidnight: NSDate = calendar.startOfDayForDate(NSDate())
let daysFromStartDate: Int = calendar.components(NSCalendarUnit.CalendarUnitDay, fromDate: startMidnight, toDate: currentMidnight, options: nil).day
println("Starting Loop")
for i: Int in 0..<daysFromStartDate {
let dateToBeExamined: NSDate = calendar.dateByAddingUnit(NSCalendarUnit.CalendarUnitDay, value: i, toDate: startMidnight, options: nil)!
if weatherData[dateToBeExamined] == nil {
let calendarUnits: NSCalendarUnit = .CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear
let components = NSCalendar.currentCalendar().components(calendarUnits, fromDate: dateToBeExamined)
var month: String
var day: String
if components.month < 10 {
month = "0\(components.month)"
} else {
month = "\(components.month)"
}
if components.day < 10 {
day = "0\(components.day)"
} else {
day = "\(components.day)"
}
var dateString = "\(components.year)\(month)\(day)"
self.baseURL = "http://api.wunderground.com/api/91e65f0fbb35f122/history_\(dateString)/q/OR/Portland.json"
println(self.baseURL)
var get: () = WeatherManager.sharedInstance.addData()
println(get)
weatherData[dateToBeExamined] = self.data
// There is no data for the NSDate dateForInspection. You need to pull data and add it to the dictionary.
} else {
// Data exists for the specified date, so you don't need to do anything.
}
}
println("Loop has finished or been skipped")
}
}
The problem is, baseURL reverts to an empty string when getRandomUser is executed, after baseURL is set to the URL. Why is this happening, and how do I fix it?
Your code is unnecessarily complex, making it hard to diagnose the problem without more information. But here is a suggestion:
Try making it impossible to instantiate more than one instance of your WeatherManager singleton:
class WeatherManager {
private static let _sharedInstance = WeatherManager()
private init() { super.init() }
static func sharedInstance() -> WeatherManager {
return _sharedInstance
}
}
When you are working from outside WeatherManager, you access it by calling:
let wm = WeatherManager.sharedInstane()
Then, when you are working inside WeatherManager, make sure that all your references are to self - i.e., self.baseURL = ... or self.updateWeatherHistory(), instead of WeatherManager.sharedInstance.baseURL = ..., etc.
Though your code is complicated, I think what is going on is you actually have two instances of WeatherManager in play. You are setting the value of baseURL on one, but not the other. If you want it to be a singleton, you need to make it impossible to create more than one.