Tag: ios

  • Apple Developer Account: Sự khác nhau giữa tài khoản developer thông thường và tài khoản doanh nghiệp

    Apple Developer Account: Sự khác nhau giữa tài khoản developer thông thường và tài khoản doanh nghiệp

    Bạn là một nhà phát triển ứng dụng của Apple, bạn muốn ứng dụng mình phát triển được bày ở trên cửa hàng ứng dụng (App Store) của Apple, hay bạn muốn phát triển một ứng dụng nội bộ chỉ dành riêng cho nhân viên trong công ty, tổ chức của bạn. Để thực hiện được việc này bạn cần phải có một tài khoản nhà phát triển của Apple cung cấp. Tuy nhiên mỗi một loại tài khoản lại có một mục đích khác nhau. Nếu bạn đang băn khoăn về việc chọn tài khoản nào phù hợp với trường hợp của mình thì ở bài viết này mình sẽ giải đáp thắc mắc đó.

    Có những loại tài khoản nào?

    Apple cung cấp hai loại tài khoản: Apple Developer ProgramApple Developer Enterprise Program. Vậy chúng ta nên chọn tài khoản nào cho ứng dụng của mình?

    Apple Developer Program

    Tài khoản này dành cho các nhà phát triển hoặc công ty muốn cho phép phân phối qua App Store cho bất kỳ ai. Các bản dựng được phát hành trong quá trình phát triển vẫn được cài đặt qua App Center.

    Ưu điểm

    • Giá rẻ nhất $99 một năm
    • Có thể đẩy ứng dụng lên cửa hàng ứng dụng của Apple cho tất cả mọi người tiếp cận và tải xuống
    • Bạn có thể thực hiện build/test trên 100 thiết bị khác nhau, điều này khá là tiện lợi cho việc phát triển ứng dụng của bạn
    • Ứng dụng của bạn có thể tìm kiếm được bằng các công cụ tìm kiếm khác nhau như Google, Bing …

    Nhược điểm

    • Trong quá trình phát triển các thiết bị được phép bị giới hạn ở 100 thiết bị, các thiết bị này cần phải đăng ký trước khi cài đặt các bản buil.
    • Do nó được công khai cho tất cả mọi người có thể tải xuống và sử dụng, nên nó sẽ không phù hợp với các ứng dụng nội bộ chỉ dành riêng cho nhân viên
    • Ứng dụng của bạn phải đảm bảo các yêu cầu khắt khe của Apple
    • In-app purchase bắt buộc phải qua Apple Payment center, Apple sẽ thu 30% phí.

    Chọn loại tài khoản này nếu bạn có ý định đưa ứng dụng của mình lên cửa hàng ứng dụng của Apple để tất cả mọi người có thể tiếp cận và tải xuống app của bạn.

    Bạn có thể xem thêm thông tin về chương trình tài khoản này ở đây

    Apple Developer Enterprise Program

    Đây là loại tài khoản cho phép các tổ chức lớn phát triển và triển khai các ứng dụng độc quyền, sử dụng nội bộ cho nhân viên của họ. Chương trình này dành cho các trường hợp sử dụng cụ thể yêu cầu phân phối riêng tư trực tiếp cho nhân viên sử dụng hệ thống nội bộ an toàn hoặc thông qua giải pháp Quản lý thiết bị di động.

    Ưu điểm

    • Không bị giới hạn số lượng thiết bị cài đặt
    • Các thiết bị không cần đăng ký trước để có thể cài đặt ứng dụng
    • Ứng dụng của bạn không cần phải tuân theo các quy tắc của Apple và không bị Apple review.
    • Vì ứng dụng không cần thiết lập trong cửa hàng ứng dụng Apple nên bạn không cần điền mô tả ứng dụng đầy đủ, tạo hình ảnh để quảng cáo ứng dụng hoặc tạo chính sách bảo mật công khai, v.v.
    • In-App purchases không cần thiết phải qua Apple Payment center vì vậy không phải chịu phí của Apple
    • Ứng dụng của bạn sẽ không bị các bên khác tìm kiếm được thông qua các công cụ tìm kiếm, vì vậy nó bảo mật hơn

    Nhược điểm

    • Người dùng sẽ phải cài lại ứng dụng hàng năm trước khi hồ sơ bị hết hạn, trừ phi bạn sử dụng giải pháp MDM(Mobile Devices Manager)
    • Việc cài đặt ứng dụng phức tạp hơn vì người dùng phải cài đặt, tin cậy nhà phát triển thay vì tự động như khi tải trên App Store
    • Giá cao nhất $299 một năm

    Bạn nên chọn loại tài khoản này nếu bạn cần phát triển các ứng dụng nội bộ, độc quyền dành riêng cho nhân viên trong công ty, tổ chức mà không muốn đẩy ứng dụng lên cửa hàng ứng dụng của Apple.

    Bạn có thể xem thêm thông tin về chương trình tài khoản này ở đây

  • Unit Tests in Swift

    Unit Tests in Swift

    Bạn đang tìm một phương pháp để tăng chất lượng source code? Bạn đang gặp vấn đề về việc source code của bạn có quá nhiều bug? Unit tests là một trong những lựa chọn giúp bạn hạn chế vấn đề đó.

    Hiện nay rất nhiều dự án yêu cầu viết Unit tests nhằm mục đích đảm bảo chất lượng source code, vì vậy bài viết này mình sẽ chia sẻ với các bạn về Unit Tests trong Swift để các bạn có thể trang bị cho mình được một kĩ năng mới, để có thể sẵn sàng và tự tin chiến các dự án hiện tại hoặc trong tương lai.

    Unit Testing là gì?

    Unit tests là tự động chạy và kiểm thử một đoạn mã để đảm bảo nó hoạt động đúng như dự định và đúng với tài liệu yêu cầu.

    Unit tests trong ngôn ngữ lập trình là việc viết các func test để đảm bảo source code hoạt động đúng như tài liệu yêu cầu. Với một đầu vào cụ thể sẽ cho ra một đầu ra cụ thể như tài liệu yêu cầu. Việc viết Unit tests để kiểm tra source code của bạn giúp bạn tự tin hơn khi release hay tái cấu trúc source code, vì bạn sẽ đảm bảo source code của mình chạy đúng mong đợi khi bạn chạy bộ test case của bạn thành công.

    Các quan điểm trái ngược về Unit tests

    Hiện nay có rất nhiều quan điểm trái ngược nhau về việc một dự án có cần phải viết Unit Tests hay không? Rất nhiều Developer thì cho rằng việc viết test tốn quá nhiều thời gian nó làm ảnh hưởng tới việc bàn giao công việc đúng thời hạn. Một số Developer thì cho rằng việc viết Unit tests không đem lại quá nhiều lợi ích mà công việc lại lặp đi lặp lại quá nhàm chán. Tuy nhiên theo mình nếu bạn viết Unit tests đúng cách thì sẽ giúp bạn giảm được thời gian phát triển ứng dụng, tuy thời gian phát triển ban đầu có tăng thêm nhưng bạn sẽ giảm được số lượng bug cơ bản có thể xảy ra từ đó giảm thời gian fix bugs và hạn chế các lỗi phát sinh sau khi sửa.

    Viết Unit Tests với Xcode

    Để giúp các nhà phát triển có thể viết Unit Tests cho các ứng dụng của họ, Apple đã tạo ra XCTest framework. Giờ đây các nhà phát triển có thể sử dụng framework này đê viết Unit tests cũng như chạy các test case để kiểm tra chất lượng source code của họ.

    Một số hàm dùng để kiểm tra của XCTest Framework

    1. XCTAssert(): Nó khá thông dụng có thể sử dụng trong hầu hết các trường hợp, VD: XCTAssert(result == 5)
    2. XCTAssertTrue(): test case của bạn sẽ pass nếu biểu thức kiểm tra có kết quả là true. VD: XCTAssertTrue(view.isHidden)
    3. XCTAssertEqual(a, b), XCTAssertNotEqual: Kiểm tra xem giá trị của 2 biểu thức.
    4. XCTAssertFalse(): ngược với XCTAssertTrue
    5. XCTAssertGreaterThan(a, b): Thường dùng khi bạn kiểm tra 2 giá trị số
    6. XCTAssertGreaterThanOrEqual(a, b): tương tự như XCTAssertGreaterThan, nếu 2 giá trị = nhau thì test case vẫn pass
    7. XCTAssertLessThan, XCTAssertLessThanOrEqual: Tương tự như mục 5 và 6
    8. XCTAssertNil(a), XCTAssertNotNil: Dùng khi cần kiểm tra một var/func có nil hay không
    9. XCTAssertNoThrow() Dùng khi cần kiểm tra xem func có throw lỗi hay không
    10. XCTAssertThrowsError() dùng khi cần kiểm tra func có throw error và kiểm tra được error

    Để các bạn dễ hình dung hơn mình sẽ đưa ra một ví dụ như sau:

    Tài liệu yêu cầu bạn phải viết một hàm để tính chu vi của hình chữ nhật khi biết chiều dài và chiều rộng của nó. Ta biết chu vi của hình chữ nhật chính là tổng tất cả các cạnh của nó, vì vậy ta có thể sẽ viết func như sau:

    class Regtangle {
        // hàm tính chu vi hình chữ nhật
        class func perimeter(width: Int, height: Int) -> Int {
           (width + height) * 2
        }
    }

    Để viết bắt đầu viết test case chúng ta sẽ cần tạo file test như sau:

    Chuột phải vào thư mục cần tạo file -> chọn new file -> Unit Test Case Class -> Next

    Đặt tên cho file test, trong trường hợp này mình đang cần test class Regtangle nên mình đặt tên như hình -> Next

    Sau khi tạo file chúng ta XCode sẽ tạo sẵn cho chúng ta một số đoạn code cơ bản như sau:

    import XCTest
    // thêm target cần test
    @testable import UTXcode14
    
    final class RegtangleTests: XCTestCase {
    
        override func setUpWithError() throws {
            // Put setup code here. This method is called before the invocation of each test method in the class.
        }
    
        override func tearDownWithError() throws {
            // Put teardown code here. This method is called after the invocation of each test method in the class.
        }
    
        func testExample() throws {
            // This is an example of a functional test case.
            // Use XCTAssert and related functions to verify your tests produce the correct results.
            // Any test you write for XCTest can be annotated as throws and async.
            // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
            // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
        }
    
        func testPerformanceExample() throws {
            // This is an example of a performance test case.
            self.measure {
                // Put the code you want to measure the time of here.
            }
        }
    
    }
    

    func testPerformanceExample() đây là hàm để kiểm tra hiệu suất của đoạn code, nếu bạn không cần kiểm tra thì bỏ nó đi giúp mỗi lần chạy test của bạn sẽ nhanh hơn đáng kể.

    Bây giờ chúng ta đã có thể viết test case để kiêm tra class Regtangle. Đối với func tính chu vi như vậy thì ta sẽ cần dựa vào yêu cầu và phân tích tích bài toán một chút.

    Chúng ta hiểu rằng chiều cao và chiều rộng của hình chữ nhật phải là số lớn hơn 0, chu vi của hình chữ nhật thì bằng tổng chiều dài bốn cạnh, vậy nên chúng ta sẽ cần viết các test case với trường hợp như sau:

    1. width và height đều lớn hơn 0: Đầu ra là (width + height) * 2
    2. width <= 0, height > 0: đầu ra cần phải là một thông báo lỗi
    3. width > 0, height <= 0: Đầu ra cần phải là một thông báo lỗi
    4. width <= 0, height <= 0: Đầu ra cần phải là một thông báo lỗi

    Đối với case số 1 yêu cầu dữ liệu đầu vào cả width và height đều phải là một số > 0 thì ta viết như sau:

       // case width > 0 and height > 0
       func test_perimeter_case1() {
            // input
            let width: Int = 3
            let height: Int = 2
            // expectation
            let expectation = 10
            // run code
            let result = Regtangle.perimeter(width: width, height: height)
            
            // verify
            XCTAssertEqual(result, expectation)
        }

    Chạy test ta thu đươc kết quả như hình, dấu tích xanh thể hiện kết quả với expectation là bằng nhau, có nghĩa là trong trường hợp này hàm Regtangle.perimeter() đã chạy đúng.

    Tiếp theo chúng ta sẽ viết tiếp test case số 2, width <=0 và height > 0, đây là trường hợp chiều rộng nhỏ hơn 0 vì vậy nó là một trường hợp lỗi, mình sẽ mong đợi một thông báo lỗi “Width must be greater than zero”. Vậy nên mình viết code như hình dưới

    Lúc này Xcode sẽ báo lỗi như hình trên là do chúng ta đang so sánh 2 kiểu dữ liệu khác nhau. Quay lại hàm tính chu vi hình chữ nhật thì chúng ta thấy không có logic kiểm tra width và height điều này khiến cho func này không đảm bảo tính đúng đắn của nó.

    Vậy viết func tính chu vi như nào mới đúng? các bạn có thể tham khảo một số cách viết của mình như sau:

    Cách 1: Sử dụng Result để trả về kết quả

    // equatable để tiện cho việc so sánh khi viết Unit test
    struct MyError: Error, Equatable {
        let message: String
    }
    
    class Regtangle {
        // hàm tính chu vi hình chữ nhật
        class func perimeter(width: Int, height: Int) -> Result<Int, MyError> {
            if width <= 0 && height <= 0 {
                return .failure(MyError(message: "Width and height must be greater than zero"))
            } else if width <= 0 {
                return .failure(MyError(message: "Width must be greater than zero"))
            } else if height <= 0 {
                return .failure(MyError(message: "Height must be greater than zero"))
            } else {
                let perimeter = (width + height) * 2
                return .success(perimeter)
            }
        }
    }

    Cách 2: Sử dụng throw để đẩy ra lỗi

    struct MyError: Error, Equatable {
        let message: String
    }
    
    class Regtangle {
        // hàm tính chu vi hình chữ nhật
        class func perimeter(width: Int, height: Int) throws -> Int {
            if width <= 0 && height <= 0 {
                throw MyError(message: "Width and height must be greater than zero")
            } else if width <= 0 {
                throw MyError(message: "Width must be greater than zero")
            } else if height <= 0 {
                throw MyError(message: "Height must be greater than zero")
            } else {
                return (width + height) * 2
            }
        }
    }

    Trong bài viết này mình sẽ hướng dẫn các bạn viết test case khi sử dụng throw, mình sẽ viết tổng cộng 7 test cases để thực hiện test func này, trong đó bao gồm 4 test cases để test logic chính và 3 cases để test giá trị biên. Cụ thể mình sẽ thực hiện như sau:

    import XCTest
    @testable import UTXcode14
    
    final class RegtangleTests: XCTestCase {
    
        override func setUpWithError() throws {
            // Put setup code here. This method is called before the invocation of each test method in the class.
        }
    
        override func tearDownWithError() throws {
            // Put teardown code here. This method is called after the invocation of each test method in the class.
        }
    
        // case width > 0 and height > 0
        func test_perimeter_case1() throws {
            // precontidtion
            let width: Int = 3
            let height: Int = 2
            // expectation
            let expectation = 10
            // run code
            XCTAssertNoThrow(try Regtangle.perimeter(width: width, height: height))
            let result = try Regtangle.perimeter(width: width, height: height)
            
            // verify
            XCTAssertEqual(result, expectation)
        }
        
        // case width < 0 and height > 0
        func test_perimeter_case2() throws {
            // precontidtion
            let width: Int = -3
            let height: Int = 2
            // expectation
            let expectation = MyError(message: "Width must be greater than zero")
            // run code
            XCTAssertThrowsError(try Regtangle.perimeter(width: width, height: height)) { error in
                XCTAssertEqual(error as? MyError, expectation)
            }
        }
        
        // case width > 0 and height < 0
        func test_perimeter_case3() throws {
            // precontidtion
            let width: Int = 3
            let height: Int = -2
            // expectation
            let expectation = MyError(message: "Height must be greater than zero")
            // run code
            XCTAssertThrowsError(try Regtangle.perimeter(width: width, height: height)) { error in
                XCTAssertEqual(error as? MyError, expectation)
            }
        }
        
        // case width < 0 and height < 0
        func test_perimeter_case4() throws {
            // precontidtion
            let width: Int = -3
            let height: Int = -2
            // expectation
            let expectation = MyError(message: "Width and height must be greater than zero")
            // run code
            XCTAssertThrowsError(try Regtangle.perimeter(width: width, height: height)) { error in
                XCTAssertEqual(error as? MyError, expectation)
            }
        }
        
        // case width = 0 and height > 0, test giá trị biên của width
        func test_perimeter_case5() throws {
            // precontidtion
            let width: Int = 0
            let height: Int = 2
            // expectation
            let expectation = MyError(message: "Width must be greater than zero")
            // run code
            XCTAssertThrowsError(try Regtangle.perimeter(width: width, height: height)) { error in
                XCTAssertEqual(error as? MyError, expectation)
            }
        }
        
        // case width = 0 and height > 0, test giá trị biên của height
        func test_perimeter_case6() throws {
            // precontidtion
            let width: Int = 2
            let height: Int = 0
            // expectation
            let expectation = MyError(message: "Height must be greater than zero")
            // run code
            XCTAssertThrowsError(try Regtangle.perimeter(width: width, height: height)) { error in
                XCTAssertEqual(error as? MyError, expectation)
            }
        }
        
        // case width = 0 and height = 0, test giá trị biên của cả width và height
        func test_perimeter_case7() throws {
            // precontidtion
            let width: Int = 0
            let height: Int = 0
            // expectation
            let expectation = MyError(message: "Width and height must be greater than zero")
            // run code
            XCTAssertThrowsError(try Regtangle.perimeter(width: width, height: height)) { error in
                XCTAssertEqual(error as? MyError, expectation)
            }
        }
    }

    Chạy test (Command U) chúng ta được kết quả như sau:

    Tất cả các test case của chúng ta đều passed, điều này chứng minh func của bạn đã đáp ứng hết tất cả yêu cầu mà bạn đặt ra

    Hi vọng bài viết sẽ giúp cho các bạn có thêm kiến thức để nâng cao năng lực của bản thân.

  • If / switch expression – Swift 5.9 (P.1)

    If / switch expression – Swift 5.9 (P.1)

    Chúng ta chắc là đã quá quen thuộc với ternary conditional operator ( toán tử ba ngôi ) như trên rồi nhỉ. Nhưng với swift 5.9, chúng ta đã có thêm một cách viết khác tường minh hơn đó là sử dụng if / switch. Và trong bài viết này chúng ta sẽ tìm hiểu về nó nhé.

    If / switch expression là gì?

    Với Swift 5.9. If và switch đã có thể được sử dụng dưới dạng biểu thức. Nói đơn giản thì toán tử ba ngôi được sử dụng như thế nào thì if và switch bây giờ đều có thể sử dụng như vậy.

    Ví dụ như bình thường chúng ta đang viết như thế này:

    Thì chúng ta hiện nay đã có thể viết như thế này:

    Đối với switch có vẻ như là mới mẻ và xịn xò hơn hẳn rồi nhỉ. Nhưng còn if thì sao. Trông cũng không khác gì đối với toán tử ba ngôi mà lại còn phải viết nhiều hơn. Vậy thì nó có gì khác biệt so với toán tử ba ngôi nhỉ.

    • Đầu tiên: Sử dụng if trông rõ ràng là giúp code được tường minh và dễ theo dõi hơn rồi. Đặc biệt là với những lúc mà có các điều kiện rẽ nhánh lồng nhau thì sự tường minh của if sẽ được thể hiện rõ hơn.
    • Thứ Hai: Toán tử ba ngôi thì kiểm tra kiểu dữ liệu một cách đồng thời còn if sẽ kiểm tra nó một cách độc lập. Nghe có chút trừu tượng nhỉ. Vậy thì chúng ta sẽ tới với ví dụ nhé:

    Ở đây chúng ta có thể thấy. Do toán tử ba ngôi kiểm tra kiểu dữ liệu một cách đồng thời vậy nên 1 ở đây hệ thống sẽ tự hiểu là 1.0. Với If thì không như vậy mà chúng ta cần viết rõ ra hơn.

    Lưu ý:

    Để sử dụng được if và switch như một biểu thức thì chúng ta cần lưu ý một vài yếu tố sau:

    • Với mỗi nhánh của if hay switch chỉ được thực thi duy nhất một biểu thức.
    • Mỗi biểu thức được tạo ra ở các nhánh đều phải cùng một kiểu dữ liệu.
    • If luôn đi kèm với else

    Và bài viết này chúng ta đã được tìm hiểu về If / switch expression ở trên swift 5.9. Vẫn còn rất nhiều thứ mới nữa ở swift 5.9 và chúng ta sẽ tiếp tục tìm hiểu chúng ở các phần tiếp theo nhé. Xin chân trọng cảm ơn!

  • Raw String in Swift

    Raw String in Swift

    Xin chào tất cả các bạn, lại là mình đây

    Hôm nay chúng ta sẽ nâng cấp thêm vũ khí giúp anh em iOS Developer tự tin chiến đấu hơn . Đây là một chủ đề khá nhỏ trong iOS & Swift nói chung, tuy nhiên nó lại có một tầm ảnh hưởng khá là lớn. Nên khi bạn nắm bắt được Raw String, thì có sẽ có thêm một công cụ khá là mạnh trong tay. Let’s goooooo !

    Raw String là gì?

    Raw String lần đầu được giới thiệu ở Swift 5, cho chúng ta khả năng viết chuỗi tự nhiên hơn, đặc biệt khi sử dụng dấu gạch chéo ngược và dấu ngoặc kép. Trong một số trường hợp, chẳng hạn như biểu thức Regex, chúng ta sẽ thấy được sức mạnh của Raw String.

    Swift 5 cung cấp cho chúng ta khả năng khai báo một dấu phân cách chuỗi tùy chỉnh bằng cách sử dụng ký hiệu "#" hay còn được gọi là dấu thăng. Khi bạn sử dụng "#" với một chuỗi, nó sẽ ảnh hưởng đến cách Swift hiểu các ký tự đặc biệt trong chuỗi: “\” không còn hoạt động như một ký tự để thoát chuỗi, vì vậy \n được hiểu là dấu gạch chéo ngược rồi đến chữ “n” thay vì ngắt dòng và \(variable)sẽ được bao gồm dưới dạng các ký tự đó.

    Công dụng của Raw String

    Đầu tiên hãy đi vào một ví dụ nhỏ

    let regularString = "\\Hello \\World"
    let rawString = #"\Hello \World"#

    Như bạn có thể thấy, ở string thứ 2 sử dụng # để đánh dấu đó là một Raw String, output của hai chuỗi này sẽ như nhau nhưng khi dùng Raw String trông có vẻ sáng sủa hơn nhỉ ?.

    Thêm ví dụ nữa cho mọi người thấy công dụng của Raw String:

    let swift4 = "This is \"Swift 4.x\"."
    print(swift4)
    let swift5 = #"This is "Swift 5.x"."#
    print(swift5)

    Trông có vẻ rõ ràng hơn rồi, khai báo & kết thúc một String với dấu #, ta có thể sử dụng các kí tự đặc biệt như là một kí tự bình thường trong chuỗi, giúp chúng ta không phải sử dụng thêm các dấu \ làm code trông khá là lú ?.

    Raw String với Variable

    let name = "Techover"
    let greeting = #"Hello, \#(name)!"#
    print(greeting)

    Với String bình thường, ta sẽ sử dụng cú pháp \(variableName) để đưa giá trị biến vào chuỗi. Còn với Raw String ta sẽ phải thêm dấu # vào nữa như ví dụ bên trên.

    Raw String với Multi-line

    let example = "Hello bro"
    let message = #"""
    This is rendered as text: \(example).
    This uses string interpolation: \#(example).
    """#
    print(message)

    Cũng khá là easy và tiện lợi nhỉ, không như String xuống dòng linh tinh cái là đi ngay ?‍?

    Raw String với dấu #

    Khi muốn sử dụng dấu # trong một Raw String sẽ khác một chút đó

    let str = #"My dog said "woof"#gooddog"#

    Xcode sẽ ngăn cản bạn thực hiện đoạn code trên. Vì nó sẽ xác định dấu # tiếp theo là kết thúc chuỗi Raw String rồi. Do đó, phần còn lại sẽ trở thành lỗi. Chế cháo đi một tí mới hết lỗi nè

    let str = ##"My dog said "woof"#gooddog"##
    print(str)

    Như trên, output của chúng ta sẽ có đầy đủ các dấu " & # luôn. Như vậy, khi ta khai báo bao nhiêu dấu # ở đầu, thì sẽ phải có bấy nhiêu dấu # ở cuối của Raw String. Lúc này, Raw String của ta mới có ý nghĩa.

    let zero = "This is a string"
    let one = #"This is a string"#
    let two = ##"This is a string"##
    let three = ###"This is a string"###
    let four = ####"This is a string"####

    Lan man một hồi giờ tổng kết lại nè

    Raw String hữu ích vì một lý do: Đơn giản hóa String, giúp chúng ta dễ đọc dễ tiếp cận và dễ dàng sửa chữa

    Đặc biệt trong các biểu thức Regex, khi mà chúng ta phải sử dụng rất nhiều kí tự đặc biệt, một ví dụ về một biểu thức Regex khá là nhiều dấu \

    let regex = try NSRegularExpression(pattern: "\\\\\\([^)]+\\)")

    Tuy nhiên với Raw String, chúng ta có thể bớt đi một nửa số dấu \ chúng ta sử dụng. Đơn giản biểu thức của chúng ta sẽ còn lại là:

    let regex = try NSRegularExpression(pattern: #"\\\([^)]+\)"#)

    Lời kết

    Như vậy là sau một hồi bàn luận về Raw String, hi vọng các bạn có thể áp dụng được vào code của mình để code của chúng ta ngày càng xịn sò hơn. Cảm ơn các bạn vì đã đọc bài viết này. Hẹn gặp lại các bạn ở các post tiếp theo ?

  • Hướng dẫn cách Fake GPS trên Simulator

    Hướng dẫn cách Fake GPS trên Simulator

    Hi, trong quá trình phát triển các ứng dụng iOS, đôi khi chúng ta sẽ được phát triển các tính năng liên quan tới bản đồ, một số ứng dụng còn cần phải sử dụng Location Service để hiển thị vị trí của người dùng. Vì vậy khi phát triển ứng dụng bạn sẽ cần phải kiểm tra ứng dụng của mình xem nó chạy đúng chưa, hiển thị đúng vị trí trên bản đồ chưa? Nếu chúng ta cầm thiết bị chạy đi chạy lại thì thật quá tốn công, tốn sức. Thấu hiểu nỗi khổ của các iOS developers Apple đã phát triển tính năng Fake GPS trên Simulator giúp cho việc kiểm tra ứng dụng của mình một cách dễ dàng và đỡ mất công mất sức hơn.

    Fake GPS trên Simulator – Di chuyển đến trụ sở chính của Apple

    Để bật tính năng này chúng ta mở Simulator lên, trên Menu bar của Simulator chọn Features -> Location -> Apple như hình dưới đây

    fake gps on simulator

    Kết quả chúng ta sẽ được vị trí GPS ở trụ sở của Apple.

    Fake GPS trên Simulator – Di chuyển đến vị trí bất kì trên bản đồ

    Việc fake GPS trên Simulator đến vị trí trung tâm Apple không giúp ích nhiều cho việc kiểm tra ứng dụng của bạn, vì vậy mình sẽ hướng dẫn các bạn cách để Fake GPS trên Simulator đến vị trí bất kì trên bản đồ. Để làm được điều này chúng ta cần Kinh độ và Vĩ độ của điểm chúng ta cần fake GPS. Các bạn có thể lên Google Map để lấy toạ độ nhá. Ví dụ mình sẽ lấy toạ độ của Đền Ngọc Sơn trên Hồ Hoàn Kiếm ở Hà Nội.

    Để ý hình mình có khoanh, Ta sẽ lấy toạ độ (lat, long) theo thứ tự từ trái qua phải là Latitude và Longitude ở trên URL của google map. Trong ví dụ này Latitude: 21.0298318Longitude: 105.8532851.

    Khi lấy được toạ độ cần fake GPS trên Simulator rồi thì ta sẽ làm như sau:
    Trên Menu bar của Simulator -> chọn Features -> Location -> Custom Location… -> Điền Latitude và Longitude vào và bấm OK như hình dưới.

    Vậy là chúng ta đã có thể fake GPS đên mọi nơi mà chúng ta cần để kiểm tra ứng dụng của mình.

    NOTE: Có một lưu ý là khi các bạn copy Latitude và Longitude từ Google đôi khi ngôn ngữ không giống với simulator nên simulator nó không nhận, bạn hãy để ý và thay dấu chấm “.” <-> thành dấu phẩy “,” hoặc ngược lại nhé.

    Fake GPS trên Simulator – Fake trường hợp vị trí chuyển động liên tục.

    Trong thực tế có một số ứng dụng yêu cầu ta phải hiển thị vị trí của người dùng chính xác và liên tục, tuy nhiên để kiểm tra trường hợp này một cách mượt mà thì sử dụng các cách phía trên là bất khả thi và tốn rất nhiều thời gian, vì vậy apple đã cung cấp một số cách để thuận tiện cho việc kiểm tra vị trí di chuyển đó là: City Run, City Bicycle Ride và Freeway Drive.
    Trên Menu bar của Simulator -> chọn Features -> Location -> City Run hoặc City Bicycle Ride hoặc Freeway Drive.

    Vậy là bạn đã có thể ngồi 1 chỗ và test được việc di chuyển trên ứng dụng của mình mà không tốn một giọt mồ hôi nào.

    Hiện tại Apple chỉ hỗ trợ 3 loại di chuyển như trên vì vậy nó khá hạn chế trong việc kiểm thử, trong trường hợp các bạn cần linh hoạt hơn thì chúng ta có thể sử dụng file GPX để fake GPS trên Simulator chủ động hơn, tuy nhiên cách này sẽ phức tạp hơn một chút. Vì vậy mình sẽ hướng dẫn các bạn ở bài viết tiếp theo nhé!

  • IBInspectable and IBDesignable in Swift

    IBInspectable and IBDesignable in Swift

    Xin chào mọi người, bài viết này mình xin giới thiệu với các bạn về IBInspectable và IBDesignable trong swift.

    IBInspectable

    Khi các bạn thực hiện code UI bằng Interface builder của Xcode, nó sẽ hiển thị cho các bạn một số các thuộc tính cơ bản để các bạn có thể chỉnh sửa. Hình dưới đây là Atributes Inspector của UIView.

    Inspectable-and-IBDesignable

    Có bao giờ bạn muốn thêm các thuộc tính của một UI trong tab Attributes Inspector chưa? Nếu bạn có ý định này thì xin chúc mừng. IBInspectable sẽ giúp bạn làm được việc này.

    IBInspectable giúp cho bạn có thể thêm được rất nhiều các thuộc tính vào tab Attributes Inspector từ đó giúp các bạn dễ dàng chỉnh sửa nó trên Interface builder của Xcode một cách dễ dàng.

    Vậy để sử dụng IBInspectable thêm các thuộc tính vào Interface builder của xCode chúng ta làm như sau:

    Ở đây mình sẽ làm một ví dụ để thêm thuộc tính cho UIView

    Như bạn đã biết thì UIView trên Interface builder không có các thuộc tính như cornerRadius(bo góc), borderColor(màu viền), borderWidth(độ rộng viền)… Vậy trong ví dụ này mình sẽ thêm các thuộc tính này vào Attributes inspector của UIView.

    Đầu tiên mình tạo một class CommonView kế thừa lại UIView như sau:

    class CommonView: UIView {
        // thêm thuộc tính để bo góc cho View
        @IBInspectable
        var cornerRadius: CGFloat = 4 {
            didSet {
                clipsToBounds = true
                layer.cornerRadius = cornerRadius
            }
        }
        
        // thêm thuộc tính để đặt độ dày của viền cho View
        @IBInspectable
        var borderWidth: CGFloat = 1 {
            didSet {
                layer.borderWidth = borderWidth
            }
        }
        // thêm thuộc tính để sửa màu viền cho View
        @IBInspectable
        var borderColor: UIColor = .red {
            
            didSet {
                layer.borderColor = borderColor.cgColor
            }
        }
    }

    Để sử dụng CommonView thì chúng ta mở file Storyboard hoặc file xib lên và kéo một UIView vào, sau đó đổi class từ UIView(mặc định) sang CommonView, vậy là xong.

    Kết quả chúng ta sẽ được như sau:

    Inspectable-and-IBDesignable
    Các thuộc tính Corner Radius, Border Witdh, Border color đã được thêm vào Attributes Inspector băng thuộc tính @IBInspectable

    Vậy là chúng ta đã thêm được các thuộc tính vào Attributes Inspector của Xcode, tuy nhiên chúng ta cần phải build app lên thì mới thấy sự thay đổi. Sao nó không thay đổi ngay khi chúng ta sửa giá trị như các thuộc tính khác? Vì một mình IBInspectable thì không làm được vì vậy các nhà phát triển của Apple mới đẻ ra IBDesignable để làm việc này.

    IBDesignable

    IBDesignable cho phép chúng ta xem trực tiếp các thay đổi của view trong storyboard hoặc trong file xib mà không cần phải run ứng dụng.

    Để sử dụng IBDesignable thì chúng ta chỉ cần thêm @IBDesignable vào đằng trước class mà chúng ta muốn và override lại func prepareForInterfaceBuilder() để nó update giá trị và hiển thị lên trên Interface builder, trong ví dụ này mình để nó ở trước class CommonView của mình như sau:

    @IBDesignable
    class CommonView: UIView {
        // set giá trị để hiển thị cho Interface builder
        override func prepareForInterfaceBuilder() {
            setupView()
        }
        // setup view
        private func setupView() {
            self.layer.cornerRadius = cornerRadius
            self.layer.borderWidth = borderWidth
            self.layer.borderColor = borderColor.cgColor
        }
    
        @IBInspectable
        var cornerRadius: CGFloat = 4 {
            didSet {
                clipsToBounds = true
                layer.cornerRadius = cornerRadius
            }
        }
        
        @IBInspectable
        var borderWidth: CGFloat = 1 {
            didSet {
                layer.borderWidth = borderWidth
            }
        }
        
        @IBInspectable
        var borderColor: UIColor = .red {
            
            didSet {
                layer.borderColor = borderColor.cgColor
            }
        }
    }

    CHÚ Ý: Bạn cần phải override lại func prepareForInterfaceBuilder() và set lại các thuộc tính để nó có thể update giá trị cho interface builder.

    Bây giờ chúng ta chỉ cần kéo UIView vào là nó sẽ tự apply các thuộc tính và khi sửa tại Attributes inspector thì nó sẽ được update ngay mà không cần phải build ứng dụng để kiểm tra lại UI.

    Kết quả chúng ta được như hình dưới đây:

    IBInspectable and IBDesignable uiview

    Trong trường hợp các bạn muốn làm common và không cho sửa thuộc tính nào trên interface builder thì bạn chỉ cần bỏ IBInspectable của thuộc tính đó đi là được.

    Tổng kết

    Vậy là mình đã giới thiệu cho các bạn một phương pháp để thực hiện làm common rất hiệu quả và tiết kiệm thời gian khi làm ứng dụng di động trên iOS. Từ ví dụ common view này chúng ta có thể phát triên cho các common khác như UILabel, UIButton … Mình hi vọng bài viết sẽ giúp ích cho các bạn trong quá trình học hỏi và phát triển ứng dụng iOS.

  • Chọn FileOwner hay Custom class khi tạo một Custom UIView

    Chọn FileOwner hay Custom class khi tạo một Custom UIView

    Thông thường chúng ta có 3 cách dùng khi tạo 1 file custom UIView. Chúng ta hãy cùng làm theo cả 3 cách sau đây để xem ưu, nhược điểm của từng cái nhé.

    A. Set File owner

    B. Set Custom class

    C. Set cả hai File owner và Custom class

    Giờ hãy tạo 1 Tabbar Controller chứa 3 view controller để thực hành add custom view theo 3 cách trên nhé.

    Cách A. Thêm custom view dùng File owner

    – Trước hết, ta cần hiểu FIleOwner là gì? File owner là một controller object giúp ta kết nối code với các elements, UI trong file nib. 

    • Bước 1: Tạo file xib và class “CustomViewA”

    Views có thể được tạo bằng hai cách: 

    + Tạo view bằng code, ta gọi hàm init(frame: CGRect)

    + Nếu dùng file nib, khi file nib được load sẽ gọi hàm  init?(coder: NSCoder) 

    Nếu muốn support cả 2 cách, ta implement cả 2 hàm trên

    Ở ví dụ này ta tạo custom view bằng cách dùng file xib chứ không dùng code

    Tạo file xib CustomViewA.xib

    Tạo file CustomViewA.swift. Ở file CustomViewA.swift, ta setup code như sau:

    import UIKit
    
    final class CustomViewA: UIView {
        override init(frame: CGRect) {
            super.init(frame: frame)
            initView()
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            initView()
        }
        
        private func initView() {
            // Để load file nib, ta tạo một instance của UINib
            // Ta set bundle là nil để dùng bundle default
            let nib = UINib(nibName: "CustomViewA", bundle: nil)
            
            // Khởi tạo contents trong file nib, objects là 1 array của tất cả top-level objects trong file nibs
            let objects = nib.instantiate(withOwner: self, options: nil)
            
            // Lưu ý: Vì list object trả ra từ file nib chưa thuộc view hierarchy nào nên ta phải addSubview vào CustomViewA
            if let view = objects.first as? UIView {
                view.backgroundColor = .clear
                addSubview(view)
                view.frame = bounds
                view.backgroundColor = .orange
            }
        }
    }
    • Bước 2: Set class cho file owner

    Mở file xib, chọn File’s Owner, rồi set Custom class là “CustomViewA” vừa tạo

    • Bước 3: Mở “ViewControllerA” trong storyboard, kéo một view vào rồi set Custom Class cho nó là “CustomViewA”.

    Chạy app và ta đã tạo được một custom view, kết quả như hình bên dưới.

    Cách B. Thêm custom view dùng Custom Class

    • Bước 1:

    Tạo file CustomViewB giống file CustomViewA như ở cách A

    • Bước 2:

    Thay vì FileOwner như cách A, ta chọn View ở dưới, rồi set Custom Class là “CustomViewB

    • Bước 3: Ta cũng kéo view “CustomViewB” vừa tạo vào ViewControllerB như bước 3 cách A, rồi chạy app

    Oops!!! Cách này dùng thì lại bị crash. Tại sao lại thế???

    Lý do vì khi load ViewControllerB, app sẽ call  “init?(coder: NSCoder)” của custom view (CustomViewB), tiếp theo sẽ call  “initView()“. Trong hàm này ta dùng “instantiate(withOwner)” để load file Nib.

    Tuy nhiên, vì ở bước 2 ta đã set top-level view của file nib là class CustomViewB nên nó sẽ load lại file nib và call hàm “init?(coder: NSCoder)” một lần nữa. Cứ thế nó sẽ tạo 1 vòng loop vô hạn khiến app bị crash.

    Để tránh trường hợp này, ta bỏ hàm “initView()” ra ngoài, không để trong “init?(coder: NSCoder)” or “init(frame: CGRect)” nữa.

    • Bước 4: Move hàm load xib ra bên ngoài View Controller (parent view controller)
    import UIKit
    
    class ViewControllerB: UIViewController {
        
        var viewCustomB: CustomViewB!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            initView()
        }
        
        private func initView() {
            let nib = UINib(nibName: "CustomViewB", bundle: nil)
            let objects = nib.instantiate(withOwner: nil, options: nil)
    
            if let firstView = objects.first as? CustomViewB {
                firstView.backgroundColor = .red
                viewCustomB = firstView
                view.addSubview(viewCustomB)
                viewCustomB.translatesAutoresizingMaskIntoConstraints = false
                viewCustomB.widthAnchor.constraint(equalToConstant: 240).isActive = true
                viewCustomB.heightAnchor.constraint(equalTo: viewCustomB.widthAnchor).isActive = true
                viewCustomB.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
                viewCustomB.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
            }
        }
    }

    Chạy app và ta đã tạo được một custom view, kết quả như hình bên dưới.

    Như vậy ta vẫn có thể add custom view bằng cách B này, nhưng nó có nhược điểm:

    • Ta sẽ phải load file nib (CustomViewB.xib) mọi nơi mà ta muốn dùng nó và sẽ gây ra duplicate code.

    Cách C. Thêm custom view dùng Custom Class và FileOwner

    • Bước 1: Tạo file CustomViewC.swift và CustomViewC.xib giống file CustomViewA như ở cách A
    • Bước 2: Tạo 1 button ở giữa view
    • Bước 3: Lần này, ta set CustomClass của view là “CustomViewC”, còn set FileOwner là parent class của nó (ViewControllerC)
    • Bước 4: Để logic load file xib ở parent class (để tránh crash như cách B) và kéo IBAction của button vào cả parent và custom view class

    Khi tap button, ta thấy debug đều chỉ ra cùng là một object.

    ==> Ok vậy ta đã thử qua 3 cách để tạo một custom view, ta có thể thấy rằng cách A đầu tiên dùng FileOwner là tiện lợi nhất

    • Dễ reuse trong toàn app thông qua code, xib hoặc storyboards
    • Không dính crash, tránh duplicate code như cách B

    Sau đây là một số tip mà các bạn chắc cũng đã biết =)))

    Tip1: Ở trong file xib, bạn có thể chọn Size “Freeform” trong Attribute Inspector để resize view

    Tip2: Trong cách A, ta có thể tạo một outlet contentView là top-level view của file xib, từ đó không cần phải get first object khi instantiate xib file nữa.

    Bài viết có tham khảo nguồn từ 2 link dưới đây, ở đó họ sẽ giải thích chi tiết view là gì, được khởi tạo như thế nào, file nib là gì, cách dùng customview bằng code hoặc file nib…

    https://github.com/codepath/ios_guides/wiki/Custom-Views#how-views-are-defined-and-instantiated

    https://medium.com/@bhupendra.trivedi14/understanding-custom-uiview-in-depth-setting-file-owner-vs-custom-class-e2cab4bb9df8

    Code: https://github.com/sonvuhwg/AddCustomView

    Have a nice day!

  • Architecture Pattern: VIPER trong iOS

    Architecture Pattern: VIPER trong iOS

    Xin chào các bạn, lại là DaoNM2 đây! Để tiếp tục series về Architecture patterns thì hôm nay mình xin giới thiệu cho các bạn một mẫu kiến trúc được sử dụng khá nhiều khi phát triển các ứng dụng di động đó là VIPER.

    VIPER là gì?

    VIPER là một mẫu kiến trúc để phát triền phần mềm, nó được sử dụng khá nhiều khi xây dựng các ứng dụng di động trên ngôn ngữ lập trình Swift. Nó được xây dựng dựa trên Clean Design Architecture. Các Modules trong VIPER được định hướng theo Protocol và mỗi chức năng, các thuộc tính input và output được thực hiện bằng các bộ quy tắc giao tiếp cụ thể.

    Các thành phần chính của VIPER architecture pattern

    VIPER là viết tắt của các chứ cái đầu trong các thành phần của nó, nó bao gồm View, Interactor, Presenter, Entity và Router. Các thành phần này sẽ tương tác với nhau như sơ đồ dưới đây:

    View

    Bao gồm các thành phần trong UIKit và ViewController, nó là nơi để hiển thị nội dung cho người dùng và nhận các tương tác từ người dùng sau đó gửi cho presenter để xử lí tiếp logic hiển thị. Trong mẫu kiến trúc này Presenter là tầng duy nhất có liên kết với View.

    Interactor

    Là nơi xử lý business logic của ứng dụng, nó sẽ thao tác với Entity, model, API fetcher và datastore. Khi nhận được request từ Presenter lúc này Interactor sẽ thực hiện logic để lấy dữ liệu tương ứng và trả về cho presenter.

    Trong VIPER mỗi một Interactor sẽ tương ứng với một Use case, nó tách biệt hoàn toàn với View vì vậy khả năng kiểm thử độc lập trên Interactor khá dễ dàng.

    Presenter

    Là nơi xử lý logic hiển thị của ứng dụng, khi nhận được request thay đổi hoặc hiển thị thông tin từ View nó sẽ thực hiện logic tương ứng để yêu cầu Interactor trả về data. Sau khi nhận được data nó sẽ format lại dữ liệu và trả về cho View để hiển thị chúng lên màn hình. Khi nhận được yêu cầu di chuyển màn hình Presenter sẽ thực hiện call Router để nó làm nốt nhiệm vụ điều hướng

    Entity

    Đây là các Data model, nó có nhiệm vụ tương tác với Interactor để trả dữ liệu về cho Presenter.

    Router

    Là nơi xử lí luồng của ứng dụng, nó làm nhiệm vụ điều hướng ứng dụng đến nơi mà người dùng cần. Khi Presenter nhận yêu cầu chuyển màn hình từ View, nó sẽ thực hiện logic hiển thị và thực hiện tương tác với Router để xử lí di chuyển luồng đúng với yêu cầu của View.

    Ưu điểm

    VIPER được chia nhỏ thành nhiều phần, các phần đảm nhiệm các vai trò và nhiệm vụ cố định, các thành phần tương tác với nhau dựa trên các quy định cụ thể vì vậy nó có khá nhiều ưu điểm

    • Các nhiệm vụ được chia đều ra cho các thành phần vì vậy việc maintain không còn quá rắc rối.
    • Việc kiểm thử (Unit test) cũng trở nên dễ dàng hơn vì giờ đây các thành phần đã được chia nhỏ và không liên kết chặt chẽ với View
    • Cấu trúc source trở nên dễ hiểu và rõ ràng hơn vì nó được chia theo từng use case và các phần được chia nhiệm vụ và trách nhiệm rõ ràng
    • Không gặp phải trường hợp một file có nội dung quá dài, vì vậy việc đọc source code của người cũng trở nên dễ hiểu hơn
    • Khá là hữu dụng với các ứng dụng lớn với team size lớn
    • Dễ dàng để mở rộng và bảo trì, các developer có thể đồng thời làm việc trên nó một cách trơn tru
    • Giảm số lượng conflict khi merge source code

    Nhược điểm

    • Do có nhiều thành phần và tương tác với nhau nên số file quản lý sẽ nhiều hơn so với các mẫu kiến trúc khác
    • Không dễ sử dụng cho người mới vì có nhiều ràng buộc và quy tắc cho từng phần, vì vậy cần thời gian để các member có thể tìm hiểu và thích nghi với mẫu kiến trúc này.
    • Một số thư viện bên thứ 3 không hỗ trợ kiến trúc này, vì vậy nếu không có lựa chọn nào khác lúc này nếu áp dụng thư viện vào ứng dụng nó sẽ phá vỡ kiến trúc ở các tính năng mà sử dụng thư viện này.

    Tổng kết

    Như mình đã phân tích ở trên VIPER có rất nhiều ưu điểm vì vậy nó rất đáng để các bạn tìm hiểu và sử dụng cho các dự án sắp tới. Tuy nhiên theo mình thì mẫu kiến trúc VIPER chỉ nên sử dụng cho những ứng dụng có kích thước vừa và lớn thì nó mới phát huy được tối đa sự hiệu quả. Đối với các dự án nhỏ nếu sử dụng VIPER architecture pattern thì nó lại trở nên quá cồng kềnh và không cần thiết.

    Mình hi vọng bài viết mình chia sẻ sẽ giúp các bạn có thêm lựa chọn khi bắt đầu một dự án mới!

    Chúc các bạn thành công!

  • Cách thêm Unity Framework vào project IOS

    Cách thêm Unity Framework vào project IOS

    Chào các bạn, nếu một ngày đẹp trời bạn nhận được 1 task tích hợp Unity hoặc đơn giản là bạn muốn thử tích hợp Unity vào project IOS thì hãy tham khảo thử bài viết dưới đây nhé !


    1. Tạo dự án Unity

    Đầu tiên để tích hợp ta cần có một cái project unity, sau đó ta export cái project unity này ra platform IOS, nếu bạn đã có project unity IOS để tích hợp rồi thì có thể bỏ qua bước này nhé

    ở đây mình tạo một project unity đơn giản, tiếp theo ta export project này ra platform IOS

    bên trong project unity chọn File -> Build Setting

    sudo gem install cocoapods

    ở đây ta chọn platform IOS -> để setting như hình bên dưới rồi nhấn build đợi một lúc sẽ ra màn hình chọn thư mục để lưu trữ

    Tiếp theo ấn Choose project unity sẽ tự export ra một project IOS có tên là unity

    Export project Unity ra swift IOS





















    2. Tạo dự án iOS

    ở đây ta tạo một project đơn giản để tích hợp, trong Xcode chọn File -> new project, ở đây mình để tên project là SimpleIOS

    sau khi xong các bước trên thì ta có 2 thư mục sau:

    • unity: được tạo bằng cách export trong project Unity dưới dạng một dự án iOS
    • SimpleIOS: project IOS chính cần tích hợp

    3. Tích hợp Unity với IOS

    Tới đây thì ta sẽ tạo một Workspace trong xcode để có thể add 2 project trên để tích hợp,

    trong xcode chọn File -> New -> Workspace

    ở đây bạn có thể đặt tên trùng với tên project IOS chính của mình hoặc một tên khác bất kỳ, ở đây mình tạo với tên là SimpleSwiftUnity

    Lưu ý: nếu project IOS chính của bạn muốn tích hợp đã có Workspace thì có thể dùng luôn không cần tạo thêm một Workspace mới

    sau khi tạo xong ta mở SimpleSwiftUnity.xcworkspace lên sau dó kéo tệp SimpleIOS.xcodeprojUnity-iPhone.xcodeproj và workspace chính

    Thêm dự án IOS
    Thêm dự án Unity IOS

    tới bước này thì cả 2 SimpleIOS.xcodeprojUnity-iPhone.xcodeproj đều thuộc 1 workspace

    Tiếp theo, nhấp vào dự án SimpleIOS chọn vào tab General cuộn xuống phần Frameworks, Libraries and Embedded Content . Nhấp vào nút + để add một framework mới.

    chọn UnityFramework.framework từ trong list và add vào dự án

    Tiếp theo, chọn thư mục Data trong Unity-iPhone project. Trong bảng điều khiển bên phải, bạn sẽ thấy phần Target Membership . Bạn cần tích chọn UnityFramework .

    Tới đây đã dủ các bước cấu hình, bây giờ mình sẽ thêm một số dòng code để show unity kia lên project IOS chính nhé !

    Tạo file UnityEmbeddedSwift.swift trong SimpleIOS project như bên dưới

    tiếp theo ta tạo một UI đơn giản để add unity vào đó như hình bên đưới

    Oke tới đây là xong, ta chạy thử ứng dụng và xem thành quả nhé ^^

    Lưu ý: Đảm bảo rằng bạn chạy ứng dụng trên thiết bị iPhone thực chứ không phải trên máy ảo!

    Các bạn cũng thể add unity thành một View Controller show full màn hình, có thể tham khảo các hàm trong UnityEmbeddedSwift ở đây! link source 
    
    Chúc các bạn thành công !!!


  • Flutter vs React Native vs Native: So sánh chi tiết hiệu năng

    Hãy so sánh hiệu năng FPS, CPU, Memory và GPU của các công cụ phát triển thiết bị di động phổ biến.

    Câu chuyện đằng sau việc nghiên cứu

    inVerita và nhóm phát triển mobile của mình liên tục nghiên cứu hiệu năng của các giải pháp mobile đa nền tảng hiện có để trả lời câu hỏi công nghệ nào tốt nhất Flutter hoặc React Native (hoặc Native) cho sản phẩm của bạn, đó là cách Flutter vs React Native vs Native Part I nổi lên. Điều đó gây ra nhiều tranh cãi vì người ta nói rằng không sử dụng React Native để thực hiện phép tính (perform multiple calculations) hàng ngày – có thể đúng như vậy – nhưng trong trường hợp này, các task nặng của CPU được ứng dụng Flutter hoặc Native thực hiện tốt hơn.

    Đó là lý do tại sao trong bài viết này, chúng tôi quyết định nghiên cứu hiệu năng của UI có tác động lớn hơn nhiều đến daily user của mobile app.

    Việc đo lường hiệu năng UI rất phức tạp và yêu cầu kỹ sư triển khai cùng chức năng theo cùng một cách trên mọi nền tảng. Chúng tôi đã sử dụng GameBench, công cụ kiểm tra toàn cầu để đảm bảo sự khách quan (nó không thay đổi sự thật là chúng tôi thực sự yêu thích Flutter ở nhiều khía cạnh 🙂 và vẫn chạy rất nhiều dự án React Native và Native). GameBench có rất nhiều không gian để cải tiến, nhưng chúng tôi đã cố gắng đưa mọi ứng dụng vào một môi trường single testing với sự trợ giúp của nó, đó là mục tiêu của chúng tôi.

    Source code mở vì vậy hãy thử nghiệm và chia sẻ suy nghĩ của bạn với chúng tôi nếu bạn muốn. UI animation chủ yếu sử dụng các công cụ khác nhau trên các nền tảng khác nhau, vì vậy chúng tôi thu hẹp mọi thứ vào các thư viện được hỗ trợ bởi mọi nền tảng (trừ một trường hợp) hoặc ít nhất chúng tôi đã làm mọi thứ để hoàn thành điều đó. Kết quả test có thể khác nhau và tùy thuộc vào phương pháp triển khai, chúng tôi tin rằng bạn có thể đẩy bộ tool đến giới hạn mà nó vượt trội so với các con số của chúng tôi. Bây giờ, chúng ta hãy xem xét các trường hợp.

    Thông tin thiết bị phần cứng:

    Đối với mục đích thử nghiệm của chúng tôi, chúng tôi đã sử dụng một chiếc Xiaomi Redmi Note 5 và iPhone 6s giá cả phải chăng.

    Repo link:

    Source code

    Use case 1 — List view benchmarking

    Chúng tôi đã triển khai cùng một UI trên cả Android và iOS ,sử dụng Native, React Native và Flutter. Chúng tôi cũng tự động hóa tốc độ scroll bằng cách sử dụng RecyclerView.SmoothScroller trên Android. Trên iOS và React Native, chúng tôi đã sử dụng cách tiếp cận với timer và lập trình scroll đến vị trí. Trên Flutter, chúng tôi đã sử dụng ScrollController để scroll qua danh sách một cách trơn tru. Trong mỗi trường hợp, chúng tôi có 1000 phần tử trong list view và cùng một thời gian scroll để đến element cuối cùng. Trong mỗi trường hợp này, chúng tôi đã sử dụng hình ảnh trong bộ nhớ đệm (image caching) với các lib khác nhau trên mỗi nền tảng. Xem thông tin chi tiết trong source code.

    iOS

    • Tải và lưu hình ảnh vào bộ nhớ đệm – Nuke

    Android

    • Tải và lưu hình ảnh vào bộ nhớ đệm – Glide

    React Native

    Android — GPU tests results are not supported by the benchmark (unfortunately, with the devices we have, and we have many:)) )

    Kết quả kiểm tra Android – GPU không được hỗ trợ bởi benchmark

    Kết quả kiểm tra
    1. Tất cả các thử nghiệm đều cho thấy FPS xấp xỉ như nhau.
    2. Android Native sử dụng một nửa memory so với Flutter và React Native.
    3. React Native yêu cầu khai thác CPU nhiều nhất. Lý do là việc sử dụng JSBridge giữa mã JS và Native kích động sự lãng phí tài nguyên khi serialization và deserialization.
    4. Về khai thác pin, Android Native có kết quả tốt nhất. React-native đang tụt hậu so với cả Android và Flutter. Chạy các animation liên tục sẽ tiêu tốn nhiều pin hơn trên React Native.

    Test trên iPhone 6s

    Kết quả kiểm tra
    1. FPS: Kết quả của React Native kém hơn so với Flutter và Swift. Lý do là không thể sử dụng biên dịch (compilation) IoT trên iOS.
    2. Memory: Flutter gần như khớp với nguyên bản (native) về mức tiêu thụ Memory nhưng vẫn nặng hơn trên CPU. React Native thua xa Flutter và native trong thử nghiệm này.
    3. Sự khác biệt giữa Flutter và Swift. Flutter đang tích cực sử dụng CPU khi iOS Native đang tích cực sử dụng GPU. Đối chiếu trong Flutter làm tăng tải trên CPU.

    Use case 2 — Heavy animations test

    Ngày nay hầu hết các điện thoại chạy trên Android và iOS đều có phần cứng rất mạnh. Trong hầu hết các trường hợp sử dụng các ứng dụng kinh doanh, có thể thấy không có sự sụt giảm số khung hình/giây nào. Đó là lý do tại sao chúng tôi quyết định thực hiện một số thử nghiệm với animation nặng. Đủ nặng để giảm số khung hình/giây. Chúng tôi đã sử dụng animation animated vector với Lottie trên Android, iOS, React Native và sử dụng các animation tương tự để sử dụng với Flare on Flutter.

    Thử nghiệm animation với Lottie cho Android, iOS, React Native và Flare cho Flutter.

    Lottie cho Android

    Android

    Kết quả kiểm tra
    1. Android và React Native có những điểm tương đồng về hiệu năng của chúng. Đó là điều hiển nhiên vì Lottie cho React Native sử dụng phương tiện Native (16–19% CPU, 30–29 FPS).
    2. Kết quả của Flutter là một bất ngờ, mặc dù nó có một chút trục trặc trong một performance. (12% CPU và 9 FPS).
    3. Android yêu cầu ít bộ nhớ nhất (205 Mb); React Native cần 280 Mb và Flutter cần 266 Mb.
    4. Khởi động lại app. Theo chỉ số này, Flutter là người dẫn đầu (2 giây). Đối với Android Native và React Native, mất khoảng 4 giây.

    Chúng tôi phát hiện ra rằng việc xóa một animation cụ thể khỏi lưới (grid) sẽ tăng FPS lên đến 40% trên Flutter. Chúng tôi cho rằng Flare nặng hơn và không được tối ưu hóa cho loại task này, đó là lý do tại sao Flutter lại bị sụt FPS như vậy.

    IOS

    Kết quả kiểm tra
    1. Kết quả của iOS và React Native trong bài kiểm tra này gần giống như Lottie đối với React Native.
    2. Flare và Flutter sẽ không ngừng khiến bạn ngạc nhiên. Flare chắc chắn có một con đường để đi 😀
    3. iOS Native yêu cầu ít bộ nhớ nhất (48 Mb). React Native cần 135 Mb và Flutter cần 117 Mb.
    4. Khởi động cold app. Theo chỉ số này, Flutter là người dẫn đầu (2 giây). Đối với iOS và React Native, mất khoảng 10 giây.

    Lưu ý: chúng tôi đã sử dụng một thư viện khác cho trường hợp này với Flutter nặng hơn nhiều so với những thư viện đã sử dụng cho các nền tảng khác và nó có thể là lý do khiến fps giảm.

    Use case 3 — Kiểm tra animation thậm chí còn nặng hơn với các rotation, scaling và fade.

    Trong thử nghiệm này, chúng tôi đã so sánh hiệu năng trong khi tạo animation cho 200 hình ảnh. Các animation xoay tỷ lệ và mờ dần được thực hiện cùng một lúc.

    200 hình ảnh

    Android

    Kết quả kiểm tra
    1. Native cho thấy hiệu năng cao nhất và tiêu thụ bộ nhớ hiệu quả nhất.
    2. Flutter cho thấy hiệu năng vừa đủ để làm việc thoải mái nhưng chi phí bộ nhớ cao hơn gấp đôi so với Native.
    3. React Native đã cho thấy hiệu năng thấp trong trường hợp này.

    IOS

    Kết quả kiểm tra
    1. iPhone 6s đủ mạnh để không giảm fps trong cả 3 trường hợp.
    2. Native sử dụng ít tài nguyên hơn và GPU được sử dụng gần hết.
    3. React Native chủ yếu sử dụng CPU để hiển thị trong khi Flutter sử dụng GPU.
    4. React Native đã sử dụng nhiều bộ nhớ hơn một chút.

    Tóm lại

    Đối với các ứng dụng thông thường có animation nhỏ và vẻ ngoài lấp lánh, công nghệ không thành vấn đề. Nhưng nếu bạn sẽ thực hiện một số animation nặng, hãy nhớ rằng shiny Native có sức mạnh hiệu năng cao nhất để làm điều đó. Tiếp theo, hãy đến với Flutter và React Native. Chúng tôi chắc chắn không khuyên bạn nên sử dụng React Native trong một hoạt động quá nặng về CPU, trong khi Flutter rất phù hợp cho các task như vậy từ cả quan điểm CPU và Memory.

    Công cụ bạn chọn tùy thuộc vào sản phẩm và business case cụ thể của bạn. Trong trường hợp bạn đang tìm cách phát triển MVP một nền tảng – hãy sử dụng các phương tiện gốc, nhưng hãy nhớ rằng các ứng dụng Flutter có thể được xây dựng cho cả môi trường mobile, web, desktop và có vẻ như Flutter có thể trở thành Vua phát triển đa nền tảng trong tương lai không xa, vì hiện tại Flutter đã tạo ra một cuộc cạnh tranh cho các công cụ phát triển native, đặc biệt nếu ngân sách phát triển không hạn chế mà bạn vẫn đang tìm kiếm hiệu năng tốt cho ứng dụng của mình trên các nền tảng khác nhau.

    Chúng tôi phải đối mặt với thực tế là có thể có nhiều yếu tố ảnh hưởng đến việc triển khai và benchmark của từng công nghệ và nhiều người trong số các bạn có thể là chuyên gia thực sự của một nền tảng cụ thể có thể khai thác nhiều hơn nữa bộ tool yêu thích. Chúng tôi đã cố gắng giải thích bằng cách tạo ra một môi trường duy nhất cho mỗi ứng dụng để thử nghiệm và một bộ công cụ duy nhất để đo lường hiệu năng và tôi hy vọng bạn thích kết quả này.

    Bài viết này được dịch từ đây.