#5 JSON parsing in Swift

Xử lý JSON response với Codable Protocol trong Swift 4

JSON nói chung, chúng ta sử dụng để gửi và nhận dữ liệu từ hệ thống web. Chúng ta có thể sử dụng dễ dàng trong ứng dụng Swift của chúng ta. Hầu hết các ứng dụng hoàn toàn dựa vào JSON. Nếu ứng dụng của bạn đang giao tiếp với web service API sau đó nó sẽ gửi về  Dictionary(Object)ArrayStringBoolNumber. Chúng ta cần ghi nhớ trước khi tạo mới model class kiểu biến từ web service API trả về là gì. Dựa vào response đó chúng ta sẽ tạo Model class. Trong chủ đề này, sẽ bao gồm hầu hết các phần phân tích liên quan từ cơ bản đến cấp độ nâng cao.

Ban đầu, đó là một nhiệm vụ khó khăn đối với tôi khi hiểu phân tích cú pháp JSON. Sau đó, tôi nhận ra nó không là gì đối với tôi. Nó rất dễ sử dụng. Chỉ cần hiểu loại trả lời. Đó là điều chính. 🙂

 

Bắt đầu với Network Request, URLSession

Trước khi bắt đầu code chúng ta phải kiểm tra API trên browser.

1__-LSxa-trIRQHwae6UXV4g.png

 

Bây giờ chúng tôi đã nhận được response trong trình duyệt web, hãy viết code trong Xcode tạo dự án mới cho phân tích cú pháp JSON.

  • Thêm plist file trong project của bạn, open it as source

1_Zrvd_NLro2wP0WdTc8i5Wg.png

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>
  • Bây giờ tạo Network request với URLSession cho web API

Chúng ta đang tạo ra request với web API đang xử lý dữ liệu và lỗi chúng sẽ tạo thành response. Tôi đang sử dụng ở đây là JSONSerialization xây dựng Foundation framework nó sẽ chuyển đổi dữ liệu JSON sang nền tảng object. 

Trong phần này, tôi đã sử dụng API thử nghiệm từ JSONPlaceholder.

guard let url = URL(string: ”https://jsonplaceholder.typicode.com/todos") else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
          error == nil else {
          print(error?.localizedDescription ?? "Response Error")
          return }  
    do{ 
        //here dataResponse received from a network request 
        let jsonResponse = try JSONSerialization.jsonObject(with: dataResponse, options: []) print(jsonResponse) //Response result } catch let parsingError { print("Error", parsingError) } } task.resume()
  • Response từ web API in JSON format trông như thế này. Bây giờ chúng ta cần lấy các values từ response. Ở đây response của chúng ta là một JSON array.
[
  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "quis ut nam facilis et officia qui",
    "completed": false
  }
]
  • Bây giờ xử lú với JSON response object
guard let jsonArray = jsonResponse as? [[String: Any]] else {
      return 
}
print(jsonArray)
//Now get title value 
guard let title = jsonArray[0]["title"] as? String else { return } print(title) // delectus aut autem

Chúng ta cố gắng in ra tất cả title keys values.

HIện tại, JSON response của chúng ta là một Array của Dictionary([[String: Any]]). Do vậy chúng ta đang lấy Dictionary từ mọi index của Array với sự trợ giúp của for – loop. Sau khi lấy được giá trị truy cập vào Dictionary từ các keys.

for dic in jsonArray{
    guard let title = dic["title"] as? String else { return }  
    print(title) //Output
}

Bây giờ chúng ta tạo User structure để xử lý dữ liệu. Trong response chúng ta đang lấy có các kiểu khác nhau của values Int, String, Boolean dựa vào các giá trị tôi chọn ở đây model structure của tôi để quản lý response.

struct User {
      var userId: Int
      var id: Int
      var title: String
      var completed: Bool
   init(_ dictionary: [String: Any]) {
      self.userId = dictionary["userId"] as? Int ?? 0
      self.id = dictionary["id"] as? Int ?? 0
      self.title = dictionary["title"] as? String ?? ""
      self.completed = dictionary["completed"] as? Bool ?? false
    }
}

Use Model để xử lý JSON response

var model = [User]() //Initialising Model Array
for dic in jsonArray{
    model.append(User(dic)) // adding now value in Model array
}
print(model[0].userId) // 1211

Bây giờ bạn biết vào thứ về JSON parsing đúng không? 😀

Chúng ta sẽ làm tương tự nhiều hơn Swifty với FlatMap

var model = [User]()
model = jsonArray.flatMap{ (dictionary) in
            return User(dictionary)
        }
print(model[0].userId)
//make more simple
model = jsonArray.flatMap{ return User($0)}
//One more time 
model = jsonArray.flatMap{User($0)}
//Or 
model = jsonArray.flatMap(User.init)
//Output
print(model[0].userId) // 1211

Codable Protocol

Điều này là một protocol được giới thiệu bởi Apple in Swift 4 có thế cung cấp Encodable và Decodable trong chức năng. Nó sẽ make JSON parsing dễ dàng hơn. Nó có thể chuyển đổi chính nó vào và ra khỏi một biểu diễn bên ngoài.

Codable model trông như thế này. Nó khá dễ để hiểu trong ít code chúng ta có thể quản lý được nó.

Đâu là Codable model ví dụ tôi thể hiện cho bạn ở đây. Bạn cần làm struct model của bạn dựa trên JSON response của bạn.

struct User: Codable{
       var userId: Int
       var id: Int
       var title: String
       var completed: Bool
}

JSON parsing với JSONDecoder

Sau khi tạo một network request. Chuyển đổi web application response dữ liệu thô sang Array Model.

Phần còn lại của JSON network request sẽ tương tự như process này. Chúng ta chỉ cần xử lý network response Data cho JSONDecoder. Không có thay đổi lớn cho protocol này.

do {
    //here dataResponse received from a network request
    let decoder = JSONDecoder() let model = try decoder.decode([User].self, from: dataResponse) //Decode JSON Response Data print(model) } catch let parsingError { print("Error", parsingError) }

 

Tại sạo tôi sử dụng model [User].self vì chúng ta nhận được response trong một Array format nếu response của bạn chỉ là Dictionary như:

 {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
 }

Chúng ta cần sử dụng mode class như User.self cho Dictionary response.

do {
    //here dataResponse received from a network request
    let decoder = JSONDecoder() let model = try decoder.decode(User.self, from: dataResponse) //Decode JSON Response Data print(model.userId) //Output - 1221 } catch let parsingError { print("Error", parsingError) }

Custom key names

Chúng ta thay đổi codable của chúng ta với custom string keys nhưng nó phải trùng với JSON response keys của bạn. Nếu không thì bạn sẽ nhận error message.

1_a7CrBGrWlsEJnm1jj4BTzQ.png

Thêm custom keys cho Codable Model:

struct User: Codable{
       var userId: Int
       var id: Int
       var title: String
       var completed: Bool
       //Custom Keys 
       enum CodingKeys: String, CodingKey {
           case userId 
           case id = "serviceId"  //Custom keys
           case title = "titleKey" //Custom keys
           case completed
       }
}

Chúng ta làm nhiều hơn với Swifty . Tôi đã làm ở đây quá trình khởi tạo thông qua extension của model. CodingKeys enum sẽ xác nhận với CodingKey protocol

struct User: Codable {
       var userId: Int
       var id: Int
       var title: String
       var completed: Bool
}
extension User{
   enum CodingKeys: String, CodingKey {
           case userId
           case id
           case title
           case completed
   }
}

Phân tích cú pháp JSON phức tạp với Codable Protocol

{
  "branch": {
    "subject": 5,
    "total_students": 110,
    "total_books": 150
  },
  "Subject": [
    {
      "subject_id": 301,
      "name": "EMT",
      "pratical": false,
      "pratical_days": [
        "Monday",
        "Friday"
      ]
    },
    {
      "subject_id": 302,
      "name": "Network Analysis",
      "pratical": true,
      "pratical_days": [
        "Tuesday",
        "Thursday"
      ]
    }
  ]
}

Struct model cho JSON response. Tôi đã viết model như sau:

struct Students : Codable {
       struct Branch : Codable {
              let subject: Int
              let totalStudents: Int
              let totalBooks: Int
           private enum CodingKeys : String, CodingKey {
              case subject
              case totalStudents = "total_students"
              case totalBooks = "total_books"
           }
       }
     struct Subject : Codable {
             let subject_id: Int
             let name: String
             let pratical: Bool
             let pratical_days: [String]
      }
   let branch: Branch
   let subject: [Subject]
}

Tôi không phải làm gì phức táp trên model. Tôi đã có gắng sử dụng cách đơn giản nhất.

Parsing data từ JSON response

Tôi đã thể hiện ở đây sau khi làm network request tới web API. Chúng ta sử lý network response data thông qua Codable model của chúng ta. Không có khác biết gì lớn ở đây.

do {
   // data we are getting from network request
   let decoder = JSONDecoder()
   let response = try decoder.decode(Students.self, from: data)
   print(response.subject[0].name) //Output - EMT
} 

 

Cuối cùng chúng dã JSON parsing xong. Đây là quá trình đơn giản để xử ký bất kỳ network response data hoặc JSON object data trong code của bạn. Do vậy bạn có thể xử lý UI dựa trên JSON response data. Chúng ta cần map tất cả yêu cầu giá trị từ response theo yêu cầu.

#4 Tự viết một thư viện nhỏ cho chính mình (reorder table view cell)

Hi, các bạn! Hôm nay mình quay trở lại viết một bài chia sẽ về “Tại sao chúng ta nên tự viết thư viện để dùng?”. Cụ thể bài viết của mình là viết một thư viện “reorder table view cell”.

Khi chúng ta lập trình sẽ dùng nhiều thư viện từ bên ngoài vào projects để hỗ trợ chúng ta làm việc nhanh và hiệu quả. Tùy nhiên, nhiều khi chúng ta chỉ biết sử dụng mà không biết cái thư viện đó thực sự được tạo nên như thế nào. Bản thân tôi thường thấy khá ức chế khi sử dụng những thư viện bên ngoài mà không thể tùy chỉnh theo ý mình. Thư viện bạn viết ra có thế không thực sự đáp ứng được hết các trường hợp, về cơ bản sẽ đáp ứng được hoàn cảnh hiện tại của bạn.

Vậy tạo sao chúng ta không tự viết một thư viện cho chính mình?

Hãy bắt đầu từ những thứ đơn giản. Trong quá trình làm việc được tôi nhận được yêu cầu như sau:

  • Tạo một UITableView có thể reorder lại vị trí của các cells.
  • Khi di lên, di xuống thì Table view sẽ scrolling theo.

Chắc các bạn cũng biết có nhiều thư viện hỗ trợ cho việc này nhưng tôi lại thích tự viết hơn vì nó sẽ mang lại những lợi ích sau.

Knowlegement

Sử dụng cách tư duy và kiến thức của bạn để viết nên thư viện bạn mong muốn, Ban đầu tuy sẽ có chút khó khăn khi tạo ra thư viện đó như kiến thức chưa đủ, suy luận chưa chặt chẽ, tư duy không rõ ràng. Từ đó chúng ta sẽ khắc phục dần những khiếm khuyết đó, hiểu rõ được từng dòng code sẽ hoạt động như nào.

Ví dụ nếu bạn chưa đủ kiến thức thì có thể tham khảo từ những thư viện của người khác. Đây cũng là cách để bạn học tập và tích lũy kiến thức. Không chỉ vậy quá trình viết sẽ giúp chúng ta trau dồi những kiến thức đã được học.

Nếu bạn đã từng viết thư viện thì hẳn sẽ hiểu được những lợi ích ở trên.

Customize

Như tôi nói lúc đầu, khi sử dụng thư viện bên ngoài sẽ gây ức chế nếu bạn không tùy chỉnh được nó.

Việc tự viết thư viện thì hoàn toàn khác. Bạn có thể customize cho từng trường hợp cụ thể vì đống code đó là do bạn viết ra mà (nếu không thể thì 99% là do bạn gặp phải những trở ngại ở bên trên)

Và một cảm giác khác mang lại cho bạn đó chính là Niềm vui!

Link github: https://github.com/vantientu1703/DragAndDropTableView

Mong được sự đóng góp của các bạn!

Thank for reading!

 

 

 

 

 

#3 Cấu hình nhiều cells với generics trong Swift

iOS developers dành phần lớn thời gian phát triển của họ để xử lý UITableView và UICollectionView. Khá đơn giản khi bạn cần hiển thị danh sách có cùng loại dữ liệu, ví dụ: danh sách người dùng. Nhưng nếu bạn cần trình bày một loạt các ô khác nhau trong một khung nhìn bảng? Điều đó có thể dẫn đến một mớ hỗn độn thực sự!

Hãy tưởng tượng trường hợp sau đây. Chúng tôi cần hiển thị nguồn cấp dữ liệu với một loại ô khác; nó có thể là UserCell, CommentCell, ImageCell, v.v.

1_NTdkz4wdcV9uop5OsoD_mw.png

Giải pháp hiển nhiên nhất là làm cho các câu lệnh trong phương thức tableView (_: cellForRowAt 🙂 và nó có thể trông như sau.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let item = viewModel.items[indexPath.row]

    if let user = item as? User {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell
        cell.configure(user: user)
        return cell
    } else if let message = item as? String {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as! MessageCell
        cell.configure(message: message)
        return cell
    } else if let imageUrl = item as? URL {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ImageCell", for: indexPath) as! ImageCell
        cell.configure(url: imageUrl)
        return cell
    }

    return UITableViewCell()
}

ViewModel:

class TableViewModel {
    let items: [Any] = [
        User(name: "John Smith", imageName: "user3"),
        "Hi, this is a message text. Tra la la. Tra la la.",
        Bundle.main.url(forResource: "beach@2x", withExtension: "jpg")!,
        User(name: "Jessica Wood", imageName: "user2"),
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
    ]
}

 

Đoạn code này có một vài vấn đề:

  • “If statements”, Nó có thể trở nên rất lớn và khó đọc được nếu như số lượng cell tăng lên.
  • Ép kiểu ( as! )
  • Mỗi kiểu dữ liệu chúng ta chỉ có một cell.
  • Boilerplate code.

Chúng ta hãy làm nó đơn giản hơn, rõ ràng hơn và thanh lịch hơn.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let item = viewModel.items[indexPath.row]

    let cell = tableView.dequeueReusableCell(withIdentifier: type(of: item).reuseId)!
    item.configure(cell: cell)

    return cell
}

 

Ưu điểm:

  • Không if statements.
  • Không typecasting.
  • Không boilerplate code.
  • Bạn không cần phải thay đổi code khi số lượng cells phát triển
  • Sẵn sàng sử dụng lại code

Nhưng làm thế nào để đạt được kết quả này? Đó là khi chương trình chung đi vào hoạt động!

Chúng ta là i<T> với generics

Mã chung cho phép bạn viết các chức năng và loại linh hoạt, có thể tái sử dụng và các loại có thể hoạt động với bất kỳ loại nào, tùy thuộc vào các yêu cầu mà bạn xác định. Bạn có thể viết mã để tránh trùng lặp và thể hiện ý định của nó theo cách rõ ràng, trừu tượng.
Tài liệu Apple

Lập trình generic là một cách tuyệt vời để tránh mã boilerplate và giúp xác định lỗi trong thời gian biên dịch.

Hãy tạo một giao thức đầu tiên mà mỗi ô phải phù hợp.

protocol ConfigurableCell {
    associatedtype DataType
    func configure(data: DataType)
}

//example of UserCell
class UserCell: UITableViewCell, ConfigurableCell {
    @IBOutlet weak var avatarView: UIImageView!
    @IBOutlet weak var userNameLabel: UILabel!

    func configure(data user: User) {
        avatarView.image = UIImage(named: user.imageName)
        userNameLabel.text = user.name
    }
}

Bây giờ chúng ta có thể tạo một bộ cấu hình generic cell để cấu hình các cell trong bảng của chúng ta.

protocol CellConfigurator {
    static var reuseId: String { get }
    func configure(cell: UIView)
}

class TableCellConfigurator<CellType: ConfigurableCell, DataType>: CellConfigurator where CellType.DataType == DataType, CellType: UITableViewCell {
    static var reuseId: String { return String(describing: CellType.self) }
    
    let item: DataType

    init(item: DataType) {
        self.item = item
    }

    func configure(cell: UIView) {
        (cell as! CellType).configure(data: item)
    }
}

CellConfigurator – chúng ta cần giao thức này vì chúng ta có thể giữ các lớp generic trong mảng mà không cần định nghĩa các kiểu dữ liệu.

TableCellConfigurator – lớp chung với các ràng buộc kiểu cho CellType và DataType. Chỉ có thể định cấu hình ô nếu DataType của ConfigurableCell bằng với DataType được sử dụng trong TableCellConfigurator.

Một vài điều chỉnh phải được thực hiện trong ViewModel của chúng tôi.

typealias UserCellConfigurator = TableCellConfigurator<UserCell, User>
typealias MessageCellConfigurator = TableCellConfigurator<MessageCell, String>
typealias ImageCellConfigurator = TableCellConfigurator<ImageCell, URL>

class TableViewModel {
    let items: [CellConfigurator] = [
        UserCellConfigurator(item: User(name: "John Smith", imageName: "user3")),
        MessageCellConfigurator(item: "Hi, this is a message text. Tra la la. Tra la la."),
        ImageCellConfigurator(item: Bundle.main.url(forResource: "beach@2x", withExtension: "jpg")!),
        UserCellConfigurator(item: User(name: "Jessica Wood", imageName: "user2")),
        MessageCellConfigurator(item: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."),
    ]
}

Đó là nó!

Bạn có thể thêm các ô mới một cách dễ dàng mà không cần phải chỉnh sửa mã của ViewController.

Hãy thêm một WarningCell vào chế độ xem bảng của chúng tôi.

  1. Tuân theo giao thức ConfigurableCell.
  2. Thêm TableCellConfigurator cho ô đó trong lớp ViewModel.
class WarningCell: UITableViewCell, ConfigurableCell {
    @IBOutlet weak var messageLabel: UILabel!

    func configure(data message: String) {
        messageLabel.text = message
    }
}

//cell configurator for WarningCell
TableCellConfigurator<WarningCell, String>(item: "This is a serious warning!")

1_VrFzGx-KlAdZLknlHpXnNA.png

Hi vọng cách giải quyết này sẽ giúp ích được trong dự án của bạn.

Các bạn vào đây để tham khảo: Github

Thanks for reading!

#2 Thiết kế chức năng tìm kiếm cho ứng dụng điện thoại

Sau 3 phân tích tính năng, tôi có một tính năng thú vị khác dành cho bạn – Tìm kiếm ứng dụng dành cho thiết bị di động. Tìm kiếm là một tính năng khá phức tạp và bài viết này không bao gồm tất cả mọi thứ có để biết về nó. Trong câu chuyện này, tôi sẽ thảo luận cách chọn giữa hai cách phổ biến nhất để sử dụng tìm kiếm trên ứng dụng của bạn, thanh tìm kiếm trên màn hình đích hoặc tab tìm kiếm trên thanh điều hướng.

Tìm kiếm cho ứng dụng điện thoại

Vì vậy, nhiều ứng dụng mà chúng tôi sử dụng trên cơ sở hàng ngày có tính năng Tìm kiếm. Cách các ứng dụng này thực hiện tìm kiếm có thể rất khác nhau. Nhưng tại sao có nhu cầu triển khai khác nhau của cùng một tính năng? Cái này tốt hơn những cái khác phải không? Hãy cùng tìm hiểu.

1. Search bar on landing screen

1_L8hbI6zINOlZwUoCXvq0YQ.png

Dưới đây là ảnh chụp màn hình của một số ứng dụng phổ biến sử dụng thanh tìm kiếm trên màn hình đích. Thanh tìm kiếm có thể dễ dàng phát hiện được vì hầu hết các lần hiển thị trên đầu màn hình đích.

Trong trường hợp này, Tìm kiếm phục vụ cho những người dùng có ý định rõ ràng sau khi thực hiện tìm kiếm. Mọi đề xuất hoặc giúp nền tảng có thể cung cấp sẽ dựa trên từ khóa do người dùng nhập.

(Giải thích này cũng áp dụng cho các ứng dụng có biểu tượng tìm kiếm ở trên cùng bên phải của màn hình đích. Tôi đã đặt hai biến thể này lại với nhau, vì chúng rất giống nhau về khả năng phát hiện, khả năng truy cập và thậm chí có cùng số lần chạm để sử dụng.)

2. Search tab on navigation bar

1_htxb3xD_rwZOeDkjGc5YnA.png

Và đây là ảnh chụp màn hình của một số ứng dụng sử dụng tìm kiếm dưới dạng tab trên thanh điều hướng. Tìm kiếm này không thể tìm kiếm như thanh Tìm kiếm trên màn hình đích nhưng có thể dễ dàng truy cập được vì người dùng có thể dễ dàng tiếp cận với ngón tay cái của họ.

Trong trường hợp này, Tìm kiếm nhận toàn bộ màn hình cho chính nó. Màn hình này có thanh tìm kiếm ở trên cùng và phần còn lại của màn hình được điền bằng dữ liệu sẽ hỗ trợ tìm kiếm của người dùng hoặc sẽ giúp người dùng khám phá nội dung trên nền tảng. Điều này tạo điều kiện tìm kiếm thăm dò cho người dùng chưa có ý định rõ ràng.

Bar or Tab?

Cả hai tìm kiếm đều hỗ trợ các ý định khác nhau của người dùng. Và đó không phải là tất cả, cả tìm kiếm cũng phụ thuộc vào loại nền tảng và loại nội dung mà nền tảng cung cấp.

Use a search bar on the landing screen when

  1. Ý định chính của người dùng sau khi mở ứng dụng có thể đang tìm kiếm thứ gì đó. Để tham khảo, hãy xem Google Maps, Uber hoặc Zomato. Hầu hết các lần mọi người mở các ứng dụng này chính xác để thực hiện tìm kiếm địa điểm, nhà hàng hoặc món ăn.
  2. Người dùng có ý định rõ ràng sau khi thực hiện tìm kiếm, như trong trường hợp Facebook, nơi người dùng thường tìm kiếm người dùng hoặc trang khác. Hầu hết các lần họ biết tên của người dùng hoặc trang có thể là gì, ngay cả khi họ không hoàn toàn chắc chắn về cách viết của nó. Đối với các nền tảng như vậy, đó là một khả năng hiếm hoi mà người dùng chỉ có thông tin mơ hồ về điều họ đang tìm kiếm. Và ngay cả khi khả năng này nảy sinh, không có nhiều nền tảng có thể làm để giúp người dùng.

Use search as a tab on the navigation bar when

  1. Bạn muốn tăng cường mức độ tương tác của người dùng bằng cách cho phép người dùng khám phá và khám phá nội dung mới trên nền tảng. Để tham khảo, hãy xem Instagram và Twitter. Những nền tảng này muốn người dùng ở lại lâu hơn trên ứng dụng, đó là lý do tại sao họ cung cấp nội dung được cá nhân hóa nằm ngoài mạng của bạn để giúp bạn khám phá những người dùng hoặc nội dung mới mà bạn có thể quan tâm.
  2. Người dùng không chắc chắn về những gì họ đang tìm kiếm và ứng dụng có thể hướng dẫn người dùng tìm thấy những gì họ muốn. Để tham khảo, hãy xem Netflix và Uber Eats. Chúng cho phép người dùng khám phá ứng dụng thông qua các phương tiện và thể loại. Điều này phục vụ cho người dùng biết anh ấy muốn xem một chương trình hài kịch nhưng không thực sự chắc chắn nên xem người nào.

Now, let’s look at Airbnb?

1_yhxaOzAg5yPGXeIdHPVRPw.png

Airbnb sử dụng kết hợp cả hai biến thể. Họ đã có thanh tìm kiếm trên màn hình đích và màn hình đích là tab tìm kiếm / khám phá.

Với bối cảnh của Airbnb tôi tin rằng nó có ý nghĩa rất nhiều. Bằng cách này, họ phục vụ hai loại người dùng – người dùng có điểm đến cụ thể trong tâm trí sẽ sử dụng thanh tìm kiếm (người dùng có ý định rõ ràng) và người dùng có thể không có điểm đến cụ thể trong đầu và đang trong quá trình tìm kiếm ngoài đích (người dùng cần loại tìm kiếm thăm dò).

 

Kết luận

Cả hai biến thể đều có ưu và nhược điểm. Cả hai đều phù hợp cho các trường hợp sử dụng cụ thể. Đi qua tất cả các ví dụ ở trên, chúng tôi có thể kết luận rằng có hai yếu tố xác định tìm kiếm để sử dụng – ý định của người dùng đến ứng dụng và các dịch vụ có thể có của ứng dụng.

#1 Tạo một TableView đa dạng trong iOS

Chúng ta sẽ phải đối mặt với các trạng thái của TableView khi tải dữ liệu như các trường hợp sau đây:

  • Dữ liệu có sẵn cho table view
  • Dữ liệu tải thành công nhưng danh sách trống
  • Phía máy chủ bị treo không mong muốn
  • Phía ứng dụng không thể kết nối với server
  • Không có kết nối internet
  • Bất kỳ những lỗi khác

Một wrapper UITableView:

Cuối cùng tôi cũng giải quyết được vấn đề sau khi tạo mới một wrapper trên UITableView. Nó là một sub-class của UITableView với vài phương thức tiện ích để hiển thị thông điệp thích hợp tùy thuộc vào các trạng thái của UITableView mà tôi đã nhắc đến ở trên.

Bắt đầu như thế nào?

Tạo một project bằng XCode và cài đặt thư viện bằng cách sử dụng cocoapods.Nếu bạn gặp lỗi như bên dưới thì bạn cần chạy lệnh “pod init”:

-bash: /usr/local/bin/pod: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby: bad interpreter: No such file or director

bạn cần cài đặt lại pods trên mac sử dụng lệnh sau:

sudo gem install cocoapods

Bao gồm “SDStateTableView” trong Podfile của bạn:

pod 'SDStateTableView'

Sau khi bạn hoàn thành lệnh pod install, bạn cần mở file .workspace trên XCode.

Nếu bạn gặp bất kỳ vấn đề gì khi cài đặt pod, bạn có thể download project ở trên Github.

Nếu không có gì bất thường xẩy ra, bạn có thể thay thế Assets.xcassets của project với assets từ  GitHub. Nó bao gồm một vài assets để hiển thị trên table view tùy thuộc vào trạng thái.

Xin lỗi, tôi sẽ không chỉ cho bạn cách sử dụng thư viện này trong project với dữ liệu gốc. Nhưng tôi hứa rằng bạn sẽ sẵn sàng xử lý tất cả các trạng thái tôi đã đề cập ở trên UITableView.

Chúng ta cùng xây dựng một UI cơ bản:

Mở file Main.storyboard. Chúng ta hãy kepso và thả một UITableView từ mục thư viện UI trên View Controller Scene với constraints sau:

Bây giờ chọn table view từ Document Out Line Area, mở Identity Inspector và set class của table view thành SDSateTableView. Thêm một cell thuộc tính và set TableViewCell là “reusable identifier” cho nó. Đừng quên set data source của TableView.

Đến lúc thêm một vài cái button để thao tác với table view. Thêm 6 buttons trong 2 UIStackViews và layout constraints tương tự như bảng dưới đây. Chúng ta sẽ thay đổi trạng thái của table view bằng những sự kiện.

Đến lúc thêm một vài dòng code:

Bây giờ mở ViewController.swift và import statement “import SDStateTableView” bên dưới dòng “import UIKit”. Tạo một IBOutlet cho table view đặt tên là stateTableView.

@IBOutlet weak var stateTableView: SDStateTableView!

Now for handling touch on the six buttons add the following IBActions in the ViewController.swift file.

Bây giờ xử lý touch cho 6 buttons, thêm IBAction trong ViewController.swift file.

// MARK: IBActions
extension ViewController {
  @IBAction func dataAvailableButtonTapped(_ sender: UIButton) {
  }
  @IBAction func noDataAvailableButtonTapped(_ sender: UIButton) {
  }
  @IBAction func errorWithImageButtonTapped(_ sender: UIButton) {
  }
  @IBAction func errorWithTitleButtonTapped(_ sender: UIButton) {
  }
  @IBAction func loadingDataButtonTapped(_ sender: UIButton) {
  }
  @IBAction func noInternetButtonTapped(_ sender: UIButton) {
  }
}

Hiển thị trên dữ liệu trên TableView:

Để hiển thị một vài dữ liệu trên table view, chúng ta sẽ implement phương thức UITableViewDataSource như cách dưới đây:

// MARK: - UITableViewDataSource Methods
extension ViewController: UITableViewDataSource {
   func numberOfSections(in tableView: UITableView) -> Int {
       return 1
   }
   func tableView(_ tableView: UITableView, 
                 numberOfRowsInSection section: Int) -> Int {
         switch (tableView as! SDStateTableView).currentState {
         case .dataAvailable:
               tableView.separatorStyle = .singleLine
               return 14
         default:
               tableView.separatorStyle = .none
               return 0
         }
    }
    func tableView(_ tableView: UITableView, 
            cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell = tableView.dequeueReusableCell(withIdentifier:         "TableViewCell")
         cell?.textLabel?.text = "Row number \(indexPath.row)"
         return cell!
    }
}

Không có gì đặc biệt trong những dòng code ngoại trừ vùng bôi đậm. Tôi chỉ enable separator style default là .singleLine, chỉ khi có dữ liệu cho table view nếu không thì separator style là .none.

Tôi quản lý các trạng thái như thế nào?

Chúng ta đã gần code xong ngoại trừ chỉnh sửa đối với các phương thức IBAction. Chúng ta hãy làm điều đó.

// MARK: IBActions
extension ViewController {
    @IBAction func dataAvailableButtonTapped(_ sender: UIButton) {
        stateTableView.setState(.dataAvailable)
    }
    
    @IBAction func noDataAvailableButtonTapped(_ sender: UIButton) {
        stateTableView.setState(.withImage(image: "empty_cart",
                                                  title: "EMPTY CART",
                                                  message: "Please add some item in your cart first"))
    }
    
    @IBAction func errorWithImageButtonTapped(_ sender: UIButton) {
        stateTableView.setState(.withImage(image: "server_error",
                                                  title: "SERVER ERROR",
                                                  message: "We are notified and working on it, we will be back soon"))
    }
    @IBAction func errorWithTitleButtonTapped(_ sender: UIButton) {
        stateTableView.setState(.withImage(image: nil,
                                                  title: "SIMPLE ERROR TITLE",
                                                  message: "Error message goes here"))
    }
    
    @IBAction func loadingDataButtonTapped(_ sender: UIButton) {
        stateTableView.setState(.loading(message: "Loading data..."))
    }
    @IBAction func noInternetButtonTapped(_ sender: Any) {
        stateTableView.setState(.withButton(errorImage: "no_internet",
                                                   title: "NO INTERNET",
                                                   message: "You are not connected to Internet, please try later",
                                                   buttonTitle: "Try Again",
                                                   buttonConfig: { (button) in
                                                    
            // configure the button as per your requirement
        },
                                                   retryAction: {
                    self.stateTableView.setState(.loading(message: "Loading data..."))
        }))
    }
}

Chúng ta đã làm xong hết tất cả,hãy chạy project và xem chuyện gì xảy ra.

Output: