Swift Tutorial - sử dụng JSON trong ios
Bài đăng này đã không được cập nhật trong 3 năm
Với bài viết này,sẽ dùng demo iTunes Search API cho iTunes Store được public bởi Apple, sử dụng gói JSON kết quả, phân tích chúng, tạo Dictionary và đưa những thông tin đó vào Table View. Sau đó, chúng
Tạo và Kết nối đến UI (Giao diện người dùng)
Việc đầu tiê cần làm là tạo một tableView. tiếp theo thêm đoạn code sau vào trong file ViewController.swift đặt nó bên dưới
@IBOutlet var appsTableView : UITableView?
đoạn code trên cho phép một kết nối đến Table View nằm trong Storyboard với một biến appsTableView. Hãy lưu file này lại và mở Storyboard lên. Chọn đối tượng View Controller
Tiếp theo thêm một biến cho TableView. Bằng cách thêm phía dưới ViewController, sử dụng đoạn code sau:
var tableData = []
Tạo API request
Bây giờ đã có UI được kết nối, tiếp theo tạo một hàm mới searchItunesFor(searchTem:String).Sử dụng hàm này khi một có một request tìm kiếm xuất hiện. Sử dụng đoạn code như dưới đây
func searchItunesFor(searchTerm: String) {
// The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
// Now escape anything else that isn't URL-friendly
if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
let urlPath = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
let url: NSURL = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
let results: NSArray = jsonResult["results"] as NSArray
dispatch_async(dispatch_get_main_queue(), {
self.tableData = results
self.appsTableView!.reloadData()
})
})
task.resume()
}
}
Tiếp theo cần phải chỉnh sửa lại các thuật ngữ tìm kiếm trong API search tương tự dưới dạng sau: “Một+Hai+Ba...” thay vì với dạng “Một%Hai%Ba...”. Vậy để thay thế cần sử dụng một phương pháp có tên là NSString. Nó sẽ gọi stringByReplacingOccurencesOfString và trả về thay đổi của từ khóa tìm kiếm đầu vào với toàn bộ khoảng trắng(space) được thay thế bằng dấu cộng (+)
Tiếp theo cần phải loại bỏ những ký tự mà không được phép nằm trong một đường dẫn (URL)
Hai dòng tiếp theo định nghĩa đối tượng NSURL để có thể được sử dụng như là một Request URL cho iOS Networking API
Đây là hai dòng này rất quan trọng:
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
Dòng đầu tiên lấy đối tượng NSURLSession mặc định được sử dụng trong toàn bộ networking calls.
Sau đó dòng thứ hai sẽ khởi tạo kết nối sẽ được sử dụng để gửi request.
dataTaskWithURL chỉ được chạy khi hoàn thành các yêu cầu trước đó. Tại đây tiếp tục kiểm tra lỗi xảy ra với các response tiếp đó parse gói JSON và gọi phương thức delegate didReceiveAPIResults.
Cuối cùng, task.resume() sẽ bắt đầu request
Bởi vì nhiệm vụ này xảy ra trong background chúng ta cần phải vào foreground trước khi UI được cập nhật Vì vậy cần phải sử dụng dispatch_async để di chuyển trở lại vào các thread chính, và load lại table view.
Tạo lời gọi API(API CALL)
Khi đã đã có một method cái bắt đầu việc tìm kiếm iTunes tiếp tục thêm đoạn code này vào cuối cùng của phương thức viewDidLoad
searchItunesFor("JQ Software")
Nó sẽ kìm kiếm tất cả những sản phẩm có chứa những từ khoá đã nhập vào trên iTunes store.
Nhận kết quả trả về
Đoạn code dùng để request tìm kiếm đã hoàn thành và tất cả những dữ liệu đã được trả về. didReceiveAPIResults được gọi để sử dụng dữ liệu đó trong app .
Một method ở đây sử dụng NSJSONSerialization để chuyển đổi dữ liệu thô sang những thông tin hữu ích và được tạo chỉ mục từ iTunes. Bây giờ có thể đặt tableData riêng self.tableData object như là một kết quả của quá trình tìm kiếm. Và refresh lại appsTableView để hiển thị kết quả.
Cập nhật Table View UI cập nhật bằng cách sử dụng dữ liệu mà chúng đã lấy về từ web.
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell")
let rowData: NSDictionary = self.tableData[indexPath.row] as NSDictionary
cell.textLabel.text = rowData["trackName"] as String
// Grab the artworkUrl60 key to get an image URL for the app's thumbnail
let urlString: NSString = rowData["artworkUrl60"] as NSString
let imgURL: NSURL = NSURL(string: urlString)
// Download an NSData representation of the image at the URL
let imgData: NSData = NSData(contentsOfURL: imgURL)
cell.imageView.image = UIImage(data: imgData)
// Get the formatted price string for display in the subtitle
let formattedPrice: NSString = rowData["formattedPrice"] as NSString
cell.detailTextLabel.text = formattedPrice
return cell
}
numberOfRowsInSection trả về số lượng các kết quả của object từ tableData, nó đã thiết lập trước trong method connectionDidFinishLoading. Trong trường hợp này thì cellForRowAtIndexPath sẽ trả lại số lượng cell để lấy ra các thông tin thông tin là tên ca khúc, url của ca khúc và giá cả.
Chia code
Đầu tiên đổi tên View Controller với một cái tên có ý nghĩa và mở file ViewController.swift và thay tất cả những object liên quan đến ViewController với cái tên mới vừa thay đổi và tiếp tục đổi tên mới trong file SearchResultsViewController.swift
Nếu như sau đó chạy app ngay thì kết quả bạn sẽ gặp là một lỗi crash. bởi vì storyboard chưa được cập nhật để biết về những tên class mới. Vậy nên tiếp mở Main.storyboard và chọn View Controller object hình dưới đây (Bên trái) và lựa chọn Identity Inspector (bên phải, nút thứ 3)
Từ đây thay tên class từ “View Controller” và “SearchResultViewController”. chạy lại và Kiểm tra ứng dụng sẽ chạy bình thường và tục các bước tiếp theo.
Tiếp theo di chuyển code API ra lớp riêng của nó. Click chuột phải vào Xcode navigation pane và chọn New File... Việc này sẽ khởi tạo một file Swift mới (bên dưới iOS → Source Navigation option)
Điều này xử lý và nắm tất cả công viecj của API, và sẽ gọi là APIController. Tiếp theo lấy phương thức searchItunesFor() rồi cut chúng ra khỏi Search Controller và paster chúng vào APIController bên trong một lớp mới của APIController.
File APIController hoàn chỉnh sẽ giống như sau:
import Foundation
class APIController {
func searchItunesFor(searchTerm: String) {
// The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
// Now escape anything else that isn't URL-friendly
if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
let urlPath = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
let url: NSURL = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
let results: NSArray = jsonResult["results"] as NSArray
dispatch_async(dispatch_get_main_queue(), {
self.tableData = results
self.appsTableView!.reloadData()
})
})
task.resume()
}
}
}
Nếu như build chương trình ngay thì sẽ thấy ba lỗi như dứoi đây:
1) searchItunesFor() undefined trong SearchResultsViewController.
2) self.tableData undefined trong APIController.
3) self.appsTableView undefined trong APIController.
Để khắc phục với điều này cần để cho các đối tượng này reference với nhau bằng cách khởi tạo APIController một object con của SearchResultsViewController.
var api = APIController()
Tiếp theo thay đổi dòng code để gọi hàm “calls searchItunesFor()” thành
api.searchItunesFor("Angry Birds")
để cho các APIController hiểu SearchResultsViewController này là các delegate. tiếp theo thiết lập các delegate
api.delegate = self
Việc này sẽ giải quyết lỗi đầu tiên, nhưng bây giờ cần phải có được kết quả của APIController trả về cho SearchResultsViewController. Để Controller API có giải quyết với gọi API, tiếp theo định nghĩa một giao thức(protocol) để Views có thể đăng ký để nhận được kết quả!
Đinh nghĩa API protocol
dispatch_async(dispatch_get_main_queue(), { // DELETE ME
self.tableData = results // DELETE ME
self.appsTableView!.reloadData() // DELETE ME
})
``` // DELETE ME
Bên trên class khởi tạo APIController thêm một giao thức để thiết lập hàm didReceiveAPIResults() như một yêu cầu cần thực hiện.
```Swift
protocol APIControllerProtocol {
func didReceiveAPIResults(results: NSDictionary)
}
Tuân thủ protocol
Bây giờ hãy thay đổi SearchResultsViewController tuân theo các protocol:
class SearchResultsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, APIControllerProtocol {```Swift
Việc buil app lúc này sẽ cho ra một lỗi mà SearchResultsViewController không phù hợp với các APIControllerProtocol. Đúng vậy, đó là những gì mà một giao thưc đã làm. Nó làm cho các trình biên dịch để đưa ra lỗi nếu có ý định thực hiện một giao thức, nhưng không hoàn toàn làm điều đó. Trong trường hợp này barfing đang thiếu các didReceiveAPIResults().
Để thêm, chỉ cần add function sau vào trong lớp SearchResultsViewController. Nó thực sự là giống với nội dung của dataTaskWithURL.
func didReceiveAPIResults(results: NSDictionary) {
var resultsArr: NSArray = results["results"] as NSArray
dispatch_async(dispatch_get_main_queue(), {
self.tableData = resultsArr
self.appsTableView!.reloadData()
})
}
Sử dụng Protocol Ở file APIController.swift, tiếp tục thêm các delegate và xây dựng một contructor
var delegate: APIControllerProtocol?
init() {
}
dấu chấm hỏi(?) ở trên là vì delegate là một giá trị tùy chọn(có thể có giá trị hoặc không). Nếu không có dấu hỏi, thì sẽ nhận được một lỗi biên dịch vì không một giá trị ban đầu cho biến này. Các đối tượng delegate có thể được của bất kỳ class nào ở đây, miễn là nó tuân thủ các APIControllerProtocol bằng cách xác định method didReceiveAPIResults() như đã làm trong SearchResultsViewController.
Bây giờ có biến delegate được add thêm, và trở trở lại SearchResultsViewController và trong method viewDidLoa để thiết lập các delegate cho api controller chính nó, nó sẽ nhận lời gọi delegate function
self.api.delegate = self
Cuối cùng, trong việc kết thúc dataTaskWithURL bên trong của method searchItunesFor () , tiếp tục thêm một giao thức nơi tableview khi load lại.việc tải lại giao diện người dùng tới các delegate, trong trường hợp này là SearchResultsViewController. “let results: NSArray = jsonResult["results"] as NSArray”.
self.delegate?.didReceiveAPIResults(jsonResult)
Dưới đây là toàn bộ method
func searchItunesFor(searchTerm: String) {
// The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
// Now escape anything else that isn't URL-friendly
if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
let urlPath = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
let url: NSURL = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
let results: NSArray = jsonResult["results"] as NSArray
self.delegate?.didReceiveAPIResults(jsonResult)
})
task.resume()
}
}
All rights reserved