diff --git a/OpenGpxTracker-Watch Extension/ComplicationController.swift b/OpenGpxTracker-Watch Extension/ComplicationController.swift index e51be322..482838d1 100644 --- a/OpenGpxTracker-Watch Extension/ComplicationController.swift +++ b/OpenGpxTracker-Watch Extension/ComplicationController.swift @@ -8,12 +8,12 @@ import ClockKit - class ComplicationController: NSObject, CLKComplicationDataSource { // MARK: - Timeline Configuration - func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) { + func getSupportedTimeTravelDirections(for complication: CLKComplication, + withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) { handler([.forward, .backward]) } @@ -36,12 +36,14 @@ class ComplicationController: NSObject, CLKComplicationDataSource { handler(nil) } - func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) { + func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, + withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) { // Call the handler with the timeline entries prior to the given date handler(nil) } - func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) { + func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, + withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) { // Call the handler with the timeline entries after to the given date handler(nil) } diff --git a/OpenGpxTracker-Watch Extension/ExtensionDelegate.swift b/OpenGpxTracker-Watch Extension/ExtensionDelegate.swift index a7863e22..4d3ce3f5 100644 --- a/OpenGpxTracker-Watch Extension/ExtensionDelegate.swift +++ b/OpenGpxTracker-Watch Extension/ExtensionDelegate.swift @@ -15,16 +15,20 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate { } func applicationDidBecomeActive() { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + // Restart any tasks that were paused (or not yet started) while the application was inactive. + // If the application was previously in the background, optionally refresh the user interface. } func applicationWillResignActive() { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Sent when the application is about to move from active to inactive state. + // This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) + // or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, etc. } func handle(_ backgroundTasks: Set) { - // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one. + // Sent when the system needs to launch the application in the background to process tasks. + // Tasks arrive in a set, so loop through and process each one. for task in backgroundTasks { // Use a switch statement to check the task type switch task { diff --git a/OpenGpxTracker-Watch Extension/GPXFileTableInterfaceController.swift b/OpenGpxTracker-Watch Extension/GPXFileTableInterfaceController.swift index 986647da..94fe1cfc 100644 --- a/OpenGpxTracker-Watch Extension/GPXFileTableInterfaceController.swift +++ b/OpenGpxTracker-Watch Extension/GPXFileTableInterfaceController.swift @@ -37,23 +37,21 @@ class GPXFileTableInterfaceController: WKInterfaceController { var fileList: NSMutableArray = [kNoFiles] /// Is there any GPX file in the directory? - var gpxFilesFound = false; + var gpxFilesFound = false /// Temporary variable to manage var selectedRowIndex = -1 /// true if a gpx file will be sent. var willSendFile: Bool { - get { - return session?.outstandingFileTransfers.count != 0 - } + return session?.outstandingFileTransfers.count != 0 } /// To ensure hide animation properly timed. var time = DispatchTime.now() /// Watch communication session - private let session : WCSession? = WCSession.isSupported() ? WCSession.default : nil + private let session: WCSession? = WCSession.isSupported() ? WCSession.default: nil override func awake(withContext context: Any?) { super.awake(withContext: context) @@ -64,7 +62,7 @@ class GPXFileTableInterfaceController: WKInterfaceController { // MARK: Progress Indicators /// States of sending files - enum sendingStatus { + enum SendingStatus { /// represents current state as sending case sending /// represents current state as successful @@ -88,7 +86,8 @@ class GPXFileTableInterfaceController: WKInterfaceController { self.progressGroup.setHeight(0) }) } - // imageview do not have to be set with stop animating, as image indicator should already have been set as successful or failure image, which is static. + // imageview do not have to be set with stop animating, + // as image indicator should already have been set as successful or failure image, which is static. } /// Displays progress indicators. @@ -98,13 +97,13 @@ class GPXFileTableInterfaceController: WKInterfaceController { self.progressGroup.setHeight(30) self.progressGroup.setHidden(false) progressImageView.setImageNamed("Progress-") - progressImageView.startAnimatingWithImages(in: NSMakeRange(0, 12), duration: 1, repeatCount: 0) + progressImageView.startAnimatingWithImages(in: NSRange(location: 0, length: 12), duration: 1, repeatCount: 0) } /// Updates progress indicators according to status when sending. /// /// If status is success or failure, method will hide and animate progress indicators when done - func updateProgressIndicators(status: sendingStatus, fileName: String?) { + func updateProgressIndicators(status: SendingStatus, fileName: String?) { switch status { case .sending: progressTitle.setText(NSLocalizedString("SENDING", comment: "no comment")) @@ -115,8 +114,7 @@ class GPXFileTableInterfaceController: WKInterfaceController { // if there are files pending for sending, filename will not be displayed with the name of file. if fileTransfersCount >= 1 { progressFileName.setText(String(format: NSLocalizedString("X_FILES", comment: "no comment"), fileTransfersCount + 1)) - } - else { + } else { progressFileName.setText(fileName) } @@ -144,8 +142,7 @@ class GPXFileTableInterfaceController: WKInterfaceController { if willSendFile == true { self.showProgressIndicators() - } - else { + } else { self.hideProgressIndicators() } @@ -179,11 +176,11 @@ class GPXFileTableInterfaceController: WKInterfaceController { if gpxFilesFound { for index in 0.. String { @@ -314,8 +306,7 @@ class InterfaceController: WKInterfaceController { print("fileName:" + dateFormatter.string(from: Date())) return dateFormatter.string(from: Date()) } - - + /// /// Checks the location services status /// - Are location services enabled (access to location device wide)? If not => displays an alert @@ -346,10 +337,11 @@ class InterfaceController: WKInterfaceController { print("LocationServicesDisabledAlert: cancel pressed") } - presentAlert(withTitle: NSLocalizedString("LOCATION_SERVICES_DISABLED", comment: "no comment"), message: NSLocalizedString("ENABLE_LOCATION_SERVICES", comment: "no comment"), preferredStyle: .alert, actions: [button]) + presentAlert(withTitle: NSLocalizedString("LOCATION_SERVICES_DISABLED", comment: "no comment"), + message: NSLocalizedString("ENABLE_LOCATION_SERVICES", comment: "no comment"), + preferredStyle: .alert, actions: [button]) } - /// /// Displays an alert that informs the user that access to location was denied for this app (other apps may have access). /// It also dispays a button allows the user to go to settings to activate the location. @@ -362,7 +354,9 @@ class InterfaceController: WKInterfaceController { print("LocationServicesDeniedAlert: cancel pressed") } - presentAlert(withTitle: NSLocalizedString("ACCESS_TO_LOCATION_DENIED", comment: "no comment"), message: NSLocalizedString("ALLOW_LOCATION", comment: "no comment"), preferredStyle: .alert, actions: [button]) + presentAlert(withTitle: NSLocalizedString("ACCESS_TO_LOCATION_DENIED", comment: "no comment"), + message: NSLocalizedString("ALLOW_LOCATION", comment: "no comment"), + preferredStyle: .alert, actions: [button]) } } @@ -383,7 +377,6 @@ extension InterfaceController: StopWatchDelegate { // MARK: CLLocationManagerDelegate - extension InterfaceController: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { @@ -417,9 +410,10 @@ extension InterfaceController: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { //updates signal image accuracy let newLocation = locations.first! - print("didUpdateLocation: received \(newLocation.coordinate) hAcc: \(newLocation.horizontalAccuracy) vAcc: \(newLocation.verticalAccuracy) floor: \(newLocation.floor?.description ?? "''")") let hAcc = newLocation.horizontalAccuracy + let vAcc = newLocation.verticalAccuracy + print("didUpdateLocation: received \(newLocation.coordinate) hAcc: \(hAcc) vAcc: \(vAcc) floor: \(newLocation.floor?.description ?? "''")") signalAccuracyLabel.setText(hAcc.toAccuracy(useImperial: preferences.useImperial)) if hAcc < kSignalAccuracy6 { @@ -434,7 +428,7 @@ extension InterfaceController: CLLocationManagerDelegate { self.signalImageView.setImage(signalImage2) } else if hAcc < kSignalAccuracy1 { self.signalImageView.setImage(signalImage1) - } else{ + } else { self.signalImageView.setImage(signalImage0) } @@ -455,6 +449,4 @@ extension InterfaceController: CLLocationManagerDelegate { //currentSegmentDistanceLabel.distance = map.currentSegmentDistance } } - } - diff --git a/OpenGpxTracker.xcodeproj/project.pbxproj b/OpenGpxTracker.xcodeproj/project.pbxproj index d0457ad1..f33d4034 100644 --- a/OpenGpxTracker.xcodeproj/project.pbxproj +++ b/OpenGpxTracker.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 0175ABFF23F014C7003323C3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0175AC0123F014C7003323C3 /* LaunchScreen.storyboard */; }; 120DE71022B274910055C4CB /* GPXSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 120DE70F22B274910055C4CB /* GPXSession.swift */; }; 120DE71122B274A40055C4CB /* GPXSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 120DE70F22B274910055C4CB /* GPXSession.swift */; }; + 12A1A44424D561AC0057ED7A /* CoreDataHelper+FetchRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A1A44324D561AC0057ED7A /* CoreDataHelper+FetchRequests.swift */; }; 4588D66B230D23AA009AA75F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4588D66D230D23AA009AA75F /* InfoPlist.strings */; }; 4588D66F230D2476009AA75F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4588D671230D2476009AA75F /* InfoPlist.strings */; }; 45A430E9230D61240003C2F2 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45A430EB230D61240003C2F2 /* Interface.storyboard */; }; @@ -49,6 +50,7 @@ 89F8749B1BBB7362004EF41A /* GPXTrack+length.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F8749A1BBB7362004EF41A /* GPXTrack+length.swift */; }; 89F8749F1BBCB97F004EF41A /* GPXRoot+length.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F8749E1BBCB97F004EF41A /* GPXRoot+length.swift */; }; BF15092223916FC100F51F1B /* UIColor+DarkMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF15092123916FC100F51F1B /* UIColor+DarkMode.swift */; }; + BF177EEA24D54F9200D5E021 /* CoreDataHelper+BatchDelete.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF177EE924D54F9200D5E021 /* CoreDataHelper+BatchDelete.swift */; }; BF1FE1A8230EAB9B00404B59 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF1FE1A6230EAB9B00404B59 /* Localizable.strings */; }; BF214B5E22E60B64000E96AA /* CLActivityType+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF214B5D22E60B64000E96AA /* CLActivityType+Info.swift */; }; BF36B6CF220C6F1A00077B46 /* GPXFileTableInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF36B6CE220C6F1A00077B46 /* GPXFileTableInterfaceController.swift */; }; @@ -149,6 +151,7 @@ 0175AC0523F01519003323C3 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LaunchScreen.strings; sourceTree = ""; }; 0175AC0723F01523003323C3 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; }; 120DE70F22B274910055C4CB /* GPXSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPXSession.swift; sourceTree = ""; }; + 12A1A44324D561AC0057ED7A /* CoreDataHelper+FetchRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreDataHelper+FetchRequests.swift"; sourceTree = ""; }; 1262DD9624F9568F00B5C2EA /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Interface.strings; sourceTree = ""; }; 1262DD9724F9568F00B5C2EA /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 1262DD9824F9568F00B5C2EA /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -222,6 +225,7 @@ 95D28CBDF2535138FA16F38F /* Pods_OpenGpxTracker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OpenGpxTracker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9A591D96EF2E41D00AD6BB3A /* Pods-OpenGpxTracker-Watch Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OpenGpxTracker-Watch Extension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OpenGpxTracker-Watch Extension/Pods-OpenGpxTracker-Watch Extension.debug.xcconfig"; sourceTree = ""; }; BF15092123916FC100F51F1B /* UIColor+DarkMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+DarkMode.swift"; sourceTree = ""; }; + BF177EE924D54F9200D5E021 /* CoreDataHelper+BatchDelete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreDataHelper+BatchDelete.swift"; sourceTree = ""; }; BF1FE1A7230EAB9B00404B59 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; BF1FE1A9230EABA500404B59 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; BF214B5D22E60B64000E96AA /* CLActivityType+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CLActivityType+Info.swift"; sourceTree = ""; }; @@ -322,6 +326,8 @@ 89EA86642157C444002E03A7 /* Int+asFilesize.swift */, 89EA86622157B42C002E03A7 /* GPXFileInfo.swift */, BF6408A2225C63E700BB5242 /* CoreDataHelper.swift */, + 12A1A44324D561AC0057ED7A /* CoreDataHelper+FetchRequests.swift */, + BF177EE924D54F9200D5E021 /* CoreDataHelper+BatchDelete.swift */, BF214B5D22E60B64000E96AA /* CLActivityType+Info.swift */, BF15092123916FC100F51F1B /* UIColor+DarkMode.swift */, BFB06F01244A037300AC0CDC /* UIColor+Keyboard.swift */, @@ -813,12 +819,14 @@ 8913CD8E1BDB2477009EC729 /* PreferencesTableViewControllerDelegate.swift in Sources */, 899D676E19CD9FAF00C88A0A /* GPXTrackPoint+MapKit.swift in Sources */, 89F8749B1BBB7362004EF41A /* GPXTrack+length.swift in Sources */, + BF177EEA24D54F9200D5E021 /* CoreDataHelper+BatchDelete.swift in Sources */, BFB06F02244A037300AC0CDC /* UIColor+Keyboard.swift in Sources */, BFA3CE8222C23B2300A8B965 /* CoreDataAlertView.swift in Sources */, 899D678219D351DA00C88A0A /* AboutViewController.swift in Sources */, 89EA86612157A08E002E03A7 /* Date+timeAgo.swift in Sources */, 895F04B41A7143CE004BEE8A /* GPXExtentCoordinates.swift in Sources */, 899D677B19D10F7C00C88A0A /* GPXFilesTableViewControlllerDelegate.swift in Sources */, + 12A1A44424D561AC0057ED7A /* CoreDataHelper+FetchRequests.swift in Sources */, 8913CD8C1BDB0A9A009EC729 /* PreferencesTableViewController.swift in Sources */, 899D677D19D22E1B00C88A0A /* StopWatchDelegate.swift in Sources */, 898EECDB19C49B5800B4B207 /* AppDelegate.swift in Sources */, diff --git a/OpenGpxTracker/AboutViewController.swift b/OpenGpxTracker/AboutViewController.swift index e63c0c40..f8cdb06f 100644 --- a/OpenGpxTracker/AboutViewController.swift +++ b/OpenGpxTracker/AboutViewController.swift @@ -43,7 +43,9 @@ class AboutViewController: UIViewController { self.title = NSLocalizedString("ABOUT", comment: "no comment") //Add the done button - let shareItem = UIBarButtonItem(title: NSLocalizedString("DONE", comment: "no comment"), style: UIBarButtonItem.Style.plain, target: self, action: #selector(AboutViewController.closeViewController)) + let shareItem = UIBarButtonItem(title: NSLocalizedString("DONE", comment: "no comment"), + style: UIBarButtonItem.Style.plain, target: self, + action: #selector(AboutViewController.closeViewController)) self.navigationItem.rightBarButtonItems = [shareItem] //Add the Webview @@ -73,7 +75,8 @@ class AboutViewController: UIViewController { extension AboutViewController: WKNavigationDelegate { /// Opens Safari when user clicks a link in the About page. - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { print("AboutViewController: decidePolicyForNavigationAction") if navigationAction.navigationType == .linkActivated { diff --git a/OpenGpxTracker/AppDelegate.swift b/OpenGpxTracker/AppDelegate.swift index 4b489546..3385ec25 100644 --- a/OpenGpxTracker/AppDelegate.swift +++ b/OpenGpxTracker/AppDelegate.swift @@ -10,8 +10,6 @@ import UIKit import CoreData import WatchConnectivity - -/// @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -57,8 +55,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { session.delegate = self session.activate() print("AppDelegate:: WCSession activated") - } - else { + } else { print("AppDelegate:: WCSession is not supported") } } @@ -74,15 +71,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } /// Default pandle load GPX file - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { print("load gpx File: \(url.absoluteString)") let fileManager = FileManager.default do { _ = url.startAccessingSecurityScopedResource() try fileManager.copyItem(at: url, to: GPXFileManager.GPXFilesFolderURL.appendingPathComponent(url.lastPathComponent)) url.stopAccessingSecurityScopedResource() - } - catch let error as NSError { + } catch let error as NSError { print("Ooops! Something went wrong: \(error)") } @@ -121,7 +117,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var failureReason = "There was an error creating or loading the application's saved data." do { - try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]) + try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, + configurationName: nil, + at: url, + options: [NSMigratePersistentStoresAutomaticallyOption: true, + NSInferMappingModelAutomaticallyOption: true]) } catch { // Report any error we got. var dict = [String: AnyObject]() @@ -211,6 +211,7 @@ extension AppDelegate: WCSessionDelegate { /// Called when a file is received from Apple Watch. /// Displays a popup informing about the reception of the file. func session(_ session: WCSession, didReceive file: WCSessionFile) { + // swiftlint:disable force_cast let fileName = file.metadata!["fileName"] as! String? DispatchQueue.global().sync { diff --git a/OpenGpxTracker/CoreDataHelper+BatchDelete.swift b/OpenGpxTracker/CoreDataHelper+BatchDelete.swift new file mode 100644 index 00000000..d13bde2f --- /dev/null +++ b/OpenGpxTracker/CoreDataHelper+BatchDelete.swift @@ -0,0 +1,80 @@ +// +// CoreDataHelper+BatchDelete.swift +// OpenGpxTracker +// +// Created by Vincent on 1/8/20. +// + +import CoreData + +extension CoreDataHelper { + + @available(iOS 10.0, *) + func modernBatchDelete(of type: T.Type) { + let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + privateManagedObjectContext.parent = appDelegate.managedObjectContext + + privateManagedObjectContext.perform { + do { + let name = "\(T.self)" // Generic name of the object is the entityName + let fetchRequest = NSFetchRequest(entityName: name) + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + + // execute delete request. + try privateManagedObjectContext.execute(deleteRequest) + + try privateManagedObjectContext.save() + + self.appDelegate.managedObjectContext.performAndWait { + do { + // Saves the changes from the child to the main context to be applied properly + try self.appDelegate.managedObjectContext.save() + } catch { + print("Failure to save context after delete: \(error)") + } + } + } catch { + print("Failed to delete all from core data, error: \(error)") + } + + } + } + + func legacyBatchDelete(of type: T.Type) { + let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + privateManagedObjectContext.parent = appDelegate.managedObjectContext + // Creates a fetch request + let fetchRequest = NSFetchRequest(entityName: "\(T.self)") + fetchRequest.includesPropertyValues = false + let asynchronousWaypointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in + + // Retrieves an array of points from Core Data + guard let results = asynchronousFetchResult.finalResult else { return } + + for result in results { + privateManagedObjectContext.delete(result) + } + + do { + try privateManagedObjectContext.save() + self.appDelegate.managedObjectContext.performAndWait { + do { + // Saves the changes from the child to the main context to be applied properly + try self.appDelegate.managedObjectContext.save() + } catch { + print("Failure to save context: \(error)") + } + } + } catch { + print("Failure to save context at child context: \(error)") + } + } + + do { + try privateManagedObjectContext.execute(asynchronousWaypointFetchRequest) + } catch let error { + print("NSAsynchronousFetchRequest (while deleting \(T.self) error: \(error)") + } + + } +} diff --git a/OpenGpxTracker/CoreDataHelper+FetchRequests.swift b/OpenGpxTracker/CoreDataHelper+FetchRequests.swift new file mode 100644 index 00000000..afc337c3 --- /dev/null +++ b/OpenGpxTracker/CoreDataHelper+FetchRequests.swift @@ -0,0 +1,121 @@ +// +// CoreDataHelper+FetchRequests.swift +// OpenGpxTracker +// +// Created by Vincent Neo on 1/8/20. +// + +import CoreData +import CoreGPX + +extension CoreDataHelper { + + func rootFetchRequest() -> NSAsynchronousFetchRequest { + let rootFetchRequest = NSFetchRequest(entityName: "CDRoot") + let asyncRootFetchRequest = NSAsynchronousFetchRequest(fetchRequest: rootFetchRequest) { asynchronousFetchResult in + guard let rootResults = asynchronousFetchResult.finalResult else { return } + + DispatchQueue.main.async { + guard let objectID = rootResults.last?.objectID else { self.lastFileName = ""; return } + guard let safePoint = self.appDelegate.managedObjectContext.object(with: objectID) as? CDRoot else { self.lastFileName = ""; return } + self.lastFileName = safePoint.lastFileName ?? "" + self.lastTracksegmentId = safePoint.lastTrackSegmentId + self.isContinued = safePoint.continuedAfterSave + } + } + return asyncRootFetchRequest + } + + func trackPointFetchRequest() -> NSAsynchronousFetchRequest { + // Creates a fetch request + let trkptFetchRequest = NSFetchRequest(entityName: "CDTrackpoint") + // Ensure that fetched data is ordered + let sortTrkpt = NSSortDescriptor(key: "trackpointId", ascending: true) + trkptFetchRequest.sortDescriptors = [sortTrkpt] + + // Creates `asynchronousFetchRequest` with the fetch request and the completion closure + let asynchronousTrackPointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: trkptFetchRequest) { asynchronousFetchResult in + + print("Core Data Helper: fetching recoverable trackpoints from Core Data") + + guard let trackPointResults = asynchronousFetchResult.finalResult else { return } + // Dispatches to use the data in the main queue + DispatchQueue.main.async { + self.tracksegmentId = trackPointResults.first?.trackSegmentId ?? 0 + + for result in trackPointResults { + let objectID = result.objectID + + // thread safe + guard let safePoint = self.appDelegate.managedObjectContext.object(with: objectID) as? CDTrackpoint else { continue } + + if self.tracksegmentId != safePoint.trackSegmentId { + if self.currentSegment.trackpoints.count > 0 { + self.tracksegments.append(self.currentSegment) + self.currentSegment = GPXTrackSegment() + } + + self.tracksegmentId = safePoint.trackSegmentId + } + + let pt = GPXTrackPoint(latitude: safePoint.latitude, longitude: safePoint.longitude) + + pt.time = safePoint.time + pt.elevation = safePoint.elevation + + self.currentSegment.trackpoints.append(pt) + + } + self.trackpointId = trackPointResults.last?.trackpointId ?? Int64() + self.tracksegments.append(self.currentSegment) + } + } + + return asynchronousTrackPointFetchRequest + } + + func waypointFetchRequest() -> NSAsynchronousFetchRequest { + let wptFetchRequest = NSFetchRequest(entityName: "CDWaypoint") + let sortWpt = NSSortDescriptor(key: "waypointId", ascending: true) + wptFetchRequest.sortDescriptors = [sortWpt] + + let asynchronousWaypointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: wptFetchRequest) { asynchronousFetchResult in + + print("Core Data Helper: fetching recoverable waypoints from Core Data") + + // Retrieves an array of points from Core Data + guard let waypointResults = asynchronousFetchResult.finalResult else { return } + + // Dispatches to use the data in the main queue + DispatchQueue.main.async { + for result in waypointResults { + let objectID = result.objectID + + // thread safe + guard let safePoint = self.appDelegate.managedObjectContext.object(with: objectID) as? CDWaypoint else { continue } + + let pt = GPXWaypoint(latitude: safePoint.latitude, longitude: safePoint.longitude) + + pt.time = safePoint.time + pt.desc = safePoint.desc + pt.name = safePoint.name + if safePoint.elevation != .greatestFiniteMagnitude { + pt.elevation = safePoint.elevation + } + + self.waypoints.append(pt) + } + + self.waypointId = waypointResults.last?.waypointId ?? Int64() + + // trackpoint request first, followed by waypoint request + // hence, crashFileRecovery method is ran in this. + self.crashFileRecovery() // should always be in the LAST fetch request! + print("Core Data Helper: async fetches complete.") + } + } + + return asynchronousWaypointFetchRequest + } + +} diff --git a/OpenGpxTracker/CoreDataHelper.swift b/OpenGpxTracker/CoreDataHelper.swift index f0a6a647..44dd027d 100644 --- a/OpenGpxTracker/CoreDataHelper.swift +++ b/OpenGpxTracker/CoreDataHelper.swift @@ -17,7 +17,7 @@ import CoreGPX /// class CoreDataHelper { - // MARK:- IDs + // MARK: IDs // ids to keep track of object's sequence /// for waypoints @@ -31,9 +31,10 @@ class CoreDataHelper { var isContinued = false var lastTracksegmentId = Int64() - // MARK:- Other Declarations + // MARK: Other Declarations /// app delegate. + // swiftlint:disable force_cast let appDelegate = UIApplication.shared.delegate as! AppDelegate // arrays for handling retrieval of data when needed. @@ -50,7 +51,7 @@ class CoreDataHelper { // last file name of the recovered file, if the recovered file was a continuation. var lastFileName = String() - // MARK:- Add to Core Data + // MARK: Add to Core Data /// Adds the last file name to Core Data /// @@ -80,8 +81,7 @@ class CoreDataHelper { print("Failure to save parent context when adding last file name: \(error)") } } - } - catch { + } catch { print("Failure to save child context when adding last file name: \(error)") } } @@ -101,6 +101,7 @@ class CoreDataHelper { childManagedObjectContext.perform { print("Core Data Helper: Add trackpoint with id: \(self.trackpointId)") + // swiftlint:disable force_cast let pt = NSEntityDescription.insertNewObject(forEntityName: "CDTrackpoint", into: childManagedObjectContext) as! CDTrackpoint guard let elevation = trackpoint.elevation else { return } @@ -118,8 +119,7 @@ class CoreDataHelper { do { let serialized = try JSONEncoder().encode(trackpoint) pt.serialized = serialized - } - catch { + } catch { print("Core Data Helper: serialization error when adding trackpoint: \(error)") } @@ -135,8 +135,7 @@ class CoreDataHelper { print("Failure to save parent context when adding trackpoint: \(error)") } } - } - catch { + } catch { print("Failure to save child context when adding trackpoint: \(error)") } } @@ -154,6 +153,7 @@ class CoreDataHelper { waypointChildManagedObjectContext.perform { print("Core Data Helper: Add waypoint with id: \(self.waypointId)") + // swiftlint:disable force_cast let pt = NSEntityDescription.insertNewObject(forEntityName: "CDWaypoint", into: waypointChildManagedObjectContext) as! CDWaypoint guard let latitude = waypoint.latitude else { return } @@ -161,8 +161,7 @@ class CoreDataHelper { if let elevation = waypoint.elevation { pt.elevation = elevation - } - else { + } else { pt.elevation = .greatestFiniteMagnitude } @@ -177,8 +176,7 @@ class CoreDataHelper { do { let serialized = try JSONEncoder().encode(waypoint) pt.serialized = serialized - } - catch { + } catch { print("Core Data Helper: serialization error when adding waypoint: \(error)") } @@ -194,14 +192,13 @@ class CoreDataHelper { print("Failure to save parent context when adding waypoint: \(error)") } } - } - catch { + } catch { print("Failure to save parent context when adding waypoint: \(error)") } } } - // MARK:- Update Core Data + // MARK: Update Core Data /// Updates a previously added waypoint to Core Data /// @@ -232,8 +229,7 @@ class CoreDataHelper { if let elevation = updatedWaypoint.elevation { pt.elevation = elevation - } - else { + } else { pt.elevation = .greatestFiniteMagnitude } @@ -252,8 +248,7 @@ class CoreDataHelper { print("Failure to update and save waypoint to parent context: \(error)") } } - } - catch { + } catch { print("Failure to update and save waypoint to context at child context: \(error)") } } @@ -267,185 +262,42 @@ class CoreDataHelper { } } - // MARK:- Retrieval From Core Data - + // MARK: Retrieval From Core Data + /// Retrieves everything from Core Data /// - /// Currently, it retrieves CDTrackpoint, CDWaypoint and CDRoot, to process from those Core Data types to CoreGPX types such as GPXTrackPoint, GPXWaypoint, etc. + /// Currently, it retrieves CDTrackpoint, CDWaypoint and CDRoot, + /// to process from those Core Data types to CoreGPX types such as GPXTrackPoint, GPXWaypoint, etc. /// /// It will also call on crashFileRecovery() method to continue the next procudure. /// func retrieveFromCoreData() { let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) privateManagedObjectContext.parent = appDelegate.managedObjectContext - // Creates a fetch request - let trkptFetchRequest = NSFetchRequest(entityName: "CDTrackpoint") - let wptFetchRequest = NSFetchRequest(entityName: "CDWaypoint") - let rootFetchRequest = NSFetchRequest(entityName: "CDRoot") - - // Ensure that fetched data is ordered - let sortTrkpt = NSSortDescriptor(key: "trackpointId", ascending: true) - let sortWpt = NSSortDescriptor(key: "waypointId", ascending: true) - trkptFetchRequest.sortDescriptors = [sortTrkpt] - wptFetchRequest.sortDescriptors = [sortWpt] - - let asyncRootFetchRequest = NSAsynchronousFetchRequest(fetchRequest: rootFetchRequest) { asynchronousFetchResult in - guard let rootResults = asynchronousFetchResult.finalResult else { - return } - - DispatchQueue.main.async { - guard let objectID = rootResults.last?.objectID else { self.lastFileName = ""; return } - guard let safePoint = self.appDelegate.managedObjectContext.object(with: objectID) as? CDRoot else { self.lastFileName = ""; return } - self.lastFileName = safePoint.lastFileName ?? "" - self.lastTracksegmentId = safePoint.lastTrackSegmentId - self.isContinued = safePoint.continuedAfterSave - } - } - - // Creates `asynchronousFetchRequest` with the fetch request and the completion closure - let asynchronousTrackPointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: trkptFetchRequest) { asynchronousFetchResult in - - print("Core Data Helper: fetching recoverable trackpoints from Core Data") - - guard let trackPointResults = asynchronousFetchResult.finalResult else { return } - // Dispatches to use the data in the main queue - DispatchQueue.main.async { - self.tracksegmentId = trackPointResults.first?.trackSegmentId ?? 0 - - for result in trackPointResults { - let objectID = result.objectID - - // thread safe - guard let safePoint = self.appDelegate.managedObjectContext.object(with: objectID) as? CDTrackpoint else { continue } - - if self.tracksegmentId != safePoint.trackSegmentId { - if self.currentSegment.trackpoints.count > 0 { - self.tracksegments.append(self.currentSegment) - self.currentSegment = GPXTrackSegment() - } - - self.tracksegmentId = safePoint.trackSegmentId - } - - let pt = GPXTrackPoint(latitude: safePoint.latitude, longitude: safePoint.longitude) - - pt.time = safePoint.time - pt.elevation = safePoint.elevation - - self.currentSegment.trackpoints.append(pt) - - } - self.trackpointId = trackPointResults.last?.trackpointId ?? Int64() - self.tracksegments.append(self.currentSegment) - } - } - - let asynchronousWaypointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: wptFetchRequest) { asynchronousFetchResult in - - print("Core Data Helper: fetching recoverable waypoints from Core Data") - - // Retrieves an array of points from Core Data - guard let waypointResults = asynchronousFetchResult.finalResult else { return } - - // Dispatches to use the data in the main queue - DispatchQueue.main.async { - for result in waypointResults { - let objectID = result.objectID - - // thread safe - guard let safePoint = self.appDelegate.managedObjectContext.object(with: objectID) as? CDWaypoint else { continue } - - let pt = GPXWaypoint(latitude: safePoint.latitude, longitude: safePoint.longitude) - - pt.time = safePoint.time - pt.desc = safePoint.desc - pt.name = safePoint.name - if safePoint.elevation != .greatestFiniteMagnitude { - pt.elevation = safePoint.elevation - } - - self.waypoints.append(pt) - } - - self.waypointId = waypointResults.last?.waypointId ?? Int64() - - // trackpoint request first, followed by waypoint request - // hence, crashFileRecovery method is ran in this. - self.crashFileRecovery() - print("Core Data Helper: async fetches complete.") - } - } do { - // Executes two requests, one for trackpoint, one for waypoint. - // Note: it appears that the actual object context execution happens after all of this, probably due to its async nature. - try privateManagedObjectContext.execute(asyncRootFetchRequest) - try privateManagedObjectContext.execute(asynchronousTrackPointFetchRequest) - try privateManagedObjectContext.execute(asynchronousWaypointFetchRequest) + try privateManagedObjectContext.execute(rootFetchRequest()) + try privateManagedObjectContext.execute(trackPointFetchRequest()) + try privateManagedObjectContext.execute(waypointFetchRequest()) } catch let error { print("NSAsynchronousFetchRequest (fetch request for recovery) error: \(error)") } } - // MARK:- Delete from Core Data - - /// Deletes all CDRoot entity objects from Core Data. - /// - /// CDRoot holds information needed for core data functionalities other than data storage of trackpoints or waypoints, etc. - /// - func deleteCDRootFromCoreData() { - let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - privateManagedObjectContext.parent = appDelegate.managedObjectContext - // Creates a fetch request - let rootFetchRequest = NSFetchRequest(entityName: "CDRoot") - - let asynchronousWaypointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: rootFetchRequest) { asynchronousFetchResult in - - print("Core Data Helper: delete last filename from Core Data.") - - // Retrieves an array of points from Core Data - guard let results = asynchronousFetchResult.finalResult else { return } - - for result in results { - privateManagedObjectContext.delete(result) - } - - do { - try privateManagedObjectContext.save() - self.appDelegate.managedObjectContext.performAndWait { - do { - // Saves the changes from the child to the main context to be applied properly - try self.appDelegate.managedObjectContext.save() - } catch { - print("Failure to save context: \(error)") - } - } - } - catch { - print("Failure to save context at child context: \(error)") - } - } - - do { - try privateManagedObjectContext.execute(asynchronousWaypointFetchRequest) - } catch let error { - print("NSAsynchronousFetchRequest (while deleting last file name) error: \(error)") - } - } - + // MARK: Delete from Core Data + /// Delete Waypoint from index /// /// - Parameters: /// - index: index of the waypoint that is meant to be deleted. /// func deleteWaypoint(fromCoreDataAt index: Int) { - lastFileName = String() let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) privateManagedObjectContext.parent = appDelegate.managedObjectContext // Creates a fetch request let wptFetchRequest = NSFetchRequest(entityName: "CDWaypoint") - + wptFetchRequest.includesPropertyValues = false let asynchronousWaypointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: wptFetchRequest) { asynchronousFetchResult in print("Core Data Helper: delete waypoint from Core Data at index: \(index)") @@ -465,8 +317,7 @@ class CoreDataHelper { print("Failure to save context (when deleting waypoint): \(error)") } } - } - catch { + } catch { print("Failure to save context at child context (when deleting waypoint): \(error)") } } @@ -478,159 +329,20 @@ class CoreDataHelper { } } - - - /// Delete all trackpoints and waypoints in Core Data. - func deleteAllTrackFromCoreData() { - - let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - privateManagedObjectContext.parent = appDelegate.managedObjectContext - - print("Core Data Helper: Batch Delete trackpoints from Core Data") - - - if #available(iOS 10.0, *) { - privateManagedObjectContext.perform { - do { - let trackpointFetchRequest = NSFetchRequest(entityName: "CDTrackpoint") - let trackpointDeleteRequest = NSBatchDeleteRequest(fetchRequest: trackpointFetchRequest) - - // execute both delete requests. - try privateManagedObjectContext.execute(trackpointDeleteRequest) - try privateManagedObjectContext.save() - - self.appDelegate.managedObjectContext.performAndWait { - do { - // Saves the changes from the child to the main context to be applied properly - try self.appDelegate.managedObjectContext.save() - } catch { - print("Failure to save context after delete: \(error)") - } - } - } - catch { - print("Failed to delete all from core data, error: \(error)") - } - - } - - } - else { // for pre iOS 9 (less efficient, load in memory before removal) - let trackpointFetchRequest = NSFetchRequest(entityName: "CDTrackpoint") - let trackpointAsynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: trackpointFetchRequest) { asynchronousFetchResult in - - guard let results = asynchronousFetchResult.finalResult else { return } - - for result in results { - privateManagedObjectContext.delete(result) - } - do { - // Save delete request - try privateManagedObjectContext.save() - } - catch let error { - print("NSAsynchronousFetchRequest (for batch delete (of type: T.Type) { - if #available(iOS 10.0, *) { - privateManagedObjectContext.perform { - do { - let waypointFetchRequest = NSFetchRequest(entityName: "CDWaypoint") - let waypointDeleteRequest = NSBatchDeleteRequest(fetchRequest: waypointFetchRequest) - - // execute delete request. - try privateManagedObjectContext.execute(waypointDeleteRequest) - - try privateManagedObjectContext.save() - - self.appDelegate.managedObjectContext.performAndWait { - do { - // Saves the changes from the child to the main context to be applied properly - try self.appDelegate.managedObjectContext.save() - } catch { - print("Failure to save context after delete: \(error)") - } - } - } - catch { - print("Failed to delete all from core data, error: \(error)") - } - - } - - } - else { // for pre iOS 9 (less efficient, load in memory before removal) - - let waypointFetchRequest = NSFetchRequest(entityName: "CDWaypoint") - waypointFetchRequest.includesPropertyValues = false - let waypointAsynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: waypointFetchRequest) { asynchronousFetchResult in - - guard let results = asynchronousFetchResult.finalResult else { return } - - //self.resetIds() - - for result in results { - let safePoint = privateManagedObjectContext.object(with: result.objectID) - privateManagedObjectContext.delete(safePoint) - } - do { - // Save delete request - try privateManagedObjectContext.save() - } - catch let error { - print("NSAsynchronousFetchRequest (for batch delete 0 || self.waypoints.count > 0 { - let root: GPXRoot let track = GPXTrack() @@ -655,8 +366,7 @@ class CoreDataHelper { let gpx = GPXFileManager.URLForFilename(self.lastFileName) let parsedRoot = GPXParser(withURL: gpx)?.parsedData() root = parsedRoot ?? GPXRoot(creator: kGPXCreatorString) - } - else { + } else { root = GPXRoot(creator: kGPXCreatorString) } // generates a GPXRoot from recovered data @@ -668,36 +378,34 @@ class CoreDataHelper { } // if gpx is saved, but further trkpts are added after save, and crashed, trkpt are appended, not adding to new trkseg. root.tracks.last?.tracksegments[Int(self.lastTracksegmentId)].add(trackpoints: self.tracksegments.first!.trackpoints) - self.tracksegments.remove(at: 0) - - - } - else { + self.tracksegments.remove(at: 0) + } else { track.tracksegments = self.tracksegments root.add(track: track) - //root.waypoints = self.waypoints - //root.add(waypoints: self.waypoints) } - //root.waypoints = [GPXWaypoint]() - //root.add(waypoints: self.waypoints) root.waypoints = self.waypoints // asks user on what to do with recovered data DispatchQueue.main.sync { - print(root.gpx()) + // for debugging + // print(root.gpx()) + // main action sheet setup - let alertController = UIAlertController(title: NSLocalizedString("CONTINUE_SESSION_TITLE", comment: "no comment"), message: NSLocalizedString("CONTINUE_SESSION_MESSAGE", comment: "no comment"), preferredStyle: .actionSheet) + let alertController = UIAlertController(title: NSLocalizedString("CONTINUE_SESSION_TITLE", comment: "no comment"), + message: NSLocalizedString("CONTINUE_SESSION_MESSAGE", comment: "no comment"), + preferredStyle: .actionSheet) // option to cancel - let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { (action) in + let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { _ in self.clearAll() } // option to continue previous session, which will load it, but not save - let continueAction = UIAlertAction(title: NSLocalizedString("CONTINUE_SESSION", comment: "no comment"), style: .default) { (action) in - NotificationCenter.default.post(name: .loadRecoveredFile, object: nil, userInfo: ["recoveredRoot" : root, "fileName" : self.lastFileName]) + let continueAction = UIAlertAction(title: NSLocalizedString("CONTINUE_SESSION", comment: "no comment"), style: .default) { _ in + NotificationCenter.default.post(name: .loadRecoveredFile, object: nil, + userInfo: ["recoveredRoot": root, "fileName": self.lastFileName]) } // option to save silently as file, session remains new - let saveAction = UIAlertAction(title: NSLocalizedString("SAVE_START_NEW", comment: "no comment"), style: .default) { (action) in + let saveAction = UIAlertAction(title: NSLocalizedString("SAVE_START_NEW", comment: "no comment"), style: .default) { _ in self.saveFile(from: root, andIfAvailable: self.lastFileName) } @@ -706,12 +414,10 @@ class CoreDataHelper { alertController.addAction(saveAction) CoreDataAlertView().showActionSheet(alertController) } - } - else { + } else { // no recovery file will be generated if nothing is recovered (or did not crash). } } - } /// saves recovered data to a gpx file, silently, without loading on map. @@ -723,11 +429,9 @@ class CoreDataHelper { if lastfileName != "" { fileName = lastfileName - } - else if let lastTrkptDate = gpx.tracks.last?.tracksegments.last?.trackpoints.last?.time { + } else if let lastTrkptDate = gpx.tracks.last?.tracksegments.last?.trackpoints.last?.time { fileName = dateFormatter.string(from: lastTrkptDate) - } - else { + } else { // File name's date will be as of recovery time, not of crash time. fileName = dateFormatter.string(from: Date()) } @@ -741,10 +445,11 @@ class CoreDataHelper { // clear aft save. self.clearAll() - self.deleteCDRootFromCoreData() + self.coreDataDeleteAll(of: CDRoot.self) + //self.deleteCDRootFromCoreData() } - // MARK:- Reset & Clear + // MARK: Reset & Clear /// Resets trackpoints and waypoints Id /// @@ -766,11 +471,10 @@ class CoreDataHelper { func clearAllExceptWaypoints() { // once file recovery is completed, Core Data stored items are deleted. - self.deleteAllTrackFromCoreData() + self.coreDataDeleteAll(of: CDTrackpoint.self) // once file recovery is completed, arrays are cleared. self.tracksegments = [] - self.currentSegment = GPXTrackSegment() // current segment should be 'reset' as well self.currentSegment = GPXTrackSegment() @@ -783,8 +487,8 @@ class CoreDataHelper { /// clears all func clearAll() { // once file recovery is completed, Core Data stored items are deleted. - self.deleteAllTrackFromCoreData() - self.deleteAllWaypointsFromCoreData() + self.coreDataDeleteAll(of: CDTrackpoint.self) + self.coreDataDeleteAll(of: CDWaypoint.self) // once file recovery is completed, arrays are cleared. self.clearObjects() diff --git a/OpenGpxTracker/Date+timeAgo.swift b/OpenGpxTracker/Date+timeAgo.swift index c2c484e2..968e00f4 100644 --- a/OpenGpxTracker/Date+timeAgo.swift +++ b/OpenGpxTracker/Date+timeAgo.swift @@ -21,7 +21,7 @@ extension Date { /// - Parameters: /// - numericDates: Set it to true to get "1 year ago", "1 month ago" or false if you prefer "Last year", "Last month" /// - func timeAgo(numericDates:Bool) -> String { + func timeAgo(numericDates: Bool) -> String { let calendar = Calendar.current let now = Date() let earliest = self < now ? self : now @@ -29,55 +29,55 @@ extension Date { let unitFlags: Set = [.minute, .hour, .day, .weekOfMonth, .month, .year, .second] let components: DateComponents = calendar.dateComponents(unitFlags, from: earliest, to: latest) - //print("") - //print(components) if let year = components.year { - if (year >= 2) { - return String(format: NSLocalizedString("T_YEARS_AGO", comment: "no comment"), year) - } else if (year >= 1) { - return numericDates ? NSLocalizedString("T_YEAR_AGO", comment: "no comment") : NSLocalizedString("T_LAST_YEAR", comment: "no comment") + if year >= 2 { + return String(format: NSLocalizedString("T_YEARS_AGO", comment: ""), year) + } else if year >= 1 { + return numericDates ? NSLocalizedString("T_YEAR_AGO", comment: "") : NSLocalizedString("T_LAST_YEAR", comment: "") } } if let month = components.month { - if (month >= 2) { - return String(format: NSLocalizedString("T_MONTHS_AGO", comment: "no comment"), month) - } else if (month >= 1) { - return numericDates ? NSLocalizedString("T_MONTH_AGO", comment: "no comment") : NSLocalizedString("T_LAST_MONTH", comment: "no comment") + if month >= 2 { + return String(format: NSLocalizedString("T_MONTHS_AGO", comment: ""), month) + } else if month >= 1 { + let monthAgo = NSLocalizedString("T_MONTH_AGO", comment: "") + let lastMonth = NSLocalizedString("T_LAST_MONTH", comment: "") + return numericDates ? monthAgo : lastMonth } } if let weekOfMonth = components.weekOfMonth { - if (weekOfMonth >= 2) { - return String(format: NSLocalizedString("T_WEEKS_AGO", comment: "no comment"), weekOfMonth) - } else if (weekOfMonth >= 1) { - return numericDates ? NSLocalizedString("T_WEEK_AGO", comment: "no comment") : NSLocalizedString("T_LAST_WEEK", comment: "no comment") + if weekOfMonth >= 2 { + return String(format: NSLocalizedString("T_WEEKS_AGO", comment: ""), weekOfMonth) + } else if weekOfMonth >= 1 { + return numericDates ? NSLocalizedString("T_WEEK_AGO", comment: "") : NSLocalizedString("T_LAST_WEEK", comment: "") } } if let day = components.day { - if (day >= 2) { - return String(format: NSLocalizedString("T_DAYS_AGO", comment: "no comment"), day) - } else if (day >= 1) { - return numericDates ? NSLocalizedString("T_DAY_AGO", comment: "no comment") : NSLocalizedString("T_YESTERDAY", comment: "no comment") + if day >= 2 { + return String(format: NSLocalizedString("T_DAYS_AGO", comment: ""), day) + } else if day >= 1 { + return numericDates ? NSLocalizedString("T_DAY_AGO", comment: "") : NSLocalizedString("T_YESTERDAY", comment: "") } } if let hour = components.hour { - if (hour >= 2) { - return String(format: NSLocalizedString("T_HOURS_AGO", comment: "no comment"), hour) - } else if (hour >= 1) { - return numericDates ? NSLocalizedString("T_HOUR_AGO", comment: "no comment") : NSLocalizedString("T_LAST_HOUR", comment: "no comment") + if hour >= 2 { + return String(format: NSLocalizedString("T_HOURS_AGO", comment: ""), hour) + } else if hour >= 1 { + return numericDates ? NSLocalizedString("T_HOUR_AGO", comment: "") : NSLocalizedString("T_LAST_HOUR", comment: "") } } if let minute = components.minute { - if (minute >= 2) { - return String(format: NSLocalizedString("T_MINUTES_AGO", comment: "no comment"), minute) - } else if (minute >= 1) { - return numericDates ? NSLocalizedString("T_MINUTE_AGO", comment: "no comment") : NSLocalizedString("T_LAST_MINUTE", comment: "no comment") + if minute >= 2 { + return String(format: NSLocalizedString("T_MINUTES_AGO", comment: ""), minute) + } else if minute >= 1 { + return numericDates ? NSLocalizedString("T_MINUTE_AGO", comment: "") : NSLocalizedString("T_LAST_MINUTE", comment: "") } } if let second = components.second { - if (second >= 3) { - return String(format: NSLocalizedString("T_SECONDS_AGO", comment: "no comment"), second) + if second >= 3 { + return String(format: NSLocalizedString("T_SECONDS_AGO", comment: ""), second) } } - return NSLocalizedString("T_JUST_NOW", comment: "no comment") + return NSLocalizedString("T_JUST_NOW", comment: "") } } diff --git a/OpenGpxTracker/DateField.swift b/OpenGpxTracker/DateField.swift index a0d78b49..af6a8cc1 100644 --- a/OpenGpxTracker/DateField.swift +++ b/OpenGpxTracker/DateField.swift @@ -19,5 +19,5 @@ struct DateField { /// To facilitate explanation of said pattern, that falls under same type, if needed. /// /// Key of subtitle should be accessible in `patterns` - var subtitles: [String : String]? + var subtitles: [String: String]? } diff --git a/OpenGpxTracker/DateFieldTypeView.swift b/OpenGpxTracker/DateFieldTypeView.swift index 4e43592c..cb4f086b 100644 --- a/OpenGpxTracker/DateFieldTypeView.swift +++ b/OpenGpxTracker/DateFieldTypeView.swift @@ -11,7 +11,7 @@ import UIKit @available(iOS 9.0, *) class DateFieldTypeView: UIScrollView { - // MARK:- Localization Strings + // MARK: Localization Strings private let kSingleDigit = NSLocalizedString("SINGLE_DIGIT", comment: "") private let kFull = NSLocalizedString("FULL_TEXT", comment: "") private let kText = NSLocalizedString("TEXT", comment: "") @@ -31,66 +31,64 @@ class DateFieldTypeView: UIScrollView { /// valid date fields that are to be displayed. private var dateFields: [DateField] { - get { - var fields = [DateField]() - // some subtitles are an attempt to clarify, in case of different Locales, causing some example of patterns to look the same. - fields.append(DateField(type: NSLocalizedString("YEAR", comment: ""), - patterns: ["YY", "YYYY"])) - fields.append(DateField(type: NSLocalizedString("MONTH", comment: ""), - patterns: ["M", "MM", "MMMMM", "MMM", "MMMM"], - subtitles: ["M" : kSingleDigit, - "MMMMM" : "•", - "MMM" : "• • •", - "MMMM" : kFull])) - fields.append(DateField(type: NSLocalizedString("DAY", comment: ""), - patterns: ["d", "dd", "D"], - subtitles: ["d" : kSingleDigit, - "dd" : kOfMonth, - "D" : kOfYear])) - fields.append(DateField(type: NSLocalizedString("HOUR", comment: ""), - patterns: ["h", "hh", "H", "HH", "K", "KK", "k", "kk"], - subtitles: ["h" : kSingleDigit, "hh" : "12hr", - "H" : kSingleDigit, "HH" : "24hr", - "K" : kSingleDigit, "KK" : "0-11", - "k" : kSingleDigit, "kk" : "1-24"])) - fields.append(DateField(type: NSLocalizedString("MINUTE", comment: ""), - patterns: ["m", "mm"], - subtitles: ["m" : kSingleDigit])) - fields.append(DateField(type: NSLocalizedString("SECOND", comment: ""), - patterns: ["s", "ss"], - subtitles: ["s" : kSingleDigit])) - fields.append(DateField(type: NSLocalizedString("DAY_OF_THE_WEEK", comment: ""), - patterns: ["e", "ee", "EEEEE", "EEEEEE", "E", "EEEE"], - subtitles: ["e" : kSingleDigit, - "EEEEE" : "•", - "EEEEEE" : "• •", - "E" : "• • •", - "EEEE" : kFull])) - fields.append(DateField(type: NSLocalizedString("TIME_OF_DAY", comment: ""), - patterns: ["aaaaa", "a", "B"], - subtitles: ["aaaaa" : kSingleDigit, - "B" : "Text"])) - fields.append(DateField(type: NSLocalizedString("WEEK", comment: ""), - patterns: ["w", "ww", "W"], - subtitles: ["w" : kSingleDigit, - "ww" : kOfYear, - "W" : kOfMonth])) - fields.append(DateField(type: NSLocalizedString("QUARTER", comment: ""), - patterns: ["Q", "QQ", "QQQ", "QQQQ"])) - fields.append(DateField(type: NSLocalizedString("ERA", comment: ""), - patterns: ["GGGGG", "G", "GGGG"])) - fields.append(DateField(type: NSLocalizedString("TIME_ZONE", comment: ""), - patterns: ["X", "Z", "ZZZZZ", "z", "O", "ZZZZ", "zzzz", "VVV", "VVVV"], - subtitles: ["z" : kAbbr, - "zzzz" : kFull, - "X" : kUTCoffset, - "O" : kGMTshort, - "ZZZZ" : kGMTfull, - "VVV" : kLocation, - "VVVV" : kLocationTime])) - - return fields - } + var fields = [DateField]() + // some subtitles are an attempt to clarify, in case of different Locales, causing some example of patterns to look the same. + fields.append(DateField(type: NSLocalizedString("YEAR", comment: ""), + patterns: ["YY", "YYYY"])) + fields.append(DateField(type: NSLocalizedString("MONTH", comment: ""), + patterns: ["M", "MM", "MMMMM", "MMM", "MMMM"], + subtitles: ["M": kSingleDigit, + "MMMMM": "•", + "MMM": "• • •", + "MMMM": kFull])) + fields.append(DateField(type: NSLocalizedString("DAY", comment: ""), + patterns: ["d", "dd", "D"], + subtitles: ["d": kSingleDigit, + "dd": kOfMonth, + "D": kOfYear])) + fields.append(DateField(type: NSLocalizedString("HOUR", comment: ""), + patterns: ["h", "hh", "H", "HH", "K", "KK", "k", "kk"], + subtitles: ["h": kSingleDigit, "hh": "12hr", + "H": kSingleDigit, "HH": "24hr", + "K": kSingleDigit, "KK": "0-11", + "k": kSingleDigit, "kk": "1-24"])) + fields.append(DateField(type: NSLocalizedString("MINUTE", comment: ""), + patterns: ["m", "mm"], + subtitles: ["m": kSingleDigit])) + fields.append(DateField(type: NSLocalizedString("SECOND", comment: ""), + patterns: ["s", "ss"], + subtitles: ["s": kSingleDigit])) + fields.append(DateField(type: NSLocalizedString("DAY_OF_THE_WEEK", comment: ""), + patterns: ["e", "ee", "EEEEE", "EEEEEE", "E", "EEEE"], + subtitles: ["e": kSingleDigit, + "EEEEE": "•", + "EEEEEE": "• •", + "E": "• • •", + "EEEE": kFull])) + fields.append(DateField(type: NSLocalizedString("TIME_OF_DAY", comment: ""), + patterns: ["aaaaa", "a", "B"], + subtitles: ["aaaaa": kSingleDigit, + "B": "Text"])) + fields.append(DateField(type: NSLocalizedString("WEEK", comment: ""), + patterns: ["w", "ww", "W"], + subtitles: ["w": kSingleDigit, + "ww": kOfYear, + "W": kOfMonth])) + fields.append(DateField(type: NSLocalizedString("QUARTER", comment: ""), + patterns: ["Q", "QQ", "QQQ", "QQQQ"])) + fields.append(DateField(type: NSLocalizedString("ERA", comment: ""), + patterns: ["GGGGG", "G", "GGGG"])) + fields.append(DateField(type: NSLocalizedString("TIME_ZONE", comment: ""), + patterns: ["X", "Z", "ZZZZZ", "z", "O", "ZZZZ", "zzzz", "VVV", "VVVV"], + subtitles: ["z": kAbbr, + "zzzz": kFull, + "X": kUTCoffset, + "O": kGMTshort, + "ZZZZ": kGMTfull, + "VVV": kLocation, + "VVVV": kLocationTime])) + + return fields } /// Default initializer override init(frame: CGRect) { @@ -104,7 +102,6 @@ class DateFieldTypeView: UIScrollView { viewDidInit() } - /// Things to do, when view successfully inits. func viewDidInit() { if #available(iOS 13.0, *) { @@ -127,8 +124,10 @@ class DateFieldTypeView: UIScrollView { } self.addSubview(scrollStack) - self.addConstraints( NSLayoutConstraint.constraints(withVisualFormat: "H:|-20-[sStack]-20-|", options: .alignAllLeft, metrics: nil, views: ["sStack": scrollStack]) ) - + self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-20-[sStack]-20-|", + options: .alignAllLeft, + metrics: nil, + views: ["sStack": scrollStack])) } /// Generates vertical stack that encapsulates text title, with all date patterns of same type. @@ -163,7 +162,6 @@ class DateFieldTypeView: UIScrollView { /// |SUBTITLE|SUBTITLE| /// func genHStack(field: DateField) -> UIStackView { - let hStack = UIStackView() hStack.axis = .horizontal hStack.distribution = .fill @@ -184,8 +182,7 @@ class DateFieldTypeView: UIScrollView { // color of button font if #available(iOS 13, *) { button.setTitleColor(.blackAndWhite, for: .normal) - } - else { + } else { button.setTitleColor(.black, for: .normal) } @@ -194,41 +191,54 @@ class DateFieldTypeView: UIScrollView { button.addTarget(self, action: #selector(buttonTapped(sender:)), for: .touchUpInside) - // Subtitle implementation (optional) if let subtitle = field.subtitles?[pattern] { - let subtitleLabel = UIInsetLabel() - let subVStack = UIStackView() - subVStack.axis = .vertical - subVStack.distribution = .fill - subVStack.alignment = .leading - subVStack.spacing = -2.5 - subVStack.translatesAutoresizingMaskIntoConstraints = false - - subtitleLabel.insets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - subtitleLabel.text = subtitle.uppercased() - subtitleLabel.font = .boldSystemFont(ofSize: 8) - subtitleLabel.textAlignment = .center - - subVStack.addArrangedSubview(button) - subVStack.addArrangedSubview(subtitleLabel) - NSLayoutConstraint(item: subtitleLabel, attribute: .width, relatedBy: .equal, toItem: button, attribute: .width, multiplier: 1, constant: 0).isActive = true - hStack.addArrangedSubview(subVStack) - } - else { + hStack.addArrangedSubview(genSubtitleView(subtitle: subtitle, button: button)) + } else { hStack.addArrangedSubview(button) } - } return hStack } + /// Generates subtitle view for genHStack(field:) + /// + /// |pattern|pattern|pattern| + /// -> |SUBTITLE|SUBTITLE| + /// + func genSubtitleView(subtitle: String, button: DatePatternButton) -> UIStackView { + let subtitleLabel = UIInsetLabel() + let subVStack = UIStackView() + subVStack.axis = .vertical + subVStack.distribution = .fill + subVStack.alignment = .leading + subVStack.spacing = -2.5 + subVStack.translatesAutoresizingMaskIntoConstraints = false + + subtitleLabel.insets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + subtitleLabel.text = subtitle.uppercased() + subtitleLabel.font = .boldSystemFont(ofSize: 8) + subtitleLabel.textAlignment = .center + + subVStack.addArrangedSubview(button) + subVStack.addArrangedSubview(subtitleLabel) + let subtitleConstraint = NSLayoutConstraint(item: subtitleLabel, + attribute: .width, + relatedBy: .equal, + toItem: button, + attribute: .width, + multiplier: 1, + constant: 0) + subtitleConstraint.isActive = true + + return subVStack + } + /// Called when any pattern button is tapped. @objc func buttonTapped(sender: DatePatternButton) { - NotificationCenter.default.post(name: .dateFieldTapped, object: nil, userInfo: ["sender" : sender.pattern]) + NotificationCenter.default.post(name: .dateFieldTapped, object: nil, userInfo: ["sender": sender.pattern]) } - } /// Notifications name of for date pattern sending diff --git a/OpenGpxTracker/DefaultDateFormat.swift b/OpenGpxTracker/DefaultDateFormat.swift index a976c5d2..e354aea6 100644 --- a/OpenGpxTracker/DefaultDateFormat.swift +++ b/OpenGpxTracker/DefaultDateFormat.swift @@ -14,33 +14,34 @@ class DefaultDateFormat { let dateFormatter = DateFormatter() /// returns a 'processed', `DateFormatter`-friendly date format. - func getDateFormat(unprocessed: String) -> String { + func getDateFormat(unprocessed: String) -> (String, Bool) { var newText = "" - + var isInvalid = false // prevents acknowledging unterminated date formats as valid if (unprocessed.countInstances(of: "{") != unprocessed.countInstances(of: "}")) || unprocessed.countInstances(of: "{}") > 0 { newText = "'invalid'" - } - else { + isInvalid = true + } else { let arr = unprocessed.components(separatedBy: CharacterSet(charactersIn: "{}")) var lastField: String? let arrCount = arr.count for i in 0...arrCount - 1 { if let lastField = lastField, lastField.countInstances(of: String(arr[i].last ?? Character(" "))) > 0 { newText = "'invalid: { ... } must not consecutively repeat'" + isInvalid = true break } - if arr.count == 1 { + if arr.count == 1 { newText += "'invalid'" - } - else if arrCount > 1 && !arr[i].isEmpty { + isInvalid = true + } else if arrCount > 1 && !arr[i].isEmpty { newText += (i % 2 == 0) ? "'\(arr[i])'" : arr[i] lastField = (i % 2 != 0) ? arr[i] : nil } } } - return newText + return (newText, isInvalid) } /// Returns sample date time based on user input. diff --git a/OpenGpxTracker/DefaultNameSetupViewController.swift b/OpenGpxTracker/DefaultNameSetupViewController.swift index 9f2166ba..fa7a3a23 100644 --- a/OpenGpxTracker/DefaultNameSetupViewController.swift +++ b/OpenGpxTracker/DefaultNameSetupViewController.swift @@ -21,6 +21,9 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate /// also used for final date formatting for use in default name callup when saving. var processedDateFormat = String() + /// A value denoting the validity of the processed date format. + var dateFormatIsInvalid = false + /// let defaultDateFormat = DefaultDateFormat() @@ -31,7 +34,7 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate var useEN = false /// Global Preferences - var preferences : Preferences = Preferences.shared + var preferences: Preferences = Preferences.shared /// Built in presets. Should be made addable customs next time. let presets = [("Defaults", "dd-MMM-yyyy-HHmm", "{dd}-{MMM}-{yyyy}-{HH}{mm}"), @@ -41,7 +44,7 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate ("Day, Date at time (24 hr)", "EEEE, MMM d, yyyy 'at' HH:mm", "{EEEE}, {MMM} {d}, {yyyy} at {HH}:{mm}")] /// Sections of table view - private enum kSections: Int, CaseIterable { + private enum Sections: Int, CaseIterable { case input, settings, presets } @@ -59,12 +62,12 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate notificationCenter.addObserver(self, selector: #selector(dateButtonTapped(_:)), name: .dateFieldTapped, object: nil) } - // MARK:- Text Field Related - + // MARK: Text Field Related @objc func dateButtonTapped(_ sender: Notification) { if cellTextField.text != nil { guard let notificationValues = sender.userInfo else { return } - let patternDict = notificationValues as! [String : String] + // swiftlint:disable force_cast + let patternDict = notificationValues as! [String: String] guard let pattern = patternDict["sender"] else { return } cellTextField.insertText("{\(pattern)}") @@ -72,6 +75,30 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate } } + /// Legacy date selection toolbar for iOS 8 use, as it lacks new API for the new toolbar layout. + func createSimpleDateSelectionBar() -> UIToolbar { + let bar = UIToolbar() + let bracket = UIBarButtonItem(title: "{ ... }", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) + bracket.tag = 6 + let day = UIBarButtonItem(title: "Day", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) + day.tag = 0 + let month = UIBarButtonItem(title: "Month", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) + month.tag = 1 + let year = UIBarButtonItem(title: "Year", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) + year.tag = 2 + let hour = UIBarButtonItem(title: "Hr", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) + hour.tag = 3 + let min = UIBarButtonItem(title: "Min", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) + min.tag = 4 + let sec = UIBarButtonItem(title: "Sec", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) + sec.tag = 5 + + bar.items = [bracket, day, month, year, hour, min, sec] + bar.sizeToFit() + + return bar + } + /// Handles text insertion as per keyboard bar button pressed. @objc func buttonTapped(_ sender: UIBarButtonItem, for event: UIEvent) { if cellTextField.text != nil { @@ -96,12 +123,14 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate /// Call when text field is currently editing, and an update to sample label is required. @objc func updateSampleTextField() { - processedDateFormat = defaultDateFormat.getDateFormat(unprocessed: self.cellTextField.text!) + let processed = defaultDateFormat.getDateFormat(unprocessed: self.cellTextField.text!) + processedDateFormat = processed.0 + dateFormatIsInvalid = processed.1 //dateFormatter.dateFormat = processedDateFormat cellSampleLabel.text = defaultDateFormat.getDate(processedFormat: processedDateFormat, useUTC: useUTC, useENLocale: useEN) } - // MARK:- UITextField Delegate + // MARK: UITextField Delegate /// Enables keyboard 'done' action to resign text field func textFieldShouldReturn(_ textField: UITextField) -> Bool { @@ -113,7 +142,7 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate /// handling of textfield when editing commence. func textFieldDidBeginEditing(_ textField: UITextField) { //remove checkmark from selected date format preset, as textfield edited == not preset anymore - let selectedIndexPath = IndexPath(row: preferences.dateFormatPreset, section: kSections.presets.rawValue) + let selectedIndexPath = IndexPath(row: preferences.dateFormatPreset, section: Sections.presets.rawValue) tableView.cellForRow(at: selectedIndexPath)?.accessoryType = .none useUTC = false // clear UTC value, unlock UTC cell, as format may now be custom. lockUTCCell(useUTC) @@ -124,10 +153,9 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate if cellTextField.text != preferences.dateFormatInput { saveDateFormat(processedDateFormat, input: cellTextField.text) // save if user input is custom / derivative of preset. - } - else { + } else { // to get back preset set, and its rules (such as UTC for ISO8601 preset) - let selectedIndexPath = IndexPath(row: preferences.dateFormatPreset, section: kSections.presets.rawValue) + let selectedIndexPath = IndexPath(row: preferences.dateFormatPreset, section: Sections.presets.rawValue) tableView.cellForRow(at: selectedIndexPath)?.accessoryType = .checkmark if preferences.dateFormatPreset == 1 { @@ -142,24 +170,24 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate return true } - // MARK:- Preference Setting + // MARK: Preference Setting /// Saves date format to Preferences/UserDefaults func saveDateFormat(_ dateFormat: String, input: String?, index: Int = -1) { guard let input = input else { return } print(dateFormat) - if dateFormat == "invalid" || dateFormat == "'invalid'" || dateFormat == "'invalid: { ... } must not consecutively repeat'" || input.isEmpty || dateFormat.isEmpty { return } // ensures no invalid date format (revert) + if dateFormatIsInvalid || input.isEmpty || dateFormat.isEmpty { return } // ensures no invalid date format (revert) preferences.dateFormat = dateFormat preferences.dateFormatInput = input preferences.dateFormatPreset = index preferences.dateFormatUseUTC = useUTC } - // MARK:- Table View + // MARK: Table View /// Locks UTC cell such that it cannot be unchecked, for preset that require it. func lockUTCCell(_ state: Bool) { - let indexPath = IndexPath(row: 0, section: kSections.settings.rawValue) + let indexPath = IndexPath(row: 0, section: Sections.settings.rawValue) useUTC = state tableView.cellForRow(at: indexPath)?.accessoryType = state ? .checkmark : .none @@ -168,39 +196,37 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate updateSampleTextField() } - /// return number of sections based on `kSections` + /// return number of sections based on `Sections` override func numberOfSections(in tableView: UITableView) -> Int { - return kSections.allCases.count + return Sections.allCases.count } /// implement title of each section override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { switch section { - case kSections.input.rawValue: return NSLocalizedString("DEFAULT_NAME_DATE_FORMAT", comment: "no comment") - case kSections.settings.rawValue: return NSLocalizedString("DEFAULT_NAME_SETTINGS", comment: "no comment") - case kSections.presets.rawValue: return NSLocalizedString("DEFAULT_NAME_PRESET", comment: "no comment") + case Sections.input.rawValue: return NSLocalizedString("DEFAULT_NAME_DATE_FORMAT", comment: "no comment") + case Sections.settings.rawValue: return NSLocalizedString("DEFAULT_NAME_SETTINGS", comment: "no comment") + case Sections.presets.rawValue: return NSLocalizedString("DEFAULT_NAME_PRESET", comment: "no comment") default: fatalError("Section out of range") } } /// implement footer for input section only override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - if section == kSections.input.rawValue { + if section == Sections.input.rawValue { return NSLocalizedString("DEFAULT_NAME_INPUT_FOOTER", comment: "no comment") - } - else { return nil } + } else { return nil } } /// return number of rows in each section override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { - case kSections.input.rawValue: return 2 - case kSections.settings.rawValue: + case Sections.input.rawValue: return 2 + case Sections.settings.rawValue: if Locale.current.languageCode == "en" { return 1 // force locale to EN should only be shown if Locale is not EN. - } - else { return 2 } - case kSections.presets.rawValue: return presets.count + } else { return 2 } + case Sections.presets.rawValue: return presets.count default: fatalError("Row out of range") } } @@ -210,7 +236,7 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate var cell = UITableViewCell(style: .default, reuseIdentifier: "inputCell") - if indexPath.section == kSections.input.rawValue { + if indexPath.section == Sections.input.rawValue { if indexPath.row == 0 { cellSampleLabel = UILabel(frame: CGRect(x: 87, y: 0, width: view.frame.width - 99, height: cell.frame.height)) @@ -222,75 +248,44 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate cell.textLabel!.text = NSLocalizedString("DEFAULT_NAME_SAMPLE_OUTPUT_TITLE", comment: "no comment") cell.textLabel?.font = .systemFont(ofSize: 17) - } - else if indexPath.row == 1 { + } else if indexPath.row == 1 { + cellTextField = UITextField(frame: CGRect(x: 22, y: 0, width: view.frame.width - 48, height: cell.frame.height)) - //let textView = UITextView(frame: CGRect(x: 25, y: 5, width: view.frame.width - 50, height: cell.frame.height)) cellTextField.text = preferences.dateFormatInput updateSampleTextField() cellTextField.clearButtonMode = .whileEditing cellTextField.delegate = self cellTextField.returnKeyType = .done - let bar = UIToolbar() - let bracket = UIBarButtonItem(title: "{ ... }", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) - bracket.tag = 6 - let day = UIBarButtonItem(title: "Day", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) - day.tag = 0 - let month = UIBarButtonItem(title: "Month", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) - month.tag = 1 - let year = UIBarButtonItem(title: "Year", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) - year.tag = 2 - let hour = UIBarButtonItem(title: "Hr", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) - hour.tag = 3 - let min = UIBarButtonItem(title: "Min", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) - min.tag = 4 - let sec = UIBarButtonItem(title: "Sec", style: .plain, target: self, action: #selector(buttonTapped(_:for:))) - sec.tag = 5 - - bar.items = [bracket, day, month, year, hour, min, sec] - bar.sizeToFit() if #available(iOS 9, *) { let dateFieldSelector = DateFieldTypeView(frame: CGRect(x: 0, y: 0, width: cellTextField.frame.width, height: 75)) cellTextField.inputAccessoryView = dateFieldSelector - } - else { - cellTextField.inputAccessoryView = bar + } else { + cellTextField.inputAccessoryView = createSimpleDateSelectionBar() } cellTextField.addTarget(self, action: #selector(updateSampleTextField), for: UIControl.Event.editingChanged) - cell.contentView.addSubview(cellTextField) - if indexPath.section == kSections.input.rawValue { - - } } cell.selectionStyle = .none cellTextField.autocorrectionType = .no - } - - else if indexPath.section == kSections.settings.rawValue { + } else if indexPath.section == Sections.settings.rawValue { if indexPath.row == 0 { cell.textLabel!.text = NSLocalizedString("DEFAULT_NAME_USE_UTC", comment: "no comment")//"Use UTC?" cell.accessoryType = preferences.dateFormatUseUTC ? .checkmark : .none - if preferences.dateFormatPreset == 1 { cell.isUserInteractionEnabled = !useUTC cell.textLabel?.isEnabled = !useUTC } - updateSampleTextField() - } - else if indexPath.row == 1 { + } else if indexPath.row == 1 { cell.textLabel!.text = NSLocalizedString("DEFAULT_NAME_ENGLISH_LOCALE", comment: "no comment")//"Force English Locale?" cell.accessoryType = preferences.dateFormatUseEN ? .checkmark : .none - updateSampleTextField() } - } - - else if indexPath.section == kSections.presets.rawValue { + updateSampleTextField() + } else if indexPath.section == Sections.presets.rawValue { cell = UITableViewCell(style: .subtitle, reuseIdentifier: "presetCell") cell.textLabel?.text = presets[indexPath.row].0 - cell.detailTextLabel?.text = defaultDateFormat.getDate(processedFormat: presets[indexPath.row].1, useUTC: useUTC, useENLocale: useEN)//presets[indexPath.row].1 + cell.detailTextLabel?.text = defaultDateFormat.getDate(processedFormat: presets[indexPath.row].1, useUTC: useUTC, useENLocale: useEN) if preferences.dateFormatPreset != -1 { // if not custom cell.accessoryType = preferences.dateFormatPreset == indexPath.row ? .checkmark : .none @@ -302,15 +297,14 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate /// handling of cell selection. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.section == kSections.settings.rawValue { + if indexPath.section == Sections.settings.rawValue { if indexPath.row == 0 { //remove checkmark from selected utc setting let newUseUTC = !preferences.dateFormatUseUTC preferences.dateFormatUseUTC = newUseUTC useUTC = newUseUTC tableView.cellForRow(at: indexPath)?.accessoryType = newUseUTC ? .checkmark : .none - } - else if indexPath.row == 1 { + } else if indexPath.row == 1 { //remove checkmark from selected en locale setting let newUseEN = !preferences.dateFormatUseEN preferences.dateFormatUseEN = newUseEN @@ -319,7 +313,7 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate } updateSampleTextField() } - if indexPath.section == kSections.presets.rawValue { + if indexPath.section == Sections.presets.rawValue { //cellSampleLabel.text = "{\(presets[indexPath.row].1)}" cellTextField.text = presets[indexPath.row].2 @@ -336,8 +330,7 @@ class DefaultNameSetupViewController: UITableViewController, UITextFieldDelegate preferences.dateFormat = processedDateFormat if preferences.dateFormatPreset == 1 { lockUTCCell(true) - } - else { + } else { lockUTCCell(false) } preferences.dateFormatUseUTC = useUTC diff --git a/OpenGpxTracker/Double+Measures.swift b/OpenGpxTracker/Double+Measures.swift index 890032b9..aef597b9 100644 --- a/OpenGpxTracker/Double+Measures.swift +++ b/OpenGpxTracker/Double+Measures.swift @@ -6,7 +6,6 @@ // // Shared file: this file is also included in the OpenGpxTracker-Watch Extension target. - import Foundation /// Number of meters in 1 mile (mi) @@ -26,7 +25,6 @@ let kKilometersPerHourInOneMeterPerSecond = 3.6 /// To convert m/s -> mph let kMilesPerHourInOneMeterPerSecond = 2.237 - /// Number of miles per hour in 1 meter per second /// /// Extension to convert meters to other units diff --git a/OpenGpxTracker/GPXFileInfo.swift b/OpenGpxTracker/GPXFileInfo.swift index 5848337e..586b4841 100644 --- a/OpenGpxTracker/GPXFileInfo.swift +++ b/OpenGpxTracker/GPXFileInfo.swift @@ -19,37 +19,29 @@ class GPXFileInfo: NSObject { /// Last time the file was modified var modifiedDate: Date { - get { - return try! fileURL.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate ?? Date.distantPast - } + // swiftlint:disable force_try + return try! fileURL.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate ?? Date.distantPast } /// modified date has a time ago string (for instance: 3 days ago) var modifiedDatetimeAgo: String { - get { - return modifiedDate.timeAgo(numericDates: true) - } + return modifiedDate.timeAgo(numericDates: true) } /// File size in bytes var fileSize: Int { - get { - return try! fileURL.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0 - } + // swiftlint:disable force_try + return try! fileURL.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0 } /// File size as string in a more readable format (example: 10 KB) var fileSizeHumanised: String { - get { - return fileSize.asFileSize() - } + return fileSize.asFileSize() } /// The filename without extension var fileName: String { - get { - return fileURL.deletingPathExtension().lastPathComponent - } + return fileURL.deletingPathExtension().lastPathComponent } /// diff --git a/OpenGpxTracker/GPXFileManager.swift b/OpenGpxTracker/GPXFileManager.swift index c8e7a292..0a372eb2 100644 --- a/OpenGpxTracker/GPXFileManager.swift +++ b/OpenGpxTracker/GPXFileManager.swift @@ -21,48 +21,44 @@ class GPXFileManager: NSObject { /// Folder that where all GPX files are stored /// class var GPXFilesFolderURL: URL { - get { - let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL - return documentsUrl - } + let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL + return documentsUrl } /// /// Gets the list of `.gpx` files in Documents directory ordered by modified date /// class var fileList: [GPXFileInfo] { - get { - var GPXFiles: [GPXFileInfo] = [] - let fileManager = FileManager.default - let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] - do { - // Get all files from the directory .documentsURL. Of each file get the URL (~path) - // last modification date and file size - if let directoryURLs = try? fileManager.contentsOfDirectory(at: documentsURL, - includingPropertiesForKeys: [.attributeModificationDateKey, .fileSizeKey], - options: .skipsSubdirectoryDescendants) { - //Order files based on the date - // This map creates a tuple (url: URL, modificationDate: String, filesize: Int) - // and then orders it by modificationDate - let sortedURLs = directoryURLs.map { url in - (url: url, - modificationDate: (try? url.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? Date.distantPast, - fileSize: (try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0) - } - .sorted(by: { $0.1 > $1.1 }) // sort descending modification dates - print(sortedURLs) - //Now we filter GPX Files - for (url, modificationDate, fileSize) in sortedURLs { - if kFileExt.contains(url.pathExtension) { - GPXFiles.append(GPXFileInfo(fileURL: url)) - //GPXFiles.append(url.deletingPathExtension().lastPathComponent) - print("\(modificationDate) \(modificationDate.timeAgo(numericDates: true)) \(fileSize)bytes -- \(url.deletingPathExtension().lastPathComponent)") - } - } + var GPXFiles: [GPXFileInfo] = [] + let fileManager = FileManager.default + let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] + do { + // Get all files from the directory .documentsURL. Of each file get the URL (~path) + // last modification date and file size + if let directoryURLs = try? fileManager.contentsOfDirectory(at: documentsURL, + includingPropertiesForKeys: [.attributeModificationDateKey, .fileSizeKey], + options: .skipsSubdirectoryDescendants) { + //Order files based on the date + // This map creates a tuple (url: URL, modificationDate: String, filesize: Int) + // and then orders it by modificationDate + let sortedURLs = directoryURLs.map { url in + (url: url, + modificationDate: (try? url.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? Date.distantPast, + fileSize: (try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0) + } + .sorted(by: { $0.1 > $1.1 }) // sort descending modification dates + print(sortedURLs) + //Now we filter GPX Files + for (url, modificationDate, fileSize) in sortedURLs { + if kFileExt.contains(url.pathExtension) { + GPXFiles.append(GPXFileInfo(fileURL: url)) + let lastPathComponent = url.deletingPathExtension().lastPathComponent + print("\(modificationDate) \(modificationDate.timeAgo(numericDates: true)) \(fileSize)bytes -- \(lastPathComponent)") } } - return GPXFiles + } } + return GPXFiles } /// diff --git a/OpenGpxTracker/GPXFilesTableViewController.swift b/OpenGpxTracker/GPXFilesTableViewController.swift index 936a5482..2860c562 100644 --- a/OpenGpxTracker/GPXFilesTableViewController.swift +++ b/OpenGpxTracker/GPXFilesTableViewController.swift @@ -35,7 +35,7 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat var fileList: NSMutableArray = [kNoFiles] /// Is there any GPX file in the directory? - var gpxFilesFound = false; + var gpxFilesFound = false /// Temporary variable to manage. var selectedRowIndex = -1 @@ -62,7 +62,10 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat addNotificationObservers() // Button to return to the map - let shareItem = UIBarButtonItem(title: NSLocalizedString("DONE", comment: "no comment"), style: UIBarButtonItem.Style.plain, target: self, action: #selector(GPXFilesTableViewController.closeGPXFilesTableViewController)) + let shareItem = UIBarButtonItem(title: NSLocalizedString("DONE", comment: "no comment"), + style: UIBarButtonItem.Style.plain, + target: self, + action: #selector(GPXFilesTableViewController.closeGPXFilesTableViewController)) self.navigationItem.rightBarButtonItems = [shareItem] @@ -97,8 +100,7 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } - - + // MARK: Table view data source /// returns the number of sections. Always returns 1. @@ -106,20 +108,17 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat // Return the number of sections. return 1 } - - + /// Returns the number of files in the section. override func tableView(_ tableView: UITableView?, numberOfRowsInSection section: Int) -> Int { return fileList.count } - - + /// Allow edit rows? Returns true only if there are files. override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return gpxFilesFound } - - + /// Displays the delete button. override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, @@ -129,8 +128,7 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat actionDeleteFileAtIndex((indexPath as NSIndexPath).row) } } - - + /// Displays the name of the cell. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell @@ -138,13 +136,14 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat let cell: UITableViewCell = UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: "Cell") //cell.accessoryType = UITableViewCellAccessoryType.DetailDisclosureButton //cell.accessoryView = [[ UIImageView alloc ] initWithImage:[UIImage imageNamed:@"Something" ]]; + // swiftlint:disable force_cast let gpxFileInfo = fileList.object(at: (indexPath as NSIndexPath).row) as! GPXFileInfo + let lastSaved = NSLocalizedString("LAST_SAVED", comment: "no comment") cell.textLabel?.text = gpxFileInfo.fileName - cell.detailTextLabel?.text = String(format: NSLocalizedString("LAST_SAVED", comment: "no comment"), gpxFileInfo.modifiedDatetimeAgo, gpxFileInfo.fileSizeHumanised) + cell.detailTextLabel?.text = String(format: lastSaved, gpxFileInfo.modifiedDatetimeAgo, gpxFileInfo.fileSizeHumanised) if #available(iOS 13, *) { cell.detailTextLabel?.textColor = UIColor.secondaryLabel - } - else { + } else { cell.detailTextLabel?.textColor = UIColor.darkGray } return cell @@ -154,24 +153,23 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat return cell } } - - + /// Displays an action sheet with the actions for that file (Send it by email, Load in map and Delete). override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let sheet = UIAlertController(title: nil, message: NSLocalizedString("SELECT_OPTION", comment: "no comment"), preferredStyle: .actionSheet) - let mapOption = UIAlertAction(title: NSLocalizedString("LOAD_IN_MAP", comment: "no comment"), style: .default) { action in + let mapOption = UIAlertAction(title: NSLocalizedString("LOAD_IN_MAP", comment: "no comment"), style: .default) { _ in self.actionLoadFileAtIndex(indexPath.row) } - let shareOption = UIAlertAction(title: NSLocalizedString("SHARE", comment: "no comment"), style: .default) { action in + let shareOption = UIAlertAction(title: NSLocalizedString("SHARE", comment: "no comment"), style: .default) { _ in self.actionShareFileAtIndex(indexPath.row, tableView: tableView, indexPath: indexPath) } - let cancelOption = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { action in + let cancelOption = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { _ in self.actionSheetCancel(sheet) } - let deleteOption = UIAlertAction(title: NSLocalizedString("DELETE", comment: "no comment"), style: .destructive) { action in + let deleteOption = UIAlertAction(title: NSLocalizedString("DELETE", comment: "no comment"), style: .destructive) { _ in self.actionDeleteFileAtIndex(indexPath.row) } @@ -201,6 +199,7 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat /// Returns the name of the file in the `rowIndex` passed as parameter. internal func fileListObjectTitle(_ rowIndex: Int) -> String { + // swiftlint:disable force_cast return (fileList.object(at: rowIndex) as! GPXFileInfo).fileName } @@ -214,8 +213,7 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat internal func actionSheetCancel(_ actionSheet: UIAlertController) { print("ActionSheet cancel") } - - + /// Deletes from the disk storage the file of `fileList` at `rowIndex`. internal func actionDeleteFileAtIndex(_ rowIndex: Int) { @@ -231,8 +229,7 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.fade) tableView.reloadData() } - - + /// Loads the GPX file that corresponds to rowIndex in fileList in the map. internal func actionLoadFileAtIndex(_ rowIndex: Int) { DispatchQueue.global(qos: .userInitiated).async { @@ -273,8 +270,7 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat if #available(iOS 13, *) { activityIndicatorView.color = .blackAndWhite - } - else { + } else { activityIndicatorView.color = .black } @@ -283,8 +279,7 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat alertController.view.addSubview(activityIndicatorView) self.present(alertController, animated: true, completion: nil) - } - else { // will dismiss alert + } else { // will dismiss alert activityIndicatorView.stopAnimating() self.presentingViewController?.dismiss(animated: true, completion: nil) } @@ -293,8 +288,7 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat guard let completion = completion else { return } completion() } - - + /// Shares file at `rowIndex`. internal func actionShareFileAtIndex(_ rowIndex: Int, tableView: UITableView, indexPath: IndexPath) { guard let gpxFileInfo: GPXFileInfo = (fileList.object(at: rowIndex) as? GPXFileInfo) else { @@ -311,9 +305,11 @@ class GPXFilesTableViewController: UITableViewController, UINavigationBarDelegat activityViewController.popoverPresentationController?.sourceView = tableView.cellForRow(at: indexPath) activityViewController.popoverPresentationController?.sourceRect = cellRect - // NOTE: as the activity view controller can be quite tall at times, the display of it may be offset automatically at times to ensure the activity view popup fits the screen. + // NOTE: As the activity view controller can be quite tall at times, + // the display of it may be offset automatically at times to ensure the activity view popup fits the screen. - activityViewController.completionWithItemsHandler = {(activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in + activityViewController.completionWithItemsHandler = { + (activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in if !completed { // User canceled print("actionShareAtIndex: Cancelled") diff --git a/OpenGpxTracker/GPXMapView.swift b/OpenGpxTracker/GPXMapView.swift index 893d9cb5..c8e42669 100644 --- a/OpenGpxTracker/GPXMapView.swift +++ b/OpenGpxTracker/GPXMapView.swift @@ -5,7 +5,6 @@ // Created by merlos on 24/09/14. // - import Foundation import UIKit import MapKit @@ -48,14 +47,15 @@ class GPXMapView: MKMapView { ///position of the compass in the map ///Example: /// map.compassRect = CGRect(x: map.frame.width/2 - 18, y: 70, width: 36, height: 36) - var compassRect : CGRect + var compassRect: CGRect /// Is the map using local image cache?? var useCache: Bool = true { //use tile overlay cache ( didSet { - if self.tileServerOverlay is CachedTileOverlay { - print("GPXMapView:: setting useCache \(self.useCache)") - (self.tileServerOverlay as! CachedTileOverlay).useCache = self.useCache + if tileServerOverlay is CachedTileOverlay { + print("GPXMapView:: setting useCache \(useCache)") + // swiftlint:disable force_cast + (tileServerOverlay as! CachedTileOverlay).useCache = useCache } } } @@ -71,14 +71,16 @@ class GPXMapView: MKMapView { print("Setting map tiles overlay to: \(newValue.name)" ) updateMapInformation(newValue) // remove current overlay - if self.tileServer != .apple { + if tileServer != .apple { //to see apple maps we need to remove the overlay added by map cache. - self.removeOverlay(self.tileServerOverlay) + removeOverlay(tileServerOverlay) } /// Min distance to the floor of the camera if #available(iOS 13, *) { - self.setCameraZoomRange(MKMapView.CameraZoomRange(minCenterCoordinateDistance: newValue.minCameraDistance, maxCenterCoordinateDistance: -1), animated: true) + let zoomRange = MKMapView.CameraZoomRange(minCenterCoordinateDistance: newValue.minCameraDistance, + maxCenterCoordinateDistance: -1) + setCameraZoomRange(zoomRange, animated: true) } //add new overlay to map if not using Apple Maps @@ -90,13 +92,13 @@ class GPXMapView: MKMapView { if newValue.maximumZ > 0 { config.maximumZ = newValue.maximumZ } - if newValue.minimumZ > 0 { + if newValue.minimumZ > 0 { config.minimumZ = newValue.minimumZ } let cache = MapCache(withConfig: config) // the overlay returned substitutes Apple Maps tile overlay. // we need to keep a reference to remove it, in case we return back to Apple Maps. - self.tileServerOverlay = useCache(cache) + tileServerOverlay = useCache(cache) } } didSet { @@ -105,8 +107,7 @@ class GPXMapView: MKMapView { if tileServer == .apple { overrideUserInterfaceStyle = .unspecified NotificationCenter.default.post(name: .updateAppearance, object: nil, userInfo: nil) - } - else { // if map is third party, dark mode is disabled. + } else { // if map is third party, dark mode is disabled. overrideUserInterfaceStyle = .light NotificationCenter.default.post(name: .updateAppearance, object: nil, userInfo: nil) } @@ -135,16 +136,16 @@ class GPXMapView: MKMapView { /// required init?(coder aDecoder: NSCoder) { var tmpCoords: [CLLocationCoordinate2D] = [] //init with empty - self.currentSegmentOverlay = MKPolyline(coordinates: &tmpCoords, count: 0) - self.compassRect = CGRect.init(x: 0, y: 0, width: 36, height: 36) + currentSegmentOverlay = MKPolyline(coordinates: &tmpCoords, count: 0) + compassRect = CGRect.init(x: 0, y: 0, width: 36, height: 36) super.init(coder: aDecoder) // Rotation Gesture handling (for the map rotation's influence towards heading pointing arrow) rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotationGestureHandling(_:))) - self.addGestureRecognizer(rotationGesture) - self.isUserInteractionEnabled = true - self.isMultipleTouchEnabled = true + addGestureRecognizer(rotationGesture) + isUserInteractionEnabled = true + isMultipleTouchEnabled = true } /// @@ -153,7 +154,7 @@ class GPXMapView: MKMapView { override func layoutSubviews() { super.layoutSubviews() // set compass position by setting its frame - if let compassView = self.subviews.filter({ $0.isKind(of:NSClassFromString("MKCompassView")!) }).first { + if let compassView = subviews.filter({ $0.isKind(of: NSClassFromString("MKCompassView")!) }).first { if compassRect.origin.x != 0 { compassView.frame = compassRect } @@ -165,23 +166,23 @@ class GPXMapView: MKMapView { /// hides apple maps stuff when map tile != apple. func updateMapInformation(_ tileServer: GPXTileServer) { if let logoClass = NSClassFromString("MKAppleLogoImageView"), - let mapLogo = self.subviews.filter({ $0.isKind(of: logoClass) }).first { + let mapLogo = subviews.filter({ $0.isKind(of: logoClass) }).first { mapLogo.isHidden = (tileServer != .apple) } if let textClass = NSClassFromString("MKAttributionLabel"), - let mapText = self.subviews.filter({ $0.isKind(of: textClass) }).first { + let mapText = subviews.filter({ $0.isKind(of: textClass) }).first { mapText.isHidden = (tileServer != .apple) } } /// Handles rotation detected from user, for heading arrow to update. @objc func rotationGestureHandling(_ gesture: UIRotationGestureRecognizer) { - self.headingOffset = gesture.rotation - self.updateHeading() + headingOffset = gesture.rotation + updateHeading() if gesture.state == .ended { - self.headingOffset = nil + headingOffset = nil } } @@ -194,10 +195,10 @@ class GPXMapView: MKMapView { /// - point: The location in which the waypoint has to be added. /// func addWaypointAtViewPoint(_ point: CGPoint) { - let coords: CLLocationCoordinate2D = self.convert(point, toCoordinateFrom: self) + let coords: CLLocationCoordinate2D = convert(point, toCoordinateFrom: self) let waypoint = GPXWaypoint(coordinate: coords) - self.addWaypoint(waypoint) - self.coreDataHelper.add(toCoreData: waypoint) + addWaypoint(waypoint) + coreDataHelper.add(toCoreData: waypoint) } @@ -207,9 +208,9 @@ class GPXMapView: MKMapView { /// - Parameters: The waypoint to add to the map. /// func addWaypoint(_ waypoint: GPXWaypoint) { - self.session.addWaypoint(waypoint) - self.addAnnotation(waypoint) - self.extent.extendAreaToIncludeLocation(waypoint.coordinate) + session.addWaypoint(waypoint) + addAnnotation(waypoint) + extent.extendAreaToIncludeLocation(waypoint.coordinate) } /// @@ -223,18 +224,16 @@ class GPXMapView: MKMapView { print("Waypoint not found") return } - self.removeAnnotation(waypoint) - self.session.waypoints.remove(at: index!) - self.coreDataHelper.deleteWaypoint(fromCoreDataAt: index!) - //TODO: update map extent? - + removeAnnotation(waypoint) + session.waypoints.remove(at: index!) + coreDataHelper.deleteWaypoint(fromCoreDataAt: index!) } /// /// Updates the heading arrow based on the heading information /// func updateHeading() { - guard let heading = self.heading else { return } + guard let heading = heading else { return } headingImageView?.isHidden = false let rotation = CGFloat((heading.trueHeading - camera.heading)/180 * Double.pi) @@ -257,14 +256,14 @@ class GPXMapView: MKMapView { /// func addPointToCurrentTrackSegmentAtLocation(_ location: CLLocation) { let pt = GPXTrackPoint(location: location) - self.coreDataHelper.add(toCoreData: pt, withTrackSegmentID: session.trackSegments.count) - self.session.addPointToCurrentTrackSegmentAtLocation(location) + coreDataHelper.add(toCoreData: pt, withTrackSegmentID: session.trackSegments.count) + session.addPointToCurrentTrackSegmentAtLocation(location) //redrawCurrent track segment overlay //First remove last overlay, then re-add the overlay updated with the new point - self.removeOverlay(currentSegmentOverlay) - currentSegmentOverlay = self.session.currentSegment.overlay - self.addOverlay(currentSegmentOverlay) - self.extent.extendAreaToIncludeLocation(location.coordinate) + removeOverlay(currentSegmentOverlay) + currentSegmentOverlay = session.currentSegment.overlay + addOverlay(currentSegmentOverlay) + extent.extendAreaToIncludeLocation(location.coordinate) } /// @@ -272,9 +271,9 @@ class GPXMapView: MKMapView { /// initializes currentSegment to a new one. /// func startNewTrackSegment() { - if self.session.currentSegment.trackpoints.count > 0 { - self.session.startNewTrackSegment() - self.currentSegmentOverlay = MKPolyline() + if session.currentSegment.trackpoints.count > 0 { + session.startNewTrackSegment() + currentSegmentOverlay = MKPolyline() } } @@ -282,22 +281,22 @@ class GPXMapView: MKMapView { /// Finishes current segment. /// func finishCurrentSegment() { - self.startNewTrackSegment() //basically, we need to append the segment to the list of segments + startNewTrackSegment() //basically, we need to append the segment to the list of segments } /// /// Clears map. /// func clearMap() { - self.session.reset() - self.removeOverlays(self.overlays) - self.removeAnnotations(self.annotations) - self.extent = GPXExtentCoordinates() + session.reset() + removeOverlays(overlays) + removeAnnotations(annotations) + extent = GPXExtentCoordinates() //add tile server overlay //by removing all overlays, tile server overlay is also removed. We need to add it back if tileServer != .apple { - self.addOverlay(tileServerOverlay, level: .aboveLabels) + addOverlay(tileServerOverlay, level: .aboveLabels) } } @@ -307,22 +306,15 @@ class GPXMapView: MKMapView { /// /// func exportToGPXString() -> String { - return self.session.exportToGPXString() + return session.exportToGPXString() } /// /// Sets the map region to display all the GPX data in the map (segments and waypoints). /// func regionToGPXExtent() { - self.setRegion(extent.region, animated: true) - } - - - /* - func importFromGPXString(gpxString: String) { - // TODO + setRegion(extent.region, animated: true) } - */ /// Imports GPX contents into the map. /// @@ -330,67 +322,69 @@ class GPXMapView: MKMapView { /// - gpx: The result of loading a gpx file with iOS-GPX-Framework. /// func importFromGPXRoot(_ gpx: GPXRoot) { - //clear current map - self.clearMap() - //add waypoints - for pt in gpx.waypoints { - self.addWaypoint(pt) - self.coreDataHelper.add(toCoreData: pt) + clearMap() + addWaypoints(for: gpx) + addTrackSegments(for: gpx) + } + + private func addWaypoints(for gpx: GPXRoot, fromImport: Bool = true) { + for waypoint in gpx.waypoints { + addWaypoint(waypoint) + if fromImport { + coreDataHelper.add(toCoreData: waypoint) + } } - //add track segments - self.session.tracks = gpx.tracks - for oneTrack in self.session.tracks { - self.session.totalTrackedDistance += oneTrack.length + } + + private func addTrackSegments(for gpx: GPXRoot) { + session.tracks = gpx.tracks + for oneTrack in session.tracks { + session.totalTrackedDistance += oneTrack.length for segment in oneTrack.tracksegments { let overlay = segment.overlay - self.addOverlay(overlay) + addOverlay(overlay) let segmentTrackpoints = segment.trackpoints //add point to map extent for waypoint in segmentTrackpoints { - self.extent.extendAreaToIncludeLocation(waypoint.coordinate) + extent.extendAreaToIncludeLocation(waypoint.coordinate) } } } } func continueFromGPXRoot(_ gpx: GPXRoot) { - //clear current map - self.clearMap() + clearMap() + addWaypoints(for: gpx, fromImport: false) - for pt in gpx.waypoints { - self.addWaypoint(pt) - } - - self.session.continueFromGPXRoot(gpx) + session.continueFromGPXRoot(gpx) // for last session's previous tracks, through resuming - for oneTrack in self.session.tracks { + for oneTrack in session.tracks { session.totalTrackedDistance += oneTrack.length for segment in oneTrack.tracksegments { let overlay = segment.overlay - self.addOverlay(overlay) + addOverlay(overlay) let segmentTrackpoints = segment.trackpoints //add point to map extent for waypoint in segmentTrackpoints { - self.extent.extendAreaToIncludeLocation(waypoint.coordinate) + extent.extendAreaToIncludeLocation(waypoint.coordinate) } } } // for last session track segment - for trackSegment in self.session.trackSegments { + for trackSegment in session.trackSegments { let overlay = trackSegment.overlay - self.addOverlay(overlay) + addOverlay(overlay) let segmentTrackpoints = trackSegment.trackpoints //add point to map extent for waypoint in segmentTrackpoints { - self.extent.extendAreaToIncludeLocation(waypoint.coordinate) + extent.extendAreaToIncludeLocation(waypoint.coordinate) } } } } - diff --git a/OpenGpxTracker/GPXRoot+length.swift b/OpenGpxTracker/GPXRoot+length.swift index 71baab67..c4ebfe4a 100644 --- a/OpenGpxTracker/GPXRoot+length.swift +++ b/OpenGpxTracker/GPXRoot+length.swift @@ -14,12 +14,10 @@ extension GPXRoot { /// Distance in meters of all the track segments public var tracksLength: CLLocationDistance { - get { - var tLength: CLLocationDistance = 0.0 - for track in self.tracks { - tLength += track.length - } - return tLength + var tLength: CLLocationDistance = 0.0 + for track in self.tracks { + tLength += track.length } + return tLength } } diff --git a/OpenGpxTracker/GPXSession.swift b/OpenGpxTracker/GPXSession.swift index ab961075..ce131342 100644 --- a/OpenGpxTracker/GPXSession.swift +++ b/OpenGpxTracker/GPXSession.swift @@ -53,7 +53,6 @@ class GPXSession { /// Current segment distance in meters var currentSegmentDistance = 0.00 - /// /// Adds a waypoint to the map. /// @@ -155,11 +154,10 @@ class GPXSession { //add track segments self.tracks = gpx.tracks + self.trackSegments = lastTrack.tracksegments // remove last track as that track is packaged by Core Data, but should its tracksegments should be seperated, into self.tracksegments. - //self.tracks.removeLast() - - self.trackSegments = lastTrack.tracksegments + self.tracks.removeLast() } diff --git a/OpenGpxTracker/GPXTileServer.swift b/OpenGpxTracker/GPXTileServer.swift index 4ab282f3..643f28d3 100644 --- a/OpenGpxTracker/GPXTileServer.swift +++ b/OpenGpxTracker/GPXTileServer.swift @@ -39,7 +39,6 @@ enum GPXTileServer: Int { case .openStreetMap: return "Open Street Map" case .cartoDB: return "Carto DB" case .openTopoMap: return "OpenTopoMap" - //case .AnotherMap: return "My Map" } } @@ -48,10 +47,8 @@ enum GPXTileServer: Int { switch self { case .apple: return "" case .openStreetMap: return "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" - // case .cartoDB: return "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png" case .cartoDB: return "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png" case .openTopoMap: return "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png" - //case .AnotherMap: return "http://another.map.tile.server/{z}/{x}/{y}.png" } } @@ -65,10 +62,9 @@ enum GPXTileServer: Int { var subdomains: [String] { switch self { case .apple: return [] - case .openStreetMap: return ["a","b","c"] - case .cartoDB: return ["a","b","c"] - case .openTopoMap: return ["a","b","c"] - //case .AnotherMap: return ["a","b"] + case .openStreetMap: return ["a", "b", "c"] + case .cartoDB: return ["a", "b", "c"] + case .openTopoMap: return ["a", "b", "c"] } } /// Maximum zoom level the tile server supports @@ -83,11 +79,14 @@ enum GPXTileServer: Int { /// var maximumZ: Int { switch self { - case .apple: return -1 - case .openStreetMap: return 19 - case .cartoDB: return 21 - case .openTopoMap: return 17 - //case .AnotherMap: return 10 + case .apple: + return -1 + case .openStreetMap: + return 19 + case .cartoDB: + return 21 + case .openTopoMap: + return 17 } } /// @@ -100,11 +99,14 @@ enum GPXTileServer: Int { /// var minimumZ: Int { switch self { - case .apple: return 0 - case .openStreetMap: return 0 - case .cartoDB: return 0 - case .openTopoMap: return 0 - //case .AnotherMap: return ["a","b"] + case .apple: + return 0 + case .openStreetMap: + return 0 + case .cartoDB: + return 0 + case .openTopoMap: + return 0 } } @@ -121,11 +123,9 @@ enum GPXTileServer: Int { case .openStreetMap: return 750.0 case .cartoDB: return 200.0 case .openTopoMap: return 2850.0 - //case .AnotherMap: return 1000.0 } } - - + /// Returns the number of tile servers currently defined static var count: Int { return GPXTileServer.openTopoMap.rawValue + 1} } diff --git a/OpenGpxTracker/GPXTrack+length.swift b/OpenGpxTracker/GPXTrack+length.swift index f40d1dfa..8e6df216 100644 --- a/OpenGpxTracker/GPXTrack+length.swift +++ b/OpenGpxTracker/GPXTrack+length.swift @@ -14,12 +14,10 @@ extension GPXTrack { /// Track length in meters public var length: CLLocationDistance { - get { - var trackLength: CLLocationDistance = 0.0 - for segment in tracksegments { - trackLength += segment.length() - } - return trackLength + var trackLength: CLLocationDistance = 0.0 + for segment in tracksegments { + trackLength += segment.length() } + return trackLength } } diff --git a/OpenGpxTracker/GPXTrackPoint+MapKit.swift b/OpenGpxTracker/GPXTrackPoint+MapKit.swift index d161b45c..d89c2036 100644 --- a/OpenGpxTracker/GPXTrackPoint+MapKit.swift +++ b/OpenGpxTracker/GPXTrackPoint+MapKit.swift @@ -5,7 +5,6 @@ // Created by merlos on 20/09/14. // - import Foundation import UIKit import MapKit @@ -22,6 +21,4 @@ extension GPXTrackPoint { self.time = Date() self.elevation = location.altitude } - - } diff --git a/OpenGpxTracker/GPXTrackSegment+MapKit.swift b/OpenGpxTracker/GPXTrackSegment+MapKit.swift index 2f9be506..52d96bfe 100644 --- a/OpenGpxTracker/GPXTrackSegment+MapKit.swift +++ b/OpenGpxTracker/GPXTrackSegment+MapKit.swift @@ -5,7 +5,6 @@ // Created by merlos on 20/09/14. // - import Foundation import UIKit import MapKit @@ -20,11 +19,9 @@ extension GPXTrackSegment { /// Returns a MapKit polyline with the points of the segment. /// This polyline can be directly plotted on the map as an overlay public var overlay: MKPolyline { - get { - var coords: [CLLocationCoordinate2D] = self.trackPointsToCoordinates() - let pl = MKPolyline(coordinates: &coords, count: coords.count) - return pl - } + var coords: [CLLocationCoordinate2D] = self.trackPointsToCoordinates() + let pl = MKPolyline(coordinates: &coords, count: coords.count) + return pl } } #endif diff --git a/OpenGpxTracker/GPXWaypoint+MKAnnotation.swift b/OpenGpxTracker/GPXWaypoint+MKAnnotation.swift index fc742749..3fcafb96 100644 --- a/OpenGpxTracker/GPXWaypoint+MKAnnotation.swift +++ b/OpenGpxTracker/GPXWaypoint+MKAnnotation.swift @@ -13,7 +13,7 @@ import CoreGPX /// Extends GPXWaypoint to support the MKAnnotation protocol. It allows to /// add the waypoint as a pin in the map /// -extension GPXWaypoint : MKAnnotation { +extension GPXWaypoint: MKAnnotation { /// /// Inits the point with a coordinate diff --git a/OpenGpxTracker/MailerManager.swift b/OpenGpxTracker/MailerManager.swift index e709c781..550dd74b 100644 --- a/OpenGpxTracker/MailerManager.swift +++ b/OpenGpxTracker/MailerManager.swift @@ -31,29 +31,23 @@ class MailerManager: NSObject, MFMailComposeViewControllerDelegate { var body = "Open GPX Tracker \n is an open source app for Apple devices. Create GPS tracks and export them to GPX files." composer.setMessageBody(body, isHTML: true) let fileData: NSData = NSData.dataWithContentsOfFile(filepath, options: .DataReadingMappedIfSafe, error: nil) - composer.addAttachmentData(fileData, mimeType:"application/gpx+xml", fileName: filepath.lastPathComponent) + composer.addAttachmentData(fileData, mimeType: "application/gpx+xml", fileName: filepath.lastPathComponent) //Display the comopser view controller controller.presentViewController(composer, animated: true, completion: nil) } - - - - + func mailComposeController(controller: MFMailComposeViewController!, - didFinishWithResult result: MFMailComposeResult, - error: NSError!) { - + didFinishWithResult result: MFMailComposeResult, + error: NSError!) { switch result.value { case MFMailComposeResultSent.value: println("Email sent") - default: println("Whoops") } controller.dismissViewControllerAnimated(true, completion: nil) - } } diff --git a/OpenGpxTracker/MapViewDelegate.swift b/OpenGpxTracker/MapViewDelegate.swift index 6e68ae0b..506db56f 100644 --- a/OpenGpxTracker/MapViewDelegate.swift +++ b/OpenGpxTracker/MapViewDelegate.swift @@ -87,17 +87,19 @@ class MapViewDelegate: NSObject, MKMapViewDelegate, UIAlertViewDelegate { let indexofEditedWaypoint = map.session.waypoints.firstIndex(of: waypoint) - let alertController = UIAlertController(title: NSLocalizedString("EDIT_WAYPOINT_NAME_TITLE", comment: "no comment"), message: NSLocalizedString("EDIT_WAYPOINT_NAME_MESSAGE", comment: "no comment"), preferredStyle: .alert) + let alertController = UIAlertController(title: NSLocalizedString("EDIT_WAYPOINT_NAME_TITLE", comment: "no comment"), + message: NSLocalizedString("EDIT_WAYPOINT_NAME_MESSAGE", comment: "no comment"), + preferredStyle: .alert) alertController.addTextField { (textField) in textField.text = waypoint.title textField.clearButtonMode = .always } - let saveAction = UIAlertAction(title: NSLocalizedString("SAVE", comment: "no comment"), style: .default) { (action) in + let saveAction = UIAlertAction(title: NSLocalizedString("SAVE", comment: "no comment"), style: .default) { _ in print("Edit waypoint alert view") self.waypointBeingEdited.title = alertController.textFields?[0].text map.coreDataHelper.update(toCoreData: self.waypointBeingEdited, from: indexofEditedWaypoint!) } - let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { (action) in } + let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { _ in } alertController.addAction(saveAction) alertController.addAction(cancelAction) @@ -110,11 +112,13 @@ class MapViewDelegate: NSObject, MKMapViewDelegate, UIAlertViewDelegate { print("[calloutAccesoryControlTapped ERROR] unknown control") } } - - + /// Handles the change of the coordinates when a pin is dropped. - func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, - didChange newState: MKAnnotationView.DragState, fromOldState oldState: MKAnnotationView.DragState) { + func mapView(_ mapView: MKMapView, + annotationView view: MKAnnotationView, + didChange newState: MKAnnotationView.DragState, + fromOldState oldState: MKAnnotationView.DragState) { + // swiftlint:disable force_cast let gpxMapView = mapView as! GPXMapView if newState == MKAnnotationView.DragState.ending { @@ -123,16 +127,18 @@ class MapViewDelegate: NSObject, MKMapViewDelegate, UIAlertViewDelegate { if let index = gpxMapView.session.waypoints.firstIndex(of: point) { gpxMapView.coreDataHelper.update(toCoreData: point, from: index) } - - print("Annotation name: \(String(describing: point.title)) lat:\(String(describing:point.latitude)) lon \(String(describing:point.longitude))") + let titleDesc = String(describing: point.title) + let latDesc = String(describing: point.latitude) + let lonDesc = String(describing: point.longitude) + print("Annotation name: \(titleDesc) lat:\(latDesc) lon \(lonDesc)") } } } - /// Adds the pin to the map with an animation (comes from the top of the screen) func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) { var i = 0 + // swiftlint:disable force_cast let gpxMapView = mapView as! GPXMapView var hasImpacted = false //adds the pins with an animation @@ -144,7 +150,10 @@ class MapViewDelegate: NSObject, MKMapViewDelegate, UIAlertViewDelegate { if gpxMapView.headingImageView == nil { let image = UIImage(named: "heading")! gpxMapView.headingImageView = UIImageView(image: image) - gpxMapView.headingImageView!.frame = CGRect(x: (annotationView.frame.size.width - image.size.width)/2, y: (annotationView.frame.size.height - image.size.height)/2, width: image.size.width, height: image.size.height) + gpxMapView.headingImageView!.frame = CGRect(x: (annotationView.frame.size.width - image.size.width)/2, + y: (annotationView.frame.size.height - image.size.height)/2, + width: image.size.width, + height: image.size.height) annotationView.insertSubview(gpxMapView.headingImageView!, at: 0) gpxMapView.headingImageView!.isHidden = true } @@ -155,7 +164,7 @@ class MapViewDelegate: NSObject, MKMapViewDelegate, UIAlertViewDelegate { let endFrame: CGRect = annotationView.frame annotationView.frame = CGRect(x: annotationView.frame.origin.x, y: annotationView.frame.origin.y - mapView.superview!.frame.size.height, - width: annotationView.frame.size.width, height:annotationView.frame.size.height) + width: annotationView.frame.size.width, height: annotationView.frame.size.height) let interval: TimeInterval = 0.04 * 1.1 UIView.animate(withDuration: 0.5, delay: interval, options: UIView.AnimationOptions.curveLinear, animations: { () -> Void in annotationView.frame = endFrame @@ -165,7 +174,7 @@ class MapViewDelegate: NSObject, MKMapViewDelegate, UIAlertViewDelegate { //aV.transform = CGAffineTransformMakeScale(1.0, 0.8) annotationView.transform = CGAffineTransform(a: 1.0, b: 0, c: 0, d: 0.8, tx: 0, ty: annotationView.frame.size.height*0.1) - }, completion: { (finished: Bool) -> Void in + }, completion: { _ -> Void in UIView.animate(withDuration: 0.1, animations: { () -> Void in annotationView.transform = CGAffineTransform.identity }) diff --git a/OpenGpxTracker/Preferences.swift b/OpenGpxTracker/Preferences.swift index 24d9f0c4..0f203a75 100644 --- a/OpenGpxTracker/Preferences.swift +++ b/OpenGpxTracker/Preferences.swift @@ -7,11 +7,9 @@ // // Shared file: this file is also included in the OpenGpxTracker-Watch Extension target. - import Foundation import CoreLocation - /// Key on Defaults for the Tile Server integer. let kDefaultsKeyTileServerInt: String = "TileServerInt" @@ -96,13 +94,15 @@ class Preferences: NSObject { } else { // get from locale config let locale = NSLocale.current _useImperial = !locale.usesMetricSystem - print("** Preferences:: NO defaults for useImperial. Using locale: \(locale.languageCode ?? "unknown") useImperial: \(_useImperial) usesMetric:\(locale.usesMetricSystem)") + let langCode = locale.languageCode ?? "unknown" + let useMetric = locale.usesMetricSystem + print("** Preferences:: NO defaults for useImperial. Using locale: \(langCode) useImperial: \(_useImperial) usesMetric:\(useMetric)") } // Use cache if let useCacheFromDefaults = defaults.object(forKey: kDefaultsKeyUseCache) as? Bool { _useCache = useCacheFromDefaults - print("Preferences:: loaded preference from defaults useCache= \(useCacheFromDefaults)"); + print("Preferences:: loaded preference from defaults useCache= \(useCacheFromDefaults)") } // Map Tile server @@ -255,10 +255,8 @@ class Preferences: NSObject { /// Get date format preset name var dateFormatPresetName: String { - get { - let presets = ["Defaults", "ISO8601 (UTC)", "ISO8601 (UTC offset)", "Day, Date at time (12 hr)", "Day, Date at time (24 hr)"] - return _dateFormatPreset < presets.count ? presets[_dateFormatPreset] : "???" - } + let presets = ["Defaults", "ISO8601 (UTC)", "ISO8601 (UTC offset)", "Day, Date at time (12 hr)", "Day, Date at time (24 hr)"] + return _dateFormatPreset < presets.count ? presets[_dateFormatPreset] : "???" } /// Get and sets whether to use UTC for date format diff --git a/OpenGpxTracker/PreferencesTableViewController.swift b/OpenGpxTracker/PreferencesTableViewController.swift index 186734e9..32e056e3 100644 --- a/OpenGpxTracker/PreferencesTableViewController.swift +++ b/OpenGpxTracker/PreferencesTableViewController.swift @@ -50,9 +50,9 @@ class PreferencesTableViewController: UITableViewController, UINavigationBarDele weak var delegate: PreferencesTableViewControllerDelegate? /// Global Preferences - var preferences : Preferences = Preferences.shared + var preferences: Preferences = Preferences.shared - var cache : MapCache = MapCache(withConfig: MapCacheConfig(withUrlTemplate: "")) + var cache: MapCache = MapCache(withConfig: MapCacheConfig(withUrlTemplate: "")) // Compute once, better performance for scrolling table view (reuse) /// Store cached size for reuse. @@ -70,7 +70,9 @@ class PreferencesTableViewController: UITableViewController, UINavigationBarDele self.view.frame.height - navBarFrame.height) self.title = NSLocalizedString("PREFERENCES", comment: "no comment") - let shareItem = UIBarButtonItem(title: NSLocalizedString("DONE", comment: "no comment"), style: UIBarButtonItem.Style.plain, target: self, action: #selector(PreferencesTableViewController.closePreferencesTableViewController)) + let shareItem = UIBarButtonItem(title: NSLocalizedString("DONE", comment: "no comment"), + style: UIBarButtonItem.Style.plain, target: self, + action: #selector(PreferencesTableViewController.closePreferencesTableViewController)) self.navigationItem.rightBarButtonItems = [shareItem] let fileSize = cache.diskCache.fileSize ?? 0 @@ -107,7 +109,7 @@ class PreferencesTableViewController: UITableViewController, UINavigationBarDele /// Uses `kCacheSection`, `kUnitsSection`, `kMapSourceSection` and `kActivityTypeSection` /// for deciding which is the section title override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - switch(section) { + switch section { case kUnitsSection: return NSLocalizedString("UNITS", comment: "no comment") case kCacheSection: return NSLocalizedString("CACHE", comment: "no comment") case kMapSourceSection: return NSLocalizedString("MAP_SOURCE", comment: "no comment") @@ -121,7 +123,7 @@ class PreferencesTableViewController: UITableViewController, UINavigationBarDele /// for `kMapSourceSection` returns the number of tile servers defined in `GPXTileServer`, /// and for kActivityTypeSection returns `CLActivityType.count` override func tableView(_ tableView: UITableView?, numberOfRowsInSection section: Int) -> Int { - switch(section) { + switch section { case kCacheSection: return 2 case kUnitsSection: return 1 case kMapSourceSection: return GPXTileServer.count @@ -148,7 +150,7 @@ class PreferencesTableViewController: UITableViewController, UINavigationBarDele // Units section if indexPath.section == kUnitsSection { - switch (indexPath.row) { + switch indexPath.row { case kUseImperialUnitsCell: cell = UITableViewCell(style: .value1, reuseIdentifier: "CacheCell") cell.textLabel?.text = NSLocalizedString("USE_IMPERIAL_UNITS", comment: "no comment") @@ -161,7 +163,7 @@ class PreferencesTableViewController: UITableViewController, UINavigationBarDele // Cache Section if indexPath.section == kCacheSection { - switch (indexPath.row) { + switch indexPath.row { case kUseOfflineCacheCell: cell = UITableViewCell(style: .subtitle, reuseIdentifier: "CacheCell") cell.textLabel?.text = NSLocalizedString("OFFLINE_CACHE", comment: "no comment") @@ -205,7 +207,9 @@ class PreferencesTableViewController: UITableViewController, UINavigationBarDele let dateFormatter = DefaultDateFormat() cell = UITableViewCell(style: .subtitle, reuseIdentifier: "DefaultNameCell") cell.textLabel?.text = preferences.dateFormatPreset == -1 ? preferences.dateFormatInput : preferences.dateFormatPresetName - let dateText = dateFormatter.getDate(processedFormat: preferences.dateFormat, useUTC: preferences.dateFormatUseUTC, useENLocale: preferences.dateFormatUseEN) + let dateText = dateFormatter.getDate(processedFormat: preferences.dateFormat, + useUTC: preferences.dateFormatUseUTC, + useENLocale: preferences.dateFormatUseEN) cell.detailTextLabel?.text = dateText cell.accessoryType = .disclosureIndicator } diff --git a/OpenGpxTracker/StopWatch.swift b/OpenGpxTracker/StopWatch.swift index 90b0986c..d19a220a 100644 --- a/OpenGpxTracker/StopWatch.swift +++ b/OpenGpxTracker/StopWatch.swift @@ -7,7 +7,6 @@ import Foundation - /// Posible status of the stop watch enum StopWatchStatus { @@ -18,7 +17,6 @@ enum StopWatchStatus { case stopped } - /// /// This class handles the logic behind a stop watch timer /// It has two statuses: started or stopped. When started it counts time. @@ -56,7 +54,9 @@ class StopWatch: NSObject { print("StopWatch: started") self.status = .started self.startedTime = Date.timeIntervalSinceReferenceDate - timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(StopWatch.updateElapsedTime), userInfo: nil, repeats: true) + timer = Timer.scheduledTimer(timeInterval: timeInterval, + target: self, selector: #selector(StopWatch.updateElapsedTime), + userInfo: nil, repeats: true) } /// Stops counting time @@ -66,7 +66,7 @@ class StopWatch: NSObject { //add difference between start and stop to elapsed time let currentTime = Date.timeIntervalSinceReferenceDate let diff = currentTime - startedTime - tmpElapsedTime = tmpElapsedTime + diff + tmpElapsedTime += diff timer.invalidate() } @@ -81,13 +81,11 @@ class StopWatch: NSObject { /// Current elapsed time. var elapsedTime: TimeInterval { - get { - if self.status == .stopped { - return self.tmpElapsedTime - } - let diff = Date.timeIntervalSinceReferenceDate - startedTime - return tmpElapsedTime + diff + if self.status == .stopped { + return self.tmpElapsedTime } + let diff = Date.timeIntervalSinceReferenceDate - startedTime + return tmpElapsedTime + diff } /// @@ -98,30 +96,28 @@ class StopWatch: NSObject { /// 2. 3h 40 min 30 sec, returns `3h40:20` /// var elapsedTimeString: String { - get { - var tmpTime: TimeInterval = self.elapsedTime - //calculate the minutes and hours in elapsed time. - - let hours = UInt32(tmpTime / 3600.0) - tmpTime -= (TimeInterval(hours) * 3600) - - let minutes = UInt32(tmpTime / 60.0) - tmpTime -= (TimeInterval(minutes) * 60) - - //calculate the seconds in elapsed time. - let seconds = UInt32(tmpTime) - tmpTime -= TimeInterval(seconds) - - //display hours only if >0 - let strHours = hours > 0 ? String(hours) + "h" : "" - //add the leading zero for minutes, seconds and millseconds and store them as string constants - - let strMinutes = minutes > 9 ? String(minutes):"0" + String(minutes) - let strSeconds = seconds > 9 ? String(seconds):"0" + String(seconds) - - //concatenate hours, minutes and seconds - return "\(strHours)\(strMinutes):\(strSeconds)" - } + var tmpTime: TimeInterval = self.elapsedTime + //calculate the minutes and hours in elapsed time. + + let hours = UInt32(tmpTime / 3600.0) + tmpTime -= (TimeInterval(hours) * 3600) + + let minutes = UInt32(tmpTime / 60.0) + tmpTime -= (TimeInterval(minutes) * 60) + + //calculate the seconds in elapsed time. + let seconds = UInt32(tmpTime) + tmpTime -= TimeInterval(seconds) + + //display hours only if >0 + let strHours = hours > 0 ? String(hours) + "h" : "" + //add the leading zero for minutes, seconds and millseconds and store them as string constants + + let strMinutes = minutes > 9 ? String(minutes):"0" + String(minutes) + let strSeconds = seconds > 9 ? String(seconds):"0" + String(seconds) + + //concatenate hours, minutes and seconds + return "\(strHours)\(strMinutes):\(strSeconds)" } /// Calls the delegate (didUpdateElapsedTimeString) to inform there was an update of the elapsed time. diff --git a/OpenGpxTracker/TrackerButton.swift b/OpenGpxTracker/TrackerButton.swift index 1f6abe5c..e3544e44 100644 --- a/OpenGpxTracker/TrackerButton.swift +++ b/OpenGpxTracker/TrackerButton.swift @@ -1,4 +1,3 @@ - import UIKit /// Creates a button with rounded corners. diff --git a/OpenGpxTracker/UIColor+DarkMode.swift b/OpenGpxTracker/UIColor+DarkMode.swift index a44f0d6c..5660c481 100644 --- a/OpenGpxTracker/UIColor+DarkMode.swift +++ b/OpenGpxTracker/UIColor+DarkMode.swift @@ -14,35 +14,47 @@ extension UIColor { /// Main UI color static let mainUIColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in switch traitCollection.userInterfaceStyle { - case .unspecified, .light: return UIColor(red: 0, green: 0.61, blue: 0.86, alpha: 1) - case .dark: return .white - @unknown default: return UIColor(red: 0, green: 0.61, blue: 0.86, alpha: 1) - } + case .unspecified, .light: + return UIColor(red: 0, green: 0.61, blue: 0.86, alpha: 1) + case .dark: + return .white + default: + return UIColor(red: 0, green: 0.61, blue: 0.86, alpha: 1) + } } /// Returns a colour opposite of trait collection (i.e. light gets black, dark gets white.) static let blackAndWhite = UIColor { (traitCollection: UITraitCollection) -> UIColor in switch traitCollection.userInterfaceStyle { - case .unspecified, .light: return .black - case .dark: return .white - @unknown default: return .systemGray - } + case .unspecified, .light: + return .black + case .dark: + return .white + default: + return .systemGray + } } static let keyboardColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in switch traitCollection.userInterfaceStyle { - case .unspecified, .light: return .lightKeyboard - case .dark: return .darkKeyboard - @unknown default: return .lightKeyboard - } + case .unspecified, .light: + return .lightKeyboard + case .dark: + return .darkKeyboard + default: + return .lightKeyboard + } } static let highlightKeyboardColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in switch traitCollection.userInterfaceStyle { - case .unspecified, .light: return .highlightLightKeyboard - case .dark: return .highlightDarkKeyboard - @unknown default: return .highlightLightKeyboard - } + case .unspecified, .light: + return .highlightLightKeyboard + case .dark: + return .highlightDarkKeyboard + default: + return .highlightLightKeyboard + } } } diff --git a/OpenGpxTracker/ViewController.swift b/OpenGpxTracker/ViewController.swift index c19c5d7f..04ff86c3 100644 --- a/OpenGpxTracker/ViewController.swift +++ b/OpenGpxTracker/ViewController.swift @@ -12,6 +12,8 @@ import CoreLocation import MapKit import CoreGPX +// swiftlint:disable line_length + /// Purple color for button background let kPurpleButtonBackgroundColor: UIColor = UIColor(red: 146.0/255.0, green: 166.0/255.0, blue: 218.0/255.0, alpha: 0.90) @@ -76,7 +78,7 @@ let kSignalAccuracy1 = 201.0 /// Displays a map and a set the buttons to control the tracking /// /// -class ViewController: UIViewController, UIGestureRecognizerDelegate { +class ViewController: UIViewController, UIGestureRecognizerDelegate { /// Shall the map be centered on current user position? /// If yes, whenever the user moves, the center of the map too. @@ -97,8 +99,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { /// TBD (not currently used) var followUserBeforePinchGesture = true - - + /// location manager instance configuration let locationManager: CLLocationManager = { let manager = CLLocationManager() @@ -143,8 +144,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { } } } - - + /// Defines the different statuses regarding tracking current user location. enum GpxTrackingStatus { @@ -179,7 +179,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { lastGpxFilename = "" //clear last filename, so when saving it appears an empty field map.coreDataHelper.clearAll() - map.coreDataHelper.deleteCDRootFromCoreData() + map.coreDataHelper.coreDataDeleteAll(of: CDRoot.self)//deleteCDRootFromCoreData() totalTrackedDistanceLabel.distance = (map.session.totalTrackedDistance) currentSegmentDistanceLabel.distance = (map.session.currentSegmentDistance) @@ -223,8 +223,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { /// Editing Waypoint Temporal Reference var lastLocation: CLLocation? //Last point of current segment. - - + //UI /// Label with the title of the app var appTitleLabel: UILabel @@ -342,7 +341,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { /// deinit { print("*** deinit") - removeNotificationObservers() + NotificationCenter.default.removeObserver(self) } /// Handles status bar color as a result from iOS 13 appearance changes @@ -352,19 +351,15 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { if self.traitCollection.userInterfaceStyle == .dark && map.tileServer == .apple { self.view.backgroundColor = .black return .lightContent - } - else { + } else { self.view.backgroundColor = .white return .darkContent } - } - // > iPhone X has no opaque status bar - else { + } else { // > iPhone X has no opaque status bar // if is > iP X status bar can be white when map is dark return map.tileServer == .apple ? .default : .darkContent } - } - else { // < iOS 13 + } else { // < iOS 13 return .default } } @@ -382,7 +377,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { //Because of the edges, iPhone X* is slightly different on the layout. //So, Is the current device an iPhone X? - if UIDevice().userInterfaceIdiom == .phone { + if UIDevice.current.userInterfaceIdiom == .phone { switch UIScreen.main.nativeBounds.height { case 1136: print("device: IPHONE 5,5S,5C") @@ -418,7 +413,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { map.isZoomEnabled = true map.isRotateEnabled = true //set the position of the compass. - map.compassRect = CGRect(x: map.frame.width/2 - 18, y: isIPhoneX ? 105.0 : 70.0 , width: 36, height: 36) + map.compassRect = CGRect(x: map.frame.width/2 - 18, y: isIPhoneX ? 105.0 : 70.0, width: 36, height: 36) //If user long presses the map, it will add a Pin (waypoint) at that point map.addGestureRecognizer( @@ -562,17 +557,16 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { // Add signal accuracy images and labels signalImageView.image = signalImage0 - signalImageView.frame = CGRect(x: self.view.frame.width/2 - 25.0, y: 14 + 5 + iPhoneXdiff, width: 50, height: 30) + signalImageView.frame = CGRect(x: self.view.frame.width/2 - 25.0, y: 14 + 5 + iPhoneXdiff, width: 50, height: 30) signalImageView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin] map.addSubview(signalImageView) - signalAccuracyLabel.frame = CGRect(x: self.view.frame.width/2 - 25.0, y: 14 + 5 + 30 + iPhoneXdiff , width: 50, height: 12) + signalAccuracyLabel.frame = CGRect(x: self.view.frame.width/2 - 25.0, y: 14 + 5 + 30 + iPhoneXdiff, width: 50, height: 12) signalAccuracyLabel.font = font12 signalAccuracyLabel.text = kUnknownAccuracyText signalAccuracyLabel.textAlignment = .center signalAccuracyLabel.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin] map.addSubview(signalAccuracyLabel) - - + // // Button Bar // @@ -589,7 +583,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { // [-----------------------|--------------------------] // map.frame/2 (center) - // Start/Pause button trackerButton.layer.cornerRadius = kButtonLargeSize/2 trackerButton.setTitle(NSLocalizedString("START_TRACKING", comment: "no comment"), for: UIControl.State()) @@ -607,8 +600,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { newPinButton.setImage(UIImage(named: "addPin"), for: UIControl.State()) newPinButton.setImage(UIImage(named: "addPinHigh"), for: .highlighted) newPinButton.addTarget(self, action: #selector(ViewController.addPinAtMyLocation), for: .touchUpInside) - //let newPinLongPress = UILongPressGestureRecognizer(target: self, action: Selector("newPinLongPress:")) - //newPinButton.addGestureRecognizer(newPinLongPress) map.addSubview(newPinButton) // Follow user button @@ -650,6 +641,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { } } + // MARK: - Add Constraints for views /// Adds Constraints to subviews /// /// The constraints will ensure that subviews will be positioned correctly, when there are orientation changes, or iPad split view width changes. @@ -657,7 +649,12 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { /// - Parameters: /// - isIPhoneX: if device is >= iPhone X, bottom gap will be zero func addConstraints(_ isIPhoneX: Bool) { - + addConstraintsToAppTitleBar(isIPhoneX) + addConstraintsToInfoLabels(isIPhoneX) + addConstraintsToButtonBar(isIPhoneX) + } + /// Adds constraints to subviews forming the app title bar (top bar) + func addConstraintsToAppTitleBar(_ isIPhoneX: Bool) { // MARK: App Title Bar // Switch off all autoresizing masks translate @@ -671,7 +668,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { NSLayoutConstraint(item: appTitleLabel, attribute: .trailing, relatedBy: .equal, toItem: coordsLabel, attribute: .trailing, multiplier: 1, constant: 5).isActive = true NSLayoutConstraint(item: appTitleLabel, attribute: .leading, relatedBy: .equal, toItem: coordsLabel, attribute: .leading, multiplier: 1, constant: 0).isActive = true NSLayoutConstraint(item: appTitleLabel, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: 0).isActive = true - + } + + /// Adds constraints to subviews forming the informational labels (top right side; i.e. speed, elapse time labels) + func addConstraintsToInfoLabels(_ isIPhoneX: Bool) { // MARK: Information Labels /// offset from center, without obstructing signal view @@ -699,8 +699,11 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { NSLayoutConstraint(item: currentSegmentDistanceLabel, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: -7).isActive = true NSLayoutConstraint(item: currentSegmentDistanceLabel, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: kSignalViewOffset).isActive = true NSLayoutConstraint(item: currentSegmentDistanceLabel, attribute: .top, relatedBy: .equal, toItem: totalTrackedDistanceLabel, attribute: .bottom, multiplier: 1, constant: 0).isActive = true - - + + } + + /// Adds constraints to subviews forming the button bar (bottom session controls bar) + func addConstraintsToButtonBar(_ isIPhoneX: Bool) { // MARK: Button Bar // constants @@ -747,9 +750,9 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) - DispatchQueue.main.async(){ + DispatchQueue.main.async { // set the new position of the compass. - self.map.compassRect = CGRect(x: size.width/2 - 18, y: 70.0 , width: 36, height: 36) + self.map.compassRect = CGRect(x: size.width/2 - 18, y: 70.0, width: 36, height: 36) // update compass frame location self.map.layoutSubviews() } @@ -763,11 +766,9 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { /// Updates polyline color func updatePolylineColor() { - for overlay in map.overlays { - if overlay is MKPolyline { + for overlay in map.overlays where overlay is MKPolyline { map.removeOverlay(overlay) map.addOverlay(overlay) - } } } @@ -789,8 +790,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { name: UIApplication.didEnterBackgroundNotification, object: nil) notificationCenter.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) - - + notificationCenter.addObserver(self, selector: #selector(applicationWillTerminate), name: UIApplication.willTerminateNotification, object: nil) notificationCenter.addObserver(self, selector: #selector(presentReceivedFile(_:)), name: .didReceiveFileFromAppleWatch, object: nil) @@ -799,13 +799,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { notificationCenter.addObserver(self, selector: #selector(updateAppearance), name: .updateAppearance, object: nil) } - - /// - /// Removes the notification observers - /// - func removeNotificationObservers() { - NotificationCenter.default.removeObserver(self) - } /// To update appearance when mapView requests to do so @objc func updateAppearance() { @@ -825,8 +818,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { let alertTitle = NSLocalizedString("WATCH_FILE_RECEIVED_TITLE", comment: "no comment") let alertMessage = NSLocalizedString("WATCH_FILE_RECEIVED_MESSAGE", comment: "no comment") let controller = UIAlertController(title: alertTitle, message: String(format: alertMessage, fileName ?? ""), preferredStyle: .alert) - let action = UIAlertAction(title: NSLocalizedString("DONE", comment: "no comment"), style: .default) { - (action) in + let action = UIAlertAction(title: NSLocalizedString("DONE", comment: "no comment"), style: .default) { _ in print("ViewController:: Presented file received message from WatchConnectivity Session") } @@ -909,8 +901,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { print("viewController:: applicationWillTerminate") GPXFileManager.removeTemporaryFiles() if gpxTrackingStatus == .notStarted { - map.coreDataHelper.deleteAllTrackFromCoreData() - map.coreDataHelper.deleteAllWaypointsFromCoreData() + map.coreDataHelper.coreDataDeleteAll(of: CDTrackpoint.self) + map.coreDataHelper.coreDataDeleteAll(of: CDWaypoint.self) } } @@ -993,8 +985,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { shareActivityIndicator.startAnimating() shareButton.setImage(nil, for: UIControl.State()) shareButton.isUserInteractionEnabled = false - } - else { + } else { // cross dissolve from indicator to button UIView.transition(with: self.shareButton, duration: 0.35, options: [.transitionCrossDissolve], animations: { self.shareActivityIndicator.removeFromSuperview() @@ -1019,7 +1010,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { /// UIGestureRecognizerDelegate required for stopFollowingUser /// func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, - shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } @@ -1039,15 +1030,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { /// Does nothing in current implementation. func pinchGesture(_ gesture: UIPinchGestureRecognizer) { print("pinchGesture") - /* if gesture.state == UIGestureRecognizerState.Began { - self.followUserBeforePinchGesture = self.followUser - self.followUser = false - } - //return to back - if gesture.state == UIGestureRecognizerState.Ended { - self.followUser = self.followUserBeforePinchGesture - } - */ } /// @@ -1080,7 +1062,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { @objc func resetButtonTapped() { self.gpxTrackingStatus = .notStarted } - /// /// Main Start/Pause Button was tapped. @@ -1095,7 +1076,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { case .tracking: gpxTrackingStatus = .paused case .paused: - //set to tracking gpxTrackingStatus = .tracking } } @@ -1120,18 +1100,18 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { textField.text = self.lastGpxFilename.isEmpty ? self.defaultFilename() : self.lastGpxFilename }) - let saveAction = UIAlertAction(title: NSLocalizedString("SAVE", comment: "no comment"), style: .default) { (action) in + let saveAction = UIAlertAction(title: NSLocalizedString("SAVE", comment: "no comment"), style: .default) { _ in let filename = (alertController.textFields?[0].text!.utf16.count == 0) ? self.defaultFilename() : alertController.textFields?[0].text print("Save File \(String(describing: filename))") //export to a file let gpxString = self.map.exportToGPXString() GPXFileManager.save(filename!, gpxContents: gpxString) self.lastGpxFilename = filename! - self.map.coreDataHelper.deleteCDRootFromCoreData() + self.map.coreDataHelper.coreDataDeleteAll(of: CDRoot.self)//deleteCDRootFromCoreData() self.map.coreDataHelper.clearAllExceptWaypoints() self.map.coreDataHelper.add(toCoreData: filename!, willContinueAfterSave: true) } - let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { (action) in } + let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { _ in } alertController.addAction(saveAction) alertController.addAction(cancelAction) @@ -1144,7 +1124,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { /// There was a memory warning. Right now, it does nothing but to log a line. /// override func didReceiveMemoryWarning() { - print("didReceiveMemoryWarning"); + print("didReceiveMemoryWarning") super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } @@ -1176,12 +1156,12 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { func displayLocationServicesDisabledAlert() { let alertController = UIAlertController(title: NSLocalizedString("LOCATION_SERVICES_DISABLED", comment: "no comment"), message: NSLocalizedString("ENABLE_LOCATION_SERVICES", comment: "no comment"), preferredStyle: .alert) - let settingsAction = UIAlertAction(title: NSLocalizedString("SETTINGS", comment: "no comment"), style: .default) { (action) in + let settingsAction = UIAlertAction(title: NSLocalizedString("SETTINGS", comment: "no comment"), style: .default) { _ in if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.openURL(url) } } - let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { (action) in } + let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { _ in } alertController.addAction(settingsAction) alertController.addAction(cancelAction) @@ -1190,7 +1170,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { } - /// /// Displays an alert that informs the user that access to location was denied for this app (other apps may have access). /// It also dispays a button allows the user to go to settings to activate the location. @@ -1199,13 +1178,18 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { if isDisplayingLocationServicesDenied { return // display it only once. } - let alertController = UIAlertController(title: NSLocalizedString("ACCESS_TO_LOCATION_DENIED", comment: "no comment"), message: NSLocalizedString("ALLOW_LOCATION", comment: "no comment"), preferredStyle: .alert) - let settingsAction = UIAlertAction(title: NSLocalizedString("SETTINGS", comment: "no comment"), style: .default) { (action) in + let alertController = UIAlertController(title: NSLocalizedString("ACCESS_TO_LOCATION_DENIED", comment: "no comment"), + message: NSLocalizedString("ALLOW_LOCATION", comment: "no comment"), + preferredStyle: .alert) + let settingsAction = UIAlertAction(title: NSLocalizedString("SETTINGS", comment: "no comment"), + style: .default) { _ in if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.openURL(url) } } - let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { (action) in } + let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", + comment: "no comment"), + style: .cancel) { _ in } alertController.addAction(settingsAction) alertController.addAction(cancelAction) @@ -1349,9 +1333,6 @@ extension ViewController: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { //updates signal image accuracy let newLocation = locations.first! - // print("isUserLocationVisible: \(map.isUserLocationVisible) showUserLocation: \(map.showsUserLocation)") - // print("didUpdateLocation: received \(newLocation.coordinate) hAcc: \(newLocation.horizontalAccuracy) vAcc: \(newLocation.verticalAccuracy) floor: \(newLocation.floor?.description ?? "''") map.userTrackingMode: \(map.userTrackingMode.rawValue)") - // Update horizontal accuracy let hAcc = newLocation.horizontalAccuracy signalAccuracyLabel.text = hAcc.toAccuracy(useImperial: useImperial) @@ -1367,7 +1348,7 @@ extension ViewController: CLLocationManagerDelegate { self.signalImageView.image = signalImage2 } else if hAcc < kSignalAccuracy1 { self.signalImageView.image = signalImage1 - } else{ + } else { self.signalImageView.image = signalImage0 } @@ -1391,8 +1372,7 @@ extension ViewController: CLLocationManagerDelegate { currentSegmentDistanceLabel.distance = map.session.currentSegmentDistance } } - - + /// /// /// When there is a change on the heading (direction in which the device oriented) it makes a request to the map @@ -1407,8 +1387,10 @@ extension ViewController: CLLocationManagerDelegate { } } - extension Notification.Name { static let loadRecoveredFile = Notification.Name("loadRecoveredFile") static let updateAppearance = Notification.Name("updateAppearance") + // swiftlint:disable file_length } + +// swiftlint:enable line_length diff --git a/OpenGpxTrackerTests/OpenGpxTrackerTests.swift b/OpenGpxTrackerTests/OpenGpxTrackerTests.swift index 265a5b51..19787d90 100644 --- a/OpenGpxTrackerTests/OpenGpxTrackerTests.swift +++ b/OpenGpxTrackerTests/OpenGpxTrackerTests.swift @@ -28,7 +28,7 @@ class OpenGpxTrackerTests: XCTestCase { func testPerformanceExample() { // This is an example of a performance test case. - self.measure() { + self.measure { // Put the code you want to measure the time of here. } }