Category: Apple

  • 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é!

  • Làm cách nào để thực hiện cuộc gọi, gọi FaceTime và gửi SMS trong ứng dụng sử dụng Swift?

    Làm cách nào để thực hiện cuộc gọi, gọi FaceTime và gửi SMS trong ứng dụng sử dụng Swift?

    Hiện nay hầu hết các ứng dụng di động đều có tính năng liên lạc nhằm mục đích giúp người sử dụng dễ dàng liên hệ được với bộ phận chăm sóc khách hàng. Để làm được việc này thì Apple có cung cấp một URL Scheme để thực hiện việc này.

    URL Scheme Mail

    Để có thể sử dụng được tính năng gửi mail thông qua app của Apple chúng ta cần cấu hình dự án cho phép sử dụng URL schemes “mailto” như sau:

    Mở file info.plist và thêm Queried URL Shemes -> add item và đặt trường value với giá trị là mailto

    Để thực hiện được việc gửi mail chúng ta thực hiện đoạn code như sau:

      func mail(to: String, cc: String = "", subject: String = "", body: String = "") {
            
            var mailURLString: String = "mailto:"
            
            // add to
            mailURLString += to
            
            // add cc
            mailURLString += "?cc=\(cc)"
            
            // add subject
            if let subjectEncode = subject.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) {
                mailURLString += "&subject=\(subjectEncode)"
            }
            
            // add content
            if let bodyEncode = body.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) {
                mailURLString += "&body=\(bodyEncode)"
            }
            
            // check url
            if let mailURL = URL(string: mailURLString), UIApplication.shared.canOpenURL(mailURL) {
                // open app mail with url
                UIApplication.shared.open(mailURL)
            }
        }

    Bây giờ bất kể chỗ nào chúng ta dùng để gửi mail đều có thể call func này để thực hiện việc gửi mail ví dụ như sau:

    mail(to: "[email protected]", cc: "[email protected]", subject: "Techover.io", body: "New post")

    URL Scheme Phone

    Tương tự như Mail việc gọi điện app cũng có 1 scheme cho phép thực hiện việc này.

        func tel(to: String) {
            if let telURL = URL(string: "tel:\(to)"), UIApplication.shared.canOpenURL(telURL) {
                UIApplication.shared.open(telURL)
            }
        }

    URL Scheme FaceTime

        func faceTime(to: String) {
            if let telURL = URL(string: "facetime-audio://\(to)"), UIApplication.shared.canOpenURL(telURL) {
                UIApplication.shared.open(telURL)
            }
        }

    URL Scheme SMS

        func sms(to: String) {
            if let telURL = URL(string: "sms:\(to)"), UIApplication.shared.canOpenURL(telURL) {
                UIApplication.shared.open(telURL)
            }
        }

    Ngoài ra chúng ta còn có thêm các Apple URL Scheme khác như MAP Links , iTunes Links, YouTube Links … Anh em có thể tham khảo thêm tài liệu của Apple ở link sau: https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007899-CH1-SW1

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

  • Value type và Reference type – ARC in Swift

    Value type và Reference type – ARC in Swift

    Value type và Reference type – ARC in Swift

    1. Value type và Reference type:

    Là một lập trình viên iOS chúng ta biết rằng, Apple khuyên chúng ta sử dụng struct và enum nhiều hơn. Lí do là chúng là kiểu Value type, dẫn tới việc bộ nhớ của chúng không ảnh hưởng tới nhau. An toàn hơn khi truy cập và sử dụng

    Vậy với Class, Closure có escaping thì sao, chúng là kiểu reference type -> Câu hỏi đặt là ra là sao để quản lý vùng nhớ của chúng, để tránh tạo instance lẫn lộn, gây ra leak memory. Câu trả lời của Apple đó là ARC

    Noted:
    Class sẽ được khởi tạo trong heap -> nên có thể sử dụng cho data có size lớn
    Struct sẽ được khởi trong stack -> nên không thể dùng cho data có size lớn, nó sẽ dẫn tới phình stack nhanh. Vậy nên chúng ta chỉ nên dùng cho những data có size nhỏ.


    Còn Heap với Stack là gì, hẹn các bạn ở một bài khác nhé.

    2. Vậy ARC là gì?

    Automatic Reference Counting: Dịch theo nghĩa đen tiếng Việt là tự động đếm số lượng reference. 

    Theo Apple định nghĩa về ARC: là cơ chế Swift sử dụng để theo dõi và quản lý bộ nhớ mà ứng dụng sử dụng. Từ đó giúp bạn phát triển ứng dụng mà không cần quan tâm tới việc quản lý bộ nhớ như một số ngôn ngữ khác, hay là Objective – C chưa implement ARC. ARC sẽ tự động frees up( hủy bỏ vùng nhớ của các strong reference = 0)

    ARC chỉ áp dụng cho instance của classes. Structures và Enumerations là kiểu value types, không phải kiểu references types và không được lưu trữ dưới dạng tham chiếu.

    3. ARC hoạt động như thế nào?

    Khi mà chúng ta tạo một instance của một class, ARC sẽ phân bổ một vùng nhớ của bộ nhớ để lưu trữ thông tin của instance đó

    Khi mà instance không được sử dụng trong một thời gian đủ dài, ARC sẽ tự động frees up memory được sử dụng bởi instance đó để giải phóng vùng nhớ để sự dụng vào các mục đích khác,

    Để xác định một biến không còn được sử dụng, ARC sẽ đếm số lượng strong reference của các object khác đang trỏ vào chính nó. Chỉ khi số lượng strong reference = 0 thì biến đó mới được giải phóng khỏi bộ nhớ.

    4. Strong reference


    Ở đây chúng ta có pen1, pen2, pen3 đều là kiểu strong reference chỉ về vùng nhớ của class Pen.

    Khi ta gán:

    pen1 = nil

    pen2 = nil

    => ARC sẽ đếm strong reference trong trường họp này sẽ là = 1. vì còn 1 intance được giữ lại bởi pen3. Nên trong trường hơp này ARC không giải phóng vùng nhớ cho Pen.

    5. Reference giữa các Class

    Khi chúng ta gán person và car = nil thì ta thấy person có car đang chỉ tới vùng nhớ của của Car kiểu strong reference, khi person = nil, thì strong reference này = 1 ( instance chưa bị hủy đi), tương tự với car, tồn tại instance trỏ tới vùng nhớ của Peson chưa được giải phóng:

    -> Trong trường hợp này strong reference = 2

    -> Tạo retain cycle, dẫn tới leak memory ( Khi bộ nhớ bị đầy dẫn tới app bị chậm, hoặc dừng lại, hoặc thoát ra đột ngột)

    Vậy để giải quyết bài toán trên Apple đã đưa ra hai kiểu định nghĩa reference đó là weak và unowned.

    6. Weak reference

    Khi chúng ta tạo một biến là kiểu weak reference, thì ARC sẽ hiểu reference counting của nó = 0 ( vì nó không phải là strong reference nên không làm tăng retain count. Từ bộ đếm counting không tăng lên không dẫn tới việc leak memory.

    Ở đây khi chúng ta set cong = nil, Car sẽ không còn reference nào chỉ đến. Từ đó vùng nhớ được giải phóng, và khi đó strong reference của Person trỏ đến Car cũng bị hủy.

    Set honda = nil, ở thời điểm này không còn strong nào trỏ đến -> instance đuợc giải phóng khỏi vùng nhớ. Không làm leak memory

    7. Unowned reference

    Tương tự như weak thì khi tạo một biến kiểu unowned, Counting Reference không tăng lên. ARC không giữ instance  và không gây ra leak memory.

    Note: “ Unowned không sử dụng cho kiểu dữ liệu optional, một instance A unowned reference ( trỏ ) đến một instance B mà instance B đó có vòng đời bằng hoặc lớn hơn instance A, nên khi bạn truy cập một biến unowned sau khi nó đã giải phóng khỏi vùng nhớ sẽ dẫn tới crash. Vì vậy khi dùng unowned cần cẩn thận!”

    Tương tự như weak khi chúng ta set cong = nil, Car sẽ không còn reference nào chỉ đến. Từ đó vùng nhớ được giải phóng, và khi đó strong reference của Person trỏ đến Car cũng bị hủy.

    set honda = nil, ở thời điểm này không còn strong nào trỏ đến -> instance đuợc giải phóng khỏi vùng nhớ. Không làm leak memory

    8. Strong Reference Cycles for Closures:

    Closure cũng giống như class là reference types.

    Trong một class, nếu một property là closure và trong closure đó lại dùng lại property / method của class nữa ( xài self.property ) thì sẽ xảy ra hiện tượng retain cycle như các ví dụ ở trên.

    Ta có ví dụ sau:

    9. Resolving Strong Reference Cycles for Closures

    Để giải quyết vấn đề retain cycle trong closure, swift đã cung cấp cho chúng ta một giải pháp đó là: closure capture list.

    Capture list sẽ quy định luật để lấy giá trị của property trong closure. Tức là lấy self.property / self.method như thế nào. 

    Ta dùng syntax sau ở phần body của closure:

    [ weak self ] in

    hoặc:

    [ unowned self ] in

    hoặc lấy nhiều property / method cũng được: 

    [ weak self, unowned self.property, … ] in

    carbon-8

    Thanks for reading!

  • iOS/Swift In-App Purchase (P2)

    iOS/Swift In-App Purchase (P2)

    Xin chào mọi người. Phần 1 mình đã hướng dẫn các bạn setup môi trường và setup payments rồi. Bài này mình sẽ tiếp tục với implement code và thực hiện test sau khi implement.

    Implement

    Đầu tiên chúng ta enable In-App Purchase trong Capability

    Bắt đầu implememt code

    Ở bài trước mình đã tạo một item tên là Apple Book và đặt productID là Demo.TechoverInAppPurchase.co.Apple.Book

    Cũng trong bài trước mình có nhấn mạnh productID rất quan trọng và phải matching trong code, bên dưới đây mình sẽ thực hiện điều đó.

    Đầu tiên mình tạo ra một file plist mình để tên là IAPProductIDs, mục đích của file này là để quản lý tất cả các purchase producIDs của ứng dụng, thường thì một ứng dụng sử dụng In-App Purchase sẽ có nhiều item thanh toán, cũng như nhiều giá trị khác nhau, mỗi giá item đó tương ứng với một productID, Hiện tại demo của mình chỉ có 1 item thanh toán vì thế trong file plist của mình chỉ có một item và value của item đó là productID.


    Tiếp theo mình tạo ra một class IAPManager như bên dưới:

    import Foundation
    import StoreKit
    
    class IAPManager: NSObject {
        
        // MARK: - Custom Types
        
        enum IAPManagerError: Error {
            case noProductIDsFound
            case noProductsFound
            case paymentWasCancelled
            case productRequestFailed
        }
        
        // MARK: - Properties
        
        static let shared = IAPManager()
        
        private var onReceiveProductsHandler: ((Result<[SKProduct], IAPManagerError>) -> Void)?
        private var onBuyProductHandler: ((Result<Bool, Error>) -> Void)?
        
        var totalRestoredPurchases = 0
        
        // MARK: - Init
        
        private override init() {
            super.init()
        }
        
        // MARK: - General Methods
        
        fileprivate func getProductIDs() -> [String]? {
            guard let url = Bundle.main.url(forResource: "IAPProductIDs", withExtension: "plist") else { return nil }
            do {
                let data = try Data(contentsOf: url)
                let productIDs = try PropertyListSerialization.propertyList(from: data, options: .mutableContainersAndLeaves, format: nil) as? [String] ?? []
                return productIDs
            } catch {
                print(error.localizedDescription)
                return nil
            }
        }
        
        func getPriceFormatted(for product: SKProduct) -> String? {
            let formatter = NumberFormatter()
            formatter.numberStyle = .currency
            formatter.locale = product.priceLocale
            return formatter.string(from: product.price)
        }
        
        func startObserving() {
            SKPaymentQueue.default().add(self)
        }
        
        func stopObserving() {
            SKPaymentQueue.default().remove(self)
        }
        
        func canMakePayments() -> Bool {
            return SKPaymentQueue.canMakePayments()
        }
        
        // MARK: - Get IAP Products
        
        func getProducts(withHandler productsReceiveHandler: @escaping (_ result: Result<[SKProduct], IAPManagerError>) -> Void) {
            onReceiveProductsHandler = productsReceiveHandler
            
            guard let productIDs = getProductIDs() else {
                productsReceiveHandler(.failure(.noProductIDsFound))
                return
            }
            
            let request = SKProductsRequest(productIdentifiers: Set(productIDs))
            request.delegate = self
            request.start()
        }
    
        // MARK: - Purchase Products
        
        func buy(product: SKProduct, withHandler handler: @escaping ((_ result: Result<Bool, Error>) -> Void)) {
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(payment)
            
            onBuyProductHandler = handler
        }
        
        func restorePurchases(withHandler handler: @escaping ((_ result: Result<Bool, Error>) -> Void)) {
            onBuyProductHandler = handler
            totalRestoredPurchases = 0
            SKPaymentQueue.default().restoreCompletedTransactions()
        }
    }
    
    // MARK: - SKPaymentTransactionObserver
    
    extension IAPManager: SKPaymentTransactionObserver {
        func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
            transactions.forEach { (transaction) in
                switch transaction.transactionState {
                case .purchased:
                    onBuyProductHandler?(.success(true))
                    SKPaymentQueue.default().finishTransaction(transaction)
                case .restored:
                    totalRestoredPurchases += 1
                    SKPaymentQueue.default().finishTransaction(transaction)
                case .failed:
                    if let error = transaction.error as? SKError {
                        if error.code != .paymentCancelled {
                            onBuyProductHandler?(.failure(error))
                        } else {
                            onBuyProductHandler?(.failure(IAPManagerError.paymentWasCancelled))
                        }
                        print("IAP Error:", error.localizedDescription)
                    }
                    SKPaymentQueue.default().finishTransaction(transaction)
                case .deferred, .purchasing: break
                @unknown default: break
                }
            }
        }
        
        func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
            if totalRestoredPurchases != 0 {
                onBuyProductHandler?(.success(true))
            } else {
                print("IAP: No purchases to restore!")
                onBuyProductHandler?(.success(false))
            }
        }
        
        func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
            if let error = error as? SKError {
                if error.code == .paymentCancelled {
                    onBuyProductHandler?(.failure(IAPManagerError.paymentWasCancelled))
                } else {
                    print("IAP Restore Error:", error.localizedDescription)
                    onBuyProductHandler?(.failure(error))
                }
            }
        }
    }
    
    // MARK: - SKProductsRequestDelegate
    
    extension IAPManager: SKProductsRequestDelegate {
        func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
            let products = response.products
            
            if !products.isEmpty {
                onReceiveProductsHandler?(.success(products))
            } else {
                onReceiveProductsHandler?(.failure(.noProductsFound))
            }
        }
        
        func request(_ request: SKRequest, didFailWithError error: Error) {
            onReceiveProductsHandler?(.failure(.productRequestFailed))
        }
        
        func requestDidFinish(_ request: SKRequest) {
            // Handle finish
        }
    }
    
    // MARK: - IAPManagerError Localized Error Descriptions
    
    extension IAPManager.IAPManagerError: LocalizedError {
        var errorDescription: String? {
            switch self {
            case .noProductIDsFound:
                return "No In-App Purchase product identifiers were found."
            case .noProductsFound:
                return "No In-App Purchases were found."
            case .productRequestFailed:
                return "Unable to fetch available In-App Purchase products at the moment."
            case .paymentWasCancelled:
                return "In-App Purchase process was cancelled."
            }
        }
    }
    

    Ở class trên mình đã viết đầy đủ các methods như: buy(), restorePurchases()… Cũng như xử lý các error.

    Bây giờ chúng ta sử dụng IAPManager để thực hiện purchase nhé.

    Đầu tiên mình thực hiện layout một để một button có tên là buy ở giữa màn hình:

    Tiếp theo mình thực hiện implement việc purchase trong class ViewController

    import UIKit
    import StoreKit
    
    class ViewController: UIViewController {
        
        private var products: [SKProduct] = []
    
        override func viewDidLoad() {
            super.viewDidLoad()
        }
        
        override func viewWillAppear(_ animated: Bool) {
            IAPManager.shared.getProducts { (result) in
                DispatchQueue.main.async {
                    switch result {
                    case .success(let products):
                        self.products = products
                    case .failure(let error):
                        self.showAlert(message: error.localizedDescription)
                    }
                }
            }
        }
        
        // MARK: - Actions
    
        @IBAction func didBuy(_ sender: UIButton) {
            DispatchQueue.main.async {
                self.verifyBeforeBuy()
            }
        }
        
        // MARK: - Private Methods
        
        private func verifyBeforeBuy() {
            guard let product = product(with: "Demo.TechoverInAppPurchase.co.Apple.Book"),
                  let price = IAPManager.shared.getPriceFormatted(for: product) else {
                return
            }
            
            let alertController = UIAlertController(title: product.localizedTitle,
                                                    message: product.localizedDescription,
                                                    preferredStyle: .alert)
            
            alertController.addAction(UIAlertAction(title: "Buy now for \(price)", style: .default, handler: { (_) in
                self.buy(product: product)
            }))
            
            alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
            self.present(alertController, animated: true, completion: nil)
        }
        
        private func product(with id: String) -> SKProduct? {
            return products.first(where: { $0.productIdentifier == id })
        }
            
        private func buy(product: SKProduct) {
            if !IAPManager.shared.canMakePayments() {
                self.showAlert(message: "In-App Purchases are not allowed in this device.")
            } else {
                IAPManager.shared.buy(product: product) { result in
                    switch result {
                    case .success(let success):
                        if success {
                            self.showAlert(message: "Buy success")
                        } else {
                            self.showAlert(message: "Buy error")
                        }
                    case .failure(let failure):
                        self.showAlert(message: failure.localizedDescription)
                    }
                }
            }
        }
    }
    
    extension ViewController {
        func showAlert(title:String = "",
                       message: String) {
            let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }
    

    Mình sẽ giải thích flow của việc purchase như sau:

    • Ở viewWillAppear() mình lấy tất cả producIDs đã set trong IAPProductIDs.plist và lưu lại với biến products: [SKProduct]
    • Khi người dùng bấm vào buy button mình thực hiện verify trước khi gọi đến IAPManager.shared.buy(product: product)
    • verifyBeforeBuy(): mình lấy product có id là Demo.TechoverInAppPurchase.co.Apple.Book, từ product đó mình lấy ra price của product đó bằng method getPriceFormatted trong IAPManager và thông báo cho người dùng số tiền cần bỏ ra để mua item này
    • Khi người dùng bấm confirm mua trên alert mình thực hiện kiểm tra xem device có cho phép thực hiện giao dịch hay không bằng method IAPManager.shared.canMakePayments, nếu có thì gọi đến IAPManager.shared.buy và nếu không cho phép thì hiển thị thông báo cho người dùng
    • IAPManager.shared.buy(): sau khi thực hiện method này hệ thống sẽ show một actionsheet để người dùng xác nhận trước khi thực hiện giao dịch
    • Sau khi thực hiện giao dịch nếu thành công sẽ nhận được một thông báo và thất bại thì mình cũng sẽ hiển thị thông báo cho người dùng

    Test In-App Purchase sau khi implement

    Trên thực tế khi thực hiện mỗi giao dịch đều mất tiền, vậy mỗi khi test in-app purchase thì đều mất tiền sao? Câu trả lời là không, vì apple đã support viêc testing thông qua apple sandbox account, điều này giúp developer cũng như tester thực hiện giao dịch mà không mất tiền.

    Chúng ta vào đây để add sandbox account. Mình add account [email protected]

    Tiếp theo chúng ta thực hiện login sandbox account trên device test.

    Setting -> App Store -> Sandbox account -> login

    Sau khi add sandbox account và login, chúng ta thực hiện purchase thì sẽ nhận được thông báo là đang purchase với account sandbox và không mất phí:

    Thông qua phần 1 và bài này mình đã giới thiệu đến các bạn cách để thực hiện in-app purchase trong Swift.
    Mình hi vọng bài viết có thể giúp ích được cho các bạn. Chúc các bạn thành công!

  • Học lập trình với ngôn ngữ Swift – Bài 1: Chào mừng bạn đến với Swift

    Học lập trình với ngôn ngữ Swift – Bài 1: Chào mừng bạn đến với Swift

    Trước khi bắt đầu thực hiện những dòng code đầu tiên cho ứng dụng của bạn bằng ngôn ngữ lập trình Swift, chúng ta sẽ tìm hiểu qua về nó để biết rằng tại sao chúng ta lại chọn Swift để làm các ứng dụng, nó có những ưu điểm gì? lịch sử hình thành như nào? Xu hướng phát triển ra sao? Liệu nó có đáng để chúng ta tìm hiểu và học hay không?. Vậy các bạn cùng mình tiếp tục theo dõi bài viết này nhé.

    Định nghĩa về Swift

    Swift là một ngôn ngữ lập trình hướng đối tượng dành cho việc phát triển iOS và macOSwatchOStvOS và z/OS. được giới thiệu bởi Apple tại hội nghị WWDC 2014.Swift được mong đợi sẽ tồn tại song song cùng Objective-C, ngôn ngữ lập trình hiện tại dành cho các hệ điều hành của Apple. Swift được thiết kế để hoạt động với các framework Cocoa và Cocoa Touch của Apple và phần lớn mã Objective-C hiện có được viết cho các sản phẩm của Apple. Nó được biên dịch với trình biên dịch LLVM và đã được đưa vào Xcode kể từ phiên bản 6, phát hành năm 2014. Trên các nền tảng của Apple[12], nó sử dụng thư viện runtime Objective-C cho phép mã CObjective-CC++ và Swift cùng chạy trong một chương trình.[13]

    Apple dự định Swift hỗ trợ nhiều khái niệm cốt lõi liên quan đến Objective-C, đáng chú ý là thu hồi động, các ràng buộc phổ thông, lập trình mở rộng và các tính năng tương tự, nhưng theo cách “an toàn hơn”, giúp dễ dàng bắt lỗi phần mềm hơn; Swift có các tính năng giải quyết một số lỗi lập trình phổ biến như con trỏ rỗng cung cấp cú pháp đặc biệt để giúp tránh kim tự tháp diệt vong. Swift hỗ trợ khái niệm về khả năng mở rộng giao thức, một hệ thống mở rộng có thể được áp dụng cho các kiểu, cấu trúc và lớp, mà Apple khuyến khích như một sự thay đổi thực sự trong mô hình lập trình mà họ gọi là “lập trình hướng giao thức” (tương tự như đặc điểm).

    Swift được giới thiệu tại Worldwide Developers Conference (WWDC) 2014 của Apple.Nó đã trải qua quá trình nâng cấp lên phiên bản 1.2 trong năm 2014 và nâng cấp lớn hơn cho Swift 2 tại WWDC 2015. Ban đầu, ngôn ngữ độc quyền, phiên bản 2.2 được được chuyển sang phần mềm nguồn mở theo Giấy phép Apache 2.0 vào ngày 3 tháng 12 năm 2015, dành cho các nền tảng của Apple và Linux.[17][18]

    Thông qua phiên bản 3.0, cú pháp của Swift đã trải qua quá trình phát triển quan trọng, với nhóm nòng cốt làm cho sự ổn định nguồn trở thành trọng tâm trong các phiên bản sau.[19][20] Trong quý đầu tiên của năm 2018, Swift đã vượt qua Objective-C về mức độ phổ biến.[21]

    Swift 4.0, được phát hành vào năm 2017, đã giới thiệu một số thay đổi đối với một số lớp và cấu trúc tích hợp. Mã được viết bằng các phiên bản trước của Swift có thể được cập nhật bằng chức năng di chuyển được tích hợp trong Xcode

    Vào tháng 3 năm 2017, chưa đầy 3 năm sau khi chính thức ra mắt, Swift đã đứng đầu trong bảng xếp hạng TIOBE hàng tháng về các ngôn ngữ lập trình phổ biến nhất.[22] Một tài liệu 500 trang về Swift cũng được phát hành tại WWDC, miễn phí trên iBooks Store.

    Nguồn: WIKI, bạn có thể đọc thêm về lịch sử hình thành và phát triển và các thông tin khác ở WIKI

    Giới thiệu về Swift

    Apple định nghĩa về ngôn ngữ lập trình Swift là một cách tuyệt vời để viết phần mềm, cho dù đó là phần mềm trên điện thoại, máy tính, server hay bất kể thứ gì khác chạy code. Nó là ngôn ngữ lập trình an toàn, nhanh chóng và có tính tương tác, kết hợp với tư duy ngôn ngữ hiện đại tốt nhất với sự không ngoan từ văn hoá kỹ thuật rộng lớn của Apple và những đóng góp đa dạng từ cộng đồng mã nguồn mở. Trình biên dịch thì tối ưu hoá cho hiệu suất và ngôn ngữ được tối ưu hoá cho sự phát triển mà không ảnh hưởng tới cả hai.

    Swift rất thân thiện với các lập trình viên mới, nó là ngôn ngữ lập trình chất lượng công nghiệp, biểu cảm và thú vị như ngôn ngữ kịch bản. Viết code Swift trong Playground cho phép bạn thử nghiệm code và xem kết quả ngay lập tức mà không tốn chi phí để xây dụng ứng dụng.

    Swift xác định loại bỏ các lớp lớn các lỗi lập trình phổ biến bằng cách áp dụng các mẫu lập trình hiện đại:

    • Các biến luôn được khởi tạo trước khi sử dụng.
    • Các chỉ số mảng được kiểm tra lỗi ngoài giới hạn(out-of-bounds).
    • Số nguyên được kiểm tra tràn(overflow).
    • Optionals đảm bảo rằng các giá trị nil được xử lý rõ ràng.
    • Bộ nhớ được quản lý tự động.
    • Xử lý lỗi cho phép phục hồi có kiểm soát từ các lỗi không mong muốn.

    Mã Swift được biên dịch và tối ưu hóa để tận dụng tối đa phần cứng hiện đại. Cú pháp và thư viện chuẩn đã được thiết kế dựa trên nguyên tắc hướng dẫn rằng cách rõ ràng để viết mã của bạn cũng sẽ hoạt động tốt nhất. Sự kết hợp giữa an toàn và tốc độ khiến Swift trở thành lựa chọn tuyệt vời cho mọi thứ từ “Xin chào, thế giới!” cho toàn bộ hệ điều hành.

    Swift kết hợp suy luận kiểu mạnh mẽ và so khớp mẫu với cú pháp nhẹ, hiện đại, cho phép các ý tưởng phức tạp được thể hiện một cách rõ ràng và ngắn gọn. Kết quả là mã không chỉ dễ viết hơn mà còn dễ đọc và dễ bảo trì hơn.

    Swift đã được phát triển trong nhiều năm và nó tiếp tục phát triển với các tính năng và khả năng mới. Mục tiêu của Apple dành cho Swift rất tham vọng. Apple rất muốn xem bạn tạo thật nhiều ứng dụng với Swift.

    Khả năng tương thích phiên bản

    Phần này mô tả Swift 5.8, phiên bản mặc định của Swift được bao gồm trong Xcode 14. Bạn có thể sử dụng Xcode 14 để xây dựng các target được viết bằng Swift 5.8, Swift 4.2 hoặc Swift 4.

    Khi bạn sử dụng Xcode 14 để xây dựng code Swift 4 và Swift 4.2, hầu hết các chức năng của Swift 5.8 đều khả dụng. Điều đó nói rằng, những thay đổi sau chỉ có sẵn cho code sử dụng Swift 5.8 trở lên:

    Các hàm trả về loại không trong suốt yêu cầu thời gian chạy Swift 5.1.

    Sự cố gắng? biểu thức không đưa ra mức tùy chọn bổ sung cho các biểu thức đã trả về các tùy chọn.

    Biểu thức khởi tạo số nguyên lớn được suy ra là kiểu số nguyên chính xác. Ví dụ: UInt64(0xffff_ffff_ffff_ffff) đánh giá giá trị chính xác thay vì tràn.

    Đồng thời yêu cầu Swift 5.8 hoặc version cao hơn và một phiên bản của thư viện tiêu chuẩn Swift cung cấp các loại đồng thời tương ứng. Trên các nền tảng của Apple, hãy đặt target triển khai ít nhất là iOS 13, macOS 10.15, tvOS 13 hoặc watchOS 6.

    Targer được viết bằng Swift 5.8 có thể phụ thuộc vào Target được viết bằng Swift 4.2 hoặc Swift 4 và ngược lại. Điều này có nghĩa là nếu bạn có một dự án lớn được chia thành nhiều Framework, bạn có thể di chuyển code của mình từ Swift 4 sang Swift 5.8 bằng một Framework.

    Tổng kết

    Vậy là chúng ta đã đã biết qua hầu hết các thông tin về Swift, bài tiếp theo mình sẽ chia sẻ với các bạn cách làm quen với công cụ XCode và thực hiện những dòng code đầu tiên trên ngôn ngữ lập trình Swift.

  • Kiểm tra kết nối của thiết bị với NWPathMonitor

    Kiểm tra kết nối của thiết bị với NWPathMonitor

    Hi các bạn, Trong quá trình develop của mình chắc hẳn các bạn đã sử dụng đến chức năng kiểm tra tình trạng kết nối của device. Đơn giản như việc kiểm tra internet khi call API hay download một cái gì đó.

    Trước đây thông thường chúng ta sử dụng Network Reachability.

    Network Reachability ở đây hiểu nôm na là trạng thái kết nối mạng của device. Chúng ta có thể sử dụng để xác định rằng device đang online hay offline, thậm chí là đang dùng mạng wifi hay gói dữ liệu data từ nhà mạng. Tuy nhiên đây không phải một thư viện chính thống từ Apple và chúng ta phải nhúng vào source code của mình trước khi sử dụng.

    Với iOS 12, Apple giới thiệu tới các developer một framework mới để kiểm tra tình trạng kết nối của device. NWPathMonitor có lẽ đây là một sự thay thế hoàn hảo cho Reachability.

    Trong sự kiện WWDC tháng 6 năm 2018, một framework mới có tên Network framework được giới thiệu dành cho iOS 12 trở đi. Một đối tượng có tên NWPathMonitor, framework này đem đến cho chúng ta một cách chính thống để xác định trạng thái kết nối mạng thay vì phải dùng thư viện của bên thư ba như từ trước tới nay.

    Dưới đây là cách sử dụng của NWPathMonitor

    Để sử dụng NWPathMonitor chúng ta chỉ cần import Network và sau đó tạo một đối tượng của NWPathMonitor như sau. Tất nhiên ứng dụng của các bạn phải hỗ trợ từ iOS 12 trở lên nhé.

    Với khởi tạo như ở trên thì chúng ta đang kiểm tra connect của tất cả tất cả các Interface type. Nếu chúng ta chỉ quan tâm đến Nếu chỉ quan tâm đến những thay đổi của một network adapter cụ thể ví dụ như Wifi chẳng hạn, chúng ta có thể khởi tạo bằng phương thức init(requiredInterfaceType:) với tham số truyền vào là một kiểu dữ liệu NWInterface.InterfaceType như sau:

    Đây là các interface type mà NWPathMonitor hỗ trợ theo dõi:

    • cellular
    • loopback
    • wifi
    • wiredEthernet
    • other

    Để nhận được các thay đổi về network state, chúng ta chỉ cần gán callback cho một thuộc tính có tên pathUpdateHandler, callback này sẽ được gọi đến bất cứ khi nào có sự thay đổi đối với network, ví dụ như khi điện thoại chuyển từ sử dụng cellular sang wifi. Trong callback có một giá trị trả về có kiểu NWPath giúp chúng ta có thể dễ dàng xác định được trạng thái đã connect hay chưa:

    Đối tượng NWPath có các thuộc tính mà qua đó chúng ta thể dùng để xác định khá nhiều trạng thái của network. Một trong số đó là một thuộc tính khá thú vị mang tên isExpensive dùng để xác định xem network hiện tại có được coi là đắt tiền hay không. Chúng ta cũng có thể kiểm tra xem có hỗ trợ DNS, IPv4 hoặc IPv6 hay không. Ngoài ra nếu muốn xem network trước khi bị thay đổi sang network hiện tại là gì chúng ta có thể sử dụng thuộc tính usedInterfaceType.

    Để bắt đầu lắng nghe trạng thái connect của device, chúng ta gọi phương thức start(). Và gọi stop() khi muốn kết thúc việc lắng nghe.

    Khi đã hoàn tất quá trình lắng nghe sự thay đổi, chúng ta chỉ cần đơn giản gọi phương thức cancel(). Lưu ý rằng một khi đã gọi phương thức cancel() chúng ta không thể gọi phương thức start() được nữa. Thay vào đó chúng ta cần khởi tạo một biến NWPathMonitor mới.

    Trên đây mình đã giới thiệu đến các bạn về NWPathMonitor một framework hỗ trợ từ iOS 12 của Apple, cũng như cách sử dụng của nó.

    Mình hi vọng bài viết có thể giúp ích được cho các bạn. Chúc các bạn thành công!

  • Lottie Animation: Biến hình màn hình Login của bạn

    Lottie Animation: Biến hình màn hình Login của bạn

    Hi mọi người, ở bài viết trước mình đã hướng dẫn các bạn cách làm sao để thêm và ứng dụng Lottie vào ứng dụng của bạn. Có thể khi đọc bài viết trước các bạn sẽ chưa hình dung được sức mạnh của Lottie Animation. Để chứng minh sức mạnh của Lottie Animation hôm nay mình sẽ hướng dẫn các bạn ứng dụng nó vào màn hình Login và biến nó trở thành 1 màn hình thú vị hơn. Để hiểu rõ hơn về cách cài đặt Lottie animation bạn có thể xem lại bài viết Hướng dẫn cách sử dụng Lottie Animation trong Swift

    Chuẩn bị

    Để có thể bắt đầu dễ dàng và nhanh chóng hơn thì chúng ta cần chuẩn bị sẵn những thứ sau:

    1. Kiến thức về Lottie, tham khảo tại đây
    2. Project demo các bạn tải tại đây

    Bắt đầu

    Đầu tiên chúng ta sẽ kéo UI vào để thực hiện thay đổi màn hình Login như sau:

    Trong màn hình Login của chúng ta sẽ có các thành phần sau:

    1. ImageView làm background cho ứng dụng
    2. 2 Lottie Animation View, 1 cái làm thành phần chính nằm trên nửa màn hình và một cái nằm ở góc nhằm trang trí thêm cho màn hình
    3. 2 UITextField để cho người dùng nhập user name và password
    4. 1 button Login

    Sau khi sử dụng interface builder thực hiện kéo các view vào màn hình thì các bạn kéo reference cho các item vào view controller để thực hiện code.

    Mở ViewController lên và import Lottie

    import UIKit
    import Lottie

    Viết thêm một số func để cài đặt lottie và cài đặt view như sau

    extension ViewController {
        func setupUI() {
            tfAccount.layer.borderWidth = 1
            tfAccount.layer.borderColor = UIColor.orange.cgColor
            tfAccount.backgroundColor = UIColor.orange.withAlphaComponent(0.2)
            tfAccount.layer.cornerRadius = 5
            tfAccount.attributedPlaceholder = NSAttributedString(string: "  Account",
                                                                 attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
            tfAccount.placeholderRect(forBounds: CGRect(x: 5, y: 5, width: 100, height: 44))
            tfPassword.layer.borderWidth = 1
            tfPassword.layer.borderColor = UIColor.orange.cgColor
            tfPassword.backgroundColor = UIColor.orange.withAlphaComponent(0.2)
            tfPassword.layer.cornerRadius = 5
            tfPassword.attributedPlaceholder = NSAttributedString(string: "  Password",
                                                                 attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
            btnLogin.layer.cornerRadius = 5
            loadingView.backgroundColor = UIColor.black.withAlphaComponent(0.4)
        }
        
        func setupLottie() {
            // 1. Set animation content mode
            animationLottieView.contentMode = .scaleAspectFit
            animationLottieView2.contentMode = .scaleAspectFit
    
            // 2. Set animation loop mode
    
            animationLottieView.loopMode = .loop
            animationLottieView2.loopMode = .loop
    
            // 3. Adjust animation speed
    
            animationLottieView.animationSpeed = 0.5
            animationLottieView2.animationSpeed = 0.5
    
            // 4. Play animation
            animationLottieView.play()
            animationLottieView2.play()
        }
        
        private func showLoading() {
            // 1. Create lottie animation view
            let lottieView: LottieAnimationView = LottieAnimationView(name: "loading_crypto")
            lottieView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
            
            // 2. Set animation content mode
            lottieView.contentMode = .scaleAspectFit
            
            // 3. Set animation loop mode
            lottieView.loopMode = .loop
            
            // 4. Adjust animation speed
            lottieView.animationSpeed = 0.5
            
            loadingView.addSubview(lottieView)
            lottieView.center = CGPoint(x: UIScreen.main.bounds.width / 2, y:  UIScreen.main.bounds.height / 2)
            view.addSubview(loadingView)
            // 5. Play animation
            lottieView.play()
            
        }
        
        @objc private func hideLoading() {
            loadingView.subviews.forEach { $0.removeFromSuperview() }
            loadingView.removeFromSuperview()
        }
    }

    Thêm loading view để thực hiện màn hình loading như sau

    class ViewController: UIViewController {
        @IBOutlet weak var tfAccount: UITextField!
        @IBOutlet weak var tfPassword: UITextField!
        @IBOutlet weak var animationLottieView: LottieAnimationView!
        @IBOutlet weak var btnLogin: UIButton!
        @IBOutlet weak var animationLottieView2: LottieAnimationView!
        
        private var loadingView: UIView = UIView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            setupUI()
            setupLottie()
        }
        
        @IBAction func login(_ sender: Any) {
            showLoading()
            perform(#selector(hideLoading), with: nil, afterDelay: 5)
        }
    }

    ở viewDidLoad chúng ta sẽ thực hiện setUpUI và setup lottie, tại func login chúng ta sẽ thực hiện showLoading và để nó ẩn đi sau 5 giây.

    Bây giờ chúng ta thực hiện cmd + R để chạy ứng dụng, kết quả thu được sẽ như sau:

    Kết quả thật tuyệt vời đúng không? Đối với các màn hình có animation phức tạp như vậy để code sử dụng animation gần như ta quá phức tạp và tốn quá nhiều effort để thực hiện, tuy nhiên nếu dùng lottie các bạn chỉ mất một chút thời gian để căn chỉnh layout.

    Vậy là chúng ta đã hoàn thành việc ứng dụng Lottie vào màn hình Login để tăng trải nghiệm của người dùng. Ngoài ra chúng ta còn có thể ứng dụng Lottie vào rất nhiều các tính năng khác để ứng dụng trở nên thú vị hơn. Ví dụ như: Màn hình launch screen với animation logo, animation trên notification, animation Tabbar icon, v.v.

    Cách sử dụng Lottie phụ thuộc rất lớn vào trí tưởng tượng và óc sáng tạo của người dùng, nếu bạn chỉ nắm vai trò là front end developer nhiều khi bạn sẽ không có quyền quyết định ứng dụng sẽ như nào, nhưng nếu bạn có ý tưởng hay ho bạn có thể đưa ý kiến lên Team leader, designer, BA, và PM của dự án để cải thiện trải nghiệm của người dùng.

    Chúc các bạn thành công với các dự án sắp tới.

  • Hướng dẫn cách sử dụng Lottie Animation trong Swift

    Hướng dẫn cách sử dụng Lottie Animation trong Swift

    Có lẽ tất cả mọi người trên thế giới đều yêu thích cái đẹp và mình cũng không ngoại lệ. Việc xây dựng những ứng dụng có UI đẹp mắt, animation mượt mà và thú vị khiến hàng triệu người dùng trầm trồ là mong muốn của rất nhiều những Developer trên toàn thế giới. Là một developer nếu bạn có tìm thấy bài viết này của mình thì chắc hẳn bạn cũng có khát khao làm những điều thú vị cho ứng dụng của mình có đúng không? Vậy chúng ta cùng đi tiếp vào bài viết này để làm cách nào để có thể làm ứng dụng của các bạn trở nên thú vị hơn nhé.

    Trong một dịp mình có cơ hội làm việc trong một dự án có sử dụng rất nhiều Animation để làm cho ứng dụng trở nên đẹp và thú vị hơn với người dùng. Lúc mới vào dự án khi sử dụng một số tính năng có sử dụng animation làm mình thấy rất hứng thú, thật khó để cưỡng lại sự đẹp đẽ và hoa mỹ của nó. Trong khi làm việc trong dự án thì mình cũng đã phát hiện ra một thư viện hỗ trợ cho việc thực hiện animation rất hay đó chính là Lottie. Vì vậy ngày hôm nay mình muốn chia sẻ với các bạn về thư viện này và cách thêm nó vào ứng dụng của các bạn.

    Lottie là gì?

    Lottie là định dạng tệp hoạt hình dựa trên JSON cho phép các nhà thiết kế gửi animation trên bất kỳ nền tảng nào dễ dàng như vận chuyển nội dung tĩnh. Bạn có thể đọc thêm về nó ở đây.

    Lottie được sử dụng khi nào?

    Lottie khá là linh hoạt và nó có thể sử dụng được trên nhiều nền tảng khác nhau từ iOS, Android, Web …. Vì vậy nó giúp đồng bộ animation trên tất cả các nền tảng mà không xảy ra sai sót nào.

    Lottie hỗ trợ làm animation rất tốt, nó có thể làm được những animation rất phức tạp mà việc code animation không thể làm được hoặc làm đơn giản hoá việc thêm animation vào trong ứng dụng.

    Hiện nay nó đang được sử dụng khá phổ biến trên các ứng dụng.

    Làm sao để sử dụng Lottie cho ứng dụng của bạn?

    Bước 1: Tạo dự án mới

    Nếu bạn muốn thêm vào dự án có sẵn của mình thì bỏ qua bước này nhé.

    Đầu tiên chúng ta mở Xcode lên và tạo một ứng dụng demo để có thể test Lottie chạy trên ứng dụng một cách dễ dàng hơn.

    Tạo ứng dụng mới

    Bước 2: Thêm thư viện Lottie vào ứng dụng

    Để thêm thư viện Lottie vào ứng dụng chúng ta có thể sử dụng bằng nhiều cách khác nhau như Cocoa Pods, Carthage hoặc Swift Package Manager. Nếu bạn chưa biết cách thêm thư viện Lottie vào dự án thì bạn có thể tham khảo hướng dẫn tại đây.

    Ở bài viết này mình sẽ hướng dẫn các bạn sử dụng Swift Package Manager để thêm vào ứng dụng như sau:

    Bạn mở dự án của bạn trên Xcode, chọn File -> Add Packages… Trên đầu bên phải của popup hiện ra có công cụ tìm kiếm các bạn đánh link: https://github.com/airbnb/lottie-ios.git để tìm thư viện. Sau đó bấm vào Add Package và chờ một lúc để Xcode tải thư viện bạn, sau khi tải xong bạn bấm Add để hoàn tất

    Bước 3: Thêm Lottie vào dự án

    Đầu tiên chúng ta cần chuẩn bị file Lottie JSON, nếu bạn chưa có thì bạn có thể download nó tại đây. Bạn nhớ tải file JSON nhé.

    Bạn cũng có thể sử dụng ứng dụng Adobe After Effect để tạo file Lottie của riêng mình.

    Khi đã có file lottie JSON rồi chúng ta sẽ thực hiện thêm file vào trong dự án bằng cách kéo thả nó trực tiếp vào thư mục trong Xcode của bạn, tại nơi mà bạn muốn lưu nó. Thông thường chúng ta tạo mới thư mục Resouces/LottieJSON và lưu nó trong đó.

    Add file lottie vào dự án

    Hãy nhớ bạn tích Copy Items if needed và các mục như trên hình nhé.

    Thêm Lottie bằng cách sử dụng code

    Đầu tiên để sử dụng được thư viện Lottie hãy nhớ import thư viện Lottie vào màn hình mà bạn sử dụng

    import Lottie

    Tiếp theo chúng ta tạo func addLottieAnimation() để thực hiện nhiệm vụ add lottie vào view hiện tại. Bạn cũng có thể custom một func riêng để handle xử lí triệt để các logic của Lottie animation. Trong bài này mình chỉ hướng dẫn cơ bản để các bạn có thể thêm Lottie vào ứng dụng của mình.

        func addLottieAnimation() {
            // 1. Create lottie animation view
            let lottieView: LottieAnimationView = LottieAnimationView(name: "404-Notfound")
            lottieView.frame = view.bounds
            
            // 2. Set animation content mode
            lottieView.contentMode = .scaleAspectFit
            
            // 3. Set animation loop mode
            lottieView.loopMode = .loop
            
            // 4. Adjust animation speed
            lottieView.animationSpeed = 0.5
            
            view.addSubview(lottieView)
            
            // 5. Play animation
            lottieView.play()
        }

    Sau đó bạn call func này ở viewDidLoad thì sẽ nhận được kết quả như sau:

    Thêm Lottie bằng cách sử dụng Builder Interface

    Đầu tiên bạn mở file giao diện của bạn lên băng Interface Builder, kéo một UIView và thực hiện constraint cho nó.

    Thêm UIview vào để sử dụng Lottie

    Sau đó ở trên cùng của tab bên phải bạn chọn Identity Inspector và thay đổi giá trị như hình dưới

    Thay đổi class sang LottieAnimationView

    Tiếp tục chuyển sang tab Attributes Inspector để điền tên file Lottie mà bạn muốn

    Tạo một liên kết giữa view của bạn với file controller để sử dụng, sau đó bạn thực hiện code như dưới đây để Lottie có thể hoạn động.

    import UIKit
    import Lottie
    
    class ViewController: UIViewController {
        @IBOutlet weak var animationLottieView: LottieAnimationView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            // 1. Set animation content mode
            animationLottieView.contentMode = .scaleAspectFit
    
            // 2. Set animation loop mode
    
            animationLottieView.loopMode = .loop
    
            // 3. Adjust animation speed
    
            animationLottieView.animationSpeed = 0.5
    
            // 4. Play animation
            animationLottieView.play()
        }
    }

    Chạy ứng dụng và bạn sẽ nhận được kết quả như mong đợi!

    Vậy là mình đã giới thiệu và hướng dẫn cho các bạn một thư viện khá hiệu quả cho các bạn sử dụng để bổ trợ cho các bạn việc thực hiện animation tốt hơn, dễ dàng hơn, và nhanh hơn so với cách code truyền thống. Từ giờ các bạn có thể tha hồ sáng tạo trên những ứng dụng sắp tới của bản thân và khiến mọi người sử dụng thích thú.

    Mình hi vọng nó sẽ giúp các bạn làm ra những ứng dụng có trải nghiệm tuyệt vời, giúp những người sử dụng ứng dụng của các bạn phải trầm trồ khi sử dụng nó. Chúc các bạn thành công với những dự án sắp tới của mình.

  • Architecture pattern: Clean Swift

    Architecture pattern: Clean Swift

    Xin chào mọi người! Lại là DaoNM2 đây! Tiếp tục với series về Architecture pattern hôm nay mình sẽ giới thiệu cho các bạn một kiến trúc khá mới so với các mẫu kiến trúc hiện tại đó là Clean Swift (VIP). Trước đây mình đã có cơ hội tiếp cận với kiến trúc này, khi tham gia vào việc xây dựng một ứng dụng rất lớn cho một công ti rất nổi tiếng về ô tô xe máy ở Việt Nam. Khi đó mình cũng đã tích luỹ được một số kinh nghiệm về kiến trúc này, vì vậy mình muốn chia sẻ với các bạn một số thông tin cũng như kinh nghiệm mà mình đã tích luỹ được khi làm việc với mẫu kiến trúc này.

    Đầu tiên nếu bạn là người mới hoặc bạn mới tìm hiều về các mẫu kiến trúc trong lập trình bao giờ đây là bài đầu tiên bạn đọc, thì để có thể hiểu kiến trúc này tốt hơn thì bạn có thể tham khảo các bài viết về các mẫu thiết kế trước khi tiếp tục ở link dưới đây:

    Bối cảnh hình thành

    Clean swift lần đầu tiên được giới thiệu bởi Raymond Law trên website clean-swift.com của anh ấy. Ý tưởng hình thành mẫu kiến trúc này là do anh ấy đã quá chán với các vấn đề của MVC(một mẫu kiến trúc mà Apple khuyên dùng) vì vậy anh ấy đã nghĩ ra Clean Swift để giải quyết các vấn đề mà các mẫu kiến trúc trước đây chưa làm được. Clean Swift được Raymond Law xây dựng dựa trên Clean Architecture của Uncle Bob.

    Clean Swift là gì?

    Clean Swift là một mẫu kiến trúc xây dựng dựa trên Clean Architecture của Uncle Bob để áp dụng cho việc xây dựng các ứng dụng iOS và MacOS.

    Trong Clean Swift Architecture pattern thì tất cả logic của ứng dụng sẽ được chia đều ra 3 thành phần chính của nó là View controller, Interactor và Presenter. Mỗi phần sẽ đảm nhiệm một số logic cụ thể, và chúng liên lạc với nhau bằng các liên kết 1 chiều, vì vậy source code của bạn sẽ luôn luôn đi theo một chiều chứ không đa chiều như các architecture pattern khác.

    Khi ứng dụng Clean Swift vào trong dự án của bạn, nó sẽ được cấu trúc theo từng màn hình của ứng dụng(scenes).

    Các thành phần của Clean Swift

    Mẫu kiến trúc Clean Swift được cấu tạo bởi các phần như sau:

    • View
    • View Controller
    • Router
    • Presenter
    • Interactor
    • Worker(optional)
    • Model(optional)
    Clean Swift architecture pattern

    Clean Swift gồm 3 phần chính là ViewController, Presenter và Interactor. 3 phần này có liên kết 1 chiều và tạo thành 1 vòng tròn. Khi View controller nhận được request nó sẽ gọi sang Interactor để nó xử lí logic, khi xong logic Interactor sẽ gửi dữ liệu sang bên Presenter để nó thực hiện format lại dữ liệu rồi trả về cho ViewController làm nhiệm vụ update lên View cho người dùng. Các thành phần này sẽ được kết nối với nhau bằng protocol.

    View

    Là các thành phần nằm trong UIKit hoặc bất kể thứ gì liên quan tới UI ví dụ như: storyboard, xib, UIView, UIControl …

    View Controller

    Định nghĩa các màn hình(scenes), nó có thể chứa 1 hoặc nhiều View

    Nó sẽ giữ các instances của Interactor và Router

    Là nơi nhận các tương tác của người dùng và gọi đến Interactor hoặc Router để xử lý, nó cũng nhận output của Presenter làm input và truyền nó lên view để hiển thị cho người dùng.

    Interactor

    Chứa các business logic của màn hình

    Giữ instance của Presenter và các Workers(nếu có)

    Nhận thông tin input từ ViewController và xử lý hoặc yêu cầu Worker làm việc để truyền kết quả sang cho Presenter

    Interactor sẽ không được import UIKit để đảm bảo source không có liên kết trực tiếp với View

    Presenter

    Giữ một tham chiếu yếu đến View Controller để truyền dữ liệu sang View Controller

    Là nơi xử lý logic hiển thị, khi nhận được input từ Interactor nó sẽ thực hiện format lại dữ liệu và truyền sang cho ViewController để nó hiển thị thông tin cho người dùng.

    Worker

    Là nơi được coi là trung tâm dữ liệu, nó sẽ thực hiện các nhiệm vụ liên quan tới việc lấy dữ liệu từ API hoặc LocalDB

    Là thành phần phụ nên ở các màn hình đơn giản không có tương tác với dữ liệu chúng ta có thể bỏ qua worker

    Router

    Nó giữ một tham chiếu yếu tới View Controller, nhằm mục đích tránh việc tham chiếu lẫn nhau(retain cycles) dẫn đến không thể release các đối tượng này khi nó không còn được sử dụng -> Lack memory.

    Router sinh ra để giảm tải công việc cho View Controller nó sẽ làm nhiệm vụ điều hướng trong ứng dụng.

    Model

    Nó là nơi định nghĩa các đối tượng cho ứng dụng, nó chỉ làm nhiệm vụ định nghĩa các đối tượng và không có xử lí logic hay liên kết trực tiếp với các thành phần khác của kiến trúc.

    Các đối tượng trong model sẽ được khai báo theo value type(struct, enum)

    Tương tự như Worker, một số màn hình đơn giản không tương tác với dữ liệu sẽ không cần đến Model

    Ưu điểm

    • Dễ maintain, fixbugs vì liên kết 1 chiều giữa các thành phần
    • Hỗ trợ viết Unit test một cách dễ dàng
    • Viết các phương thức ngắn hơn với trách nhiệm duy nhất
    • Tách được business logic sang cho Interactor xử lí
    • Có thể tái sử dụng các Workers và Services
    • Có thể áp dụng được cho các dự án lớn để giảm tình trạng conflict khi merge source.

    Nhược điểm

    • Quá nhiều các protocol với các nhiệm vụ riêng biệt, làm cho việc đặt tên trở nên khó khăn và không cẩn thận sẽ gây khó hiểu cho người đọc
    • Kích thước ứng dụng lớn do chứa nhiều protocol và nhiều file
    • Cần thời gian để cho các thành viên dự án có thể hiểu và tuân theo

    Tổng kết

    Clean swift là một mẫu kiến trúc không phổ biến như các mẫu khác, tuy nhiên ưu điểm của nó mang lại là rất lớn. Để vận hành các dự án lớn có yêu cầu viết Unitest chúng ta có thể coi Clean Swift là một trong những ứng cử viên sáng giá. Mình hi vọng bài viết có thể giúp các bạn có thêm kiến thức về mẫu kiến trúc Clean Swift và giúp các bạn có thể chọn được mẫu kiến trúc ưng ý cho những dự án sắp tới.

    Nếu các bạn muốn biết thêm nhiều thông tin hơn về Clean Swift Architecture Pattern thì các bạn có thể tham khảo tại link sau: Clean Swift