diff --git a/mobile/ios/.gitignore b/mobile/ios/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/mobile/ios/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/mobile/ios/1-Swift.md b/mobile/ios/1-Swift.md new file mode 100644 index 0000000..334f3bf --- /dev/null +++ b/mobile/ios/1-Swift.md @@ -0,0 +1,386 @@ +# iOS +[Home](README.md) + +[Xcode Introduction >](2-XcodeIntro.md) +## Swift + +Swift is a relatively new language introduced by Apple. One cool thing about Swift is that it's open-source. It's even hosted [right here on GitHub]()! + +To get started **open a new "Playground" document** in Xcode (File -> New -> File -> Playground). +Let's start off fresh so **delete the contents of the file**. + +## Hello world! +Traditionally the first program that you write when you're learning a new programming language is one that prints "Hello world!" to the screen. To do this in Swift just type: + +```swift +print("Hello world!") +``` + +### Comments +In Swift, if you type "//", the computer will ignore the rest of the line. This is useful to add some explanation of what your code does to other humans or to yourself when you come back to the code later. +Let's add comments to our first program... +```swift +// This program prints out "Hello world!" +print("Hello world!") +``` + +## Data types +There are many different data types in Swift. We will go over the most commonly used ones: Strings, Integers, Floats, and Booleans. + +### `String`s +Strings are just what programmers call text. In the case of our program above, "Hello world!" is a string. We can store these strings inside a [variable]() using `var`, for example: + +```swift +var myName = "Your Name" +``` + +We can also "add" strings together like this: +```swift +var myName = "Your Name" +print("Hello! My name is " + myName) +``` + +Here the `var` means that `myName` will be a [variable](), then we assign the value of this variable to the string "Your Name". + +### `Int`egers +We can store numbers too. Any whole numbers are "Integers" and decimal numbers are called "Floats". +```swift +var numPlanetsInSolarSystem = 8 +var floatVariableName = 4.3 +``` + +We can also do some math! Be careful though, when you divide two integers, you will always get an integer. The end or decimal is chopped off. This is called "floor division". +```swift +var five = 2 + 3 +var three = 7 / 2 +var threeAndAHalf = 7.0 / 2.0 +``` + +### `Bool`eans +Booleans are values that can either be `true` or `false`. + +```swift +var wittyBoolName = true +``` + +We can also use [comparison operators](), such as ">", "<", "==", "<=", ">=". +*Note: We use "==" for checking if two things are equal, since we use a single equals sign to assign one thing to another.* + +```swift +var moonDistance = 370300 +var astronautDistance = 295322 + +var astronautArrivedToMoon = moonDistance <= astronautDistance +// astronautArrivedToMoon is false, because the moonDistance is greater than astronautDistance +``` + +### Printing results +We can also print out strings, numbers and booleans, like we did in our first program: +```swift +print(4+3) +``` + +### Variables (`var`) +We've already been creating variables, but we can also go ahead and change their value! +```swift +var numberOfPlanets = 9 +numberOfPlanets = numberOfPlanets - 1 // numberOfPlanets is now 8 +print("We now have \(numberOfPlanets) planets. We'll miss you Pluto.") +numberOfPlanets -= 1 // This is the short form of the line above +// numberOfPlanets is now 7. :o + +// We can also double the number of apples: +var numberOfSpaceRockets = 3 +print("We start off with \(numberOfSpaceRockets) rockets.") +numberOfSpaceRockets *= 2 +print("Woah! We suddenly have \(numberOfSpaceRockets) rockets! Thanks Elon!") +``` + +### Constants (`let`) +But some things we want to make sure don't change. Those are constants. We create them with the `let` keyword: +```swift +let zero = 0 // Will always be 0. Forever. +``` + +## If statements +Now that we're getting comfortable storing values into variables and constants, now we can make some decisions in our code. +We do this with an "if-statement", it looks like this: + +```swift +var condition = true +if (condition) { + print("The condition was true") +} +``` + +You can also use the comparisons operators we saw earlier. It's also possible to execute different code if the condition is not true, using the `else` statement: + +```swift +var firstNum = 3 +var secondNum = 2 +if (firstNum > secondNum) { + print("firstNum is greater than secondNum") +} else { + print("firstNum is less than or equal to secondNum") +} +``` + +With an `if-else`, you can check for more conditions: + +```swift +var cond1 = false +var cond2 = true +if (cond1) { + print("Cond1 is true") +} else if (cond2) { + print("Cond1 is false, but cond2 is true") +} else { + print("Cond1 and cond2 are both false") +} +``` + +You can also connect different expressions with `&&`, which means "and", as well as, "||", which means "or". +```swift +cond1 = false +cond2 = true +if (cond1 || cond2) { + print("At least one of a or b is true") +} + +if (cond1 && cond2) { + print("A and B are both true") +} +``` + +## While loop +Humans have no trouble doing what we've done so far. But where the real power of computers kicks in is loops. Computers can repeat similar tasks again and again, and never get bored! + +The while loop will repeat certain instructions until a condition is false. + +```swift +var countdown = 10 +while (countdown > 0) { + print(countdown) + countdown -= 1 +} +print("Blastoff!") +``` + +## Types + +Swift is what is known as a "strongly typed language". All that means is that every variable has a specific type, and it has to stick to it. Swift is pretty smart though, and it can usually figure out what type a variable is supposed to be, but sometimes we want to specify it. (Why we do this will become more obvious a bit further down this page.) + +We can specify the variable `name` to be a `String` like this. +```swift +var name: String = "My Name" +``` + +## Lists +We can also create a group of elements. One way to group elements is into a list. + +```swift +var list = [1,2,3,4] +var anotherlist = 1...4 // A shortcut in Swift that does the same as above +var crew = ["Joe", "Sarah", "Dave"] // Lists can hold anything, as long as they are the same type +``` + +Looking at the `class` list, the first student, "Joe" is stored at the "zeroth" index. We can assign this to a variable. + +```swift +crew = ["Joe", "Sarah", "Dave"] +var firstCosmonaut = crew[0] // joe +var secondCosmonaut = crew[1] // sarah +``` + +You can also change the values in a list, as long as the list is a [variable](). +```swift +crew = ["Joe", "Sarah", "Dave"] +crew[0] = "Alice" +print(crew) + +// Crew is now ["Alice", "Sarah", "Dave"] +``` + +We can also add new elements to the end like this: +```swift +crew = ["Jason", "Sarah", "Dave"] +crew.append("Cora") +print(crew) + +// Now crew is ["Jason", "Sarah", "Dave", "Cora"] +``` + +## Dictionaries +You can also group items in dictionaries, which act exactly like an actual dictionary. + +```swift +var dictionary = ["spaceship":"vehicle to get around space time", "astronaut":"space explorer"] +var spaceShipDefinition = dictionary["spaceship"] +print(spaceShipDefinition) // prints "vehicle to get around space time" + +// As long as they are variables, they can be change just like lists +dictionary["astronaut"] = "space wanderer" + +print(dictionary) +``` + +## Types Again + +We can also specify the type of objects that are arrays or dictionaries. + +We can define an array and a dictionary like this: +```swift +var engineeringTeam: [String] = ["First Member", "Second Member"] // this is an array of strings +var yearsAtNasa: [String:Int] = ["First Member":2, "Second Member":1] // this is a dictionary from strings to ints +``` + +Using the same notation, we can also make empty lists like this: +```swift +var engineersInSpace = [String]() // this is an empty array of strings +``` + +> Tidbit: The `[String]` syntax is simply a shorthand for `Array`. Read more [here](link_here) + +## For loop +There is also another type of loop that can iterate through elements of a list. + +```swift +crew = ["Joe", "Sarah", "Dave"] +for crewMember in crew { + print("\(crewMember) is in the crew") +} +``` + +### Optionals +Swift is a language the supports optionals. In other languages it may be called "nullables" or something of the sort. All it means is that if a value is an optional, it can either have a value assigned to it or it can be `nil`. The only catch is that if it is `nil` then if you try to call a method on it, you simply won't be able to. You have to unwrap it. To "force unwrap" the optional you can use `!`. But if you unwrap it, and it's nil and try to call a function on it. It'll crash. +Ouch! To avoid this, we can use the `?` to safely unwrap the optional. What does unwrapping mean? Let's take a look at some examples: + +Note. Since we start defining this as `nil`, we need to specify a type when we create the object. + +```swift +var optional: [String]? = nil +// optional!.count // CRASH + +optional = ["hi", "hello"] +optional?.count +``` + +## Function +We can group blocks of code into a function. This allows us to reuse the group of code without typing it all out again. +Functions can take arguments, but we need to specify the types of the arguments and what type it returns. + +In Swift, it's common to have very descriptive function names. This will make more sense as we start making apps: +```swift +func methodWithFirstArgument(a: Int, andSecondArgument b: Int) { + print("This is a common way to pass \(a) and \(b) into a function in Swift") +} +``` + +We can also make functions that don't return anything, it looks like this: +```swift +func introduce(name: String) { + print("Hello my name is " + name) +} +``` + +Let's make a function called `add` which takes two integers, `a` and `b` and returns their sum as an integer. We can use `_` to hid the second argument name. +```swift +func add(a: Int, _ b: Int) -> Int { + return a + b +} +``` + +We can call these functions like this: +```swift +methodWithFirstArgument(42, andSecondArgument: 13) +introduce("Jonathan") +var seven = add(4, 3) +``` + +## Objects +We can create a collection of related properties and functions into what is called an `Object`. This grouping of properties and functions act a lot like real world objects. You can set their attributed (properties), and ask them to do things (call their functions). Grouping these properties and methods into an object really helps us clean up our code so that we can reuse them. This concept is called [modularity]() and is key in computer science. + +To create an `object` we need to create a template for it, which properties and functions should be grouped together. This template is called a `class` and each object is an [instance]() of a particular `class`. This would look something like: + +```swift +class Person { + var name: String + var age: Int + + // The initializer is the function that creates the object + // You need at least one initializer for every class + init(givenName: String, givenAge: Int) { + // We can access the properties of an object using dot notation as below + // "self" is how we refer to the current instance of the class + self.name = givenName + self.age = givenAge + } + + func introduce() { + print("Hello! My name is \(self.name) and I am \(self.age) years old. Nice to meet you.") + } +} + +var person = Person("Alex", 16) +person.introduce() // Call a method like this +var personAge = person.age // Access properties like this +person.age += 1 // Change properties like this. Happy birthday! +``` + +## Inheritance +We can also [inherit]() all of the properties of another class. For example, we will create a "vehicle" class, and we can make a "Mars Rover" class which will inherit from the "Vehicle" class because a Mars Rover has all of the attributes of a vehicle. + +```swift +class Astronaut: Person { + var yearsInSpace: Int = 0 + var authorizedVehicles: ["Space Shuttle", "Soyuz"] + + func canFly(vehicle) { + return authorizedVehicles.contains(vehicle) + } +} +``` + +## Protocols +We can list a set of properties and functions that we want a method to implement, this is called a protocol. Then an object can declare that it implements a protocol. This is usually done in an extension. For example, we can have the flying protocol. + +Let's start with a vehicle class. +```swift +class Vehicle { + let numberOfWheels: Int + var speed = 0 + + init(numberOfWheels: Int) { + self.numberOfWheels = numberOfWheels + } +} +``` + +Nothing too surprising so far, just like our `Person` class. How let's define a protocol. A set of properties that we need for an object to be flyable. + +```swift +protocol Flyable { + var distanceOffGround: Int { get set } // Note: we need to specify whether we can read (get) and write (set), or just read + + func land() +} +``` + +So we can create a space ship that we can declare as Flyable. + +```swift +class SpaceShip: Vehicle, Flyable { + var distanceOffGround: Int = 0 + + func land() { + self.speed = 0 + self.distanceOffGround = 0 + } +} +``` + +Link to all of this code is [here](https://gist.github.com/pbardea/e86289692efbbb444df8d31db75383bd) + +That's it! Now using these tools, we're going to go ahead and build our very own mobile application! + +[Xcode Introduction >](2-XcodeIntro.md) diff --git a/mobile/ios/10-Map.md b/mobile/ios/10-Map.md new file mode 100644 index 0000000..3237054 --- /dev/null +++ b/mobile/ios/10-Map.md @@ -0,0 +1,131 @@ +# Map +[Home](README.md) + +[< Annotation Object](9-AnnotationObject.md) - [Tab Bar >](11-TabBar.md) + +Now, we're going to get started on building the map portion of the application. To do this, we're going to use a [framework]() called [MapKit](). A framework is a package of useful methods and classes. In the case of MapKit, it provides some classes and methods to interface with maps. One of the view's we're going to use is `MKMapView`, which is a view that automatically provides a map for use to use. As we create a new screen, we're going to create a new [View Controller](). Let's make a new "CocoaTouchClass" file, and make it a subclass of "UIViewController". +The standard for naming view controllers is to have the class name end in "ViewController". So you can name this class "MapViewController" or something of the kind. +View controllers can have [private properties](), properties that only that view controller can see. Some properties that this `MapViewController` may have is the map view it will display, and a list of annotations we want to display on the map. So we can add these three properties to the view controller with these three lines: +```swift +private let mapView = MKMapView() +private var annotations: [Annotation] = [] +private var caches = [Cache]() +``` + +## `viewDidLoad` + - Our `viewDidLoad` method is called when the view controller's view did load, as the name suggests. It's important to note that it will only be called the first time it is loaded, not every time it appears. The first thing that needs to be done is to ensure that the [super class]() is set up, so we call `super.viewDidLoad()`. We can also set the background colour of the view of the view controller by setting the `backgroundColor` property of `self.view`, which is the view property of the view controller. + + - The `mapView`, just like the table view, needs a [delegeate]() to manage it's annotations. The `MapViewController` will be the delegate for now. To do this, we can set the `delegate` property to itself (`self`). + - Now let's add the `mapView` as a [subview]() to the view controller's view. + - If we run the application, we still won't see a map on this page. If we set up a break point at the end of `viewDidLoad` and see what's going on. It turns out that the frame of the `mapView` is not set. So let's set the `frame` property of the `mapView` to the `frame` of the view controller's view. This can be done like this: `self.mapView.frame = self.view.frame`. + - Running again, we can see that now we have a nice map! Now rotate the device. If you're on a simulator, checkout the "hardware" menu for rotation options. The map doesn't seem to be resizing. This is because the frame of the map is the same to what we originally set it. There are many ways to control the layout of views on the screen, but the easiest way to do it right now is to use the `viewDidLayoutSubviews` method. This method is called whenever the screen size changes, so in this case, when the device is rotated. + +## `viewDidLayoutSubviews` + - Let's try moving the code we wrote to set the frame of the mapView into a method called `viewDidLayoutSubviews`. Since this is a method that comes part of `UIViewController`, we're going to have to override it, as well as to make sure that we call the `super` class' implementation of the method. So this would look something like this: + ```swift + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubview() + + // Code to set the mapView's frame + } + ``` + - Next, we need a list of all of the caches. We can get this using our nifty `DataModelManager` that we created earlier. To access the shared instance just use `DataModelManager.sharedInstance`, then we can simply access the `caches` property of the shared instance to get all of the caches. + - Now we want to get a list of annotations based on this list of caches. In Swift we can do this using the `map` function, which is like a short cut for a for loop: + ```swift + annotations = caches.map { (id, cache) in + Annotation(cache: cache) + } + ``` + - Next, we want to add all of our annotations to the map, which looks something like this. Easy peasy. + ```swift + self.mapView.addAnnotations(self.annotations) + ``` + - Finally, let's make it go to the default location. Let's pretend we have a method called `goToDefaultLocation()` and call it here like this: `self.goToDefaultLocation()` + + + - Now let's implement this method that will take the user to the default location. + - First, using the `self.annotations` list, find the average latitude and longitude of all our annotations, then create a new variable and make it an instance of `MKCoordinateRegion`. Now set the `center` of this object to the average location and set the `span` property to `0.02` in the `latitude` and `longitude` directions. (This is just the zoom of the region.) + - Give this a try. If you get stuck, remember, Google is your friend. This is how I did it: + ```swift + func goToDefaultLocation() { + let defaultScale = 0.02 + + var newRegion = MKCoordinateRegion() + + // TODO, to consider + // Get all of the latitudes and longitudes + // This is the functional way... should the for-loop method be shown? + let latitudes = self.annotations.map { $0.coordinate.latitude } + let longitudes = self.annotations.map { $0.coordinate.longitude } + + newRegion.center.latitude = average(latitudes) + newRegion.center.longitude = average(longitudes) + + newRegion.span.latitudeDelta = defaultScale + newRegion.span.longitudeDelta = defaultScale + + self.mapView.setRegion(newRegion, animated: true) + } + ``` + - And to get the averages, I used an extension: + ```swift + // Break this off into an extension of [Double] + extension MapViewController { + func average(array: [Double]) -> Double { // TODO: Make as an extension of array + var sum = 0.0 + for element in array { + sum += element + } + return sum / Double(array.count) + } + + func fancyAverage(array: [Double]) -> Double { + return array.reduce(0, combine: +) / Double(array.count) + } + } + ``` + - Sidenote: Since we are using a navigation view controller, the view will actually start underneath the navigation view which we can't access. To fix this, we can set it to opaque like this: + ```swift + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + + self.navigationController?.navigationBar.barStyle = .Black + self.navigationController?.navigationBar.translucent = true + } + ``` + - The MapViewDelegate + - Now we need a method to return the view for a specific annotation. + - Some goals that we want to get out of this is to: + - Have a green flag show on the map for the found caches and a red flag show up for caches that have not been found yet. + - We want to be able to tap on the flag to have a name and description of the cache + - A button to go see the details of the cache (we will set up the button, but link to the details later) + - Let's start with an extension which implements `MKMapViewDelegate` + ```swift + extension MapViewController: MKMapViewDelegate { + func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? { + + } + } + ``` + - This method will be automatically called by the `mapView` for every annotation, including the annotation to indicates the user's location. So we can detect if the annotation is the user, and return `nil` in that case like: + ```swift + if annotation.isKindOfClass(MKUserLocation.self) { return nil } + ``` + - Then using the `createViewAnnotationForMapView` method we implemented in the previous module, we can set up a `returnedAnnotationView` constant and assign it to this. + - Now, we can set up two images, which you can download [here]() and [here]() and drag it into the "Assets" folder in the project. To get these images we can use the following code: + ```swift + let notFoundFlag = UIImage(named: "flag") + let foundFlag = UIImage(named: "foundFlag") + ``` + + - We can safely ensure that the annotation passed in is of the `Annotation` type we defined (as they all should be). This check can be done like this: + ```swift + if let annot = annotation as? Annotation { + //... + } + ``` + - We can setup an info button with `UIButton(type: .DetailDisclosure)` and assign it to a constant. + - `annot` will have an [optional]() cache property. Try and write some code to assign the appropriate flag to `returnedAnnotationView.image` depending on whether or not the cache was found. + - Also we will set the `rightCalloutAccessoryView` of the `returnedAnnotationView` to the detail button we created. + +[Previous](9-AnnotationObject.md) diff --git a/mobile/ios/11-TabBar.md b/mobile/ios/11-TabBar.md new file mode 100644 index 0000000..80fce5b --- /dev/null +++ b/mobile/ios/11-TabBar.md @@ -0,0 +1,11 @@ +# Tab Bar +[Home](README.md) +[< Map](10-Map.md) + +Now we're going to connect the "Found List" portion of our application with our "Map View". One way to do this is to is with a tab bar. + +We're going to have a tab bar at the bottom of our screen where we can toggle between the list of items that we found and the map. To do this we're going to use a `UITabBarController`. This is a special type of [view controller]() that comes with the iOS [SDK](). It is what we call a [container view controller](). This type of view controller is responsible for holding and managing other view controllers. Another example of a container view controller that we've already used is the `UINavigationController`. The way we set the tabs is by providing the `UITabBarController` and list of view controllers. + +In this case we want the TabBar to hold the 2 `UINavigationController`s that we made. The first holds the "Found List", and the second contains the `MapViewController`. Since we want to create these on startup, we will put this code directly in our `AppDelegate` file. + +[< Map](10-Map.md) diff --git a/mobile/ios/2-XcodeIntro.md b/mobile/ios/2-XcodeIntro.md new file mode 100644 index 0000000..d1ad4a3 --- /dev/null +++ b/mobile/ios/2-XcodeIntro.md @@ -0,0 +1,70 @@ +# Gettings Started + +[Home](README.md) + +[< Swift](1-Swift.md) - [Git Tutorial >](3-GitTutorial.md) + +## Create the project +1. Make a new project ("File" -> "New" -> "Project") +2. Select "iOS" -> "Single View Application". We'll start off with the most basic starting point. Starting off from scratch. +3. This of a name for your app! The "Product Name" field will be the name of your app. I named the sample on "Scavenger". +4. The organization field just specifies the author of the app, you can put your name there. +5. For the organization identifier, try to make it unique. For example you can do "com.firstName.lastName". +6. Make sure to choose "Swift" as the programming language +7. Select "iPhone" for the devices filed +8. Make sure the "Use Core Data" field is unchecked. This will add some template code that we don't want for this app. +10. After clicking next you can choose where to save your project on your computer. +11. Make sure "Create Git repository on My Mac" is selected. This will let us use source control. +12. Click "Create"! + +## Let's look around! + - Congrats! You've created the foundation for your first mobile application! + - Now let's take a look at the panel on the left. This is the navigator. It'll show you all of the files in your project. + +### The files +**"AppDelegate.swift"** +- This is where your program starts, whenever the app is launched iOS will start running some of the code in here. + +**"ViewController.swift"** +- This is a basic "ViewController". We'll get into more details later, but we'll want to make our own. + +**"Main.storyboard"** +- Xcode lets you create the UI of application using some visual elements. But a lot of the time, it can actually be limiting, so we're going to make our application entirely through code. + +**Assets.xcassets"** +- This is where all of the images and other media of the app is stored. This includes the app icon. + +**"LaunchScreen.storyboard"** +- This is the screen that shows while the app is loading + +**"Info.plist"** +- This is where you can specify various information about the application, like the permissions it needs and other things like that. We won't be spending much/any time in here. + +### Cleaning up +Since we want to start from scratch, we can delete a few files: +1. Delete the "ViewController.swift" file. (Select "Move to trash") +2. Delete the "Main.storyboard" file. (Select "Move to trash") +3. After deleting the "Main.storyboard" file, go to the top item on the navigator (it should be the name of your app, with a blue icon beside it) +4. Look for the field labeled "Main Interface" and delete the text in the text box beside it. + +### Bear necessities (The Jungle Book shoutout *hoot*) +The backbones of the applications will be the [View Controllers](#). They will control everything on the screen. +1. Navigate to "File" -> "New" -> "File" +2. Go to iOS, and choose "Cocoa Touch Class" +3. Click "Next" +4. Name your [class](#) "MenuViewController" +5. Make it a [subclass](#) of "UIViewController" +6. Make sure "Also create XIB file" is *unchecked* and the language is "Swift" +7. Click "Next" +8. Click "Create" + +### Check up +Let's make sure everything is set up properly. +1. In the top left, click the drop down and select your phone. +2. Hit the run button (play button in the top left), or you can hit "Command + R" + +If the app doesn't run, try and get a mentor to help you + + + +[< Swift](1-Swift.md) - [Git Tutorial >](3-GitTutorial) diff --git a/mobile/ios/3-GitTutorial.md b/mobile/ios/3-GitTutorial.md new file mode 100644 index 0000000..ad5b658 --- /dev/null +++ b/mobile/ios/3-GitTutorial.md @@ -0,0 +1,9 @@ +# Git in Xcode + +[Home](README.md) + +[< Xcode Introduction](2-XcodeIntro.md) - [iOS Introduction >](4-iOSIntro.md) + +// TODO + +[< Xcode Introduction](2-XcodeIntro.md) - [iOS Introduction >](4-iOSIntro.md) diff --git a/mobile/ios/4-iOSIntro.md b/mobile/ios/4-iOSIntro.md new file mode 100644 index 0000000..152143d --- /dev/null +++ b/mobile/ios/4-iOSIntro.md @@ -0,0 +1,60 @@ +[Home](README.md) + +[Previous](3-GitTutorial.md) - [Next](5-CacheObject.md) + +# Hello, world! +Now that we're all set up. Let's get started. +1. App startup + - Remember how our application starts in "AppDelegate.swift"? Let's take a look where exactly that happens. + - Open the "AppDelegate.swift" file and find the `func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool` function. + - Although it has a long name it's job is very simple. It is called when the program starts, then it returns `true` if everything to get the app setup goes alright. + - The first thing we want to do is create a window, so let's add the line: `self.window = UIWindow(frame: UIScreen.mainScreen().bounds)` + - Let's break this down. The app delegate has a [property](#) called window, which is the window for the application. So we need to set it to a new `UIWindow` object. When we create the `UIWindow` we specify the size of the window as the size of the main screen. + - Next let's make a function that looks like this: + ```swift + func start() { + let menu = MenuViewController() + let navController = UINavigationController(rootViewController: menu) + + self.window!.rootViewController = navController + } + ``` + - This function [instantiates]() our menu [view controller]() + - Then creates a [navigation controller](), with our menu as the base + - And the sets the root of the window to the [navigation controller]() + - Now let's call it. Under the line where we [initialize]() the window, call the start method we created using the code: `self.start()` +2. View Controllers + - "[Views]()" are elements on the screen that users interact with (e.g. Buttons, cells, labels, etc...) + - "[View controllers]()" as the name suggests, controls the contents of the view. It acts as the brain of the application and tells the views what to do. + - The "MenuViewController" will control the menu. It will manage the views and control what happens when the user interacts with them. + - By default, view controllers have a lot of code that we don't need. In "MenuViewController.swift" you can delete the `didReceiveMemoryWarning` function and the comments after it. It should look like: + ```swift + import UIKit + + class MenuViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + } + ``` + + - [`ViewController`]()s have a [property]() called `view`. It can be accessed via `self.view` This is the [root view]() of the view controller. + - Let's set the background colour of the view controller's view by setting its `backgroundColor` property to a colour. You can create a colour by using one of the `UIColor` [constructors](). + - Let's start by adding a label. We will add it in the [`viewDidLoad`]() method + - To add a label, we want to add the label as a property of the view controller. We add properties at the top of the class declaration, so in this case, right before [`viewDidLoad`](). + - Since we want it to stay a label always, we can make it a constant. And also, since only this class needs to know about the label, we can also make it private. This means that we can add the property with the line: `private let helloLabel = UILabel()` + - Now that `helloLabel` is a [property]() of the [view controller]() we can go ahead and access it in [`viewDidLoad`]() with `self.helloLabel`. + - Then we want to set the `text`, `font`, and `textColor` properties of the label. For the font, you can set it like `self.helloLabel.font = UIFont.systemFontOfSize(20)`. Give the other two properties a shot! (hint: colours are [object]()s too! You can create the colour red [object]() using `UIColor.redColor()`. + - We also want to set the `frame` of the [view](). For now, we will just set it to the `bounds` of `MenuViewController`'s view. + - Finally, we want to add the label as a [`subview`]() of the `MenuViewController`'s view. +3. Run! + - When you run the application, you should see some text displayed on the screen. +4. Tidying up. + - If you haven't already, make sure to commit your code ("Source Control" -> "Commit") + - Now we want to make room for our scavenger app, so let's comment out all of the code that has to do our label. + +[Previous](3-GitTutorial.md) - [Next](5-CacheObject.md) diff --git a/mobile/ios/5-CacheObject.md b/mobile/ios/5-CacheObject.md new file mode 100644 index 0000000..99ddc8a --- /dev/null +++ b/mobile/ios/5-CacheObject.md @@ -0,0 +1,48 @@ +# Models + +## The Cache + +Since we're making a geocaching application, we're going to create a `Cache` class. Just like we did for `Astronaut` earlier. + +Let's take a minute to think about what properties and methods a `Cache` object might need. + +Okay, here's what I've come up with. +These are the properties I think it's going to need: +- The cache name +- The cache description +- The difficulty of finding the cache +- If the cache was found + - When the cache was found (we can just set the found time to `nil`) +- The location of the cache + +These are some of the methods that may be useful: +- Finding a cache +- Losing a cache +- Finding the distance to other locations + + +1. Let's get started by making a new file called "Cache.swift" (File -> New -> File -> Swift File) +2. Create the `Cache` object just like we did with the astronauts. We should have 5. + - Note, if we `import MapKit` at the top of our file, we can use a type called `CLLocationCoordinate2D`, which is a useful way to store locations. +3. Add the properties that we described above. +4. Now we need to make an initializer like we did for the `Person` class so we can set all of the properties. +5. Implement the initializer +6. A method that takes in a time and sets it to the found time. + - There is a special type called `NSDate` which can be any time value and handles tricky things like timezones and special calendars for us. + - To convert this into a number we can use "epoch time". This is a popular time format in computer science. It is simply the number of seconds or milliseconds since January 1st 1970. Read more about it [here](). + - To get the "epoch time" (an integer) from an NSDate, you can use the `timeIntervalSince1970` property. +7. Implement a method that loses the cache. This should set the found time to `nil`. +Need a hint? Take a look at how I did it [here](gistLink). +8. How we want to be able to find the distance between two caches? + - Create a function called `getDistanceFrom(origin: CLLocationCoordinate2D)` that returns an integer, the distance between `self` and `origin` + - It may look something like this: + + ```swift + func getDistanceFrom(origin: CLLocationCoordinate2D) -> Int { + let originLocaiton = CLLocation(latitude: origin.latitude, longitude: origin.longitude) + let distance = originLocaiton.distanceFromLocation(CLLocation(latitude: self.location.latitude, longitude: self.location.longitude)) + return Int(distance) + } + ``` + +That's it! That's the cache object that we're going to use in our app. diff --git a/mobile/ios/6-FoundItems.md b/mobile/ios/6-FoundItems.md new file mode 100644 index 0000000..09ed97b --- /dev/null +++ b/mobile/ios/6-FoundItems.md @@ -0,0 +1,98 @@ +# Found cache list + +[Home](README.md) +[Previous](5-CacheObject.md) - [Next](7-DetailView.md) + +### Make the view controller +- Create a new view controller. Let's call it "FoundCacheListViewController" +- Once we have this view controller let's make it visible. Heading over to our `AppDelegate` we can create a `UINavigationViewController` instance and assign it to a constant call `navVC`. + +A navigation view controller, is what we call a [container view controller](). This is a special type of [view controller]() that manages other view controllers. Other examples include the tab bar commonly found in iOS applications (like the clock app). A `UINavigationViewController` will let us go to other [view controller]()s and automatically give us a back button that the user can tap. This is a common design structure in iOS. We can initialize our navigation view controller with our own custom view controller which will act as the starting point. The `UINavigationViewController` will also display the `title` property of the view controller it is currently displaying in it's navigation bar. So our `applicationDidFinishLaunchingWithOptions` method should look something like this: + +```swift +func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + // Override point for customization after application launch. + self.window = UIWindow(frame: UIScreen.mainScreen().bounds) + + let foundList = FoundCacheListViewController() + foundList.title = "Found Caches" + + let navVC = UINavigationController(rootViewController: foundList) + + self.window!.rootViewController = navVC + + self.window?.makeKeyAndVisible() + + return true +} +``` + +//TODO: Add image for explanation. + +- In this view controller, we want to make the following screen: + +** INSERT PIC OF SCREEN HERE ** + +- Before we start coding, let's see what's going on here. What we have is a [view]() called a [UITableVIew](), which is just a group of cells. You see this in many iOS applications. + +### Creating the table view + - We're going to follow the same steps as we did to add a label. Now we want to create a `private`, *constant* item named "tableView", which is an [instance]() of `UITableView`. Give it a try! + - In `viewDidLoad`, we need to set a few properties of the `tableView`. Start off by setting the `rowHeight` and `frame` of the `tableView`, just like you did for the `helloLabel`. I set my row height to 100. This is measured in [points]() + - Now let's remember what a [view]() does. All it knows to do is display information and if someone is trying to interact with it. But it doesn't know **what** to do if someone interacts with it. That's where the view controller comes it. The [view]() has a [`delegate`]() property. The [view]() then passes on its actions to let the delegate deal with it. + - The `FoundCacheListViewController` will act as the `tableView`'s [delegate](). To specify this, set the `tableView`'s `delegate` property to `self`. `self` is referencing the `FoundCacheListViewController `. + - The `tableView` also needs a way to know what the data to put in the table. It does this similarly to the `delegate`. It does it with a `dataSource` property. Go ahead and also set its `dataSource` property to `self`. + - The last thing we need to specify is what type of cells the table is going to use. For this app we're going to use the default cell, but we still need to tell the `tableView` that we want to use the default cell. So we're going to add the line `tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "menuCellIdentifier")`. This lets the `tableView` do some cool optimizations for large tables, which you can read about [here]() if you're interested. + - Now if you try running the app, you'll see some errors. This is because we told the `tableView` that we can be its `delegate` and `dataSource`, but it doesn't believe us. The only way for the `tableView` to know if we can actually live up to that claim is if we declare that we implement the `UITableViewDelegate` and `UITableViewDataSource` [protocols](). + - A [protocol]() is simply a list of methods that we need to implement. It can have required methods, which we *must* implement, and optional ones too. + - To keep the code clean, we're going to implement the delegate and the data source in extensions of the class. + - **Delegate methods** + - For the delegate, make an extension that declares it implements the `UITableViewDelegate` as such: + ```swift + // this line means that FoundCacheListViewController says it implements the UITableViewDelegate protocol + extension FoundCacheListViewController: UITableViewDelegate { + // this function is called when a use taps on a cell + func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + // TODO: We will implement this function later + } + } + ``` + - **Data Source methods** + - Make another extension to `FoundCacheListViewController `, this time declaring that you implement `UITableViewDataSource` + ```swift + // Since we only have one section, we can just return how many rows we want + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 // TODO: return the number of menu items. Display 1 for now so we can see the table view + } + + // This function returns the cell we want to go at a certain row + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + // Remember when we registered this identifier for the tableView. This is where it comes in. + // Make sure the two identifiers are the same. In this case, "menuCellIdentifier" + let cell = tableView.dequeueReusableCellWithIdentifier("menuCellIdentifier", forIndexPath: indexPath) + + cell.backgroundColor = UIColor.lightGrayColor() // set the colour to light grey for now + + return cell + } + ``` +### Populating the table view + - We're going to start off with four fake caches as placeholders for now + - To model this, we're going to define an [array]() of cache names. (These can be strings for now.) + - Make an array with the names of the menu items as a property of the view controller, just like the `tableView`. + - Now let's go back to the `func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int` + - We want to return how many cache items we have, so we can return the length of the array we defined + + - Next we want to set the content of each cell to display the name of the cache. + - Let's take a look at `func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)`: + - This method gets called for every cell that is visible, with a different `indexPath` + - We can know which row it is being called for using the `indexPath.row` property. That will be set to 0 for the first row, 1 for the second and so on + - The cell has a textLabel property. You can set the text of this textLabel with `cell.textLabel?.text = "Your text here"` + - Use the `foundCaches` array, and what you know about arrays to set the title of the cell to its cache name. + - Also, the cell has a property called the `accessoryType`. Try setting it to `UITableViewCellAccessoryType.DisclosureIndicator` and see what happens. + + +### Putting cache objects in the table +- Let's revisit our 'foundCaches' array. We can replace the placeholder strings for actual cache objects now! Let's go ahead and add placeholder cache objects. +- In the method where we set the title of the cell, let's set the cell name to be the name of the cache! +- +[Previous](5-CacheObject.md) - [Next](7-DetailView.md) diff --git a/mobile/ios/7-DetailView.md b/mobile/ios/7-DetailView.md new file mode 100644 index 0000000..21a3371 --- /dev/null +++ b/mobile/ios/7-DetailView.md @@ -0,0 +1,87 @@ +# iOS +[Home](README.md) +[< Found Items](6-FoundItems.md) - [Data Manger >](8-DataManager.md) +## Make the detail page +### Make the detail view controller +We're going to make a quick view controller that will allow us to display information about the cache, such as its name, description and location. It will also allow us to "find" a cache. + +Let's create another file called "CacheDetailViewController.swift", which as you may have guessed by the name inherits from `UIViewController`. + +Let's start off with defining the properties of this view controller. What we want this view controller to do is to display the details of a cache object, and also allow ability to find the cache. So we're going to need: +- A cache object (of type `cache`) +- 4 `UILabel`s, for the title, description, difficulty, and location. +- A button to indicate that the item was found. + +### Initializers +We want our initializer to take in the cache object that it describes. So our initializer would look something like this: + +```swift +init(cache: Cache) { + self.cache = cache + super.init(nibName: nil, bundle: nil) +} + +required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") +} +``` + +Initialization is the process of preparing an instance of a class, structure or enumeration for use which involves providing an initial value in the instance's properties. To undergo this process, special methods called initializers are implemented to create new instances of a specific type. + +### Setting up the views +Since we'll have quite a lot of views to set up on this page, it would be best if we break down the setting up of each view into its own function. + +I made a function to set all of these properties separately: +- The title + - Font color + - Font size + - The text +- The description + - Font color + - Font size + - The text +- The location label + - Font color + - Font size + - The text +- The found button + - The title + - Font color + - Font size + - Title string + - The background color + - The action. We can hook it up to a method like we did in our [hello world]() example + +These methods would also be a great time to add them as [subviews]() to our main view controller's [view]() +Let's go ahead and try setting the frame of these views inside these methods. + +Make sure to call all of them from `viewDidLoad`. + +### Make the cells tapable + - Next, let's make the cells do something when we tap them + - Let's go back to `func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)`. + - This returns the `indexPath` the same way as in the `cellForRowAtIndexPath` method. + - Right now, if you select a row it gets grayed out and stays grayed out. We can change that by animating a deselection when it's selected using: `tableView.deselectRowAtIndexPath(indexPath, animated: true)` + - What we want is to show a new [view controller]() whenever a cell is pressed. + - We can create one of our nifty `CacheDetailViewControllers` using the initializer that takes in a cache. Let's put that view controller into a constant called `vc`. Now we can push that view controller like so: +```swift +self.navigationController?.pushViewController(vc, animated: true) +``` + +### Sizing the views +Now let's run our application, it should look like below: +//TODO: Add gif + +That looks all find and dandy, but what happens when we rotate our phone? Uh-oh, that's not what we want. Since we set our view only when the view loaded our view's don't change when our screen size changes. There are many ways to format views in iOS. We're going to do it one way that is probably the easiest to implement. Other methods if you want to look into them are using [autolayout]() or [Visual Formatting Language (VFL)](). The way we're going to do it, is going to be by using a `viewWillLayoutSubviews` method. When the frame of a [view controller]()'s [view]() changes, it calls this method. This gives us the opportunity to resize our subviews. Let's move all of the code that sets the frame for our views in this method. And since `UIViewController` implements this method, we need to remember to `override` it and call `super`'s implementation: + +```swift +override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + // Set subviews frames here +} +``` + +Let's run our code again! Nice, now the views resize properly. + +[< Found Items](6-FoundItems.md) - [Data Manger >](8-DataManager.md) diff --git a/mobile/ios/8-DataManager.md b/mobile/ios/8-DataManager.md new file mode 100644 index 0000000..e911c7c --- /dev/null +++ b/mobile/ios/8-DataManager.md @@ -0,0 +1,155 @@ +# Data Model Manager +[Home](README.md) +[Previous](7-DetailView.md) - [Next](9-AnnotationObject.md) + +## The JSON + - We are going to read in the values of the caches from a JSON file. JSON is a popular format to store information, especially when asking for information from web servers. We are going to start off with our JSON locally. + - Download the two starter JSON files for the [Initial Cache List]() and the [Initial Caches Found]() + After downloading these files, we're going to drag them into our project. Once they're in your project, we need to create a class to turn our JSON files into classes that our program can understand. We're going to call this class a `DataModelManager`. + Looking at the JSON a bit first, we can see that the entire structure is shaped as a dictionary. The "caches.json" file has the generic format: + ``` + { + "Cache name": { + name: "Cache name", + description: "Cache description", + difficulty: 8, + location: { + latitude: 12.123, + longitude: 43.123 + } + } + } + ``` +If we look at the types of this, we would get this: + ``` + { + String: { + String: String, + String: String, + String: Int, + String: { + String: Double, + String: Double + } + } + } + ``` + + - We can go through and [parse]() it. That means we are going to convert the JSON into an object. Since this is a fairly uninteresting part of the application, the code will be provided and explained. + - In this code, we load a file called "caches.json". `NSBundle.mainBundle().pathForResource("caches", ofType: "json")` will load that. If the file does not exist, it will return nil and the `if let` statement will fail. Next we convert the contents of this file to `NSData`, which is just a series of "1"s and "0"s: the raw data of the file. From this format, we can use a class called `NSJSONSerialization` provided with Swift that tries to convert this NSData to the dictionary we specified above. The `try`, `guard` and `throw` keywords work together to `throw` an `InvalidFormat` exception, which just means that it will error with a custom error (InvalidFormat). If anything went wrong, it will print out "Something went wrong...", which is done by `do`, `catch` pair. + ```swift + func loadCaches() { // Returns a dictionary of String ids to the cache object + do { + if let path = NSBundle.mainBundle().pathForResource("caches", ofType: "json") { + if let jsonData = NSData(contentsOfFile: path) { + guard let jsonResult = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers) as? CacheListJSONFormat else { + throw DataModelError.InvalidFormat + } + caches = [String:Cache]() // clear out old caches + for (id, cacheObject) in jsonResult { + let cache = Cache(json: cacheObject) // We have not implemented this yet. We will by the end of the module + caches[id] = cache + } + } + } + } catch { + print("Something went wrong...") + } + } + ``` + + We can do nearly the same for the "found.json" file, which specifies the caches that we already found. + ```swift + func updateFoundStates() { + do { + if let path = NSBundle.mainBundle().pathForResource("found", ofType: "json") { + if let jsonData = NSData(contentsOfFile: path) { + guard let jsonResult = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers) as? [String:[String:Double]] else { + throw DataModelError.InvalidFormat + } + + if let cacheEntry = jsonResult["found_times"] { + for (id, time) in cacheEntry { + if let cache = self.caches[id] { + cache.found = time + } + } + } + } + } + } catch { + print("Something went wrong...") + } + } + ``` + +Now we're going to make an initializer that takes in a dictionary of this format to create an actual `Cache` object. We're going to go to our "Cache.swift" file and add an additional initializer. This initializer is a bit different. Start off with a normal initializer, similar to the other one. This one, however, can fail. Because it can fail, we add a `?` after the `init`. If it does fail we will return [nil](). +Looking at the structure of the file we can define some types at the top of the `Cache` file. Looking at a cache item, specifically: +``` +{ + name: "Cache name", + description: "Cache description", + difficulty: 8, + location: { + latitude: 12.123, + longitude: 43.123 + } +} +``` +We notice, that it's just a dictionary, where the value can be either a `String`, `Int`, or a location. The location object itself would be a dictionary with `String`s and keys and coordinates as `Double`s. We can use the `typealias` keyword to call this a `LocationJSON` type. Together it looks like `typealias LocationJSON = [String:Double]`. This typically goes at the top of the file. +Going back to the JSON, we can look back an see that the value can be a `String`, `Int`, or `LocationJSON`. We can use the special `AnyObject` type to describe this type. Since typing out `[String:AnyObject]` again and again would be tedious, we can just call that the `CacheJSONFormat` with the `typealias` keyword. Now looking at the entire JSON file, we have the following: +``` + { + "Cache name": { + name: "Cache name", + description: "Cache description", + difficulty: 8, + location: { + latitude: 12.123, + longitude: 43.123 + } + } + } +``` +Which is just a dictionary with a string as the key and the `CacheJSONFormat` we just defined as the value. We can use a type alias and call this `CacheListJSONFormat`. +In the end, we will have something that looks like this: + +```swift +typealias CacheListJSONFormat = [String:CacheJSONFormat] +typealias CacheJSONFormat = [String:AnyObject] + +typealias LocationJSON = [String:Double] +``` + +This will allow us to manage types more neatly. + +Going back to our initializer, we will take in JSON of type `CacheJSONFormat`. + + ```swift + // We add the question mark to this initializer, because it could fail if the input format is incorrect + init?(json: CacheJSONFormat) { + // code to parse the json goes here + } + ``` + + Using the `if let` command in swift, we can make sure that the format is correct. If any condition fails, we'll set some default initial values and return nil. This is some sample code that can be used as the body of the initializer. +```swift + // This large if statement makes sure everything is of proper format + if let name = jsonObject["name"] as? TypeOfNameValue, + let desc = jsonObject["description"] as? String, + let diff = jsonObject["difficulty"] as? Int, + let location = jsonObject["location"] as? TypeOfLocationValue, + let long = location["longitude"], + let lat = location["latitude"] { + + self.name = name + self.description = desc + self.difficulty = diff + self.location = CLLocationCoordinate2D(latitude: lat, longitude: long) + } else { // This code runs if the JSON input format is bad + print("JSON FORMAT IS INVALID") + return nil + } +``` + +[Previous](7-DetailView.md) - [Next](9-AnnotationObject.md) diff --git a/mobile/ios/9-AnnotationObject.md b/mobile/ios/9-AnnotationObject.md new file mode 100644 index 0000000..bce6e37 --- /dev/null +++ b/mobile/ios/9-AnnotationObject.md @@ -0,0 +1,51 @@ +# The Annotation +[Home](README.md) +[Previous](8-DataManager.md) - [Next](10-Map.md) +- Create another "CocoaTouchClass" file, we're going to create a class to represent an Annotation object on the map +- For this annotation object, we want it to conform to the "MKAnnotation" protocol. So right beside `NSObject` add `, MKAnnotation` to show this +- Since we say we're implementing the `MKAnnotation` protocol, if you command-click on the protocol you can see that we need to have three properties we need to have: + + ```swift + var coordinate: CLLocationCoordinate2D + var title: String? + var subtitle: String? + ``` +- We're also going to add our own custom property, a `Cache` object that the Annotation object is presenting. +- When we set the cache object of this class, we also want to set the `coordinate`, `title` and `subtitle` to match the cache. Swift let's us do this with the following code: + + ```swift + var cache: Cache { + didSet { + self.titleProperty = cache.name + self.subtitleProperty = cache.description + self.placeProperty = cache.location + } + } + ``` +- Let's create the initializer. The initializer should take in a `Cache` object and set it to the local `cache` property. Note, since we're in the initializer, the `didSet` code will not be run in this special case. + + ```swift + init(cache: Cache) { + self.title = cache.name + self.subtitle = cache.description + self.coordinate = cache.location + self.cache = cache + } + ``` + +- The last piece of this class is to return an actual view that we can display on our map. This will be a class function, very similar to the function where we returned a cell. It'll start off like this. + +```swift +static func createViewAnnotationForMapView(mapView: MKMapView, annotation: MKAnnotation) -> MKAnnotationView { + var returnedAnnotationView: MKAnnotationView + + // populate returnedAnnotationView + + return returnedAnnotationView +} +``` +- Similarly to when we made a cell in a table, we will have a "reusable identifier", defined the same was as we did in the "MenuViewController". +- Then use an `if let` to see if `mapView`'s `dequeueReusableAnnotationViewWithIdentifer` method returned `nil` or an `MKAnnotationView`. If it does return a `MKAnnotationView`, then assign it to the variable we just declared (`returnedAnnotationView`), and set its annotation property to the `annotation` we passed into the function. +- If it returns `nil`, then `returnedAnnotationView` should be set to an instance of `MKAnnotationView`. When we create an instance, we need to make sure we pass in the annotation as well as the reuseIdentifier. On this view, we also want to set the `canShowCallout` to be true. For more information about this property, we can option-click on `canShowCallout` for more information. + +[Previous](8-DataManager.md) - [Next](10-Map.md) diff --git a/mobile/ios/README.md b/mobile/ios/README.md new file mode 100644 index 0000000..1466119 --- /dev/null +++ b/mobile/ios/README.md @@ -0,0 +1,31 @@ +# Mobile/iOS +Learn the fundamentals of programming, the Swift programming language adn build a geocaching iOS app! + +## What you'll make: Scavenger +- A geocaching mobile application + + + + +## Setup +- Setup Xcode + - [Download](https://itunes.apple.com/us/app/xcode/id497799835?ls=1&mt=12) + - USBs (Have a few the day of, with varying DMGs of Xcode supporting back to Lion) + +### I. Intro +1. [Swift 101](1-Swift.md) +2. [Xcode Tour](2-XcodeIntro.md) +3. [Git in Xcode](3-GitTutorial.md) +4. [Hello iOS](4-iOSIntro.md) + +### II. Scavenger +5. [Cache Object](5-CacheObject.md) +6. [Found Caches List](6-FoundItems.md) +7. [Cache Detail Page](7-DetailView.md) +8. [Reading in data](8-DataManager.md) +9. [Annotation Object](9-AnnotationObject.md) +10. [Map](10-Map.md) +11. [Tab Bar](11-TabBar.md) + +### III. Resources + - [Swift cheat sheet](http://swiftmonthly.com/wp-content/themes/swiftmonthly_theme/files/5b1356909f5eba2998766bbda077293c040416034729.pdf) diff --git a/mobile/ios/archive/Scavenger.md b/mobile/ios/archive/Scavenger.md new file mode 100644 index 0000000..d23e2a4 --- /dev/null +++ b/mobile/ios/archive/Scavenger.md @@ -0,0 +1,21 @@ +#Scavenger + +- [Getting Started](STARTING_SCAVENGER.md) +- [Hello world!](HELLO_WORLD.md) + +- [Main menu](menu.md) +- [Models](models.md) +- [Map](map.md) +- [Data Model Manager](data_model_manager.md) +- [Found List](foundList.md) +- [Neaby List](nearbyList.md) +- [Networking](networking.md) + +## Extensions +- [Improve UI]() +- [Settings]() + +## Other +- [Git in Xcode]() +- [Xcode debugging tips]() +- [Vocabulary](Vocabulary.md) diff --git a/mobile/ios/archive/Vocabulary.md b/mobile/ios/archive/Vocabulary.md new file mode 100644 index 0000000..4626591 --- /dev/null +++ b/mobile/ios/archive/Vocabulary.md @@ -0,0 +1,5 @@ +# Definitions! +## Vocabulary +- **views**: a specific element that is shown on the screen (button, table, box, etc...) +- **controllers**: a class that controls and gives life to the views +- ... \ No newline at end of file diff --git a/mobile/ios/archive/Xcode_Tips.md b/mobile/ios/archive/Xcode_Tips.md new file mode 100644 index 0000000..fa525c1 --- /dev/null +++ b/mobile/ios/archive/Xcode_Tips.md @@ -0,0 +1 @@ +# Xcode tips and tricks diff --git a/mobile/ios/archive/extensions.md b/mobile/ios/archive/extensions.md new file mode 100644 index 0000000..d967b96 --- /dev/null +++ b/mobile/ios/archive/extensions.md @@ -0,0 +1 @@ +#Extensions diff --git a/mobile/ios/archive/playground.md b/mobile/ios/archive/playground.md new file mode 100644 index 0000000..ce2848b --- /dev/null +++ b/mobile/ios/archive/playground.md @@ -0,0 +1 @@ +#Playground diff --git a/mobile/ios/assets/ScavengerDemo.gif b/mobile/ios/assets/ScavengerDemo.gif new file mode 100644 index 0000000..b4baa4c Binary files /dev/null and b/mobile/ios/assets/ScavengerDemo.gif differ diff --git a/mobile/ios/menu.md b/mobile/ios/menu.md new file mode 100644 index 0000000..a923bd5 --- /dev/null +++ b/mobile/ios/menu.md @@ -0,0 +1,76 @@ +### Main Menu +[Home](Scavenger.md) + +We're well on our way to making our geocaching app! The first thing that we're going to do is make the screen below: +**INSERT PIC OF MENU HERE** + +Before we start coding, let's see what's going on here. What we have is a [view]() called a [UITableVIew](), which is just a group of cells. You see this in many iOS applications. +1. Add a table view + - We're going to follow the same steps as we did to add a label. Not we want to create a `private`, *constant* item named "tableView", which is an [instance]() of `UITableView`. Give it a try! + - In `viewDidLoad`, we need to set a few properties of the `tableView`. Start off by setting the `rowHeight` and `frame` of the `tableView`, just like you did for the `helloLabel`. I set my row height to 100. This is measured in [points]() + - Now let's remember what a [view]() does. All it knows to do is display information and if someone is trying to interact with it. But it doesn't know **what** to do if someone intereacts with it. That's where the view controller comes it. The [view]() has a [`delegate`]() property. The [view]() then forwards its actions to let the delegate deal with it. + - The `MenuViewController` will act as the `tableView`'s [delegate](). To specify this, set the `tableView`'s `delegate` property to `self`. `self` is referencing the `MenuViewController`. + - The `tableView` also needs a way to know what the data to put in the table. It does this similarly to the `delegate`. It does it with a `dataSource` property. Go ahead and also set its `dataSource` property to `self`. + - The last thing we need to specify is what type of cells the table is going to use. For this app we're going to use the default cell, but we still need to tell the `tableView` that we want to use the default cell. So we're going to add the line `tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "menuCellIdentifier")`. This lets the `tableView` do some cool optimizations for large tables, which you can read about [here]() if you're interested. + - Now if you try running the app, you'll see some errors. This is because we told the `tableView` that we can be its `delegate` and `dataSource`, but it doesn't believe us. The only way for the `tableView` to know if we can actually live up to that claim is if we declare that we implement the `UITableViewDelegate` and `UITableViewDataSource` [protocols](). + - A [protocol]() is simply a list of methods that we need to implement. It can have required methods, which we *must* implement, and optional ones too. + - To keep the code clean, we're going to implement the delegate and the data source in extensions of the class. + - **Delegate methods** + - For the delegate, make an extension that delcares it implements the `UITableViewDelegate` as such: + ```swift + // this line means that MenuViewController says it implements the UITableViewDelegate protocol + extension MenuViewController: UITableViewDelegate { + // this function is called when a use taps on a cell + func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + // TODO: We will implement this function later + } + } + ``` + - **Data Source methods** + - Make another extension to `MenuViewController`, this time declaring that you implement `UITableViewDataSource` + ```swift + // Since we only have one section, we can just return how many rows we want + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 // TODO: return the number of menu items. Display 1 for now so we can see the table view + } + + // This function returns the cell we want to go at a certain row + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + // Remember when we registered this identifier for the tableView. This is where it comes in. + // Make sure the two idenfiers are the same. In this case, "menuCellIdentifier" + let cell = tableView.dequeueReusableCellWithIdentifier("menuCellIdentifier", forIndexPath: indexPath) + + cell.backgroundColor = UIColor.lightGrayColor() // set the colour to light grey for now + + return cell + } + ``` +2. Manage the menu items. + - We're going to start off with 4 menu items: Map, Found Locations, Nearest Locations, and Settings + - To model this, we're going to define an [array]() of menu items. + - Make an array with the names of the menu items as a property of the view controller, just like the `tableView`. + - Now let's go back to the `func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int` + - We want to return how many menu items we have, so we can return the length of the array we defined + + - Next we want to set the content of each cell to display the name of the menu item. + - Let's take a look at `func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)`: + - This method gets called for every cell that is visible, with a different `indexPath` + - We can know which row it is being called for using the `indexPath.row` property. That will be set to 0 for the first row, 1 for the second and so on + - The cell has a textLabel property. You can set the text of this textLabel with `cell.textLabel?.text = "Your text here"` + - Use the `menus` array, and what you know about arrays to set the title of the cell to the name in `menus` + - Also, the cell has a property called the `accessoryType`. Try setting it to `UITableViewCellAccessoryType.DisclosureIndicator` and see what happens. + + - Next, let's make the cells do something when we tap them + - Let's go back to `func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)`. + - This returns the `indexPath` the same way as in the `cellForRowAtIndexPath` method. + - Right now, if you select a row it gets grayed out and stays grayed out. We can change that by animating a deselection when it's selected using: `tableView.deselectRowAtIndexPath(indexPath, animated: true)` + - What we want is to show a new [view controller]() whenver a cell is pressed. So, let's create those first. Create 4 new files called: + - `MapViewController` + - `FoundCachesListViewController` + - `ClosestCachesViewController` + - `SettingsViewController` + - We can create an array with an instance of all of them like this: `[MapViewController(), FoundCachesListViewController(), ... ]` (fill in the `...` with the other two controllers). This goes right under where we created the array with the menu strings. + - Then using the `indexPath` select the right view controller and put it into a constant. Let's call it `VC`. + - To display that view controller, we can display it like this: `self.navigationController?.pushViewController(VC, animated: true)` + +[Next ->](models.md)