Transition from preferences to file storage

main
Laurent 3 years ago
parent c42735d1c0
commit 76db659137
  1. 40
      Notes.xcodeproj/project.pbxproj
  2. 18
      Notes/AppDelegate.swift
  3. 26
      Notes/Base.lproj/Main.storyboard
  4. 16
      Notes/NoteViewController.swift
  5. 14
      Notes/Notes.entitlements
  6. 29
      Notes/Preferences.swift
  7. 13
      Notes/Storage/Document.swift
  8. 15
      Notes/Storage/FileOperator.swift
  9. 105
      Notes/Storage/FileStorage.swift
  10. 31
      Notes/Storage/PreferencesStorage.swift
  11. 20
      Notes/UIViewController+Extensions.swift

@ -18,7 +18,11 @@
C41A31EA28C49B320019B951 /* NotesUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41A31E928C49B320019B951 /* NotesUITests.swift */; };
C41A31EC28C49B320019B951 /* NotesUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41A31EB28C49B320019B951 /* NotesUITestsLaunchTests.swift */; };
C41A31F928C49DC80019B951 /* NoteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41A31F828C49DC80019B951 /* NoteViewController.swift */; };
C41A31FB28C4D8040019B951 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41A31FA28C4D8040019B951 /* Preferences.swift */; };
C41A31FB28C4D8040019B951 /* PreferencesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41A31FA28C4D8040019B951 /* PreferencesStorage.swift */; };
C43417F428D4ADB10098C15A /* FileOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43417F328D4ADB10098C15A /* FileOperator.swift */; };
C43417F628D4ADDB0098C15A /* FileStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43417F528D4ADDB0098C15A /* FileStorage.swift */; };
C43417FA28D4B9370098C15A /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43417F928D4B9370098C15A /* Document.swift */; };
C4EEE40728D89C46003DDC24 /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EEE40628D89C46003DDC24 /* UIViewController+Extensions.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -54,7 +58,12 @@
C41A31E928C49B320019B951 /* NotesUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesUITests.swift; sourceTree = "<group>"; };
C41A31EB28C49B320019B951 /* NotesUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesUITestsLaunchTests.swift; sourceTree = "<group>"; };
C41A31F828C49DC80019B951 /* NoteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteViewController.swift; sourceTree = "<group>"; };
C41A31FA28C4D8040019B951 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
C41A31FA28C4D8040019B951 /* PreferencesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesStorage.swift; sourceTree = "<group>"; };
C43417F128D4AACD0098C15A /* Notes.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Notes.entitlements; sourceTree = "<group>"; };
C43417F328D4ADB10098C15A /* FileOperator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOperator.swift; sourceTree = "<group>"; };
C43417F528D4ADDB0098C15A /* FileStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileStorage.swift; sourceTree = "<group>"; };
C43417F928D4B9370098C15A /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = "<group>"; };
C4EEE40628D89C46003DDC24 /* UIViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extensions.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -105,11 +114,13 @@
C41A31C428C49B310019B951 /* Notes */ = {
isa = PBXGroup;
children = (
C43417F128D4AACD0098C15A /* Notes.entitlements */,
C41A31C528C49B310019B951 /* AppDelegate.swift */,
C41A31C728C49B310019B951 /* SceneDelegate.swift */,
C41A31C928C49B310019B951 /* ViewController.swift */,
C41A31F828C49DC80019B951 /* NoteViewController.swift */,
C41A31FA28C4D8040019B951 /* Preferences.swift */,
C4EEE40628D89C46003DDC24 /* UIViewController+Extensions.swift */,
C43417F228D4AD9C0098C15A /* Storage */,
C41A31CB28C49B310019B951 /* Main.storyboard */,
C41A31D128C49B320019B951 /* Assets.xcassets */,
C41A31D328C49B320019B951 /* LaunchScreen.storyboard */,
@ -136,6 +147,17 @@
path = NotesUITests;
sourceTree = "<group>";
};
C43417F228D4AD9C0098C15A /* Storage */ = {
isa = PBXGroup;
children = (
C41A31FA28C4D8040019B951 /* PreferencesStorage.swift */,
C43417F328D4ADB10098C15A /* FileOperator.swift */,
C43417F528D4ADDB0098C15A /* FileStorage.swift */,
C43417F928D4B9370098C15A /* Document.swift */,
);
path = Storage;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -269,8 +291,12 @@
files = (
C41A31CA28C49B310019B951 /* ViewController.swift in Sources */,
C41A31C628C49B310019B951 /* AppDelegate.swift in Sources */,
C41A31FB28C4D8040019B951 /* Preferences.swift in Sources */,
C4EEE40728D89C46003DDC24 /* UIViewController+Extensions.swift in Sources */,
C41A31FB28C4D8040019B951 /* PreferencesStorage.swift in Sources */,
C41A31C828C49B310019B951 /* SceneDelegate.swift in Sources */,
C43417F428D4ADB10098C15A /* FileOperator.swift in Sources */,
C43417FA28D4B9370098C15A /* Document.swift in Sources */,
C43417F628D4ADDB0098C15A /* FileStorage.swift in Sources */,
C41A31F928C49DC80019B951 /* NoteViewController.swift in Sources */,
C41A31D028C49B310019B951 /* Notes.xcdatamodeld in Sources */,
);
@ -378,7 +404,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@ -432,7 +458,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
@ -448,6 +474,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Notes/Notes.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 526E96RFNP;
@ -478,6 +505,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Notes/Notes.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 526E96RFNP;

@ -14,13 +14,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
for fontFamilyName in UIFont.familyNames {
print("family: \(fontFamilyName)\n")
// let fn = "main"
// if let content = PreferencesStorage.main.getContent(filename: fn) {
// FileStorage.main.requestStorage(filename: "main.txt", content: content)
// }
for fontName in UIFont.fontNames(forFamilyName: fontFamilyName) {
print("font: \(fontName)")
}
}
// for fontFamilyName in UIFont.familyNames {
// print("family: \(fontFamilyName)\n")
//
// for fontName in UIFont.fontNames(forFamilyName: fontFamilyName) {
// print("font: \(fontName)")
// }
// }
return true
}

@ -9,32 +9,6 @@
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Notes" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="oMh-UP-9ML">
<rect key="frame" x="20" y="48" width="374" height="242"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" name="DINAlternate-Bold" family="DIN Alternate" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53.623188405797109" y="81.696428571428569"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="hez-bJ-diu">
<objects>

@ -10,7 +10,7 @@ import UIKit
class NoteViewController : UIViewController, UITextViewDelegate {
var filename: String = "main"
var filename: String = "main.txt"
@IBOutlet weak var textView: UITextView!
@ -27,8 +27,13 @@ class NoteViewController : UIViewController, UITextViewDelegate {
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addHandler))
self.navigationItem.rightBarButtonItems = [addButton, shareButton]
self.textView.text = Preferences.getContent(filename: self.filename)
self.textView.font = UIFont.systemFont(ofSize: 18.0, weight: .medium)
do {
self.textView.text = try FileStorage.main.getContent(filename: self.filename)
} catch {
self.showAlert(message: error.localizedDescription, title: "Error :(")
}
self.textView.font = UIFont.systemFont(ofSize: 18.0, weight: .regular)
self.textView.delegate = self
self.textView.inputAccessoryView = self._inputAccessoryView()
@ -51,12 +56,13 @@ class NoteViewController : UIViewController, UITextViewDelegate {
}
func textViewDidChange(_ textView: UITextView) {
Preferences.store(filename: self.filename, content: textView.text)
FileStorage.main.requestStorage(filename: self.filename, content: textView.text)
self._updateLastEdit()
}
fileprivate func _updateLastEdit() {
if let date = Preferences.lastEditDate(filename: self.filename) {
if let date = try? FileStorage.main.lastEditDate(filename: self.filename) {
let formattedDate: String = date.formatted()
self._lastEditLabel?.text = "last edit: \(formattedDate)"
}

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array/>
</dict>
</plist>

@ -1,29 +0,0 @@
//
// Preferences.swift
// Notes
//
// Created by Laurent Morvillier on 04/09/2022.
//
import Foundation
class Preferences {
static func store(filename: String, content: String) {
UserDefaults.standard.set(content, forKey: filename)
Preferences._fileChanged(filename: filename)
}
fileprivate static func _fileChanged(filename: String) {
UserDefaults.standard.set(Date(), forKey: filename + "_date")
}
static func getContent(filename: String) -> String? {
return UserDefaults.standard.object(forKey: filename) as? String
}
static func lastEditDate(filename: String) -> Date? {
return UserDefaults.standard.object(forKey: filename + "_date") as? Date
}
}

@ -0,0 +1,13 @@
//
// Document.swift
// Notes
//
// Created by Laurent Morvillier on 16/09/2022.
//
import Foundation
import UIKit
class Document : UIDocument {
}

@ -0,0 +1,15 @@
//
// FileOperator.swift
// Notes
//
// Created by Laurent Morvillier on 16/09/2022.
//
import Foundation
protocol FileOperator {
func requestStorage(filename: String, content: String)
func getContent(filename: String) throws -> String?
func lastEditDate(filename: String) throws -> Date?
}

@ -0,0 +1,105 @@
//
// CloudFileStorage.swift
// Notes
//
// Created by Laurent Morvillier on 16/09/2022.
//
import Foundation
import UIKit
struct StorageRequest {
var filename: String
var content: String
}
let idleTimeBeforeSaving = 2.0
/// Should we have a way to go from local to iCloud?
/// https://stackoverflow.com/questions/33886846/best-way-to-use-icloud-documents-storage
/// Should we store the files locally whatever the iCloud choice is?
class FileStorage : FileOperator {
static var main: FileStorage = FileStorage()
fileprivate var _containerURL: URL?
fileprivate var _cloudStorageDetermined: Bool = false
fileprivate let containerIdentifier = "notes"
fileprivate var _timer: Timer? = nil
fileprivate var _storageRequests: [String : String] = [:]
init() {
DispatchQueue.global(qos: .userInteractive).async {
self._containerURL = FileManager.default.url(forUbiquityContainerIdentifier: self.containerIdentifier)
self._cloudStorageDetermined = true
print("Cloud container URL is : \(String(describing: self._containerURL?.absoluteString))")
}
}
fileprivate func _directoryURL() throws -> URL {
let documentDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
return documentDirectory
}
// MARK: - FileOperator
func requestStorage(filename: String, content: String) {
self._storageRequests[filename] = content
self._timer?.invalidate()
self._timer = Timer.scheduledTimer(timeInterval: idleTimeBeforeSaving, target: self, selector: #selector(self._storageRequested), userInfo: nil, repeats: false)
}
@objc fileprivate func _storageRequested() {
for (filename, content) in self._storageRequests {
do {
try self._store(filename: filename, content: content)
} catch {
// TODO show errors to users, possibly by notifications
print("error: \(error.localizedDescription)")
}
}
self._storageRequests.removeAll()
}
fileprivate func _store(filename: String, content: String) throws {
let fileURL = try self._directoryURL().appending(path: filename)
print("Store file to: \(fileURL.absoluteString)")
try content.write(to: fileURL, atomically: true, encoding: .utf8)
try self._copyToCloudContainerIfNecessary(fileURL: fileURL, filename: filename)
}
func getContent(filename: String) throws -> String? {
let fileURL = try self._directoryURL().appending(path: filename)
return try String(contentsOf: fileURL)
}
func lastEditDate(filename: String) throws -> Date? {
let fileURL = try self._directoryURL().appending(path: filename)
let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.absoluteString)
return attributes[FileAttributeKey.modificationDate] as? Date
}
fileprivate func _copyToCloudContainerIfNecessary(fileURL: URL, filename: String) throws {
guard let containerURL = self._containerURL else {
return
}
let cloudURL = containerURL.appending(path: filename)
try FileManager.default.copyItem(at: fileURL, to: cloudURL)
print("cloud copy to: \(cloudURL)")
}
deinit {
self._timer?.invalidate()
}
}

@ -0,0 +1,31 @@
//
// Preferences.swift
// Notes
//
// Created by Laurent Morvillier on 04/09/2022.
//
import Foundation
//class PreferencesStorage : FileOperator {
//
// static var main = PreferencesStorage()
//
// func requestStorage(filename: String, content: String) {
// UserDefaults.standard.set(content, forKey: filename)
// PreferencesStorage._fileChanged(filename: filename)
// }
//
// fileprivate static func _fileChanged(filename: String) {
// UserDefaults.standard.set(Date(), forKey: filename + "_date")
// }
//
// func getContent(filename: String) -> String? {
// return UserDefaults.standard.object(forKey: filename) as? String
// }
//
// func lastEditDate(filename: String) -> Date? {
// return UserDefaults.standard.object(forKey: filename + "_date") as? Date
// }
//
//}

@ -0,0 +1,20 @@
//
// UIViewController+Extensions.swift
// Notes
//
// Created by Laurent Morvillier on 19/09/2022.
//
import Foundation
import UIKit
extension UIViewController {
@objc public func showAlert(message: String, title: String?, completion: (() -> ())? = nil) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let action = UIAlertAction(title: NSLocalizedString("OK", comment: "OK"), style: .default, handler: nil)
alertController.addAction(action)
self.present(alertController, animated: true, completion: completion)
}
}
Loading…
Cancel
Save