Tạo amination cho header table view

1. Bài toán đặt ra

Giả dụ ta muốn làm 1 cái header tableview khi scroll xuống thì 1 phần header sẽ được mở rộng ra để hiển thị thêm thông tin, khi scroll lên phần mở rộng đó sẽ được đóng lại để chúng ta có nhiểu không gian hơn hiển thị nội dung trong tableview.

2. Chọn cách giải quyết bài toán

Nhìn vào hình vẽ trên ta có thể hình dung lúc này header của chúng ta sẽ có 2 khu vực hiển thị data

  • Data 1: là phần hiển thị khi chúng ta tiến hành scroll tableview từ dưới lên
  • Data 2: khi scroll từ trên xuống thì khu vực này mới hiện ra còn nếu scroll từ dưới lên thì nó sẽ được ẩn đi

3. Thực hiện

3.1 Xây dựng UI storyboard

Trong file storyboard, ta kéo 1 view to chứa 2 view nhỏ chính là data 1 và data 2 lên trên tableview như hình vẽ. Tiếp đó ta kéo 2 constraint vào trong viewcontroller

  • Contraint thứ nhất là chiều cao của view cha chứ cả 2 view con
  • Constraint thứ hai là khoảng cách từ data 1 đến top layout
@IBOutlet weak var headerHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var titleTopConstraint: NSLayoutConstraint!

3.2 Xây dựng code

Để khi scroll xuống header của chúng ta sẽ mở rộng ra thêm chúng ta sẽ cần 1 hàm expandHeader(), khi scroll lên chúng ta sẽ thu gọn header tableview chúng ta viết hàm collapseHeader()

let maxHeaderHeight: CGFloat = 88
let minHeaderHeight: CGFloat = 44

func collapseHeader() {
    self.view.layoutIfNeeded()
    UIView.animateWithDuration(0.2, animations: {
        self.headerHeightConstraint.constant = self.minHeaderHeight
        self.updateHeader()
        self.view.layoutIfNeeded()
    })
}

func expandHeader() {
    self.view.layoutIfNeeded()
    UIView.animateWithDuration(0.2, animations: {
        self.headerHeightConstraint.constant = self.maxHeaderHeight
        self.updateHeader()
        self.view.layoutIfNeeded()
    })
}

func updateHeader() {
    let range = self.maxHeaderHeight - self.minHeaderHeight
    let openAmount = self.headerHeightConstraint.constant - self.minHeaderHeight
    let percentage = openAmount / range

    self.titleTopConstraint.constant = -openAmount + 10
    self.logoImageView.alpha = percentage
}

Ở đây chúng ta có thêm hàm updateHeader(), hàm này có tác dụng update lại vị trí của vùng data 1 khi chúng ta scroll tableview lên hoặc xuống. Để việc scroll tableview tác dụng lên các constraint chúng ta cần dùng phương thức func scrollViewDidScroll(scrollView: UIScrollView)

func scrollViewDidScroll(scrollView: UIScrollView) {
    let scrollDiff = scrollView.contentOffset.y - self.previousScrollOffset

    let absoluteTop: CGFloat = 0;
    let absoluteBottom: CGFloat = scrollView.contentSize.height - scrollView.frame.size.height;

    let isScrollingDown = scrollDiff > 0 && scrollView.contentOffset.y > absoluteTop
    let isScrollingUp = scrollDiff < 0 && scrollView.contentOffset.y < absoluteBottom

    // Tinh toan lai do cao cua header view
    var newHeight = self.headerHeightConstraint.constant
    if isScrollingDown {
        newHeight = max(self.minHeaderHeight, self.headerHeightConstraint.constant - abs(scrollDiff))
    } else if isScrollingUp {
        newHeight = min(self.maxHeaderHeight, self.headerHeightConstraint.constant + abs(scrollDiff))
    }

    // Neu height khong giong nhau thi update
    if newHeight != self.headerHeightConstraint.constant {
        self.headerHeightConstraint.constant = newHeight
        self.updateHeader()
        self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, position)
    }

    self.previousScrollOffset = scrollView.contentOffset.y
}

Ở đoạn code trên mỗi khi chúng ta scroll tableview, nó sẽ tính toán lại độ chênh lệch giữa contentOffset của tableview và vị trí trước đó để biết được chungs ta đang scroll tableview lên hay xuống

  • chênh lệch contentOffset vị trí hiện tại so với vị trí trước đó > 0 và contentOffset tableview tăng -> scroll tableview down (xuống)
  • chênh lệch contentOffset vị trí hiện tại so với vị trí trước đó < 0 và contentOffset tableview giảm -> scroll tableview up (lên)

3.2 Thêm animation

Nếu chỉ như vậy chúng ta sẽ thấy là khi scroll tableview mà không lên hết khoảng cần thiết thì header của chungs ta sẽ không che hết được hoặc không lên hết được phần data 2, để xóa bỏ lỗi này chungs ta cần thêm 2 phương thức của scroll view là

func scrollViewDidEndDecelerating(scrollView: UIScrollView)

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool)

chúng ta sẽ tính toán lại ở 2 phương thức này, nếu khoảng scroll không lên quá 1 nửa chiều cao của header chúng ta sẽ scroll ngược trở lại header view, còn nếu khoảng scroll đã lên hơn 1 nửa chiều cao của header view thì sẽ scroll hẳn lên trên.

func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    self.scrollViewDidStopScrolling()
}

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
        self.scrollViewDidStopScrolling()
    }
}

func scrollViewDidStopScrolling() {
    let range = self.maxHeaderHeight - self.minHeaderHeight
    let midPoint = self.minHeaderHeight + (range / 2)

    if self.headerHeightConstraint.constant > midPoint {
        self.expandHeader()
    } else {
        self.collapseHeader()
    }
}