Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on March 9, 2022

File Manager trong 10 phút – Swift

iOS & Swift . Tutorials

Contents

  • Chuẩn bị
  • File Manager
  • File Path
    • Get Bundle Resource Path
    • Get Documents Directory Path
    • Append Path Component
    • File Exists
  • File Handle
    • Read File
    • Write File
    • Attributes
    • Copy, Move & Remove file
  • Bundles
  • System Defined Folders
    • Documents
    • Temporary Diretory
    • Custom Foders
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Chủ đề quản lý File thì rất quen thuộc với tất cả mọi người khi tìm hiểu một nền tảng nào đó rồi. Và bài viết này giúp bạn tìm hiểu thêm về quản lý file trong iOS với File Manager. Hướng dẫn bạn các thao tác cơ bản với file.

Đây là một chủ đề về lưu trữ dữ liệu trong iOS. Bạn có thể tham khảo các bài viết liên quan khác tại các link dưới đây:

  • Core Data
  • UserDefaults
  • Keychain
  • Realm Swift

Còn nếu mọi việc đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

Về mặt tools và versions, bạn sẽ không cần phải lo lắng nhiều. Vì đối tượng chúng ta cần tương tác thì có mặt trong Core iOS từ rất lâu rồi.

Về mặt lý thuyết, bạn cần đảm bảo là đã và đang học iOS cơ bản rồi. Còn nếu bạn chưa biết gì về iOS & Swift, thì có thể tham khảo link bài viết sau:

  • Lập trình iOS cho mọi người

Về mặt demo, chúng ta hãy tạo một iOS project đơn giản nhóe. Bạn sẽ không cần tới giao diện ứng dụng, các kết quả sẽ hiển thị ở console.

File Manager

Theo định nghĩa, thì File Manager là một giao diện thuận tiện cho nội dung của hệ thống files và là phương tiện chính để tương tác với nó.

Đối tượng File Manager cho phép bạn:

  • Kiểm tra nội dung của các file & thư mục của hệ thống hoặc được tạo ra bởi người dùng
  • Thực hiện các thay đổi với các file & thư mục

Lớp FileManager sử dụng để thực hiện những công việc cơ bản với file và thư mục như: tạo, sửa, ghi , di chuyển, đọc nội dung, đọc thông tin thuộc tính của file, thư mục… Ngoài ra cũng cung cấp phương thức lấy thư mục hiện hành, thay đổi, tạo mới thư mục, liệt kê danh sách nội dung có trong thư mục … vâng vâng và mây mây.

Chúng ta sẽ bắt đầu tìm hiểu thông qua các ví dụ ở các phần sau.

File Path

Đường dẫn tới file (File Path) là điều quan trọng nhất khi bạn muốn làm việc với hệ thống files trong bất cứ ngôn ngữ hay nền tảng nào. Và iOS thì không phải ngoại lệ. Bên cạnh đó, iOS sẽ có thêm một số vấn đề liên quan tới File Path nữa mà bạn cần chú ý tới.

Đối tượng thể hiện File Path chính là URL. Bạn sẽ dùng đối tượng URL để lưu trữ các đường đẫn tới File & Thư mục. Bên cạnh đó, bạn cũng dễ dàng chỉnh sửa chúng.

Get Bundle Resource Path

Đầu tiên, bạn cần phải lấy được đường dẫn tới các files tài nguyên (resources) mà bạn đính kèm trong Project. Hay cũng chính là Bundle ứng dụng của bạn.

Bạn sẽ tìm hiểu Bundle sau nhóe!

Chúng ta xem ví dụ code để tìm đường dẫn tới một file *.txt nào đó trong được đính kèm theo project nhóe.

let filePath = Bundle.main.url(forResource: "hello_file", withExtension: "txt")

Với phương thức được sử dụng là Bundle.main.url và cần truyền vào các tham số:

  • forResource cho tên file
  • withExtension cho đuôi file (hay format, hay phần mở rộng …)

Là bạn có được đường dẫn tới đúng file mà bạn đính kèm trong Project rồi nhóe. Và bạn có còn một phương thức khác để lấy trực tiếp đường dẫn, nhưng với kiểu dữ liệu trả về là String.

Get Documents Directory Path

Với ứng dụng iOS, bạn sẽ có thêm một nơi dùng để lưu trữ các files riêng của bạn. Nó không thuộc hệ thống của thiết bị. Nó thuộc về riêng của ưng dụng iOS bạn. Hay còn gọi là Documents Directory.

Và cách lấy đường dẫn tới thư mục đó như sau:

func getDocumentsDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let documentsDirectory = paths[0]
    return documentsDirectory
}

Trong đó:

  • Sử dụng đối tượng File Manager để lấy đường dẫn tới thư mục Documents
  • Dữ liệu trả về sẽ là một mãng các AnyObject, do đó bạn chỉ cần lấy phần tử đầu tiên thôi nhóe

Bạn nên tạo riêng một function để lấy đường dẫn tới thư mục này. Chúng ta sẽ dùng lại nó trong nhiều ví dụ phía dưới.

Append Path Component

Với Bundle thì chúng ta sẽ xác định đường dẫn trực tiếp tới thẳng file. Còn với Document thì ta phải xác định từng bước.

  • Đầu tiên, sẽ là đường dẫn tới thư mục.
  • Sau đó, bạn sẽ nối thêm các đường dẫn khác, nếu file của bạn nằm trong các thư mục con trong Documents
  • Cuối cùng, bạn sẽ nối tiếp tên file vào đường dẫn

Xem ví dụ nhóe!

func getDocumentFilePath(fileName: String) -> URL {
    let documentPath = getDocumentsDirectory()
    let filePath = documentPath.appendingPathComponent(fileName)
    
    return filePath
}

Bạn sử dụng đối tượng URL và gọi phương thức .appendingPathComponent(_) của chính nó để thêm các phần đường dẫn tiếp theo. Với phương thức này, bạn hoàn toàn yên tâm với các dấu / hay \ … khi chỉnh sửa đường dẫn cho phù hợp.

Để dễ so sánh, bạn hãy thử thực thi đoạn code sau nhóe!

print("Documents Directory Path: \(getDocumentsDirectory().absoluteString)")
        
print("Append Path Component:  \(getDocumentFilePath(fileName: "hello.txt"))")

File Exists

Bạn không thể nào biết được file của bạn có tồn tại hay là không. Và bạn cần đảm bảo dữ liệu nội dung của file sẽ lấy được. Thì bạn cần phải kiểm tra được file đó với đường dẫn đó là có tồn tại hay là không.

Chúng ta sẽ lại sử dụng tới đối tượng FileManager để thực hiện công việc này. Xem ví dụ code tiếp nhóe!

func checkFileExist(fileName: String) -> Bool {
    let filePath = getDocumentFilePath(fileName: fileName)
    let fileManger = FileManager.default
    
    if fileManger.fileExists(atPath: filePath.path) {
        print("FILE: \(fileName) is AVAILABLE")
        return true
    } else {
        print("FILE: \(fileName) NOT AVAILABLE")
        return false
    }
}
  • Sử dụng đối tượng FileManager.default và gọi phương thức fileExists để kiểm tra file có tồn tại hay là không.
  • Tham số cần truyền là thuộc tính path của đối tượng URL

Ngoài ra, bạn có thể tạo 1 extension cho URL để xác định tồn tại của file.

extension URL    {
    func checkFileExist() -> Bool {
        let path = self.path
        if (FileManager.default.fileExists(atPath: path))   {
            print("FILE AVAILABLE")
            return true
        }else        {
            print("FILE NOT AVAILABLE")
            return false;
        }
    }
}

// Sử dụng
if fileUrl.checkFileExist()
   {
      // Do Something
   }

Thực thi các đoạn code và cảm nhận kết quả nhóe!

File Handle

Sau khi bạn đã tìm hiểu cơ bản về File Path, thì chúng ta sẽ tiếp tục với việc thao tác với các file. Các thao tác cơ bản chính là đọc & ghi file.

Read File

Đầu tiên, chúng ta sẽ tìm cách đọc nội dung của một File là như thế nào. Bạn đã có được path (đường dẫn) tới một file và nó được lưu trữ bằng một đối tượng URL.

Tiếp theo, bạn sẽ sử dụng kiểu dữ liệu Data và khởi tạo bằng phương thức lấy nội dung của từ một url. Bạn xem qua ví dụ nhóe!

guard let txtFileURL = Bundle.main.url(forResource: "hello_file", withExtension: "txt") else {
    return
}

do {
    let data = try Data(contentsOf: txtFileURL)
    let content = String(data: data, encoding: .utf8)
    print("Content File: \(content ?? "n/a")")
} catch {
    print(error.localizedDescription)
}

Trong đó:

  • Ta sẽ lấy đường dẫn của file hello_file.txt có trong Bundle
  • Nội dung của file thì sẽ lấy bằng việc khởi tạo một Data với phương thức (contentsOf)
  • Chuyển đổi định dạng của Data về kiểu dữ liệu mong muốn, là String
  • Quá trình này có thể sinh ra lỗi nên tốt nhất là đặt trong try catch và bắt các error phát sinh

Tuy nhiên, Swift cũng hỗ trợ một số kiểu chuyển đổi trực tiếp nhanh hơn cho các kiểu dữ liệu cơ bản. Bạn thử thay đoạn code xử lý data trên bằng dòng code sau nhóe!

let content = try String(contentsOf: txtFileURL)

Khá là EZ phải không nào. Bạn có thể khám phá thêm với các kiểu dữ liệu khác khi đọc trực tiếp nội dung từ file. Tuy nhiên, chúng ta nên sử dụng Data như là một kiểu dữ liệu trung gian & an toàn nhất.

Ví dụ với việc đọc một file chung chung trong Documents và sử dụng Data như sau:

func readFile(fileName: String) -> Data? {
    if checkFileExist(fileName: fileName) {
        let filePath = getDocumentFilePath(fileName: fileName)
        do {
            let data = try Data(contentsOf: filePath)
            return data
        } catch {
            print("Can not read file")
            return nil
        }
    } else {
        print("File not available.")
        return nil
    }
}

Write File

Bạn có thể áp dụng cách ngược lại với việc đọc file. Và bạn sử dụng phương thức .write() có trong một số lớp dữ liệu cơ bản, để tiến hành ghi vào file với đường dẫn cho trước.

Việc lưu file hay việc ghi file chỉ thực hiện với các file ở thư mục Documents. Với các file tài nguyên ở Bundle thì chúng ta chỉ đọc mà thôi.

Ví dụ với việc ghi một nội dung String vào một file TXT nhóe!

func writeFile(fileName: String, content: Data) -> Bool {
    let filePath = getDocumentFilePath(fileName: fileName)
    
    do {
        try content.write(to: filePath)
        return true
        
    } catch {
        print("Can not write file")
        return false
    }
}

Trong đó:

  • Sử dụng kiểu dữ liệu Data như là một lớp trung gian cho việc đọc nội dung của file
  • Sử dụng phương thức .write với tham số là path.
  • Bạn sẽ tiến hành ghi đè file, nên việc xác định file tồn tại hay không thì không quan trọng.

Ví dụ tiếp cho việc sử dụng như sau:

// infor
let stringContent = "Hello, I am supperman!"
let fileName = "superman.txt"

// write file
if let stringData = stringContent.data(using: .utf8) {
    let okay = writeFile(fileName: fileName, content: stringData)
    if okay {
        print("DONE")
    } else {
        print("FAILED")
    }
}

// read file
if let dataFile = readFile(fileName: fileName) {
    let content = String(data: dataFile, encoding: .utf8) ?? "n/a"
    print("File: \(fileName) : \(content)")
}

Còn với các kiểu dữ liệu khác (như: Array, Dictionary, UIImage …) bạn vẫn sử dụng Data là lớp trung gian để xử lý nội dung của file.

Attributes

Tiếp theo, đối tượng File Manager còn hỗ trợ bạn có thể thấy được các thuộc tính (Attributes) của một file. Và công việc này cũng khá đơn giản. Bạn chỉ chần xác định được file path mà thôi.

Xem ví dụ code nhóe!

let fileSupperManPath = getDocumentFilePath(fileName: "superman.txt").path
do {
    let fileManager = FileManager.default
    let attributes = try fileManager.attributesOfItem(atPath: fileSupperManPath)
    print("File Attributes:")
    for item in attributes {
        print(item)
    }
} catch {
    print(error.localizedDescription)
}

Trong đó, với phương thức .attributesOfItem(atPath: ) giúp bạn lấy được tất cả thuộc tính của một file theo đường dẫn cho trước. Bạn hãy thực thi đoạn code và cảm nhận kết quả nhóe.

File Attributes:
(key: __C.NSFileAttributeKey(_rawValue: NSFileCreationDate), value: 2022-03-07 07:49:33 +0000)
(key: __C.NSFileAttributeKey(_rawValue: NSFileExtensionHidden), value: 0)
(key: __C.NSFileAttributeKey(_rawValue: NSFileSystemFileNumber), value: 49429720)
(key: __C.NSFileAttributeKey(_rawValue: NSFileGroupOwnerAccountName), value: staff)
(key: __C.NSFileAttributeKey(_rawValue: NSFileGroupOwnerAccountID), value: 20)
(key: __C.NSFileAttributeKey(_rawValue: NSFileOwnerAccountID), value: 503)
(key: __C.NSFileAttributeKey(_rawValue: NSFileType), value: NSFileTypeRegular)
(key: __C.NSFileAttributeKey(_rawValue: NSFileSystemNumber), value: 16777230)
(key: __C.NSFileAttributeKey(_rawValue: NSFileReferenceCount), value: 1)
(key: __C.NSFileAttributeKey(_rawValue: NSFileModificationDate), value: 2022-03-08 03:04:02 +0000)
(key: __C.NSFileAttributeKey(_rawValue: NSFilePosixPermissions), value: 420)
(key: __C.NSFileAttributeKey(_rawValue: NSFileSize), value: 22)

Cũng lấy được khá nhiều thuộc tính đó. Ahihi!

Copy, Move & Remove file

Tiếp theo, bạn sẽ cần tới một số lệnh cơ bản trên file nhóe. Ví dụ như là: copy, move & remove … Tất cả cũng đều thực hiện bằng đối tượng File Manager và bạn cần xác định file path của file cần thao tác trước.

moveItem(atPath: toPath:)

copyItem(atPath: toPath:)

removeItem(atPath:)

Bạn thử tạo ví dụ riêng cho mình nhóe!

Bundles

Bây giờ, ta sẽ bàn tới khái niệm Bundles được đề cập ở trên. Một số thông tin như sau:

  • Trên nền tảng của Apple, các ứng dụng được phân phối dưới dạng bundles.
  • Một Project của bạn có thể có nhiều Bundles.
  • Các file có trong Bundle được xem là file tài nguyên (resources) và nó được thêm vào project.
  • Tất cả chúng chỉ ở trạng thái chỉ đọc (read-only)
  • Bundle mà bạn hay dùng nhất là .main

Về demo cho việc tương tác file có trong Bundle thì mình có thể tổng hợp theo ví dụ code sau đây:

struct ContentLoader {
    enum Error: Swift.Error {
        case fileNotFound(name: String)
        case fileDecodingFailed(name: String, Swift.Error)
    }

    func loadBundledContent(fromFileNamed name: String) throws -> Content {
        guard let url = Bundle.main.url(
            forResource: name,
            withExtension: "json"
        ) else {
            throw Error.fileNotFound(name: name)
        }

        do {
            let data = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            return try decoder.decode(Content.self, from: data)
        } catch {
            throw Error.fileDecodingFailed(name: name, error)
        }
    }
    
    // ...
}

Trong đó:

  • Struct ContentLoader chỉ là mình họa cho việc handle các file thuộc Bundle
  • Trước tiên, bạn phải xác định được file path của file trong Bundler với funtion Bundle.main.url(forResource:_, withExtension:_)
  • Tiếp theo, bạn sẽ dùng Data làm lớp dữ liệu trung gian để lấy nội dung của file
  • Cuối cùng chính là việc xử lý dữ liệu về đúng kiểu dữ liệu mong muốn. Trong ví dụ là kiểu JSON.

Và điều quan trọng bạn cần nhớ là:

Xác định đúng Bundle mà bạn muốn sử dụng. Vì không phải lúc nào cũng chỉ dùng Main Bundle mà thôi.

System Defined Folders

Với các file thuộc Bundles thì nhược điểm lớn nhất chính là việc chỉ đọc (read-only). Nên bạn sẽ tìm tới một giải pháp khác cho việc cả đọc & ghi các file trong quá trình sử dụng ứng dụng.

Documents

Đầu tiên, chính là thư mục Documents của ứng dụng. Với MacOS, bạn có thể sử dụng chính thư mục Documents của user trong MacOS. Nhưng với iOS, với cơ chế sandbox thì các ứng dụng sẽ có các thư mục Documents riêng lẻ với nhau. Và ta có các ưu điểm sau:

  • Đảm bảo bảo mật với các file của ứng dụng
  • Đọc & ghi file
  • Hỗ trợ các phương thức đọc/ghi khá đơn giản
  • Bạn có thể tạo thêm các thư mục khác của bạn bên trong thư mục Documents, giúp cho việc quản lý file hiệu quả hơn.

Với đối tượng File Manager thì việc đọc/ghi file lại càng đơn giản. Với:

  • write dùng cho ghi file
  • contentOf dùng cho đọc file

Với các ví dụ trên, thì bạn cũng đã được thao tác nhiều với các file từ Documents rồi. Sau đây, mình tóm tắt lại với ví dụ code sau:

struct FileIOController {
    func write<T: Encodable>(
        _ value: T,
        toDocumentNamed documentName: String,
        encodedUsing encoder: JSONEncoder = .init()
    ) throws {
        let folderURL = try FileManager.default.url(
            for: .documentDirectory,
            in: .userDomainMask,
            appropriateFor: nil,
            create: false
        )

        let fileURL = folderURL.appendingPathComponent(documentName)
        let data = try encoder.encode(value)
        try data.write(to: fileURL)
    }
    
    // ...
}

Temporary Diretory

Tương tự, chúng ta cũng có thể sử dụng API File Manager ở trên để giải quyết các system folders khác. Ví dụ: folder mà hệ thống cho là thích hợp nhất để sử dụng cho disk-based caching:

let cacheFolderURL = try FileManager.default.url(
    for: .cachesDirectory,
    in: .userDomainMask,
    appropriateFor: nil,
    create: false
)

Trong đó:

  • Với cachesDirectory là tham số dùng để xác định thư mục tạm thời

Tuy nhiên, nếu tất cả những gì chúng ta đang tìm là URL cho một folder tạm thời, chúng ta có thể sử dụng hàm NSTemporaryDirectory đơn giản hơn nhiều. Hàm trả về một URL cho một system folder có thể được sử dụng để lưu trữ dữ liệu mà chúng ta chỉ muốn tồn tại trong thời gian ngắn:

let temporaryFolderURL = URL(fileURLWithPath: NSTemporaryDirectory())

Lợi ích của việc sử dụng các API ở trên, thay vì hard coding các đường dẫn thư mục cụ thể trong code, chúng ta cho phép hệ thống quyết định thư mục nào phù hợp nhất cho nhiệm vụ hiện tại.

Custom Foders

Mặc dùng bạn có thể tạo được thêm các thư mục của riêng bạn trong ứng dụng. Tuy nhiên, với iOS thì mọi thứ bạn có thể làm thì đều ở trong thư mục Documents của ứng dụng mà thôi.

Nhưng với việc bạn có một thư mục riêng để lưu trữ các file của riêng bạn, thì sẽ tránh đi việc xung đột với các file/thư mục hệ thống. Xem ví dụ code tổng hợp như sau:

struct FileIOController {
    var manager = FileManager.default

    func write<T: Encodable>(
        _ object: T,
        toDocumentNamed documentName: String,
        encodedUsing encoder: JSONEncoder = .init()
    ) throws {
        let rootFolderURL = try manager.url(
            for: .documentDirectory,
            in: .userDomainMask,
            appropriateFor: nil,
            create: false
        )

        let nestedFolderURL = rootFolderURL.appendingPathComponent("MyAppFiles")
        
        if !manager.fileExists(atPath: nestedFolderURL.relativePath) {
            try manager.createDirectory(
                at: nestedFolderURL,
                withIntermediateDirectories: false,
                attributes: nil
            )
        }

        let fileURL = nestedFolderURL.appendingPathComponent(documentName)
        print("File URL: \(fileURL.path)")
        let data = try encoder.encode(object)
        try data.write(to: fileURL)
    }
    
}

Trong đó:

  • Bạn sẽ có một thư mục riêng với tên là MyAppFiles.
  • Để xác định URL tới thư mục đó, thì bạn thực hiện như bình thường
  • Các công việc đọc/ghi file cũng tương tự với thao tác ở trên

Điều chú ý, là việc bạn tạo mới thư mục. Bạn cần phải kiểm tra sự tồn tại của nó trước.

if !manager.fileExists(atPath: nestedFolderURL.relativePath) {
    try manager.createDirectory(
        at: nestedFolderURL,
        withIntermediateDirectories: false,
        attributes: nil
    )
}

Nếu thư mục chưa tồn tại, thì khi đó bạn sẽ tạo mới một thư mục theo đường dẫn nestedFolderURL.

Xem qua ví dụ sử dụng struct trên nhóe.

let fileIO = FileIOController()
let fileContent = "Custom Folder"
let customFileName = "abc.txt"

do {
    try fileIO.write(fileContent, toDocumentNamed: customFileName)
} catch {
    print(error.localizedDescription)
}

Hãy thực thi đoạn code và cảm nhận kết quả nhóe.

Tạm kết

  • Tìm hiểu cơ bản về hệ thống file được sử dụng trong ứng trong ứng dụng iOS
  • File Manager và các thao tác cơ bản trên files
  • Phân biệt giữa Bundles & Documents files
  • Đọc ghi với files
  • Xử lý các Custom Folder

 

Okay! Tới đây, mình xin kết thúc bài viết về File Manager trong iOS . Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.

  • Bạn có thể checkout code tại đây.
  • Nguồn tham khảo: Working with files and folders in Swift

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares13

Related Posts:

  • feature_bg_swiftui_4
    Regular Expression (Regex) trong Swift
  • KeyPath
    KeyPath trong 10 phút - Swift
  • feature_bg_swift_04
    [Swift 6.2] Raw Identifiers - Đặt tên hàm có dấu…
  • POP
    Lập trình hướng giao thức (POP) với Swift
Tags: basic ios tutorial, iOS
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift AI api AppDistribution autolayout basic ios tutorial blog ci/cd closure collectionview combine concurrency crashlytics dart dart basic dart tour Declarative delegate deploy design pattern fabric fastlane firebase flavor flutter GCD gradients iOS MVVM optional Prompt engineering protocol Python rxswift safearea Swift Swift 5.5 SwiftData SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Role-playing vs. Persona-based Prompting
  • [Swift 6.2] Raw Identifiers – Đặt tên hàm có dấu cách, tại sao không?
  • Vibe Coding là gì?
  • Cách Đọc Sách Lập Trình Nhanh và Hiệu Quả Bằng GEN AI
  • Nỗ Lực – Hành Trình Kiến Tạo Ý Nghĩa Cuộc Sống
  • Ai Sẽ Là Người Fix Bug Khi AI Thống Trị Lập Trình?
  • Thời Đại Của “Dev Tay To” Đã Qua Chưa?
  • Prompt Engineering – Con Đường Để Trở Thành Một Nghề Nghiệp
  • Vấn đề Ảo Giác (hallucination) khi tương tác với Gen AI và cách khắc phục nó qua Prompt
  • Điều Gì Xảy Ra Nếu… Những Người Dệt Mã Trở Thành Những Người Bảo Vệ Cuối Cùng Của Sự Sáng Tạo?

You may also like:

  • Lập trình hướng giao thức (POP) với Swift
    POP
  • Swift Optional trong 10 phút
    feature_bg_swift_10
  • Quick trong 10 phút
    Quick
  • Clean Architecture trong iOS
    feature_bg_3
  • Flavor & Câu chuyện config trong Flutter
    Flavor

Archives

  • May 2025 (2)
  • April 2025 (1)
  • March 2025 (8)
  • January 2025 (7)
  • December 2024 (4)
  • September 2024 (1)
  • July 2024 (1)
  • June 2024 (1)
  • May 2024 (4)
  • April 2024 (2)
  • March 2024 (5)
  • January 2024 (4)
  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (44)
  • Code (11)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (102)
  • No Category (1)
  • RxSwift (37)
  • SwiftUI (80)
  • Tutorials (87)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2025 Fx Studio - All rights reserved.