Xcode
1. easy to test for multiple devices
- There is no need to install many device drivers, all are in Xcode.
- Without test run, you can see the layouts for many different devices by selecting the device below storyboard window.
2. MVC, Storyboard, ViewController.swift, connect, no id for each ui element
- Apple adopts MVC application architure.
- M is data, in this case, it is pretty separated.
- The common model type for json data is [Stirng, AnyObject].
- There is no need a swift class for a model.
- Storyboard is the drawingboard for ios app in xml
- Unlike working in Android Studio,you don't edit the xml file.
- Using the assistant edior to have both your storyboard file and your controller file open,
you can connect a ui element between V and VC.
- When you try to access a ui element, there is no need to get it by its id.
3. Indentity Inspector
- First,have MVC in mind.
- Template Single View Application provides its VC code for you.
- lab as below:
- new project, using Single View Controller template
- add a VC into the storyboard, add a label as "Second VC"
- in storyboard, move the entrance arrow to the new
- test to see the lab skeleton works.
- in menu file new, select view, create secondVC.swift
- in method, viewDidLoad, add code, print("from the secondVC")
- Back to the storyboard, select the secondVC.swift
- then select class with secondVC.swift
- test again to see the print message in output window near the bottom of the screen.
4. Connection Inspector for Code Delegation
- Use a lab for demo, create a app, add TableView ui element in the storyboard.
- open the connection inspector, in section Outlets,
- you'll see two items - dataSource and delegates.
- For each, click the plus icon and drag it into the vc icon in the storyboard.
- Open ViewController.swift, modify the class declaration as below:
class ViewController: UIViewController,
UITableViewDataSource,
UITableViewDelegate
- Both are protocal.
- It is a way to inherit code from different contexts. A protocal difines its contracts.
- Both ask the VC as delegate.
- DataSource is for presenting the data item in a cell.
- Delegate is about the behavior, like clicking a table row
- You can find the protocal method names in Google, like TableView, protocal, datasource...
5. Segue(Action Segue) on the storyboard rendering, Attribute Inspector for Segue
- step 1: setup the skeleton app
- create a new app, uing Single Page tmeplate, add a label , "page 1", and a button "next"
- add a ViewController, add a label, "page 2"
- With folder app selected, click menu new, select cocaoa touch class, give a name for the subclass of ViewController.
- Then, use the Indetify Inspector to bind.
- ctrl-l Drag the button from the first VC to the 2nd, select action seque to show detail
- test to see from the 1st screen to the 2nd screen.
- The following are the review
- The storyboard has two kinds of items - view controller, and segue.
- Segue is for screen navigation.
- There might be many navigation, an identifier for a segue is required.
- step 2: see the action segue in action with passing data
- The following futhers the senario of the navigation
- An UIwindow object is behind the scene, orchestras the navigation.
- To begin with, two VC ojbects are created. The first one is loaded by default.
- The second VC is not loaded.
- In order to accessing the data in the 2nd. An instance varialbe(not ui element) is needed.
- In the code of the 2nd VC, add one instance variable
var data: String = ""
- Also, in method viewDidLoad of the 2nd, add one print statement
print("the passing data is \(data)")
// You can use ui elements to render also, not the focus here.
- In the 1st VC swift file, add method prepare as below:
- test to see the data from print method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondView = segue.destination as! SecondViewController
if (segue.identifier == "id1"){
secondView.data = " data from screen 1 "
}
}
6. Relation Segue, created from a helper - Navigation Controller
- Navigation controller is a development helper.
- continue from the previous lab(# 5).
- select the 1st VC, then menu editor | embeded in | click navigation controller.
- From the storyboard and document outline window, you see the following:
- step 1: adding navigation controller
- relation segue between the navigation controller and the 1st VC.
- The action segue stays the same between the 1st VC and the 2nd VC.
- The height for the topn title becomes larger.
- When you click button next, it goes to the 2nd as before.
- On the top of the second screen, you can see a back button. You get it for free.
- click it, back to the 1st.
- The back button is on a navigation bar, which stays on the top for all screens.
- step 2.1: adding tile on 1st Screen
- On the 1st screen, double click around the center area of the navigation bar,
- enter the screen name, FIRST
- test, you see the title FIRST on the 1st scrren
- When you navigate to the 2nd, you see the the FIRST is for return.
- step 2.2: adding a title on 2st Screen
- Select the 2st VC, open the attribute inspection, in section view controller, set the title to SECOND.
- test to see the tile SECOND on the 2nd screen.
7. Tabbed Template
- Tabbed template is self-explained.
- The template creates a Tab Bar Controller, and two view controllers.
- The relation segues are used for navigation.
- The sencario is similar to the one in the previous section.
- At the bottom of the tab bar controller is the tab bar.
- There are two items on it, First and Second.
- At the bottom of the First veiw controller is the tab bar, only the item First shows up...
8. Master-Detail Template
- The following shows its overview:
REST iOS Client
setup the application
- create a new app in Xcode.
- in the storyboard, add three buttons, get, post, delete from the object windows
- open the assistant window, you see the code
- ctrl-drag the buttons from the storyboard to code, creating the handlers.
overview
- step 1.1
create a String variable for the rest service,
then create a URL object,
the type of URL is structure,
Like class, swift use its init method to instantiate an object.
let obj = URL(string: restAddress)
note: string is a property in URL structure
- step 1.2
create a http request object,
for get, using class URLRequest,
for post, delete, using class NSMutableURLRequest,
note-1: the default http method is get
note-2: Becasue of the need to change the method, a mutable class is required.
note-3: NS stands for next step, a legacy Apple naming style.
- step 1.3 for post and delete
set the method of the http request to post or delete
- step 2
Create a objects for Session
- step 3
declare a task object as below:
let task = session.dataTask(with: todosUrlRequest as URLRequest, completionHandler: {
(data, response, error) in ... })
note 1: Casting to URLRequest is needed for compiling in post and delete
with current ios version based on my experience.(5/6/2017)
note 2: The coding inside the closure is pretty sequential.
note 3: You will not see some low level stuffs like thread, security, delegation.
note 4: dataTask method has two parameters
--------These two parameters requires parameter names.
--------the first parameter name is with
--------the first parameter content is the request object name
--------the second name is completionHander
--------the second content is a closure
note 5: the closure has two parts
--------part 1, the closure has 3 parameters, which do not require name.
--------part 2, the closure body after in.This is the place to handle the data, error..., from the rest service sequentially.
--------This is one example of closure usages.
--------There are many different syntax shortcuts for a closure.
--------Because of passing closure in a function, rest tasks can be handled in function level in swift.
- step 4
task.resume()
Execute the task for download in the network thread.
- step 5 for method get
1. After the json data is returned from the rest service,
2. Convert the download byte streams into swift object.
3. The collection type is [Sting, AnyObject]. It fits json array.
4. No model class is needed.
5. Then, copy the closure data out for public access.
6. For the second parameter for the conversion method, leave the option empty.
7. Property description for [String: anyObject] is default. It will output all the array contents in text.
- step 6 for method post
1. create one to-do in [String, AnyObject] in Swift array.
2. convert it into the format for upload.
3. add the converted one into the http request's property httpbody.
4. ... upload ....
5. After the json data is returned from the rest service, the same as get.
- step 7 for method delete
The url, http://.... /todos/1
and, the http method, DELETE will take care of the business.
Related swift language features
- 1. guard to handle boolean results
In the three button click handlers, there are many guard statements.
For an assignment statment, if the assingment result is true, do nothing.
Otherwise, using return to exit the handler function.
- 2. do-catch swift language construct for any runtime exceptions.
In other language, try-catch construct is used.
The syntax is a bit terse.
code snippets
class ViewController: UIViewController {
var myData = "hello"
@IBAction func getClick(_ sender: AnyObject) {
print("GET, 5517 11:10 ");
// First, set up the URL request
let todoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
// The guard statement lets us check that the URL we’ve provided is valid.
guard let urlObj = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: urlObj)
print("location before call ");
// Then we need an NSURLSession to use to send the request:
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
//let task = session.dataTaskWithRequest(urlRequest, completionHandler:{ _, _, _ in });
let task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
// do stuff with response, data & error here
// check for any error
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
print("after call, check ok")
do {
print("entering do")
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: AnyObject] else {
print("error trying to convert data to JSON")
return
}
print("The todo is: " + todo.description)
// the todo object is a dictionary
guard let todoTitle = todo["title"] as? String else {
print("Could not get todo title from JSON")
return
}
print("The title is: " + todoTitle)
self.myData = todoTitle
} catch {
print("error trying to convert data to JSON")
return
}
}) // end of dataTask
print("location after dataTask ");
task.resume();
}
@IBAction func postClick(_ sender: AnyObject) {
print("POST test p2");
let todosEndpoint: String = "http://jsonplaceholder.typicode.com/todos"
guard let todosURL = URL(string: todosEndpoint) else {
print("Error: cannot create URL")
return
}
let todosUrlRequest = NSMutableURLRequest(url: todosURL)
todosUrlRequest.httpMethod = "POST"
let newTodo = ["title": "Frist todo", "completed": false, "userId": 1] as [String : Any]
let jsonTodo: Data
do {
jsonTodo = try JSONSerialization.data(withJSONObject: newTodo, options: [])
todosUrlRequest.httpBody = jsonTodo
} catch {
print("Error: cannot create JSON from todo")
return
}
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
//let task = session.dataTaskWithRequest(todosUrlRequest, completionHandler:{ _, _, _ in })
let task = session.dataTask(with: todosUrlRequest as URLRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling POST on /todos/1")
//print(error)
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let receivedTodo =
try JSONSerialization.jsonObject(with: responseData, options: [])
as? [String: AnyObject]
else {
print("Could not get JSON from responseData as dictionary")
return
}
print("The todo is: " + receivedTodo.description)
guard let todoID = receivedTodo["id"] as? Int
else {
print("Could not get todoID as int from JSON")
return
}
print("The ID is: \(todoID)")
} catch {
print("error parsing response from POST on /todos")
return
}
})
task.resume()
print("after resume")
}
@IBAction func deleteClick(_ sender: AnyObject) {
print("DELETE test 2")
let firstTodoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
let firstTodoUrlRequest = NSMutableURLRequest(url: URL(string: firstTodoEndpoint)!)
firstTodoUrlRequest.httpMethod = "DELETE"
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: firstTodoUrlRequest as URLRequest, completionHandler: {
(data, response, error) in
guard let _ = data else {
print("error calling DELETE on /todos/1")
return
}
})
task.resume()
print("after return from DELETE")
}
@IBAction func test3(_ sender: Any) {
print(self.myData)
}