SnapShot: How to Create a Digital Scrapbook in iOS - Part 2
Tags: ios, tutorial, swift, xcode
Published: 2021-04-22 14:48:05

Welcome to Part 2 of **SnapShot: A Digital iOS Scrapbook**! If you haven’t seen [Part 1](https://blog.sashido.io/snapshot-how-to-create-a-digital-scrapbook-in-ios-part-1/), I recommend you check it out as it takes care of all of the setup and registration.

Just to refresh your mind, SnapShot is an iOS app made using Xcode and [SashiDo](https://www.sashido.io/en/). It is kind of like a digital scrapbook, but for more information, here is the demo:

{% youtube wTXN-c1tPHs %}
Again, I recommend looking at [SnapShot Part 1](https://blog.sashido.io/snapshot-how-to-create-a-digital-scrapbook-in-ios-part-1/), or even my first tutorial [*Fish Classification iOS App with SashiDo and Teachable Machine*](https://dev.to/carolinebaillie/fish-classification-ios-app-with-sashido-and-teachable-machine-417i)). Additionally, all my code is available on my [GitHub](https://github.com/CarolineBaillie/snapShot).

## Table of Contents
* [Map](#map)
   * [Setup](#setup)
   * [Memory Class](#memory-class)
   * [Request Memories](#request-memories)
   * [Annotations](#interactive-annotations)
   * [Memory Info Page](#memory-info-page)
   * [Add Annotation](#add-annotation)
   * [New Memory](#new-memory)
* [Delete](#delete)
* [Edit](#edit)
* [Storing](#storing)
* [Closing Remarks](#closing-remarks)
* [Resources](#resources)

## Map
Adding the map is probably the biggest step in this project. Below I have outlined all the steps I took to create and connect everything. However, if you want more visual instructions on how to create and display the map, I recommend checking out [this video playlist](https://www.youtube.com/watch?v=p0dOywHx0VI&list=RDCMUCmJi5RdDLgzvkl3Ly0DRMlQ&start_radio=1&t=1)!
### Setup
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Go to the **storyboard** map controller (I created mine on the default **ViewController**)
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Add MKMapView controller
&nbsp;&nbsp;&nbsp;&nbsp; 3\. Drag the sides so the MKMapView takes up the whole screen
&nbsp;&nbsp;&nbsp;&nbsp; 4\. On the top bar, click Editor -> Resolve Auto Layout Issues -> Add Missing Constraints (selected views)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n67sjlfjiydcw2qy2nqx.png)
&nbsp;&nbsp;&nbsp;&nbsp; 5\. Open dual editors (**storyboard** and **ViewController.swift**)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a\. Control drag from MKMapView into the ViewController class
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b\. Call the outlet mapView
&nbsp;&nbsp;&nbsp;&nbsp; 6\. Select the MKMapView/mapView
&nbsp;&nbsp;&nbsp;&nbsp; 7\. Control drag to ViewController and click <u>delegate</u>
&nbsp;&nbsp;&nbsp;&nbsp; 8\. Make sure you have *import MapKit* and *import CoreLocation* in **ViewController.swift**
&nbsp;&nbsp;&nbsp;&nbsp; 9\. Make sure **ViewController.swift** conforms to MKMapViewDelegate
We also have to set up a reload function that could be called from a different controller (used when memory is [edited](#edit)). The code is also given in the Extras [Backpropagation Function Section](https://blog.sashido.io/xcode-collection-of-useful-functions-and-tips/), but basically there are only two sections of code for **ViewController.swift**:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. In viewDidLoad add this:
```swift
// setup for function that is called from newMemory
NotificationCenter.default.addObserver(self, selector: #selector(reload), name: Notification.Name("reload"), object: nil)
```
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Add the following reload function inside the class:
```swift
// function to reload newMemories bc when dismissed adding controller doesn't show without
@objc func reload (notification: NSNotification){
    self.mapView.removeAnnotations(self.mapView.annotations)
    for m in sessionManager.shared.memories {
        // if there is a mapView (protection)
        if mapView != nil {
            // add that Memory
            mapView.addAnnotation(m)
        }
    }
}
```

### Memory Class
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Inside the <u>Classes</u> folder, create a new <u>Cocoa Touch Class</u> file
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Make sure your options are filled in as follows:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ju5ltngt1zn0s52vb3d.png)
&nbsp;&nbsp;&nbsp;&nbsp; 3\. Click <u>next</u> and <u>create</u>
&nbsp;&nbsp;&nbsp;&nbsp; 4\. Inside this file, create a class with all the elements of each memory:
```swift
import UIKit
import MapKit

class Memory: NSObject, MKAnnotation {
    var title: String?
    var coordinate: CLLocationCoordinate2D
    var desc: String
    var category: String?
    var tags: String?
    var location: String
    var date: String
    var Image: UIImage
    var objID: String
    
    init(title:String, Image:UIImage, coordinate:CLLocationCoordinate2D,desc:String,category:String,tags:String, location:String,date:String, objID:String){
        self.title = title
        self.Image = Image
        self.coordinate = coordinate
        self.desc = desc
        self.category = category
        self.tags = tags
        self.location = location
        self.date = date
        self.objID = objID
    }
}
```
Now you can create a variable of type Memory and add it to the annotation map. However, we want to use the information from the database to populate this annotation. Therefore we must create a function in sessionManager.

### Request Memories
For our current task, we want a function that gets all the data from SashiDo:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Inside the **sessionManager** class, add *var memories : [Memory]! = []* to create an array of type Memory
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Add the following function:
```swift
// get all memories
func GetAllMem(completion:@escaping (_ success:Bool) -> ()){
    // clear previous data
    memories.removeAll()
    // get all rows
    let query = PFQuery(className: "Memory")
    // where user is equal to current user
    query.whereKey("userID", equalTo:user.id)
    query.findObjectsInBackground { (objects, error) in
        // no errors
        if error == nil {
            // if there are objects in the array
            if let returnedObjects = objects {
                // loop through all objects in array
                for object in returnedObjects {
                    // extract the image
                    let file = object["Image"] as! PFFileObject
                    file.getDataInBackground { (imageData: Data?, error: Error?) in
                        if error == nil {
                            if let imageData = imageData {
                                let image = UIImage(data: imageData)
                                // convert coord of type GeoPoint to CLLocationCoordinate2D
                                let coor = object["coordinate"]! as! PFGeoPoint
                                let coord = CLLocationCoordinate2D(latitude: coor.latitude, longitude: coor.longitude)
                                // create a new memory
                                let memData = Memory(title: object["title"]! as! String, Image: (image ?? UIImage(named: "test"))!, coordinate: coord, desc: object["desc"]! as! String, category: object["category"]! as! String, tags: object["tags"]! as! String, location: object["location"]! as! String, date: object["date"]! as! String, objID: object.objectId!)
                                // add memory to the global array
                                self.memories.append(memData)
                            }
                        }
                    }
                }
            }
            completion(true)
        }
        else {
            //return false completion if fails
            completion(false)
        }
    }
}
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a\. Essentially the function is getting all the information from the database rows that correspond with the current user (using [Parse Documentation](https://docs.parseplatform.org/ios/guide/#queries))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b\. It then loops through all the rows and creates a variable of type Memory
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c\. Finally, it adds that memory to the array
&nbsp;&nbsp;&nbsp;&nbsp; 3\. Now go to **settings.swift** and add this code to viewDidLoad:
```swift
// get all memories from database
sessionManager.shared.GetAllMem { (success) in
        if success {
            self.removeSpinner()
        }
}
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a\. This means that whenever a user logs in, all their information will be saved to the array *memories* in sessionManager (sessionManager.shared.memories)
&nbsp;&nbsp;&nbsp;&nbsp; 4\. To show these memories as annotations on the map, return to **ViewContoller.swift** and add this code in viewDidLoad:
```swift
// go through all memories in global list
for m in sessionManager.shared.memories {
    // if there is a mapView (protection)
    if mapView != nil {
        // add that Memory
        mapView.addAnnotation(m)
    }
}
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a\. This code just loops through all the memories and adds the memory to the mapView

### Interactive Annotations
Now that we have displayed all our memories, we want to be able to click on them and open a new controller that displays all the information. To do this we need to add two functions to **ViewController.swift**:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. This function changes the style of the pin and displays the i button when clicked:
```swift
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    // make sure only right type of pins display
    guard annotation is Memory else {return nil}
    // create identifier for annotation views
    let identifier = "Memory"
    // get back an annotation if it is one with identifier, otherwise nil
    var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
    if annotationView == nil {
        annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
        annotationView?.canShowCallout = true
        //create button - i button for info
        let btn = UIButton(type: .detailDisclosure)
        annotationView?.rightCalloutAccessoryView = btn
    } else {
        // if have in dq go ahead and use
        annotationView?.annotation = annotation
    }
    return annotationView
}
```
&nbsp;&nbsp;&nbsp;&nbsp; 2\. This function presents a different view controller when the i button is clicked
```swift
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
    // make sure is type Memory
    guard let memory = view.annotation as? Memory else {return}
    if control == view.rightCalloutAccessoryView {
        // go to info page for that memory
        let obj = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "memInfoPage") as! memInfoPage
        // pass memory information
        obj.page = memory
        self.present(obj, animated: true, completion: nil)
   }
}
```

### Memory Info Page
The previous code showed you how to make an annotation clickable and open a new controller. It also sent information to the next controller (memInfoPage). To display this information in **memInfoPage.swift** you must:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Create outlets for all the storyboard elements (labels, images, etc.)
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Add *var page : Memory!* to the top of the class to collect the memory passed from ViewContoller.swift
&nbsp;&nbsp;&nbsp;&nbsp; 3\. In viewDidLoad, pass the information from *page* into all the storyboard elements
&nbsp;&nbsp;&nbsp;&nbsp; 4\. Add a [function](https://blog.sashido.io/xcode-collection-of-useful-functions-and-tips/) to reload the controller (this is needed for [editing the memory](#edit))
&nbsp;&nbsp;&nbsp;&nbsp; 5\. All together it should look like this:
```swift
import Foundation
import UIKit
import Parse

class memInfoPage : UIViewController {
    // var from prev page
    var page : Memory!
    // connections
    @IBOutlet weak var memTitle: UILabel!
    @IBOutlet weak var memImage: UIImageView!
    @IBOutlet weak var memDesc: UITextView!
    @IBOutlet weak var memCategory: UILabel!
    @IBOutlet weak var memLocation: UILabel!
    @IBOutlet weak var memTags: UITextView!
    @IBOutlet weak var memDate: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.memTitle.text = page.title
        memTitle.adjustsFontSizeToFitWidth = true
        self.memImage.image = page.Image
        self.memDesc.text = page.desc
        self.memCategory.text = page.category
        self.memLocation.text = page.location
        self.memTags.text = page.tags
        self.memDate.text = page.date
        NotificationCenter.default.addObserver(self, selector: #selector(reloadContent), name: Notification.Name("reloadContent"), object: nil)
    }
    
    @objc func reloadContent (notification: NSNotification){
        self.memTitle.text = page.title
        memTitle.adjustsFontSizeToFitWidth = true
        self.memImage.image = page.Image
        self.memDesc.text = page.desc
        self.memCategory.text = page.category
        self.memLocation.text = page.location
        self.memTags.text = page.tags
        self.memDate.text = page.date
    }
}
```
Now when you click the i button on an annotation, a page with all the information about the memory will appear.

### Add Annotation
The next logical step is to create a way to add new memories:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Add a button to the **storyboard** map controller and create an action function (addAnnotation) connecting to **ViewController.swift**
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Insert the following code to present a camera controller (picController):
```swift
@IBAction func addAnnotation(_ sender: Any) {
    // should pop up different controller so that can enter info
    let obj = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "picController") as! picController
    self.present(obj, animated: true, completion: nil)
}
```
To create **picController.swift**: 
&nbsp;&nbsp;&nbsp;&nbsp; 1\. If you haven’t already, create a controller with a button and imageView
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Create a file called picController.swift with the following code:
```swift
import Foundation
import UIKit

class picController: UIViewController {
    
    @IBOutlet weak var img: UIImageView!

     // create camera
    let pickerController = UIImagePickerController()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // set camera
        pickerController.sourceType = UIImagePickerController.SourceType.camera
        pickerController.delegate = self
    }
    // linked to button that shows the camera
    @IBAction func onClickTakePic(_ sender: Any) {
        present(pickerController, animated: true, completion: nil)
    }
}
// image taken
extension picController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        picker.dismiss(animated: true, completion: nil)
        if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            // set image taken to image on screen
            img.image = image
            dismiss(animated: true, completion: nil)
            // send image to create new mem
            let obj = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "newMemory") as! newMemory
            obj.image = image
            self.present(obj, animated: true, completion: nil)
        }
    }
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        //close out (camera has been closed)
    }
}
```
&nbsp;&nbsp;&nbsp;&nbsp; 3\. Connect *img* and *onClickTakePic* to the image and button on the controller
&nbsp;&nbsp;&nbsp;&nbsp; 4\. Allow camera access:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a\. Go to the outermost snapShot.xcodeproj
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b\. info -> Custom iOS Target Properties -> right click → add row -> Privacy - Camera Usage Description

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ioafm2vb9xj1t9k2d7x4.png)
From the code, you can see that once the picture is taken, it sends the information to a different controller called *newMemory*.

### New Memory
**newMemory** is the controller where the user inputs all the different information on the memory they wish to create. The corresponding file has 4 major parts to it:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Basic setup
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Gets users location
&nbsp;&nbsp;&nbsp;&nbsp; 3\. Gets current date
&nbsp;&nbsp;&nbsp;&nbsp; 4\. Creates a new memory
Breaking each part down… 

Basic setup: 
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Add *var image : UIImage!* to the top of the **newMemory** class (it will store the image passed from picController)
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Create connections between each input field, image, and button
&nbsp;&nbsp;&nbsp;&nbsp; 3\. In viewDidLoad, add *self.memImage.image = image* to set the image

Location:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Allow location access:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a\. Go to the outermost snapShot.xcodeproj
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b\. info -> Custom iOS Target Properties -> right click add row (x2) -> Privacy - Location When In Use Usage Description & Privacy - Location Always and When In Use Usage Description
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Add *private var locationManager:CLLocationManager?* to the top of the **newMemory** class so we can access the user’s location
&nbsp;&nbsp;&nbsp;&nbsp; 3\. Add the following two functions to the class:
```swift
func getUserLocation() {
    locationManager = CLLocationManager()
    locationManager?.requestAlwaysAuthorization()
    locationManager?.startUpdatingLocation()
    locationManager?.delegate = self
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    if let location = locations.last {
        sessionManager.shared.currentCoord = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
    }
}
```
&nbsp;&nbsp;&nbsp;&nbsp; 4\. In **sessionManager.swift** add *var currentCoord : CLLocationCoordinate2D?* as a variable at the top of the class (this will store the users current coordinate)
&nbsp;&nbsp;&nbsp;&nbsp; 5\. Finally, back in **newMemory.swift**, create a variable for the location in the save button function (saveToggled)
```swift
getUserLocation()
let coord = sessionManager.shared.currentCoord
```

Date:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Add the following to **saveToggled**:
```swift
let date = Date()
let calendar = Calendar.current
let y = calendar.component(.year, from: date)
let m = calendar.component(.month, from: date)
let d = calendar.component(.day, from: date)
let dateNow = "\(m)/\(d)/\(y)"
```

New Memory:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. In **saveToggled**, create a new variable of type Memory and save it to sessionManager

Code all together:
```swift
import Foundation
import UIKit
import MapKit
import Parse
import CoreLocation

class newMemory : UIViewController, UITextFieldDelegate, CLLocationManagerDelegate {
    
    var image : UIImage!
    private var locationManager:CLLocationManager?
    
    @IBOutlet weak var memTitle: UITextField!
    @IBOutlet weak var memCategory: UITextField!
    @IBOutlet weak var memImage: UIImageView!
    @IBOutlet weak var memDesc: UITextView!
    @IBOutlet weak var memLocation: UITextField!
    @IBOutlet weak var memTags: UITextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Set image from passed in value
        self.memImage.image = image
    }
    
    //dismiss keyboard when tapped
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }
    
    @IBAction func saveToggled(_ sender: Any) {
        self.showSpinner()
        // find current location
        getUserLocation()
        let coord = sessionManager.shared.currentCoord
        // get current date
        let date = Date()
        let calendar = Calendar.current
        let y = calendar.component(.year, from: date)
        let m = calendar.component(.month, from: date)
        let d = calendar.component(.day, from: date)
        let dateNow = "\(m)/\(d)/\(y)"
        // create new memory
        let newMem = Memory(title: memTitle.text!, Image: image, coordinate: coord, desc: memDesc.text!, category: memCategory.text!, tags: memTags.text!, location: memLocation.text!, date: dateNow, objID: "none")
        // save memory
        sessionManager.shared.saveMemory(memory: newMem) { (success) in
            // reload map controller
            NotificationCenter.default.post(name: Notification.Name("reload"), object: nil)
            // move to next view controller
            self.dismiss(animated: true, completion: nil)
        }
    }
    // get user location stuff
    func getUserLocation() {
        locationManager = CLLocationManager()
        locationManager?.requestAlwaysAuthorization()
        locationManager?.startUpdatingLocation()
        locationManager?.delegate = self
    }
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            sessionManager.shared.currentCoord = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
        }
    }
}
```
In **sessionManager.swift**, the function to save a memory looked like this:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Set all the database rows to be the inputted information
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Reduce the image size (otherwise might cause error)
&nbsp;&nbsp;&nbsp;&nbsp; 3\. Save changes to the database
&nbsp;&nbsp;&nbsp;&nbsp; 4\. Add the new memory to global array (memories)
```swift
func saveMemory (memory:Memory, completion:@escaping (_ success:Bool) -> ()) {
    // set values for new memory
    let mem = PFObject(className:"Memory")
    mem["title"] = memory.title
    mem["desc"] = memory.desc
    mem["location"] = memory.location
    mem["date"] = memory.date
    mem["userID"] = user.id
    mem["tags"] = memory.tags
    mem["category"] = memory.category
    mem["coordinate"] = PFGeoPoint(latitude: memory.coordinate.latitude, longitude: memory.coordinate.longitude)
    // reducing image size
    let image = memory.Image
    let actualHeight:CGFloat = image.size.height
    let actualWidth:CGFloat = image.size.width
    let imgRatio:CGFloat = actualWidth/actualHeight
    let maxWidth:CGFloat = 1024.0
    let resizedHeight:CGFloat = maxWidth/imgRatio
    let compressionQuality:CGFloat = 0.5
    let rect:CGRect = CGRect(x: 0, y: 0, width: maxWidth, height: resizedHeight)
    UIGraphicsBeginImageContext(rect.size)
    image.draw(in: rect)
    let img: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
    let imageData:Data = img.jpegData(compressionQuality: compressionQuality)!
    UIGraphicsEndImageContext()
    let imageFinal = UIImage(data: imageData)!
    // prepping to save image
    let imgData = imageFinal.pngData()
    let imageFile = PFFileObject(name:"image.png", data:imgData!)
    mem["Image"] = imageFile
    // save all
    mem.saveInBackground { (succeeded, error)  in
        if (succeeded) {
           // The object has been saved.
            memory.objID = mem.objectId as! String
            self.memories.append(memory)
            completion(true)
        } else {
            // There was a problem
            completion(false)
        }
    }
}
```

## Delete
I wanted to allow the user to delete a memory if they no longer wanted it. Fortunately, this process was relatively simple:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Create an action function from the **memInfoPage** delete button (deleteToggled)
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Create an alert asking the user to confirm that they want to delete this item
&nbsp;&nbsp;&nbsp;&nbsp; 3\. If canceled dismiss
&nbsp;&nbsp;&nbsp;&nbsp; 4\. If confirmed call the sessionManager delete function and return to the dismiss the controller
```swift
@IBAction func deleteToggle(_ sender: Any) {
    // alert are you sure you want to delete this
    let confirmAlert = UIAlertController(title: "Delete", message: "Are you sure you want to delete this memory?", preferredStyle: UIAlertController.Style.alert)
    // alert confirmed
    confirmAlert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { (action: UIAlertAction!) in
        // run delete function
        sessionManager.shared.deleteMemory(memory: self.page) { (success) in
            // reload map controller
            NotificationCenter.default.post(name: Notification.Name("reload"), object: nil)
            self.dismiss(animated: true, completion: nil)
        }
    }))
    // alert canceled
    confirmAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action: UIAlertAction!) in
        // do nothing
    }))
    // show the alert
    present(confirmAlert, animated: true, completion: nil)
}
```
To delete the memory in the **sessionManager.swift** function I had to:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Find that memory in the database
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Delete it from the database
&nbsp;&nbsp;&nbsp;&nbsp; 3\. Delete it from the global memories array (by comparing objectIDs)
```swift
func deleteMemory (memory:Memory, completion:@escaping (_ success:Bool) -> ()) {
    // get the memory
    let query = PFQuery(className:"Memory")
    query.getObjectInBackground(withId: memory.objID) { (object, error) in
        if error == nil {
            // Success!
            if let object = object {
                // delete this row
                object.deleteInBackground()
                // delete from array memories
                self.deleteMemArray(memory: memory)
            }
            completion(true)
        } else {
            // Fail!
            completion(false)
        }
    }
}

func deleteMemArray(memory:Memory) {
    // loop through all memories in array
    for i in 0..<memories.count-1 {
        // if the objectIDs match
        if memories[i].objID == memory.objID {
            // delete the memory
            memories.remove(at: i)
        }
    }
}
```
I used two separate functions as a stylistic choice.

## Edit
I also wanted users to be able to edit memories instead of having to delete and recreate them. This proved to be a little bit harder, but I eventually got it to work:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Create an action function from the **memInfoPage** edit button (editToggled)
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Present and pass information to the edit controller (editMem)
```swift
@IBAction func editToggle(_ sender: Any) {
    // put all inputs into the text stuff to be resaved
    let VC = self.storyboard?.instantiateViewController(withIdentifier: "editMem") as! editMem
    // pass memory information
    VC.page = page
    self.present(VC, animated: true, completion: nil)
}
```
&nbsp;&nbsp;&nbsp;&nbsp; 3\. In **editMem.swift**, write the following code:
```swift
import Foundation
import UIKit
import MapKit
import Parse
import CoreLocation

class editMem: UIViewController, UITextFieldDelegate, CLLocationManagerDelegate {
    
    var page : Memory!
    
    @IBOutlet weak var memTitle: UITextField!
    @IBOutlet weak var memCategory: UITextField!
    @IBOutlet weak var memImage: UIImageView!
    @IBOutlet weak var memDesc: UITextView!
    @IBOutlet weak var memTags: UITextView!
    @IBOutlet weak var memLocation: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // set all input fields to the previous values
        memTitle.text = page.title
        memCategory.text = page.category
        memDesc.text = page.desc
        memTags.text = page.tags
        memLocation.text = page.location
        memImage.image = page.Image
    }
    
    @IBAction func memUpdate(_ sender: Any) {
        self.showSpinner()
        // can't edit coord or image
        let coord = page.coordinate
        let image = page.Image
        // create new memory with inputted info
        let newMem = Memory(title: memTitle.text!, Image: image, coordinate: coord, desc: memDesc.text!, category: memCategory.text!, tags: memTags.text!, location: memLocation.text!, date: page.date, objID: page.objID)
        // call update function
        sessionManager.shared.updateMemory(memory: newMem) { (success) in
            self.removeSpinner()
            // go back to memInfoPage
            let VC = self.storyboard?.instantiateViewController(withIdentifier: "memInfoPage") as! memInfoPage
            VC.page = newMem
            self.dismiss(animated: true, completion: nil)
            // call reload functions so update appears
            NotificationCenter.default.post(name: Notification.Name("reloadContent"), object: nil)
            NotificationCenter.default.post(name: Notification.Name("reload"), object: nil)
        }
    }
}
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a\. This code first sets all the inputs equal to whatever the previous information on the memory was
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b\. The user can then edit the information and click the update button when finished (which runs memUpdate)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c\. memUpdate creates a new memory with the updated information and runs the updateMemory function in sessionManager
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d\. It then dismisses the controller (returning to memInfopage)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e\. Finally, it triggers the reload functions mentioned earlier in memInfoPage and ViewController so that the updated information is displayed
In **sessionManager**, create an update function that:
&nbsp;&nbsp;&nbsp;&nbsp; 1\. Finds the object in the database
&nbsp;&nbsp;&nbsp;&nbsp; 2\. Replaces with and saves the new information
&nbsp;&nbsp;&nbsp;&nbsp; 3\. Replaces the old memory with the new memory in the global memories array (by comparing objectIDs)
```swift
func updateMemory (memory:Memory, completion:@escaping (_ success:Bool) -> ()) {
    // find memory
    let query = PFQuery(className:"Memory")
    // with the same objectID
    query.getObjectInBackground(withId: memory.objID) { (object, error) in
        if error == nil {
            // Success!
            if let object = object {
                // update all values
                object["title"] = memory.title
                object["desc"] = memory.desc
                object["location"] = memory.location
                object["date"] = memory.date
                object["tags"] = memory.tags
                object["category"] = memory.category
            }
            // save object
            object!.saveInBackground()
            // change in global array
            self.updateMemArray(memory: memory)
            completion(true)
        } else {
            // Fail!
            completion(false)
        }
    }
}

func updateMemArray(memory:Memory) {
    // loop through memories
    for m in memories {
        // if objectIDs the same
        if m.objID == memory.objID {
            // update that memory in the global array
            m.title = memory.title
            m.desc = memory.desc
            m.location = memory.location
            m.date = memory.date
            m.tags = memory.tags
            m.category = memory.category
        }
    }
}
```
Again, using two functions is stylistic.

## Storing
Most of the previous code has been edited to exclude the sorting tableViewControllers and collectionViewControllers. This is because accessing information this way presented challenges when editing the memories (it was difficult to get the updated information to show up when clicking the navigation back button). Although I managed to do it in the end, including those steps and code in this tutorial added another layer of length and complexity. However, if you are interested in adding a similar kind of sorting mechanism, check out my code on [GitHub](https://github.com/CarolineBaillie/snapShot) to see how it was done!

## Closing Remarks
Congratulations, you have now created your own digital scrapbook! I know I learned a lot from this project, and hope you did, too. I encourage all readers to embark on the rewarding journey of experimenting with and creating tutorials about Xcode, SashiDo, and iOS Maps. There are so many great resources all over the internet that help make your life easier when coding so there really is no downside to taking things into your own hands and having fun. I hope you all enjoyed my tutorial and again, make sure to check out (or download) my code on GitHub to get the full experience or my [previous tutorial](https://dev.to/carolinebaillie/fish-classification-ios-app-with-sashido-and-teachable-machine-417i) that uses [Teachable Machine](https://teachablemachine.withgoogle.com/) to classify fish types.

Thanks so much to SashiDo and all you readers! Happy future coding!

## Resources
SnapShot Part 1: https://blog.sashido.io/snapshot-how-to-create-a-digital-scrapbook-in-ios-part-1/
An Xcode Collection of Useful Functions and Tips: https://blog.sashido.io/xcode-collection-of-useful-functions-and-tips/
SashiDo: https://www.sashido.io/en/
Parse Documentation: https://docs.parseplatform.org/ios/guide/
Parse Video Playlist: https://www.youtube.com/playlist?list=PLMRqhzcHGw1ZFjFyHGJTTPuvcLbwVCuG4
GitHub: https://github.com/CarolineBaillie/snapShot