Quản lý View Controller bằng Container View Controllers

Bạn đã bao giờ suy nghĩa rằng TabbarControllerNavigationController đã làm việc như thế nào chưa? => cả hai cũng chỉ là subclass của UIViewController. Vậy điểm chung của các class đó là gì? Tất cả đều cho phép hiển thị nội dung theo form từ một hoặc nhiều ViewController. Ví dụ như Navigaion là quản lý stack các ViewController, bạn có thể push hoặc pop View Controller vào hoặc ra khỏi stack, Nó cũng như thế với Tabbar Controller. Nó quản lý list các ViewController và được điều khiển bởi UITabbar để show ViewController mong muốn lên.

Container View Controllers

Cả hai UINavigationController và UITabbarController đều là Container Controller, có nghĩa la nó quản lý view giống như các UIViewController khác nhưng có cái khác là, Container View Controller còn quản lý một hoặc nhiều Child View Controller, đóng vài trò là người cha (parent view controller) và quản lý một đàn con (View Controller) Parent View Controller có trách nhiệm điều chỉnh size và vị trí cho view của từng Child View Controller.

Lợi ích

Reusability

Lợi ích lớn nhất của Container View Controller là reusability (dùng lại). Tương tự các subclass của UIViewController như UINavigationController, UITabbarController, và UISplitViewController. Mỗi loại đều có cách chuyển View khác nhau nhưng được viết thành common của các ưng dụng iOS.

Lean View Controllers

Làm việc với Container View Controller giúp đơn giản hơn nhiều việc quản lý các View Controller, không còn việc 1 View Controller sử lý toàn bộ mọi hoạt động của app nửa, giao diện người dùng sẽ được chia nhỏ theo nhóm logic và các control liên quan, mỗi thứ được quản lý bởi View Controller con. Dễ dàng cho việc chỉnh sữa các thành phần nhỏ hoặc sử dụng lại các control có sẵn trong project.

An Example

Trong bài hướng dẫn này, mình sẽ hướng dẫn cách tạo một Container View Controller và lợi ích của nó. Bạn sẽ dễ dàng hiểu được mối quan hệ giữa Container View Controller và các child Controller của nó. Lấy app Samsara, Người dùng sẽ dễ dàng nhìn thấy các thông tin được chia thành nhiều phần trên màng hình, phía trên Top, người dùng có thể chuyển đổi qua lại giữa Summary và Session. Nhưng qua, ta có thể thấy 2 View Controller khác nhau được quản lý bằng một Container View Controller.

Project Setup

Việc đầu tiên, chung ta tiến hành tạo một project đơn giản Single View Application Đặt tên là ViewControllerContainment và set DeviceUniversal

Setting Up the User Interface

Bắt đầu bằng việc rename ViewController.swift bằng MasterViewController.swift.. Mở MasterViewController.swift và đổi tên class thành MasterViewController

import UIKit

final class MasterViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

Tiếp tục tạo 2 subclass của UIViewController là SummaryViewControllerSessionsViewController, sử dụng Cocoa Touch Class. Các View Controller này sẽ là các View Controller con của MasterViewController. Mở Main.storyboard và set Custom Class của ViewController (có sẵn) là MasterViewController Chọn Embed In > Navigation Controller từ Menu Editor. Mục đích chỉ để lấy thanh Navigation Bar cho tiện thôi. Để chuyển đổi giữa 2 child view controller, chúng ta sử dụng segmented control. Mở Object Library ở bên phải vào add segmented control vào thanh navigatin bar.

Mở MasterViewController.swift và tạo outlet cho segmented control. Ở Main.storyboard, connect outlet với segmented control.

import UIKit

final class MasterViewController: UIViewController {

    @IBOutlet var segmentedControl: UISegmentedControl!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

Chúng ta đã hoàn thành gần hết giao diện cho MasterViewController. tiếp tục tạo thêm 2 View Controller nửa cho SummaryViewControllerSessionsViewController và nhớ đặt custom class cho cả hai.

Configuring the Segmented Control

Mở MasterViewController.swift và thêm function setupView()

override func viewDidLoad() {
    super.viewDidLoad()

    setupView()
}

Ở hàm setupView(), chúng ta thêm vào một function hỗ trợ việc set up segmente.

private func setupView() {
    setupSegmentedControl()
}
private func setupSegmentedControl() {
    // Configure Segmented Control
    segmentedControl.removeAllSegments()
    segmentedControl.insertSegment(withTitle: "Summary", at: 0, animated: false)
    segmentedControl.insertSegment(withTitle: "Sessions", at: 1, animated: false)
    segmentedControl.addTarget(self, action: #selector(selectionDidChange(_:)), for: .valueChanged)

    // Select First Segment
    segmentedControl.selectedSegmentIndex = 0
}

Tiếp theo là thêm hàm selectionDidChange(😃 để nhận việc bắn sự kiện đổi tap.

func selectionDidChange(_ sender: UISegmentedControl) {
    updateView()
}

Adding a Child View Controller

Có nhiều cách để tạo child View Controller, Ở đây, chúng ta sử lazy properties cho MasterViewController để tới lúc nào thì mới tạo đối tượng đó.

private lazy var summaryViewController: SummaryViewController = {
    // Load Storyboard
    let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

    // Instantiate View Controller
    var viewController = storyboard.instantiateViewController(withIdentifier: "SummaryViewController") as! SummaryViewController

    // Add View Controller as Child View Controller
    self.add(asChildViewController: viewController)

    return viewController
}()

private lazy var sessionsViewController: SessionsViewController = {
    // Load Storyboard
    let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

    // Instantiate View Controller
    var viewController = storyboard.instantiateViewController(withIdentifier: "SessionsViewController") as! SessionsViewController

    // Add View Controller as Child View Controller
    self.add(asChildViewController: viewController)

    return viewController
}()

Viết thêm hàm hỗ trợ chuyển View Controller

private func add(asChildViewController viewController: UIViewController) {
    // Add Child View Controller
    addChildViewController(viewController)

    // Add Child View as Subview
    view.addSubview(viewController.view)

    // Configure Child View
    viewController.view.frame = view.bounds
    viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

    // Notify Child View Controller
    viewController.didMove(toParentViewController: self)
}

Removing a Child View Controller

private func remove(asChildViewController viewController: UIViewController) {
    // Notify Child View Controller
    viewController.willMove(toParentViewController: nil)

    // Remove Child View From Superview
    viewController.view.removeFromSuperview()

    // Notify Child View Controller
    viewController.removeFromParentViewController()
}

Updating the View

Phần cuối cùng, chúng ta update lại function updateView để cập nhập container View khi user switch tab ở segment controler.

private func updateView() {
    if segmentedControl.selectedSegmentIndex == 0 {
        remove(asChildViewController: sessionsViewController)
        add(asChildViewController: summaryViewController)
    } else {
        remove(asChildViewController: summaryViewController)
        add(asChildViewController: sessionsViewController)
    }
}

func setupView() {
    setupSegmentedControl()

    updateView()
}

Build thử và chạy thôi.

What's Next?

Gần như toàn bộ iOS Application đều sử dụng container View Controller căn băn là UINavigationController, UITabBarController hoặc UISplitViewController, Ở ví dụ lần này, chúng ta đã hiểu được cách thức hoạt động của các Container View Controller và làm thể nào để có thể tự Custom một cái để phục vụ công việc sau này.