Blog

  • Hướng dẫn cài đặt yarn trên MacOS

    Hướng dẫn cài đặt yarn trên MacOS

    Trong bài viết này tôi sẽ hướng dẫn các bạn cài đặt và sử dụng yarn để quản lý node packages thay cho npm

    Cài đặt brew

    /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    
    brew update && brew upgrade
    

    Cài đặt yarn

    brew install yarn
    

    Kiểm tra phiên bản yarn

    hieunv@HieuNV ~ % yarn -v
    1.22.0
    

    Tạo mới node project bằng yarn

    yarn init
    

    Thêm dependencies

    yarn add react
    

    Thêm devDependencies

    yarn add node-sass -D
    

    Xoá dependencies hoặc devDependencies

    yarn remove react
    
  • Sử dụng nhiều phiên bản node cùng lúc

    Sử dụng nhiều phiên bản node cùng lúc

    Khi sử dụng node nhiều bạn nghĩ rằng chỉ cần sử dụng phiên bản LTS mới nhất là đủ rồi. Tuy nhiên nếu bạn phải tham gia nhiều dự án cùng lúc (như mình chẳng hạn) thì mới thấy là chẳng dễ gì các dự án sống chung với nhau được. Nói vậy để thấy rằng dùng có thể dùng nhiều phiên bản cùng lúc là cần thiết. Trong bài viết này tôi sẽ hướng dẫn các bản cách sử dụng nhiều phiên bản node cùng lúc.

    nvm là gì?

    nvm là viết tắt của Node Version Manager. Như đã nói ở trên bài viết này sẽ hướng dẫn cách các bạn có thể sử dụng nhiều phiên bản node cùng lúc. Vậy thì nm là cần thiết nhỉ.

    Để cài đặt nvm bạn có thể sử dụng lệnh sau:

    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
    
    wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
    

    Tuỳ thuộc vào hệ điều hành bạn đang sử dụng thì cần thêm đoạn sau vào ~/.bash_profile hoặc ~/.profile hoặc ~/.bashrc hoặc ~/.zshrc. Mình đang sử dụng zsh nên mình sẽ thêm vào ~/.zshrc

    export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
    

    Kiểm tra xem nvm đã được cài thành công chưa

    nvm -v
    

    Giờ thì cài đặt phiên bản node mà muốn sử dụng thôi nào

    Cài đặt phiên bản mới nhất:

    nvm install node
    

    Cài đặt phiên bản xác định mà bạn muốn

    nvm install 10.10.0
    

    Để chuyển qua phiên bản node bản muốn sử dụng

    nvm use v13.9.0
    

    v13.9.0 là tên phiên bản các bạn nhìn thấy sau khi sử dụng nvm ls như ở trên.

    Chi tiết cách sử dụng nvm thì các bạn có thể tham khảo hướng dẫn ở link sau

    https://github.com/nvm-sh/nvm
    
  • Android Animation (Part 2)

    Android Animation (Part 2)

    Xin chào các bạn, bài tiếp theo trong series về animation của mình đó là FrameAnimation.

    Như ở bài trước mình đã nói thì FrameAnimation là khởi tạo một animation bằng cách sử dụng một chuỗi các hình ảnh được hiển thị theo một thứ tự nhất định với AnimationDrawable. Chúng ta cùng đi tìm hiểu về nó. Les’t go…

    Với định nghĩa về FrameAnimation bên trên thì chúng ta sẽ hình dung là trước tiên chúng ta cần tạo file xml để định nghĩa các frame.
    Bạn có thể tham khảo file numeric_animation.xml của tôi dưới đây:

     <!-- Animation frames are ic_numeric_0.png through ic_numeric_2.png
         files inside the res/drawable/ folder -->
     <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/selected"
        android:oneshot="false">
        <item
            android:drawable="@drawable/ic_numeric_0"
            android:duration="1000" />
        <item
            android:drawable="@drawable/ic_numeric_1"
            android:duration="1000" />
        <item
            android:drawable="@drawable/ic_numeric_2"
            android:duration="1000" />
    </animation-list>

    syntax:

    <?xml version="1.0" encoding="utf-8"?>
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot=["true" | "false"] >
        <item
            android:drawable="@[package:]drawable/drawable_resource_name"
            android:duration="integer" />
    </animation-list>

    element:
    <animation-list>: là thẻ bắt buộc, nó chứa một hoặc nhiều thẻ <item>.
    android:oneshot: kiểu giá trị kiểu Boolean. “true” là khi bạn muốn thực hiện 1 lần duy nhất. “false” là khi bạn muốn lặp lại.
    <item>: là 1 item trong danh sách, bạn hiểu nó là 1 khung hình khi chạy.
    android:drawable: Drawable của bạn.
    android:duration: kiểu giá trị Integer, nó là thời lượng hiển thị được tính bằng milliseconds.
    Chạy nào, chạy nào…

    private lateinit var numericAnimation: AnimationDrawable
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            // Load the ImageView that will host the animation and
            // set its background to our AnimationDrawable XML resource.
            findViewById<ImageView>(R.id.numeric_image).apply {
                setBackgroundResource(R.drawable.numeric_animation)
                // Get the background, which has been compiled to an AnimationDrawable object.
                numericAnimation = background as AnimationDrawable
                // Start the animation (looped playback by default).
                numericAnimation.start()
            }
    
        }

    Ở đây thì bạn cũng thấy rằng trên màn hình main của tôi sẽ có 1 ImageView. Sau đó tôi sẽ chạy animation với AnimationDrawable đã được định nghĩa trong numeric_animation.xml.

    FrameAnimation


    Thật đơn giản phải không các bạn? 😀
    Nhưng nó có nhược điểm khi bạn sử dụng nhiều ảnh với animation có độ phức tạp cao sẽ làm tăng kích thước của ứng dụng, hay là bạn sẽ mất khá nhiều thời gian để custom lại animation đó cho phù hợp với bài toán của bạn. Để giải quyết vấn đề này, bài tiếp theo tôi sẽ giới thiệu cho các bạn về AnimatedVectorDrawable, các bạn hãy đón chờ nhé 😀

    Link tham khảo:

    • https://developer.android.com/guide/topics/graphics/drawable-animation
  • Binding library android -Xamarin Android

    Kết quả hình ảnh cho binding android xamarin

    Với một vài dự án khách hàng đã code sẵn cho 1 cái library android rồi, và họ sẽ gửi cho bạn sử dụng cái lib đó. Hoặc là bạn có 1 cái lib rất ngon trên android. Nhưng muốn dùng trong project Xamarin của bạn mà không muốn code lại logic của nó. Như vậy bạn cần binding nó vào project của bạn và chỉ việc dùng không cần code lại 1 tí logic nào.

    I. Input:
    Project dự án code bằng ngôn ngữ Xamarin
    Các file android libraries .aar (Android Archive Library)
    Bản chất của file này là file .zip gồm
    – Compiled Java code
    – Resource Ids (File R)
    – Resources
    – Meta-data (for example, Activity declarations, permissions)

    II. Output
    Project Xamarin có thể gọi và sử dụng được class chứa trong file .aar

    III.Guideline
    1. Tạo 1 project binding với 1 file .aar cơ bản
    1) Add new project Binding library(Android)
    2) Add file .aar vào folder Jar
    3) Set build action cho .AAR file là LibraryProjectZip
    4) Chọn target framework cho project lib nếu cần
    5) Build Bindings Library.
    Sau khi tạo xong và build thành công chúng ta sẽ add file đó vào project Xamarin của chúng ta muốn sử dụng.
    Vậy là chúng ta có thể call tới class chứa trong file .aar.
    Mình chỉ nói các bước thực hiện thôi, chi tiết các bạn nên tham khảo tại : https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/binding-an-aar

    Như ở trên là cách tạo và add 1 project binding library với file source code trong file aar là đơn giản.
    Với những file .aar phức tạp và chúng có link đến nhau thì sao ?

    2. Tạo project binding nâng cao
    1. Đầu tiên các bạn vẫn phải thực hiện các bước như trên. Vì xamarin có 1 nhược điểm là 1 file aar bắt buộc bạn cần phải tạo ra 1 project binding
    2. Cần phải xác định file aar nào sử dụng file aar nào.
    Ví dụ chúng ta có 2 file aar, file thứ nhất call đến 1 class trong file thứ 2
    Như vậy làm thế nào để biết được file nào dùng đến file nào
    – Bạn cần 1 tool để decode java trong file aar (Android studio chẳng hạn)
    – Sau đó bạn mở những class trong file aar ra. Xem phần import của chúng sử dụng những class nào có chứa trong file aar khác không
    – Đôi khi trong file aar nó sẽ sử dụng dependence đến 1 lib nào mà k chứa trong tất cả file aar của bạn. Thì bạn cần xem nó là những lib nào.
    Click chuột phải vào Packages của Project đó chọn Add packages. Rồi tìm xem tên lib đó có trong nuget không.
    Nếu không có, thì bạn có thể tìm và tải file jar của lib ấy về rồi copy vào folder Jar. Build action bạn để là EmbeddedJar
    Bạn thử build xem còn lỗi gì không.
    Nếu còn lỗi. Bạn cần phải customizing binding.
    3.Customizing Binding
    Với android chúng ta chỉ cần customize lại file Metadata.xml Transform File.
    Trong file Metadata.xml thì sẽ có 3 element :
    – add-node
    – attr
    – remove-node

    Mình cũng làm 1 vài project liên quan đến binding library. Và cũng customize file metadata.xml này, mình thấy như sau

    khi thấy error bạn chọn vào 1 item error

    Bạn có thể nhìn thấy dòng // Metadata.xml Xpath classs ….
    hãy coppy từ chỗ path=”…”
    Bạn có thể sử dụng remove-node trong file metadata.xml với lỗi ”do not exist in the namespace …’
    Thực ra bạn remove node này nó không ảnh hưởng gì đến code trong project cả. Việc generate class java sang c# ở đây chỉ là Android.Runtime.Register thôi.
    chứ không phải là nó generate all source code của cả class java sang c# .

    Một số trường hợp bạn không nhìn thấy class mình dùng được generate ra
    Trường hợp này chúng ta sẽ add bằng tay dùng add-node
    Ví dụ :

    <add-node path="/api/package[@name='your package name']">	
            <class abstract="false" deprecated="not deprecated" extends="java.lang.Object" 	
                extends-generic-aware="java.lang.Object" final="false" name=" your class name" static="true" visibility="public">	
            </class>	
    </add-node>	

    Một số trường hợp khác như tên classs trùng nhau, rồi đổi visibility của class … thì bạn dùng thằng att để thay đổi attribute của nó.
    Về chi tiết các bạn có thể tham khảo tại : https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/customizing-bindings/java-bindings-metadata
    Một vài lưu ý nữa là kể cả việc file aar bị mã hoá cũng không ảnh hưởng gì đến việc binding nhé. Vì những class a, b, c trong đó bạn trả bao giờ dùng đến cả.

    Bài viết của mình xin hết, nếu bạn nào dùng mà cảm thấy không hiểu thì contact với mình.

    Nếu các bạn cần binding với xamarin ios xem tại: https://magz.techover.io/2019/12/30/binding-objective-c-libraries-xamarin-ios/

    Thanks you!

  • Sự khác nhau giữa Authentication và Authorization

    Sự khác nhau giữa Authentication và Authorization

    1. Authentication

    Authentication là xác thực, chỉ quá trình định danh một tài khoản đang vào hệ thống chính là người đó chứ không phải ai khác. Đây là bước ban đầu của mọi hệ thống có yếu tố người dùng. Nếu không có bước xác thực này, hệ thống của bạn sẽ không biết được người đang truy cập vào hệ thống là ai để có các phản hồi phù hợp, thường biểu hiện ở hình thức đơn giản nhất chính là form đăng nhập vào hệ thống. Đây là mô hình xác thực dựa trên yếu tố “mật khẩu”. Mật khẩu là một trong những phương pháp được triển khai cho hệ thống xác thực (authentication). Một số phương thức xác thực thông dụng khác là khóa (public & private), sinh học (vân tay, tròng mắt, khuôn mặt)…

    2. Authorization

    Sau khi xác định được “danh tính” của tài khoản thì hệ thống mới chỉ trả lời được câu hỏi “Đó là ai?”, chúng ta sẽ tiến hành một bước quan trọng không kém đó là trả lời câu hỏi “Người đó có thể làm được gì?”, hay xác định quyền (phân quyền) của tài khoản hiện tại vừa mới được xác thực.
    Hệ thống của bạn có thể sẽ rất phức tạp bởi nhiều tính năng quan trọng và mạng lưới phòng ban dày đặc và cần phân chia quyền sử dụng rõ ràng nên việc thiết kế một hệ thống phân quyền cho từng thành viên là một việc làm cực kỳ quan trọng và cần thiết.

    Các hình thức phân quyền thường gặp là:

    + Role-based authorization: Phân quyền dựa trên vai trò của người dùng. Ví dụ trong WordPress có các role như là  Subscriber, Contributor, Author, Editor, Administrator và mỗi một role sẽ có những quyền khác nhau và mỗi người dùng sẽ được phân role có quyền tương ứng. Đối với những hệ thống có nhiều người dùng thì role-based là cách tiếp cận tốt nhất để tiết kiệm thời gian trong việc phân quyền.

    + Object-based authorization: Phân quyền theo đối tượng. Cách này sẽ phân quyền cho từng đối tượng cụ thể. Ví dụ những đối tượng trong nhóm A, B được phân quyền chỉnh sửa các bài viết trong danh mục. Nhưng đối tượng trong nhóm A chỉ chỉnh sửa được bài viết trong danh mục C, đối trượng trong nhóm B chỉ chỉnh sửa bài viết trong danh mục D.

    Nguồn tham khảo thêm:

    http://searchsecurity.techtarget.com/definition/authentication


  • iOS/Auto Layout – Phần 1: Auto layout là gì?

    iOS/Auto Layout – Phần 1: Auto layout là gì?

    Lời mở đầu

    Để có thể tạo ra các ứng dụng iOS thì việc sử dụng auto layout là không thể tránh khỏi. Đối với các Developer đã có kinh nghiệm thì có vẻ không khó khăn là bao. Nhưng đối với các iOS Developer mới họ thường khá lúng túng với công việc này. Vì vậy loạt bài viết lần này mình sẽ chia sẻ về Auto layout để các bạn có thể cảm thấy tự tin hơn khi sử dụng Auto Layout 😀



    Để sử dụng Auto Layout thì đầu tiên chúng ta phải hiểu nó là cái gì và sử dụng để làm gì. Vì vậy phần 1 mình sẽ nói về định nghĩa của Auto layout cho các bạn mới làm về iOS giúp các bạn hiều và dần làm quen với nó.
    OK, Chúng ta bắt đầu thôi :v

    Auto layout là gì?

    Auto layout là việc tự động tính toán kích thước và vị trí của tất cả các “view” trong view hierarchy của bạn, dựa trên các ràng buộc được đặt trên các “view” đó.

    Ví dụ: Bạn có thể giới hạn một Button sao cho Button nằm ở giữa theo chiều ngang với ImageView và sao cho cạnh trên của Button luôn cách 8 “points” so với cạnh dưới ImageView. Lúc này, nếu kích thước hoặc vị trí của ImageView thay đổi thì vị trí của Button sẽ tự động điều chỉnh cho phù hơp. (Hình 1)

    Cách tiếp cận dựa trên ràng buộc này để thiết kế cho phép bạn xây dựng giao diện người dùng tự động đáp ứng với cả những thay đổi bởi các tác nhân bên trong và bên ngoài(Internal and external changes).

    Hình 1

    NOTE:
    • “view”: Ở đây được hiểu là tập hợp các UI để hiển thị trên màn hình như: UILabel, UIButton, UIView .v.v.
    • “points”: Là đơn vị đo lường sử dụng trong thiết kế UI của iOS.

    Những tác nhân thay đổi view từ bên ngoài (External Changes)

    Nó xảy ra khi kích thước hoặc hình dạng của supperview thay đổi. Với mỗi thay đổi, bạn sẽ phải cập nhật lại layout của view hierarchy để sử dụng không gian có sẵn một cách tốt nhất. Dưới đây là một số tác nhân thay đổi bên ngoài phổ biến:
    • Người dùng thay đổi kích thước cửa sổ (window) trong OS X.
    • Người dùng truy cập hoặc rời khỏi Split View trên iPad.
    • Người dùng sử dụng tính năng xoay màn hình (Rotates) (iOS).
    • Khi các thanh cuộc gọi hoặc ghi âm xuất hiện hoặc biến mất (iOS).
    • Khi bạn muốn hỗ trợ các size class khác nhau.
    • Khi bạn muốn hỗ trợ các kích thước màn hình khác nhau

    Hầy hết những tác nhân thay đổi này có thể xảy ra trong thời gian chạy của ứng dụng và chúng yêu cầu phản hồi linh hoạt từ ứng dụng của bạn. Ngoài ra, như là việc hỗ trợ cho các màn hình với kích thước khác nhau, ứng dụng cũng cần thích nghi với các môi trường khác nhau. Mặc dù kích thước màn hình thường không thay đổi lúc runtime, nhưng việc tạo một giao diện người dùng dễ dàng thích thích nghi(adaptive) khiến cho ứng dụng của các bạn chạy tốt trên cả iPhone 4S, iPhone 6 Plus, iPhone X thậm chí là cả iPad. Auto Layout cũng là phần chủ đạo cho việc hỗ trợ Slide Over và Split view trên iPad.

    Những tác nhân thay đổi view từ bên trong (Internal Changes)

    Nó xảy ra khi kích thước của các view hoặc các controls(Thanh điều khiển) trong giao diện người dùng của bạn thay đổi.

    Dưới đây là một vài nguyên nhân phổ biến:

    • Nội dung bị thay đổi bởi những thay đổi từ ứng dụng.
    • Ứng dụng hỗ trợ đa quốc gia
    • Ứng dụng hỗ trợ Dynamic type (iOS): Cho phép thay đổi style của ứng dụng (font, fontsize, …)

    Khi mà mội dung của ứng dụng thay đổi, nội dung mới có thể yêu cầu một layout khác với layout hiện tại. Điều này thường xảy ra trong ứng dụng khi nó hiển thị text hoặc image(hình ảnh). Ví dụ, một ứng dụng tin tức cần phải điều chỉnh layout của nó dựa trên kích thước của từng loại tin tức. Tương tự, một ứng dụng ghép ảnh phải xử lí một loạt các kích thước của hình ảnh và tỉ lệ khung hình(aspect ratio).

    Việc làm ứng dụng hỗ trợ đa quốc gia là quá trình tạo ra một ứng dụng có thể hiển thị tốt ở trên các ngôn ngữ, khu vực và văn hóa khác nhau. Bố cục của ứng dụng hỗ trợ đa quốc gia phải tính đến những khác biệt này và cần hiển thị chính xác trong tất cả các ngôn ngữ và khu vực mà ứng dụng hỗ trợ.

    Ứng dụng hỗ trợ đa quốc gia có 3 tác dụng chính trên bố cục. Đầu tiên khi bạn chuyển giao diện sang một ngôn ngữ khác thì các Label sẽ yêu cầu phải có một khoảng trống khác. Ví dụ như: Tiếng đức thì cần nhiều không gian hơn tiếng anh. Tiếng nhật thì lại cần ít khoảng trống hơn nhiều.

    Thứ hai, Đinh dạng bạn sử dụng để hiển thị ngày và số có thể thay đổi giữa các khu vực, ngay cả khi ngôn ngữ không bị thay đổi. Mặc dù những thay đổi này thường không to bằng thay đổi ngôn ngữ, nhưng giao diện người dùng vẫn cần thích ứng với những thay đổi đó.

    Thứ ba, thay đổi ngôn ngữ có thể ảnh hưởng không chỉ đến kích thước của văn bản, mà cả tổ chức bố cục của nó. Các ngôn ngữ khác nhau sẽ sử dụng các hướng bố trí khác nhau. Ví dụ như: Tiếng Anh thì bố trí từ trái qua phải, còn tiếng Ả Rập và Do Thái thì ngược lại. Nói chung, thứ tự của các phần trong giao diện người dùng phải phù hợp với hướng bố trí bố cục trong ngôn ngữ đó. Nếu một Button nằm ở góc dưới bên phải của màn hình khi sử dụng Tiếng Anh thì nó nên được được nằm ở góc dưới bên trái khi sử dụng tiếng Ả Rập.

    Cuối cùng, nếu ứng dụng iOS của bạn hỗ trợ kiểu Dynamic type, người dùng có thể thay đổi kích thước, phông chữ được sử dụng trong ứng dụng của bạn. Điều này có thể thay đổi cả chiều cao lẫn chiều rộng của bất kỳ text trong giao diện người dùng của bạn. Nếu người dùng thay đổi kích thước phông chữ(font) của bạn khi ứng dụng đang chạy thì cả phông chữ (font) và bố cục(layout) đều phải thích ứng với thay đổi đó.

    Tổng kết

    Auto Layout là việc định nghĩa tập hợp các ràng buộc. Các ràng buộc này đại diện cho quan hệ giữa các view với nhau. Auto layout sau đó sẽ tính toán kích thước và vị trí của các view dựa trên các ràng buộc của chúng. Điều này tạo ra các bố cục tự đáp ứng được cả với thay đổi bên trong và bên ngoài.
    Để sử dụng auto layout chúng ta cần phải hiểu về logic đằng sau các layouts dựa trên các ràng buộc của nó.



    Cảm ơn các bạn đã theo dõi bài viết 😀

  • Operation (P3)

    Operation (P3)

    Đến với phần cuối của loạt bài viết về Operation nhưng cũng không kém phần quan trọng, mình sẽ nói về Async Operation và Cancel Operation.

    Nội dung bài viết:

    • Cancel Operation
    • Async Operation
    • Demo

    Cancel Operation

    • Cách dùng khá đơn giản, chỉ cần gọi cancel() để cancel 1 operation. Tuy nhiên, 1 operation chỉ có thể bị cancel trước khi nó được bắt đầu được thực hiện.
    • Bản chất của việc gọi cancel là sẽ set state của operation thành isCancelled = true.
    operation.cancel()

    Cancel toàn bộ operation:

    Để cancel toàn bộ các operations trong 1 operation queue, chỉ cần gọi:

    operationQueue.cancelAllOperations()

    Note: Chỉ cancel những task chưa được thực hiện

    Vậy làm thế nào có thể cancel 1 operation đang thực hiện? Câu trả lời là sẽ kiểm tra state isCancelled trong thân hàm.
    Hãy xem ví dụ ở cuối bài để hiểu thêm.

    Async operation

    1 Operation nếu được khởi tạo default thì sẽ hoạt động theo kiểu synchronous. 1 vòng đời của operation khi đó theo các state là: isReady -> isExecuting -> isFinished.

    Nhưng nếu bạn muốn các operation của bạn hoạt động theo kiểu asynchoronous bởi diều đó chắc chắn sẽ khiến app của bạn họat động nhanh hơn?
    Điều đó là hoàn toàn có thể, tuy nhiên nếu operation hoạt động kiểu async, nó sẽ trả về quyền điều khiển ngay lập tức (xem lại bài GCD part 1). Vì vậy, state của operation đó sẽ trở thành isFinished ngay lập tức. Do đó, bạn sẽ phải làm thêm 1 vài việc để custom lại state của operation nếu bạn muốn operation đó chạy theo kiểu async.

    Cách làm ở đây về cơ bản là sẽ viết 1 async operation subclass để quản lí state, và giao tiếp với lớp cha Operation của nó thông qua KVO.
    Nghe có vẻ khá dài, nhưng đừng lo lắng, chỉ cần làm 1 lần thôi, lần sau bạn sẽ chỉ cần gọi và dùng.

    class AsyncOperation: Operation {
        // State enumaration
        enum State: String {
            case ready, excuting, finished
    
            // 1
            fileprivate var keyPath: String {
                return "is" + rawValue.capitalized
            }
        }
        // State property
        // 2
        var state: State = .ready {
            // 3
            willSet {
                debugPrint("New value \(newValue)")
                willChangeValue(forKey: newValue.keyPath)
                debugPrint("Will change value 1 for key \(newValue.keyPath)")
                willChangeValue(forKey: state.keyPath)
                debugPrint("Will change value 2 for key \(state.keyPath)")
            }
            didSet {
                debugPrint("old value \(oldValue)")
                didChangeValue(forKey: oldValue.keyPath)
                debugPrint("Did change value 1 for key \(oldValue.keyPath)")
                didChangeValue(forKey: state.keyPath)
                debugPrint("Did change value 2 for key \(state.keyPath)")
            }
        }
    }
    1. State của operation default sẽ là ready
    2. keyPath là 1 computed property sẽ giúp bạn lấy state hiện tại của operation.
    3. Bởi vì bạn cần gửi các notification khi bạn thay đổi state, bạn sẽ dùng didSet và willSet để hứng notification. -> Trong nhiều trường hợp có thể bạn sẽ không cần dùng đến, nhưng nên khai báo để dễ dàng quan sát, debug.

    Base Properties

    extension AsyncOperation {
        // 1
        open override var isReady: Bool {
            return super.isReady && state == .ready
        }
    
        open override var isExecuting: Bool {
            return state == .excuting
        }
    
    
        open override var isFinished: Bool {
            return state == .finished
        }
        // 2
        open override var isAsynchronous: Bool {
            return true
        }
        // 3
        open override func start() {
            // 4
            if isCancelled {
                state = .finished
                return
            }
    
            main()
        }
    
        open override func cancel() {
            super.cancel()
            state = .finished
        }
    }
    1. override lại các state vì bạn muốn tự quản lí state cho riêng mình.
    2. set thuộc tính isAsynchronous thành true để operation chạy async.
    3. override lại func start(). Khi đưa 1 operation vào queue, queue sẽ gọi start để thực hiện chạy 1 operation.
    4. Kiểm tra trước khi thực hiện xem operation này đã bị cancel chưa, nếu chưa thì sẽ bắt đầu thực hiện.

    Note: Không bao giờ gọi super.start() trong hàm start().
    Refer: https://developer.apple.com/documentation/foundation/operation/1416837-start

    Custom 1 Operation

    Mình sẽ custom 1 opeartion dùng để download ảnh, và check state isCancelled trong thân hàm để có thể dừng 1 operation đang chạy.

    1. Override lại hàm main cho operation. Đây là hàm operation sẽ chạy vào khi thực được gọi để thực hiện.
    2. set state thành .executing khi bắt đầu thực hiện operation.
    3. Luôn nhớ set state thành .finished khi operation được thực hiện xong
    1. check nếu operation đã bị cancel thì sẽ không download image.
    2. check nếu opeartion đã bị cancel thì sẽ không convert data thành image.
    3. Ở đây bạn có thể check nếu operation đã bị cancel thì không hiển thị image cũng được, tuy nhiên mình nghĩ download xong image rồi thì hiển thị cũng ok.

    Kết luận:

    • Operation thích hợp để dùng hơn GCD khi những task của bạn đòi hỏi sự kiểm soát state, hoặc những task có tính reuseable cao. Còn với những task đơn giản, hoặc không cần reuse nhiều thì có thể dùng GCD.
    • Operation là API bậc cao hơn GCD, nên cách dùng sẽ khó hiểu hơn, nên hãy cẩn trọng khi kiểm soát state của các custom Operation.

    Tham khảo: Ray wenderlich
    End

  • 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:

  • Một số animation cho UITableView

    Một số animation cho UITableView

    TableView là được sử dụng rất nhiều trong các ứng dụng của chúng ta. Vì vậy, việc tạo thêm một số hiệu ứng cho TableView khiến cho ứng dụng trở lên sinh động và bớt nhàm chán hơn. Chỉ với một vài câu lệnh, mọi thứ sẽ trở lên mới mẻ và dễ gần hơn rất nhiều lần.

    Hầu như các animation của tableview sẽ được tạo trong method dưới đây:

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        // Add animations here
    }

    Chúng ta bắt đầu với hiệu ứng đơn giản nhất nhưng được sử dụng nhiều nhất:

    Kết quả đạt được:

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            cell.alpha = 0
            UIView.animate(withDuration: 0.5,
                           delay: 0.1 * Double(indexPath.row),
                animations: {
                    cell.alpha = 1
            })
        }

    Hiệu ứng Bounce animation:

    Bounce animation
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
          cell.transform = CGAffineTransform(translationX: 0, y: 60)
            UIView.animate(
                withDuration: 1,
                delay: 0.05 * Double(indexPath.row),
                usingSpringWithDamping: 0.4,
                initialSpringVelocity: 0.1,
                options: [.curveEaseInOut],
                animations: {
                    cell.transform = CGAffineTransform(translationX: 0, y: 0)
            })
    }

    Move and Fade Animation

    Kết hợp hai hiệu ứng trên và chung ta ngừng sử dụng hiệu ứng Spring cho Cell, một hiệu ứng mới được tạo thành.

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        cell.transform = CGAffineTransform(translationX: 0, y: 30)
            cell.alpha = 0
    
            UIView.animate(
                withDuration: 1,
                delay: 0.05 * Double(indexPath.row),
                options: [.curveEaseInOut],
                animations: {
                    cell.transform = CGAffineTransform(translationX: 0, y: 0)
                    cell.alpha = 1
            })
    }

    Slide in Animation

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            cell.transform = CGAffineTransform(translationX: tableView.bounds.width, y: 0)
    
            UIView.animate(
                withDuration: 1,
                delay: 0.5 * Double(indexPath.row),
                options: [.curveEaseInOut],
                animations: {
                    cell.transform = CGAffineTransform(translationX: 0, y: 0)
            })
        }

    Trên đây là một số ví dụ về việc thêm animation vào tableview bằng các hiệu ứng cơ bản của UIView( transform, alpha …) ngoài ra với 1 số hiệu ứng khác như Flip, Change color … cũng được sử dụng rất nhiều trong các hiệu ứng animation. Chúng ta hoàn toàn có thể tạo ra các animation mang mang màu sắc của cá nhân như các họa sĩ code vậy.
    Một lưu ý đó là khi muốn kiểm soát tốt hơn các animation thì chúng ta nên tạo ra một class riêng  để xử lý việc chạy animation. Nó đồng thời cũng kiểm soát việc animation chỉ chạy một lần duy nhất với tất cả các cell.

  • Android Animation (Part 1)

    Android Animation (Part 1)

    Bài viết này mình muốn chia sẽ về Animation trong Android.

    Tổng quan

    Như các bạn đã biết thì Animation trong Android sẽ có loại View Animation (Android 2.3 và các bản trước đó), loại Property Animation (Android 3.0 và các bản sau này).

    Property Animation

    Là Animation được tạo bằng cách thay đổi các giá trị của thuộc tính của các đối tượng trong một khoảng thời gian đã được định sẵn bằng Animator.

    View Animation

    View Animation có Tween Animation, Frame Animation.

    • Tween Animation: Khởi tạo một animation bằng cách thực hiện một loạt các thay đổi trên một hình ảnh duy nhất với Animation.
    • Frame Animation: Khởi tạo một animation bằng cách sử dụng một chuỗi các hình ảnh được hiển thị theo một thứ tự nhất định với AnimationDrawable.
      Bài viết này mình sẽ giới thiệu cho các bạn về Tween Animation.

    Tween Animation

    Để thực hiện thì chúng tôi sẽ gọi method loadAnimation() của AnimationUtils.

    AnimationUtils.loadAnimation(applicationContext, R.anim.my_animation)

    R.anim.my_animation : các bạn sẽ tạo 1 thư mục anim trong thư mục res và tạo 1 file xml với tên là my_animation.
    Để áp dụng animation có đối tượng cần chạy animation thì các bạn gọi method startAnimation().

    animation_fade_in.startAnimation(AnimationUtils.loadAnimation(applicationContext, R.anim.fade_in))

    Ở đây tôi có 1 TextView đã được đặt tên là animation_fade_in và 1 file xml fade_in.xml để thực hiện animation fade in cho TextView.
    Chúng ta có một số animation thông dụng dưới đây:

    • Fade In
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <alpha
            android:duration="1000"
            android:fromAlpha="0.0"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toAlpha="1.0" />
    </set>
    • Fade Out
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <alpha
            android:duration="1000"
            android:fromAlpha="1.0"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toAlpha="0.0" />
    </set>
    • Blink
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <alpha
            android:duration="1000"
            android:fromAlpha="0.0"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toAlpha="1.0" />
    </set>
    • Zoom In
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <scale
            android:duration="1000"
            android:fromXScale="1"
            android:fromYScale="1"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="2"
            android:toYScale="2" />
    </set>
    • Zoom Out
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <scale
            android:duration="1000"
            android:fromXScale="2.0"
            android:fromYScale="2.0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="1"
            android:toYScale="1" />
    </set>
    • Rotate
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <rotate
            android:duration="1000"
            android:fromDegrees="0"
            android:interpolator="@android:anim/cycle_interpolator"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toDegrees="360" />
    </set>
    • Move
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true"
        android:interpolator="@android:anim/linear_interpolator">
        <translate
            android:duration="1000"
            android:fromXDelta="0%p"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXDelta="50%p" />
    </set>
    • Slide Up
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <scale
            android:duration="1000"
            android:fromXScale="1.0"
            android:fromYScale="1.0"
            android:interpolator="@android:anim/linear_interpolator"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="1.0"
            android:toYScale="0.0" />
    </set>
    • Slide Down
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <scale
            android:duration="1000"
            android:fromXScale="1.0"
            android:fromYScale="0.0"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="1.0"
            android:toYScale="1.0" />
    </set>
    • Sequential
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true"
        android:interpolator="@android:anim/linear_interpolator"
        android:repeatCount="infinite"
        android:repeatMode="restart">
        <!-- Move -->
        <translate
            android:duration="1000"
            android:fillAfter="true"
            android:fromXDelta="0%p"
            android:startOffset="300"
            android:toXDelta="75%p" />
        <translate
            android:duration="1000"
            android:fillAfter="true"
            android:fromYDelta="0%p"
            android:startOffset="1100"
            android:toYDelta="70%p" />
        <translate
            android:duration="1000"
            android:fillAfter="true"
            android:fromXDelta="0%p"
            android:startOffset="1900"
            android:toXDelta="-75%p" />
        <translate
            android:duration="1000"
            android:fillAfter="true"
            android:fromYDelta="0%p"
            android:startOffset="2700"
            android:toYDelta="-70%p" />
    
        <!-- Rotate 360 degrees -->
        <rotate
            android:duration="1200"
            android:fromDegrees="0"
            android:interpolator="@android:anim/cycle_interpolator"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:startOffset="3800"
            android:toDegrees="360" />
    
    </set>
    • Together
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true"
        android:interpolator="@android:anim/linear_interpolator">
    
        <!-- Move -->
        <scale
            android:duration="4000"
            android:fromXScale="1"
            android:fromYScale="1"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="3"
            android:toYScale="3" />
    
        <!-- Rotate 180 degrees -->
        <rotate
            android:duration="1000"
            android:fromDegrees="0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toDegrees="360" />
    
    </set>

    Các thuộc tính bạn cần phải biết:
    android:duration – Thời gian hoàn thành.
    android:startOffset – Thời gian chờ trước khi một animaiton bắt đầu và thường được sử dụng khi có nhiều animation.
    android:repeatMode – Thiết lập lặp lại animation.
    android:repeatCount – Xác định số lần lặp lại animation. Nếu bạn thiết lập giá trị này là infinite thì animation sẽ lặp lại lần vô hạn.
    android:interpolator – Tỷ lệ thay đổi animation.
    android:fillAfter – Xác định liệu có áp dụng việc chuyển đổi đối tượng về trạng thái ban đầu sau khi một animation đã hoàn thành hay không.

    Các thành phần chính trong interpolator để các bạn dùng cho phù hợp:

    • accelerateDecelerateInterpolator: Tốc độ thay đổi bắt đầu và kết thúc chậm nhưng tăng tốc qua giữa.
    • accelerateInterpolator: Tốc độ thay đổi bắt đầu chậm, sau đó tăng tốc.
    • anticipateInterpolator: Bắt đầu một khoảng lùi lại sau đó bay về phía trước.
    • anticipateOvershootInterpolator: Bắt đầu lùi lại, bay về phía trước và vượt qua giá trị đích, sau đó lùi lại giá trị đích.
    • bounceInterpolator: Sau khi đến vị trí giá trị cuối thì quay lại giá trị ban đầu
    • cycleInterpolator: Lặp lại hoạt ảnh động theo một số chu kỳ xác định. Tốc độ thay đổi theo mô hình hình sin.
    • decelerateInterpolator: Hoạt ảnh có tốc độ thay đổi bắt đầu nhanh chóng, sau đó giảm tốc.
    • linearInterpolator: Tốc độ thay đổi hoạt ảnh là không đổi(tuyến tính).

    Các cú pháp cho các bạn tham khảo:

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@[package:]anim/interpolator_resource"
        android:shareInterpolator=["true" | "false"] >
        <alpha
            android:fromAlpha="float"
            android:toAlpha="float" />
        <scale
            android:fromXScale="float"
            android:toXScale="float"
            android:fromYScale="float"
            android:toYScale="float"
            android:pivotX="float"
            android:pivotY="float" />
        <translate
            android:fromXDelta="float"
            android:toXDelta="float"
            android:fromYDelta="float"
            android:toYDelta="float" />
        <rotate
            android:fromDegrees="float"
            android:toDegrees="float"
            android:pivotX="float"
            android:pivotY="float" />
        <set>
            ...
        </set>
    </set>
    • Fade In
    • Fade Out
    • Blink
    • Zoom In
    • Zoom Out
    • Rotate
    • Move
    • Slide Up
    • Slide Down
    • Sequential
    • Together

    Link tham khảo:

    • https://developer.android.com/training/animation