Have been working on Day 77 Challenge which asks to create a contact app to practice using PHPickerViewController, saving to documentDirectory, encoding/decoding to/from JSON and more...
I have the app working however it will not save data to the documentDirectory. The save function does not throw any errors but when I save some contacts and quit the app the saved contacts are not loaded.
Any help would be appreciated.
import Foundation
import SwiftUI
struct Contact: Codable, Comparable, Identifiable {
let id: UUID
let name: String
let picture: Data
static func <(lhs: Contact, rhs: Contact) -> Bool {
lhs.name < rhs.name
}
}
import SwiftUI
struct ContentView: View {
@State private var picture: Image?
@State private var contacts = [Contact]()
@State private var showingAddContact = false
let savePath = FileManager.documentsDirectory.appendingPathComponent("SavedContacts")
var body: some View {
NavigationView {
List {
Section {
ForEach(contacts.sorted(), id: \.id) { contact in
NavigationLink {
DetailView(contact: contact)
} label: {
HStack {
convertPicture(contact: contact)
.resizable()
.scaledToFit()
.frame(width: 75, height: 75)
Spacer()
Text( contact.name)
}
}
}
}
}
.navigationTitle("Contact Keeper")
.toolbar {
Button {
showingAddContact = true
} label: {
Image(systemName: "plus.circle.fill")
}
}
.sheet(isPresented: $showingAddContact) {
AddView(contacts: $contacts)
}
}
}
init() {
do {
let contactData = try Data(contentsOf: savePath)
contacts = try JSONDecoder().decode([Contact].self, from: contactData)
} catch {
contacts = []
}
}
}
import SwiftUI
struct AddView: View {
@Environment(\.dismiss) var dismiss
@State private var picture: Image?
@State private var inputImage: UIImage?
@State private var contactName = ""
@Binding var contacts: [Contact]
@State private var showingImagePicker = false
let savePath = FileManager.documentsDirectory.appendingPathComponent("SavedContacts")
var body: some View {
NavigationView {
Form {
Section {
TextField("Contact Name", text: $contactName)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
}
Section {
ZStack {
Text("Click here to select an image")
picture?
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
}
.onTapGesture {
showingImagePicker = true
}
}
}
.toolbar {
Button {
save()
dismiss()
} label: {
Image(systemName: "square.and.arrow.down.fill")
}
}
}
.sheet(isPresented: $showingImagePicker) {
ImagePicker(image: $inputImage)
}
.onChange(of: inputImage) { _ in loadImage() }
}
func loadImage() {
guard let inputImage = inputImage else { return }
picture = Image(uiImage: inputImage)
}
func save() {
do {
if let jpegData = inputImage?.jpegData(compressionQuality: 0.8) {
let newContact = Contact(id: UUID(), name: contactName, picture: jpegData)
contacts.append(newContact)
let contactData = try JSONEncoder().encode(contacts)
try contactData.write(to: savePath, options: [.atomicWrite, .completeFileProtection])
}
} catch {
print("Unable to save your data")
}
}
}
import SwiftUI
struct DetailView: View {
var contact: Contact
var body: some View {
VStack {
convertPicture(contact: contact)
.resizable()
.scaledToFit()
.frame(width: 400, height: 400)
Text(contact.name)
.font(.headline.bold())
}
}
}
import SwiftUI
func convertPicture(contact: Contact) -> Image {
let pictureData = contact.picture
let uiPicture = UIImage(data: pictureData)
let image = Image(uiImage: uiPicture!)
return image
}
import PhotosUI
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
@Binding var image: UIImage?
class Coordinator: NSObject, PHPickerViewControllerDelegate {
var parent: ImagePicker
init(parent: ImagePicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
// Tell the picker to go away
picker.dismiss(animated: true)
// Exit if no selection was made
guard let provider = results.first?.itemProvider else { return }
// If this has an image we can use, use it
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { image, _ in
self.parent.image = image as? UIImage
}
}
}
}
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration()
config.filter = .images
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
}
import UIKit
class ImageSaver: NSObject {
var successHandler: (() -> Void)?
var errorHandler: ((Error) -> Void)?
func writeToPhotoAlbum(image: UIImage) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveCompleted), nil)
}
@objc func saveCompleted(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
if let error = error {
errorHandler?(error)
} else {
successHandler?()
}
}
}
import Foundation
extension FileManager {
static var documentsDirectory: URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
}