When you're standing before the Great Wall of China, going out to lunch with friends, or even wandering through the middle of nowhere, don’t you want to snap a picture and easily keep track of all your adventures and experiences? Sure, you can take hundreds of photos on your phone, but there are always so many to go through. Wouldn’t it be nice to have a digital map filled with specific pictures for specific locations?
As a traveler and wanderer, I definitely wanted an app like this, and as a coder, I wanted to learn more about the iOS map feature. Inspired, I set out to create a sort of digital scrapbook that I call SnapShot. Today I want to share my newly gained experience through a tutorial, but first, here is a demo of the final product:
In this tutorial, I will be going over how to set up and use the amazing backend platform SashiDo (also used in my prior tutorial: Fish Classification iOS App with SashiDo and Teachable Machine) as well as going over the basic components of my app. Also, all my code is available on my GitHub. Before continuing, I recommend you have a Xcode installed and basic knowledge of swift.
Table of Contents
Podfile
As mentioned before, I used SashiDo’s backend platform to store user information. Because SashiDo uses Parse open-source technology to communicate with your app, you first must install the podfile:
1. Create your Xcode project (I called mine snapShot)
2. Go to terminal and enter your project’s folder
3. Type sudo install cocoapods
4. Then type pod init
5. If you type ls you should see the following:
6. Type open -a TextEdit Podfile so that you can edit the file
7. Modify the file to look like this:
8. Save the file and return to terminal
9. Type pod update
10. Now if you type ls you should see all these files:
11. Type open snapShot.xcworkspace to open in Xcode
From now on you must open .xcworkspace, NOT .xcodeproj
SashiDo
Now we will actually set up the connection with SashiDo:
1. Create an account with SashiDo
2. Go to your dashboard
3. Click Create New App and follow the instructions
4. Once your app has been created, you should see a page saying Connect Applications With SashiDo
5. Making sure swift is selected, copy the 6 lines at the bottom:
a. If you are not shown this page or accidentally move past it, you can find it again by clicking Getting Started at the top of the menu on the left
If you had any trouble with this section, refer to the Getting Started Guide
6. Next, go back to Xcode and open AppDelegate.swift
7. Inside func application, paste the code you copied
8. At the top of this file, add import Parse
9. It should look like this (note: the line about the local datastore is not required):
To communicate with SashiDo, we will use Parse documentation. I will be going over some of the functions and calls I used, but it is also beneficial to refer to the Parse iOS Documentation. Additionally, to gain a more in-depth understanding, I found this video playlist on using parse for iOS development very useful.
Outline
Once you have the connection to SashiDo set up, it’s important to create an outline of your app. This diagram should contain all your controllers and the connections between them. It is also a good practice to color different types of controllers (in my case viewController=black, TableViewController=blue, and CollectionViewController=red).
This is just the initial outline so your actual controllers might look or act differently.
Controller Setup
With this outline, you can now begin working on Xcode and adding the controllers:
1. Inside the project folder, create a folder called viewControllers
2. Go to Main.Storyboard
3. Add the necessary (types of) controllers
4. For each added controller create a swift file in the viewControllers folder with an appropriate name
5. In the file, paste and modify this template code:
import Foundation
import UIKit
import MapKit
import Parse
import CoreLocation
class fileName: viewControllerType /*other types might be necessary depending on your usage of this class*/ {
override func viewDidLoad() {
super.viewDidLoad()
}
}
All these imports are not necessary for every controller, but it can’t hurt to have all of them.
6. Under the corresponding controller, go to identity inspector and under class, select the swift file
7. Copy the swift files name and paste it in Storyboard ID
8. Add the necessary buttons, labels, images, etc.
a. If your controller contains text inputs (e.g. login, signup, or newMemory), then I recommend adding a scrollView so the user can see all the input boxes
b. Don’t add anything to the controller for the map (ViewController)
Now you should set up a navigationController to make movements between controllers smooth:
1. Drag a navigationController onto the storyboard
2. Under Attributes Inspector, check Is Initial View Controller
3. Hover over the navigation controller, click control and drag your mouse to your first controller (e.g. login/signup screen)
4. Release your mouse and select root view controller
5. In settings.swift viewDidLoad, add the following code to keep the controller with navigation:
var objVC: UIViewController? = storyboard!.instantiateViewController(withIdentifier: "settings")
var aObjNavi = UINavigationController(rootViewController: objVC!)
From here you can create segues between view controllers (login/signup screen -> login controller) or wait to create these segues through code (login -> settings).
This is my final storyboard:
Session Manager
The complexity of this app requires us to store information in a database (taken care of by SashiDo). However, we still need to request and send information from and to the database. To do this we will create a file called sessionManager (also used in my previous tutorial). sessionManager is a class that contains all the query functions that can then be accessed from any other file. The basic setup for this file is as follows:
1. In snapShot, create a new folder called Classes
2. In the this folder, create a new swift file called sessionManager
3. Add this template code to sessionManager.swift:
import Foundation
import Parse
class sessionManager: NSObject {
static let shared = sessionManager()
override init() {
super.init()
}
}
4. In this class we will:
a. Create query functions
b. Store global variables on the user and location
c. Store a global array with all information from the database so we only need to send one request
This file will be used throughout the app so it is important to have a basic understanding of and to create this file at the beginning.
Registration
In most apps, you will need the user to create and use an account. SashiDo makes this process super easy!
Current User
In order to show individual information, I created a variable that kept track of the current user:
1. In the Classes folder, create a new file called currentUser.swift
2. Write this code:
import Foundation
import UIKit
import Parse
class currentUser : NSObject {
var username:String
var id:String
init(username:String, id:String) {
self.username = username
self.id = id
}
}
3. In sessionManager.swift, add var user : currentUser! at the top
4. This variable will be used in other functions in sessionManager
Signup
1. You should have all the necessary text inputs (preferably in a scrollView)
2. Connect these inputs (as an outlet) and the button (as an action) to its file (signup.swift)
3. Add the function to dismiss the keyboard
4. Inside the button action function (signupToggled):
a. Display waiting spinner
b. Create and save the user (Parse)
c. Go to the next controller (settings)
import Foundation
import UIKit
import Parse
class signup: UIViewController {
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
//get rid of keyboard when touch screen
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
usernameTextField.resignFirstResponder()
passwordTextField.resignFirstResponder()
emailTextField.resignFirstResponder()
}
@IBAction func signupToggled(_ sender: Any) {
// loading icon
self.showSpinner()
// create new user
let user = PFUser()
user.username = usernameTextField.text!
user.password = passwordTextField.text!
user.email = emailTextField.text!
// save user
user.signUpInBackground { (result, error) in
if error == nil && result == true {
//successfully signed up
// store current user
let cUser = currentUser(username: user["username"] as! String, id: user.objectId!)
sessionManager.shared.user = cUser
//next screen
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "settings") as! settings
self.navigationController?.pushViewController(secondViewController, animated: true)
}
}
}
}
Login
1. Same as sign up code but login rather than signup:
import Foundation
import UIKit
import Parse
class login: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
//get rid of keyboard when touch screen
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
usernameTextField.resignFirstResponder()
passwordTextField.resignFirstResponder()
}
@IBAction func loginToggled(_ sender: Any) {
// loading icon
self.showSpinner()
// login user
PFUser.logInWithUsername(inBackground:usernameTextField.text!, password:passwordTextField.text!) {
(user, error) -> Void in
if user != nil {
// successful
// store current User
var cUser = currentUser(username: user?["username"] as! String, id: user?.objectId as! String)
sessionManager.shared.user = cUser
// next controller
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "settings") as! settings
self.navigationController?.pushViewController(secondViewController, animated: true)
} else {
// The login failed.
print("incorrect username or password")
}
}
}
}
Logout
To logout you can override the navigation back function on a chosen controller (settings.swift):
1. In viewDidLoad:
//change back button name for navigation controller
let newBackButton = UIBarButtonItem(title: "Logout", style: UIBarButtonItem.Style.plain, target: self, action: #selector(settings.back(sender:)))
self.navigationItem.leftBarButtonItem = newBackButton
2. In Class:
@objc func back(sender: UIBarButtonItem) {
// logout
PFUser.logOut()
// go back to first viewController
self.navigationController?.popToRootViewController(animated: true)
}
Part 2
This is only the beginning of a larger project. This mainly included the setup and basics, and part 2 will go more into the specifics of this project and all the map functions. Check out Part 2 here!
Resources
An Xcode Collection of Useful Functions and Tips: https://blog.sashido.io/xcode-collection-of-useful-functions-and-tips/
Part 2: https://blog.sashido.io/snapshot-how-to-create-a-digital-scrapbook-in-ios-part-2/
SashiDo: https://www.sashido.io/en/
Parse Documentation: https://docs.parseplatform.org/ios/guide/
Parse Video Playlist: https://www.youtube.com/playlist?list=PLMRqhzcHGw1ZFjFyHGJTTPuvcLbwVCuG4
Install Cocoapods: https://www.youtube.com/watch?v=BSYLs0wl5ME
GitHub: https://github.com/CarolineBaillie/snapShot
Top comments (0)