Author: DaoNM2

  • iOS/Swift: Animate your Launch screen

    iOS/Swift: Animate your Launch screen

    Lời mở đầu

    Chào mọi người, thông thường mành hình Launch screen là nơi để hiển thị logo của ứng dụng khi mà nó được khởi động, và mọi người thường để nó là một ảnh tĩnh để đảm bảo ứng dụng khởi động nhanh nhất có thể. Nhưng gần đây, mình có nhận được một yêu cầu của khách hàng về việc làm cho màn hình này trở nên sinh động hơn. Mình thấy nó cũng khá hay nên mình muốn chia sẻ với mọi người.

    Chia sẻ cách tạo Launch screen một cách sinh động

    Cách làm của chúng ta sẽ là tạo một LaunchScreen mới thay thế cho cái cũ. Trên màn hình LaunchScreen mới chúng ta sẽ thêm vào một số đối tượng và chúng ta sẽ làm cho nó chuyển động để tạo hiệu ứng. Ở phần hướng dẫn này mình sẽ dùng 2 UIImageView và 1 UILabel.
    Cụ thể mục đích của chúng ta sẽ làm chuyển động như hình này:

    Mình sẽ phải tách logo GST ra làm 2 phần và cho nó chuyển động từ 2 bên vào chính giữa và ghép thành một logo hoàn chỉnh.

    Đây là assets mình dùng trong project này:

    Bắt đầu

    Bước 1: Mở XCode và tạo mới Project (Single View App)

    Bước 2: Chúng ta cần file LaunchScreen.storyboard và tạo mới file LaunchScreen bằng cách:
    New File -> View -> đặt tên là LaunchScreen.xib.

    Bước 3: Mở tab General của Target Chọn lại Launch Screen File là LaunchScreen như hình dưới:

    Bước 4: Chỉnh sửa lại file LaunchSceen.xib để chuẩn bị cho việc sử dụng animtion

    Mở file LaunchScreen.xib chúng ta kéo vào 2 UIImageView và set image và tag cho từng lần lượt cho các view theo thứ tự từ trên xuống là 1, 2, 3 sao cho nó như hình dưới:

    Tiếp đến để đảm bảo nó chạy đúng với các kích thước màn hình khác nhau chúng ta cần set constraint cho các item trên màn hình.

    Ta cho imgeview với tag = 1 ẩn khỏi màn hình để sau chúng ta sẽ animate nó ra giữa màn hình vậy ta sẽ set constraint như hình dưới đây.

    Tương tự ta cũng set constraint imageview còn lại như hình dưới:

    Vậy là chúng ta đã chuẩn bị xong UI, giờ chúng ta cần đi vào code để thưc hiện animation.

    Bước 5. Tạo file LaunchScreenManager.swift để thực hiện animation bằng cách:
    Chọn new file -> Swift file -> đặt tên là LaunchScreenManager.swift

    Thêm code vào như dưới đây:

    import Foundation
    import UIKit
    
    class LaunchScreenManager {
        // dòng này dùng để tạo singleton: Nó đảm bảo sẽ chỉ tạo ra 1 instance duy nhất.
        static let instance = LaunchScreenManager()
    
        var view: UIView?// Đây sẽ là LaunchScreen của chúng ta
        var parentView: UIView?
    
        init() {}
    
        func loadView() -> UIView {
            let launchScreen = UINib(nibName: "LaunchScreen", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
            return launchScreen
        }
    
        // Phương thức này thực hiện việc thêm view(launch screen) vào màn hình
        // Và set lại frame và vị trí cho LaunchScreen view
        func fillParentViewWithView() {
            guard let view = view, let parentView = parentView else { return }
            parentView.addSubview(view)
            view.frame = parentView.bounds
            view.center = parentView.center
        }
        
        // MARK: - Animation
    
        // Phương thức này để setup launch screen view và gọi hàm thực hiện animation
        func animateAfterLaunch(_ parentViewPassedIn: UIView) {
            parentView = parentViewPassedIn
            view = loadView()
            fillParentViewWithView()
            // start animation
            startAnimation()
        }
    
        func startAnimation() {
    
            // Lúc này chúng ta cần lấy các view đã add vào file LaunchScreen.xib để thực hiện animation
            // Để tránh sai sót trong việc đánh tag cho các view -> crash app
            //Chúng ta cần kiểm tra các item này có bị nil hay không
            guard let logo1 = view?.viewWithTag(1),
                let logo2 = view?.viewWithTag(2),
                let label = view?.viewWithTag(3) else { return }
    
            let screen = UIScreen.main.bounds.size
    
            UIView.animateKeyframes(withDuration: 2, delay: 0, options: [], animations: {
                //0
                UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.1) {
                    logo1.transform = CGAffineTransform.identity.translatedBy(x: screen.width / 2 + logo1.frame.size.width / 2, y: 0)
                    logo2.transform = CGAffineTransform.identity.translatedBy(x: -(screen.width / 2 + logo2.frame.size.width / 2), y: 0)
                }
                UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.1) {
                    label.center.x = screen.width / 2
                }
                UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
                    self.view?.alpha = 0
                }
            }) { (_) in
                self.view?.removeFromSuperview()
            }
        }
    }

    Bước cuối cùng:
    Mở file SceneDelegate.swift thêm đoạn code thực thi animation để khi app được khởi động sẽ chạy animation 😀

    Done, giờ chúng ta sẽ build app lên và tận hưởng 😀

    Kết quả

    Đối với việc tạo animation nếu mọi người quan tâm có thể tham khảo các bài viết của
    Vũ Đức Cương nhé!
    Part1
    Part2
    Part3

    Tổng kết

    Mình hi vọng bài viết của mình có thể giúp ích cho mọi người, để góp phần tạo ra các ứng dụng ngon hơn 😀

    Dưới đây là code project demo:

  • iOS/Swift: UIPanGestureReconizer

    iOS/Swift: UIPanGestureReconizer

    Lời mở đầu

    Xin chào mọi người, hôm nay mình sẽ giới thiệu với các bạn về UIPanGestureReconizer. Và mình sẽ hướng dẫn mọi người cách sử dụng và ứng dụng nó vào thực tế. Mình hi vọng sau khi xem hết bài viết này, mọi người có thể áp dụng nó vào các ứng dụng sau này nếu cần đến 😀

    Ý tưởng

    Chắc hẳn ai sử dụng iOS đều sử dụng qua tính năng “Control Center” của iOS. Để mở Control center lên ta phải vuốt từ dưới màn hình lên và ẩn đi thì ngược lại, nó còn cho phép người dùng kéo thả một cách mượt mà.

    UIGestureRecognizer là gì?

    UIGestureRecognizer là class định nghĩa một tập hợp các hành vi phổ biến có thể được cấu hình cho tất cả các cử chỉ cụ thể. Dạng như chạm, kéo thả …

    UIGestureRecognizer được sử dụng để nhận dạng các loại hành vi của người dùng khi tương tác lên màn hình và thực hiện hành động được cấu hình.

    Dưới đây là một số subclasses của nó:

    UIPanGestureRecognizer là gì?

    UIPangestureRecognizer là subclass của UIGestureRecognizer dùng để nhận biết hành vi kéo thả.

    Ví dụ về UIPanGestureRecognizer

    Để bắt đầu chúng ta cần mở XCode và tạo mới một Single View App. Sau khi tạo project xong chúng ta mở file Main.storyboard và kéo vào một UIView -> đổi màu nền(Background color) của view sang một màu khác cho dễ nhìn.

    Khi này chúng ta cần gán constraint cho view đó theo Hình 1
    Cần chú ý ở đây chúng ta sẽ constraint bottom của view vào bottom của superview thay vì Safe Area để khi build trên các dòng điện thoại tai thỏ (iphone x, xs ….) sẽ không bị trắng ở dưới của màn hình.

    Hình 1

    Tiếp đến chúng ta sẽ add 1 UIView nhỏ vào trong UIView vừa mới tạo để thể hiện view này có thể kéo thả được.
    Chúng ta cũng constraint UIView nhỏ để nó nằm trên top và giữa của superview kết quả chúng ta thu được như Hình 2

    Hình 2

    Vậy là chúng ta đã xong phần UI, tiếp đến chúng ta cần kéo IBOutlet top constraint cho cái view to và đặt tên nó là topViewContainer kết quả sẽ được như hình 3

    Chúng ta kéo IBOutlet cho top constraint của view để làm gì? Mục đích ở đây là để sau này chúng ta sẽ thay đổi giá trị của topConstraint.constant -> thay đổi vị trí và để tạo hiệu ứng chuyển động.

    Tiếp đến chúng ta kéo outlet cho thằng View to đặt tên là viewContainer

    OK, giờ chúng ta sẽ đi vào code chi tiết.
    Giờ chúng ta mở file ViewController.swift ra và tạo enum cho các trạng thái mở rộng

    // 1: Tạo 3 trạng thái mở rộng cho viewContainer
    enum ExpansionState {
        case compressed
        case haft
        case expanded
    }

    Tạo pangesture và gán nó cho viewContainer, trong code mình đã comment rõ từng dòng, từng hàm để các bạn có thể hiểu được nó làm gì.

       // thêm UIGestureRecognizerDelegate để sử dụng được PanGesture
    class ViewController: UIViewController, UIGestureRecognizerDelegate {
    
        @IBOutlet weak var viewContainer: UIView!
        @IBOutlet weak var topViewContainer: NSLayoutConstraint!
        // Khởi tạo trạng thái
        var expansionState: ExpansionState = .haft
        // Khởi tạo giá trị cũ của topViewContainer
        var oldTopViewContainer: CGFloat = 0
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            setupGestureRecognizers()
            // Khởi tạo vị trí bắt đầu cho việc animte viewContainer.
            // Ở đây chúng ta đang để nó ở trạng thái compressed có nghĩa là ở trạng thái expansion nhỏ nhất.
            topViewContainer.constant = topViewContainer(forState: .compressed)
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            // Do mình muốn viewContainer sẽ chuyển động từ dưới lên trên và dừng lại ở trạng thái expansion 1 nửa màn hình
            // Nên mình gọi hàm phía dưới để nó thực hiện chuyển động vì ở viewDidLoad nó đang ở trạng thái compressed
            animateTopConstraint(constant: topViewContainer(forState: .haft), velocity: CGPoint(x: 0, y: 50))
        }
    
        // Cài đặt Pangesture,
        func setupGestureRecognizers() {
            let panGestureRecognizer = UIPanGestureRecognizer(target: self,
                                                              action: #selector(panGestureDidMove(sender:)))
            panGestureRecognizer.delegate = self
            viewContainer.isUserInteractionEnabled = true
    
            viewContainer.addGestureRecognizer(panGestureRecognizer)
        }
    
        @objc func panGestureDidMove(sender: UIPanGestureRecognizer) {
            let translationPoint = sender.translation(in: view.superview)
            // velocity là vận tốc chuyển động của hành động, dạng như bạn vuốt vào màn hình nhanh hay chậm
            let velocity = sender.velocity(in: view.superview)
    
            switch sender.state {
            case .changed:
                panGesture(didChangeTranslationPoint: translationPoint, withVelocity: velocity)
            case .ended:
                panGesture(didEndTranslationPoint: translationPoint, withVelocity: velocity)
            default:
                return
            }
        }
    
        // Phương thức này để tính toán giá trị viewContainer.top theo các trạng thái thái mà nó cần tính
        func topViewContainer(forState state: ExpansionState) -> CGFloat {
            let heightView = self.view.bounds.height
            switch state {
            case .compressed:
                return heightView - 100
            case .haft:
                return heightView / 2
            case .expanded:
                return 100
            }
        }
    }

    Tiếp đến chúng ta cần tạo Extenstion cho phần animation để phục vụ cho việc chuyển động view container

    / MARK: Animation
    extension ViewController {
        /// Animates the top constraint of the drawerViewController by a given constant
        /// using velocity to calculate a spring and damping animation effect.
        func animateTopConstraint(constant: CGFloat, velocity: CGPoint) {
            let previousConstraint = topViewContainer.constant
            let distance = previousConstraint - constant
            let springVelocity = velocity.y != 0 ? max(1 / (abs(velocity.y / distance)), 0.08) : 0.08
            let springDampening = CGFloat(0.6)
    
            UIView.animate(withDuration: 0.5,
                           delay: 0.0,
                           usingSpringWithDamping: springDampening,
                           initialSpringVelocity: springVelocity,
                           options: [.curveLinear],
                           animations: {
                            self.topViewContainer.constant = constant
                            self.oldTopViewContainer = constant
                            self.view.layoutIfNeeded()
            }, completion: nil)
        }
    }

    Tiếp đến chúng ta cần các hàm để xử lí khi mà người dùng kéo viewContainer
    Ở trong code mình đã comment các hàm, các dòng để mọi người dễ hiểu, hãy đọc trong code nhé.

    //MARK: PanGesture handle
    extension ViewController {
    
        // Hàm này được gọi khi PanGestureRecognizer nhận ra được sự thay đổi của view,
        // trong hàm này chúng ta có thể thực hiện một số điều kiện để giới hạn việc kéo của người dùng
        func panGesture(didChangeTranslationPoint translationPoint: CGPoint,
                                  withVelocity velocity: CGPoint) {
            let newConstraintConstant = oldTopViewContainer + translationPoint.y
            // Giới hạn việc người dùng kéo quá xa so với phía trên
            if newConstraintConstant >= 0 {
                topViewContainer.constant = newConstraintConstant
            }
        }
    
        // Phương thức này được gọi khi kết thúc pan
        func panGesture(didEndTranslationPoint translationPoint: CGPoint,
                                  withVelocity velocity: CGPoint) {
            // Velocity là kiểu CGPoint vì vậy nó có 2 giá trị x và y
            // x: đại diện cho vận tốc theo chiều ngang
            // y: đại điện cho vận tốc theo chiều dọc
            // Do mình đang muốn thực hiện kéo thả theo chiều dọc nên ở ví dụ này mình sẽ dùng thuộc tính vận tốc y
            if abs(velocity.y) <= 50.0 {
                // 50: ở đây là vận tốc mà mình coi nó là người dùng đang kéo từ từ. Con số này các bạn có thể tùy chỉnh
                // Giá trị của y < 0 có nghĩa là người dùng đang kéo lên trên và y > 0 là kéo xuống dưới
                // dấu của y sẽ thể hiện chiều di chuyển của nó.
                // Vì vậy mình cần sử dụng hàm abs() trước khi thực hiện phép so sánh
                drag(lowVelocity: velocity)
            } else if velocity.y > 0 {
                dragDown(highVelocity: velocity)
            } else {
                dragUp(highVelocity: velocity)
            }
        }
    
        // Hàm này thực hiện việc xác định xem view đang gần vị trí của trạng thái nào hơn sẽ chuyển sang trạng thái đó.
        func drag(lowVelocity velocity: CGPoint) {
            let compressedTopConstraint = topViewContainer(forState: .compressed)
            let haftTopConstraint = topViewContainer(forState: .haft)
            let expandedTopConstraint = topViewContainer(forState: .expanded)
    
            let expandedDifference = abs(topViewContainer.constant - expandedTopConstraint)
            let haftDifference = abs(topViewContainer.constant - haftTopConstraint)
            let compressedDifference = abs(topViewContainer.constant - compressedTopConstraint)
    
            let heightArray = [expandedDifference, haftDifference, compressedDifference]
            let minHeight = heightArray.min()
    
            if expandedDifference == minHeight {
                expansionState = .expanded
                animateTopConstraint(constant: expandedTopConstraint, velocity: velocity)
            } else if haftDifference == minHeight {
                expansionState = .haft
                animateTopConstraint(constant: haftTopConstraint, velocity: velocity)
            } else {
                expansionState = .compressed
                animateTopConstraint(constant: compressedTopConstraint, velocity: velocity)
            }
        }
    
        // Hàm này để xử lí khi người dùng kéo thả view với tốc độ cao,
        // nó sẽ xác định và chuyển tới trạng thái liền kề theo hướng phía dưới
        func dragDown(highVelocity velocity: CGPoint) {
            // Handle High Velocity Pan Gesture
    
            switch expansionState {
            case .compressed, .haft:
                expansionState = .compressed
                animateTopConstraint(constant: topViewContainer(forState: .compressed), velocity: velocity)
            case .expanded:
                expansionState = .haft
                animateTopConstraint(constant: topViewContainer(forState: .haft), velocity: velocity)
            }
        }
    
        // Hàm này để xử lí khi người dùng kéo thả view với tốc độ cao,
        // nó sẽ xác định và chuyển tới trạng thái liền kề theo hướng phía trên
        func dragUp(highVelocity velocity: CGPoint) {
            // Handle high Velocity Pan Gesture
            switch expansionState {
            case .compressed:
                expansionState = .haft
                animateTopConstraint(constant: topViewContainer(forState: .haft), velocity: velocity)
            case .haft, .expanded:
                expansionState = .expanded
                animateTopConstraint(constant: topViewContainer(forState: .expanded), velocity: velocity)
            }
        }
    }

    Và đây là kết quả khi chúng ta build app lên:

    Vậy là xong!
    Ngoài ra các bạn có thể chỉnh sửa lại view sao cho phù hợp với ứng dụng đang làm 😀


    Cảm ơn mọi người đã theo dõi bài viết!
    Mọi ý kiến đóng góp mọi người hãy comment xuống phía dưới để mình có thể thay đổi cho bài biết được tốt hơn. :v

  • iOS/Swift – View Controller Lifecycle

    iOS/Swift – View Controller Lifecycle

    Lời mở đầu

    Nói về ViewController thì chắc hẳn tất cả iOS Developer đều biết đến và đã sử dụng rất nhiều. Nhưng đối với các bạn mới bắt đầu với iOS, mọi người thường không chú ý nhiều đến vòng đời của ViewController, dẫn đến mắc phải một số lỗi không đáng có.
    Bài viết này mình sẽ giới thiệu cho các bạn mới bắt đầu với iOS về vòng đời của View Controller và cách sử dụng để tránh những lỗi không đáng có.

    View Controller Lifecycle là gì?

    View Controller lifecycle là vòng đời của một view controller được tính từ lúc nó được nạp vào bộ nhớ (RAM) cho tới khi nó bị giải phóng khỏi bộ nhớ.

    Phân tích vòng đời của View Controller

    Dưới đây là sơ đồ về vòng đời của nó:

    Như các bạn đã thấy, trên sơ đồ này có khá nhiều trạng thái mà các bạn chắc dã nhìn thấy rất nhiều nhưng một số thì không phải không 😀
    Và nó cũng là các phương thức tương ứng được gọi tự động trong vòng đời của View Controller

    OK, bây giờ chúng ta sẽ đi vào chi tiết.

    loadView

    Phương thức này được gọi khi View hiện tại đang bằng nil. Cơ bản nó sẽ đưa View mà bạn tạo trong phương thức này vào view của ViewController.

    NOTE: Phương thức này được sử dụng khi View Controller được tạo bằng code. Nếu chúng ta tạo View Controller từ file .xib hoặc storyboard thì tốt nhất không sử dụng hương thức này.

    viewDidload:

    Phương thức này được gọi một lần duy nhất trong vòng đời của ViewController. Nó được gọi khi tất cả các view đã được load vào bộ nhớ(RAM).

    Ứng dụng:
    1. Khi bạn muốn cài đặt giao diện người dùng (User Interface)
    2. Những công việc mà bạn muốn nó chỉ chạy duy nhất một lần trên View Controller này.

    viewWillAppear:

    Phương thức này được gọi mỗi lần trước khi nội dung của View được thêm vào view hierarchy của ứng dụng.

    Ứng dụng:
    Vì phương thức này sẽ được gọi mỗi lần trước khi View được xuất hiện nên nó thường dùng khi bạn muốn 1 công việc nào đó luôn được gọi mỗi khi View Controller đó hiển thị trên màn hình.
    VD: Kiểm tra kết nối mạng, kiểm tra service state, add observer Notification v.v.

    NOTE:
    • Tránh làm các công việc mà bạn chỉ muốn thực hiện nó một lần trong vòng đời của View Controller trong phương thức này.
    • Nếu bạn add observer notification ở hàm này thì cần remove notification ở phương thức viewDidDisappear:. Để tránh trường hợp khi quay trở lại màn hình này hàm add observer notification sẽ được đăng kí một lần nữa -> nó sẽ thực thi hàm trong #selector nhiều lần.
    • Với tương tác tầng Application (VD: Bấm Home, show notification center, show control center… ) rồi trở lại ứng dụng thì sẽ không kích hoạt phương thức này mà nó sẽ kích hoạt các phương thức của UIApplication.

    viewDidAppear:

    Phương thức này được gọi mỗi lần sau khi nội dung của View được thêm vào view hierarchy của ứng dụng.

    Ứng dụng:
    Thường được sử dụng để lưu dữ liệu, bắt đầu animation, bắt đầu chơi Video hoặc âm thanh hoặc thu thập dữ liệu từ network.

    NOTE:
    • Tương tự như viewWillAppear, với tương tác tầng Application (VD: Bấm Home, show notification center, show control center… ) rồi trở lại ứng dụng thì sẽ không kích hoạt phương thức này mà nó sẽ kích hoạt các phương thức của UIApplication.
    • Nếu bạn add observer notification ở hàm này thì cũng phải xóa notification ở viewDidDisappear:

    viewWillDisappear:

    Phương thức này được gọi trước khi view được xóa khỏi view hierarchy. View vẫn còn trên view hierarchy nhưng chưa được xóa.

    Ứng dụng:
    Thường dùng để quản lí các timer, ẩn bàn phím, hủy các network request và lưu lại các trạng thái.

    viewDidDisappear:

    Phương thức này được gọi sau khi view của ViewController được xóa khỏi view hierarchy.

    Ứng dụng:
    Thường sử dụng để hủy việc lắng nghe các thông báo (Notification) hoặc các cảm biến của thiết bị các trên màn hình này.

    deinit:

    Phương thức này được gọi trước khi một view controller bị xóa khỏi bộ nhớ.

    Ứng dụng:
    Thường được sử dụng để xóa tài nguyên mà view controller đã được phân bổ nhưng không được giải phóng bới ARC(Automatic reference counting)

    NOTE:
    Hãy nhớ rằng một view controller không còn hiển thị trên màn hình nữa không có nghĩa là nó đã được giải phóng. Ngay cả khi màn hình bị tắt nếu nó vẫn còn trong bộ nhớ thì nó vẫn hoạt động như thường.

    didReceiveMemoryWarning:

    Phương thức này được gọi khi bộ nhớ (RAM) của máy gần đầy. Và iOS không tự động di chuyển dữ liệu từ bộ nhớ sang không gian ổ cứng hạn chế của nó.

    Ứng dụng:
    Xóa một số đối tượng ra khỏi bộ nhớ.

    NOTE:
    Hãy nhớ rằng nếu bộ nhớ của ứng dụng vượt quá một ngưỡng nhất định, iOS sẽ tắt ứng dụng của bạn. Và nó trông giống như là ứng dụng bị crash


    Cảm ơn mọi người đã theo dõi bài viết!
    Mọi ý kiến đóng góp mọi người hãy comment xuống phía dưới để mình có thể thay đổi cho bài biết được tốt hơn. :v

  • Swift: Map, Flat Map, Filter and Reduce

    Swift: Map, Flat Map, Filter and Reduce

    Xin chào mọi người.
    Trong swift có một số tính năng rất hay đó là Higher Order Function. Nó có một số hàm như là map, CompactMap, Filter and Reduce được sử dụng cho các kiểu dữ liệu dạng collection.

    Khởi tạo giá trị mẫu

    struct Person {
        let name: String
        let age: Int
        let pets: [String]
    }
    struct Pet {
        let name: String
        let age: Int
    }
    
    var peopleArray = [Person(name: "Jack", age: 11, pets: ["Dog", "Cat"]),
                       Person(name: "Queen", age: 12, pets: ["Pig"]),
                       Person(name: "King", age: 13, pets: [])]

    MAP

    Trước khi sử dụng chúng ta cùng tìm hiểu về syntax của hàm này trước nhé:

    let resultCollection = inputCollection.map { (elementOfCollection) -> ResultType in
       return ResultType()
    }

    Nhìn vào đoạn code trên ta có thể hiểu hàm này sẽ trả về cho ta một collection có kiểu dữ liệu là ResultType( Một kiểu dữ liệu bất kì mà bạn mong muốn, nó có thể là Int, Double, String …)

    OK, Giờ chúng ta đi vào code mẫu để dễ hiểu hơn

    Dạng đầy đủ:

    var ages = peopleArray.map { (person) -> Int in
        return person.age
    }
    print(ages)
    // OUTPUT: [11, 12, 13]

    Ngoài dạng thông thường thì hàm này còn có thể viết dưới dạng rút gọn như sau:

    let ages = peopleArray.map({ $0.age })
    print(ages)
    // OUTPUT: [11, 12, 13]

    $0: ở đây được hiểu là argument đầu tiên của function map, trong trường hợp này nó sẽ đại diện cho 1 phần tử trong mảng có kiểu dữ liệu là Person

    Ứng dụng:
    Hàm này nên được sử dụng khi mà bạn muốn tạo một collection mới có kiểu dữ liệu khác từ 1 collection hiện tại

    Flat Map

    let resultCollection = inputCollection.flatMap { (elementOfCollection) -> [ResultType] in
       return [ResultType]
    }

    Cũng giống như hàm map, hàm flatMap cũng trả về 1 collection nhưng flat map sẽ bỏ qua các tầng (nếu có).
    Để dễ hiểu hơn chúng ta sẽ đi vào ví dụ sau:

    Sử dụng hàm Map:

    let pets = peopleArray.map({ $0.pets })
    print(pets)
    //OUTPUT: [["Dog", "Cat"], ["Pig"], []]

    Dạng đầy đủ:

    let flatPets = peopleArray.flatMap { (person) -> [String] in
        return person.pets
    }
    print("Flat pets: \(flatPets)")
    // OUTPUT: ["Dog", "Cat", "Pig"]

    Dạng rút gọn:

    let flatPets = peopleArray.flatMap({ $0.pets })
    print("flatPets: \(flatPets)")
    // OUTPUT: flatPets: ["Dog", "Cat", "Pig"]

    Có thể thấy flat map sẽ loaị bỏ hết các tầng collection bên trong và chuyển về 1 collection chỉ còn 1 tầng duy nhất thay vì collection chứa collection như khi sử dụng hàm Map.
    Ngoài ra hàm flatMap cũng bỏ đi các giá trị collection empty hoặc nil.

    NOTE: Hàm flatMap chỉ trả về giá trị đúng như mong đợi khi kiểu của nó là non-optional.


    Vậy trong trường hợp khai báo kiểu optional thì sao: let pets: [String]?
    Trong trường hợp này hàm Flat Map sẽ trả về giá trị như hàm Map vì vậy chúng ta sẽ call flatMap thêm 1 lần nữa như sau:

    let flatPetsShort = peopleArray.flatMap({ $0.pets }).flatMap({ $0 })
    print("flatPetsShort: \(flatPetsShort)")
    //OUTPUT: flatPetsShort: ["Dog", "Cat", "Pig"]

    Ứng dụng:
    Flat Map thường sử dụng trong trường hợp lọc các giá trị nil ra khỏi collection hoặc chuyển 1 collection nhiều tầng thành collection 1 tầng

    Reduce

    Cấu trúc hàm Reduce

    let result = inputCollection.reduce(initialValue) { (result, nextElement) -> ResultType in
        return a value that can be computed in the next element.
    }

    Hàm reduce sẽ duyệt lần lượt các phần tử trong collection và trả về kết quả dựa trên initialValue(Giá trị khởi tạo) và phép tính ở hàm return.

    Bây giờ chúng ta đi vào ví dụ để dễ hiểu hơn:

    Bài toán cụ thể: Cần tính tổng số tuổi của mọi người trong collection bằng hàm Reduce thì chúng ta sẽ làm như sau:

    Dạng đầy đủ:

    let ageTotal = peopleArray.reduce(0) { (result, personNext) -> Int in
        return result + personNext.age
    }
    print(ageTotal)
    // OUTPUT: 36

    Dạng rút gọn:

    let ageTotal2 = peopleArray.map({ $0.age }).reduce(0, +)
    print(ageTotal2)
    // OUTPUT: 36

    Ở dạng rút gọn, để sử dụng được hàm Reduce trong trường hợp này. Chúng ta cần sử dụng hàm Map để tạo ra một collection mới, chứa các phần tử là tuổi của tất cả mọi người trước, rồi sử dụng hàm Reduce dạng rút gọn để thực hiện.

    Ứng dụng:
    Bạn nên sử dụng hàm Reduce khi bạn muốn kết hợp các phần tử trong collection

    Filter

    Cấu trúc hàm Filter

    let result = inputCollection.filter { (elementOfCollection) -> Bool in 
        return (Conditions)
    }

    Hàm Filter sẽ trả về kết quả là 1 collection chứa tất cả các phần tử thỏa mãn điều kiện (Conditions)

    Bây giờ chúng ta sẽ đi vào ví dụ cho dễ hiểu hơn:
    Bài toán của chúng ta là cần lọc ra được tất cả các Person có số tuổi > 11.

    Chúng ta sẽ sử dụng hàm Filter dạng đầy đủ như sau:

    let result = peopleArray.filter { (person) -> Bool in
        return person.age > 11
    }
    print("result: \(result)")

    Dạng rút gọn:

    let results = peopleArray.filter({ $0.age > 11 })
    print(results)

    Ứng dụng:
    Sử dụng khi bạn cần giải quyết bài toán lọc các phần tử trong collection có điều kiện xác định

    NOTE:

    print("Cảm ơn mọi người đã theo dõi bài viết của mình.\n
    Mọi đóng góp cũng như góp ý, mọi người hãy comment ở phía dưới để mình có thể hoàn thiện bài viết tốt hơn.") 

    THANK YOU!