Category: iOS Development

English Posting for iOS development

  • Swift, ARC Automatic Reference Counting

    Swift, ARC Automatic Reference Counting

    ✍️ Note

    All the codes and contents are sourced from Apple’s official documentation. This post is for personal notes where I summarize the original contents to grasp the key concepts

    Apple document

    class Person {
        let name: String
        init(name: String) {
            self.name = name
            print("\(name) is being initialized")
        }
        deinit {
            print("\(name) is being deinitialized")
        }
    }

    Check Strong Reference Count

    Example 1

    var person = Person(name: "Shawn") //1
    CFGetRetainCount(person) //2

    Example 2

    CFGetRetainCount(Person(name: "Shawn") //1

    Example 3

    var person = Person(name: "Shawn") //2
    var person2 = person //3
    var person3 = person //4
    
    CFGetRetainCount(person) //4

    As you can see when you initiate an object, the retain count is 1. And when you assign it, retain count will be counting up.

    Strong Reference Cycle between classes

    class Person {
        let name: String
        init(name: String) { self.name = name }
        var apartment: Apartment?
        deinit { print("\(name) is being deinitialized") }
    }
    
    
    class Apartment {
        let unit: String
        init(unit: String) { self.unit = unit }
        var tenant: Person?
        deinit { print("Apartment \(unit) is being deinitialized") }
    }

    Example 1

    var person = Person(name: "Shawn")
    var apt = Apartment(unit: "3")
    
    CFGetRetainCount(person) //2
    CFGetRetainCount(apt) //2

    Example 2 (🔥 Strong Reference Cycle)

    var person = Person(name: "Shawn")
    var apt = Apartment(unit: "3")
    
    person.apartment = apt
    apt.tenant = person
    
    CFGetRetainCount(person) //3 (apt.tenant -> count up)
    CFGetRetainCount(apt) //3 (person.apartment -> count up)
    

    ✅ Solution -> weak / unowned

    What is weak reference and when to use it?

    Use a weak reference when the other instance has a shorter lifetime — that is, when the other instance can be deallocated first.

    Apple – https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    ARC automatically sets a weak reference to nil when the instance that it refers to is deallocated. And, because weak references need to allow their value to be changed to nil at runtime, they’re always declared as variables, rather than constants, of an optional type.

    class Person {
        let name: String
        init(name: String) { self.name = name }
        var apartment: Apartment?
        deinit { print("\(name) is being deinitialized") }
    }
    
    
    class Apartment {
        let unit: String
        init(unit: String) { self.unit = unit }
        weak var tenant: Person?
        deinit { print("Apartment \(unit) is being deinitialized") }
    }

    What is unowned reference and when to use it?

    In contrast, use an unowned reference when the other instance has the same lifetime or a longer lifetime. Unlike a weak reference, an unowned reference is expected to always have a value. As a result, marking a value as unowned doesn’t make it optional, and ARC never sets an unowned reference’s value to nil.

    Important

    Use an unowned reference only when you are sure that the reference always refers to an instance that hasn’t been deallocated.

    If you try to access the value of an unowned reference after that instance has been deallocated, you’ll get a runtime error.

    Apple – https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    class Customer {
        let name: String
        var card: CreditCard?
        init(name: String) {
            self.name = name
        }
        deinit { print("\(name) is being deinitialized") }
    }
    
    
    class CreditCard {
        let number: UInt64
        unowned let customer: Customer
        init(number: UInt64, customer: Customer) {
            self.number = number
            self.customer = customer
        }
        deinit { print("Card #\(number) is being deinitialized") }
    }

    Example 1

    var customer: Customer! = Customer(name: "Shawn")
    customer.card = CreditCard(number: 7788_9999_1223_1225, customer: customer)
    
    CFGetRetainCount(customer) //2 (because CreditCard's customer is defined as unowned let)
    
    customer = nil
    
    //Shawn is being deinitialized
    //Card #7788999912231225 is being deinitialized 

    unowned(unsafe)

    Swift also provides unsafe unowned references for cases where you need to disable runtime safety checks — for example, for performance reasons. As with all unsafe operations, you take on the responsibility for checking that code for safety. You indicate an unsafe unowned reference by writing unowned(unsafe). If you try to access an unsafe unowned reference after the instance that it refers to is deallocated, your program will try to access the memory location where the instance used to be, which is an unsafe operation.

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    unownedself

    When you access the unowned(unsafe), It try to access the memory location where the instance used to be

    unowned

    when use unowned, error type is different.

    Unowned Optional References

    You can mark an optional reference to a class as unowned. In terms of the ARC ownership model, an unowned optional reference and a weak reference can both be used in the same contexts. The difference is that when you use an unowned optional reference, you’re responsible for making sure it always refers to a valid object or is set to nil.

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    class Department {
        var name: String
        var courses: [Course]
        init(name: String) {
            self.name = name
            self.courses = []
        }
    }
    
    
    class Course {
        var name: String
        unowned var department: Department
        unowned var nextCourse: Course?
        init(name: String, in department: Department) {
            self.name = name
            self.department = department
            self.nextCourse = nil
        }
    }

    Example

    var department: Department! = Department(name: "Horticulture")
    
    let intro = Course(name: "Survey of Plants", in: department)
    var intermediate: Course! = Course(name: "Growing Common Herbs", in: department)
    var advanced: Course = Course(name: "Caring for Tropical Plants", in: department)
    
    intro.nextCourse = intermediate
    intermediate.nextCourse = advanced
    department.courses = [intro, intermediate, advanced]
    
    //Deinit department and intermediate
    department = nil
    intermediate = nil
    
    //Try to access nextCourse
    intro.nextCourse //SIGABRT Error

    Unlike weak, when you try to access the deallocated object, It causes a crash issue. Even you set it as unowned optionals.

    Unowned References and Implicitly Unwrapped Optional Properties

    class Country {
        let name: String
        var capitalCity: City! //Unwrapped Optional Properties
        init(name: String, capitalName: String) {
            self.name = name
            //At this point, Country already initiated. So can pass self to City initializer
            self.capitalCity = City(name: capitalName, country: self)
        }
    }
    
    
    class City {
        let name: String
        unowned let country: Country //Unowned References
        init(name: String, country: Country) {
            self.name = name
            self.country = country
        }
    }

    var capitalCity: City!

    • It’s initial value is nil

    Above the example is Two-Phased initialization

    🤔 Strong Reference Cycles for Closures

    It’s such an important topic.

    A strong reference cycle can also occur if you assign a closure to a property of a class instance, and the body of that closure captures the instance. This capture might occur because the closure’s body accesses a property of the instance, such as self.someProperty, or because the closure calls a method on the instance, such as self.someMethod(). In either case, these accesses cause the closure to “capture” self, creating a strong reference cycle.

    This strong reference cycle occurs because closures, like classes, are reference types. When you assign a closure to a property, you are assigning a reference to that closure. In essence, it’s the same problem as above — two strong references are keeping each other alive. However, rather than two class instances, this time it’s a class instance and a closure that are keeping each other alive.

    Swift provides an elegant solution to this problem, known as a closure capture list

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    class HTMLElement {
        let name: String
        let text: String?
    
        lazy var asHTML: () -> String = {
            if let text = self.text {
                return "<\(self.name)>\(text)</\(self.name)>"
            } else {
                return "<\(self.name) />"
            }
        }
    
        init(name: String, text: String? = nil) {
            self.name = name
            self.text = text
        }
    
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
    print(paragraph!.asHTML())
    
    //Deallocate HTMLElement
    paragraph = nil // Can't deallocate it because the reference cycle between property (asHTML) and closure's capture list
    screenshot 2024 03 10 at 12.02.07e280afam

    Even though the closure refers to self multiple times (self.text and self.name), it only captures one strong reference to the HTMLElement instance.

    Apple

    Resolving Strong Reference Cycles for Closures

    Use weak or unowned at capture lists

    Defining a capture list

    Each item in a capture list is a pairing of the weak or unowned keyword with a reference to a class instance (such as self) or a variable initialized with some value (such as delegate = self.delegate). These pairings are written within a pair of square braces, separated by commas.

    Place the capture list before a closure’s parameter list and return type if they’re provided:

    Apple

    lazy var someClosure = {
            [unowned self, weak delegate = self.delegate]
            (index: Int, stringToProcess: String) -> String in
        // closure body goes here
    }
    
    
    lazy var someClosure2 = {
            [unowned self, weak delegate = self.delegate] in
        // closure body goes here
    }

    🔥 Think it again, weak vs unowned in Closures

    Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.

    Conversely, define a capture as a weak reference when the captured reference may become nil at some point in the future. Weak references are always of an optional type, and automatically become nil when the instance they reference is deallocated. This enables you to check for their existence within the closure’s body.

    Apple

    class HTMLElement {
        let name: String
        let text: String?
        //Use unowned self to break reference cycle
        lazy var asHTML: () -> String = {
                [unowned self] in
            if let text = self.text {
                return "<\(self.name)>\(text)</\(self.name)>"
            } else {
                return "<\(self.name) />"
            }
        }
    
        init(name: String, text: String? = nil) {
            self.name = name
            self.text = text
        }
    
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    screenshot 2024 03 10 at 12.21.50e280afam

    Capture value from nested Closure

    class Test {}
    DispatchQueue.global().async {
      let test = Test()
       print("closure A: Start retain count is: \(CFGetRetainCount(test))")
       DispatchQueue.global().async {
         print("nested closure B retainCount: \(CFGetRetainCount(test))")
       }
       print("closure A: End")
    }
    
    //Prints
    closure A: Start retain count is: 2
    closure A: End
    nested closure B retainCount: 3

    As you can see nested closure also counting up ARC.

    But… what about this example?

    screenshot 2024 03 14 at 11.00.59e280afpm

    test object was already deallocated. And when you trying to access test, It should not be nil because It’s unowned.

    Conclusion

    Understanding How ARC works is very important to avoid memory leaks and crash issues. It’s also good to know what happens when objects are deallocated and How ARC handles those objects.

  • Leetcode official iOS app – How can I change Chinese language to English

    Leetcode official iOS app – How can I change Chinese language to English

    Download Leetcode official app

    Did you know leetcode has an official iOS app? You can download it.

    But there is one problem. They are not fully support English.

    Good news is we can change Leetcode problem descriptions to English. It’s enough to solve problem!

    Step 1. Change Settings

    img 0453 1

    The seconds option meaning Question Language. Let’s change it to English.

    Check your setting (blue circle)

    Step 2. Go to the second tab (Question bank)

    img 0454 1

    You can see some Chinese characters are changed to English (Blue circle)

    img 0455 1

    As you can see all the questions are English. Let’s enjoy problem solving!

  • Do you require a server for your iOS app? CloudKit might be the perfect fit for you!

    Do you require a server for your iOS app? CloudKit might be the perfect fit for you!

    Choosing a server can be challenging, especially if you’re not a backend engineer but still need one for your iOS app. You may find yourself in this situation.

    So, Why did I consider CloudKit?

    Well, first off, it’s maintained by Apple and has been around since its introduction at the 2014 WWDC.

    Apple actively uses it for various apps like Photos and Notes, which gives me confidence in its longevity and reliability. Plus, I expect it to receive regular updates from Apple.

    My experience

    Parse.com

    I used Parse (Acquired by Facebook) and contributed to the Parse iOS SDK. I liked it, but Facebook announced its discontinuation.

    Consequently, I had to migrate to alternatives like back4app or Sashido.io, which serve as alternatives to parse.com. While they were good, I encountered issues with the lack of active updates to the iOS SDK. This led me to contribute to its development, but I felt it was a waste of time. I wanted to focus on my app rather than on open-source projects.

    Vapor

    I like Vapor because I can create a server using Swift, which is a significant benefit as an iOS Engineer. Additionally, there are numerous helpful resources available, such as books, YouTube videos, and Udemy courses.

    But why did I consider CloudKit?

    Unlike CloudKit, I would need to implement server logic from scratch, including authentication, deployment, database setup, migration, and data sharing between users.

    My approach

    screenshot 2024 02 12 at 4.14.49e280afpm

    Context

    • I’ve already implemented a server using Vapor to handle authentication. While working on sharing data between users, I researched an easy way to implement the invite/accept feature and found that CloudKit fully supports it.

    The server I implemented using Vapor is deployed on AWS, using ECS with a Load Balancer. Its primary role is handling SignIn/SignUp and supporting user-related features such as changing passwords and usernames. All user information is saved in PostgreSQL.

    For data synchronization across user devices, I utilize NSPersistentCloudKitContainer, which syncs data between CoreData and CloudKit seamlessly. This integration is very convenient and eliminates the need for manual synchronization logic.

    CloudKit also supports invite and accept functionality for sharing data between users, eliminating the need to implement server-side logic for this feature.

    Be aware of CloudKit topics

    Now, returning to Vapor,

    I can access and edit CloudKit’s data using CloudKit Web Service. NSPersistentCloudKitContainer use a special zone called com.apple.coredata.cloudkit.zone. If you’re interested how to access it using CloudKit Web Service, you can check out Reading CloudKit Records for Core Data.

    Public Data

    As for public data storage, I haven’t decided yet where to store it. I’ll update you once I make a decision.

    How about CloudKit costs?

    Currently, the pricing seems like a black box. I can’t find pricing information on the Apple Developer website. That’s why I can’t decide where I should store public data.

    If you want to know AWS costs, see my previous post.

    I captured pricing information by accessing the archived website.

    screenshot 2024 02 12 at 2.15.25e280afpm
    screenshot 2024 02 12 at 2.15.42e280afpm
    screenshot 2024 02 12 at 2.14.45e280afpm

    Overage Fees

    Asset Storage $0.03/GB
    Database Storage $3.00/GB
    Data Transfer $0.10/GB
    Requests per sec $100 per 10 requests
  • [Draft] Python cheat sheet for iOS Engineer

    [Draft] Python cheat sheet for iOS Engineer

    I asked my self Is python worth to learn for iOS development? My answer is Yes.

    Because I can use it for

    • Build scripts
    • Core ML + coreml tools
    • BE for Mobile (Django)

    Getting Principles

    • You can read the list of the python principles by importing this
    import this

    Cheatsheet

    Standard LibrarySwiftPython
    variablevar name = "Shawn"name = "Shawn"
    constantslet name = "Shawn"Not support
    But UPPERCASED Naming indicates constants It’s like a implicit rule in Python
    string"Hello World!"

    Multiline String
    """
    Hello
    Swift
    """


    Extended Delimiters
    #"Hello\n Swift"#

    It will print Hello\n Swift
    "Hello World!"
    'Hello World!'


    Swift can’t use single quote but Python can use


    formatted stringlet name = "Shawn"
    "My name is \(name)"
    name = "Shawn"
    f"My name is {name}"


    f means formatted string
    capitalizedvar greeting = “shawn baek”
    print(greeting.capitalized)
    //’Shawn Baek’
    name = “shawn baek”
    print(name.title())

    //’Shawn Baek’
    uppercased / lowercasedvar greeting = “Shawn Baek”

    print(greeting.uppercased())
    //’SHAWN BAEK’

    print(greeting.lowercased())
    //’shawn baek’
    name = “Shawn Baek”
    print(name.upper())
    //’SHAWN BAEK’

    print(name.lower())
    //’shawn baek’
    trimmingCharacters(in: .whitespacesAndNewlines)var greeting = ” Shawn Baek “

    //MARK: Remove leading / trailing spaces

    greeting.trimmingCharacters(in: .whitespacesAndNewlines)

    //’Shawn Baek’
    name = ” Shawn Baek “

    name.rstrip()
    // ‘ Shawn Baek’

    name.lstrip()
    //’Shawn Baek ‘

    name.strip()
    //’Shawn Baek’
    trimPrefixvar blogUrl = “https://shawnbaek.com&#8221;
    blogUrl.trimPrefix(“https://&#8221😉

    //’shawnbaek.com’
    blog_url = ‘https://shawnbaek.com&#8217;

    blog_url.removeprefix(‘https://&#8217😉
    //’shawnbaek.com’
    powvar number = pow(3.0, 2.0)

    print(number)
    //9.0
    number = 3.0 ** 2

    print(number)
    //9.0
    comment//Hello World#Hello World

  • Xcode: How to upload dSYM to Firebase Crashlytics

    Xcode: How to upload dSYM to Firebase Crashlytics

    Have you faced the missing dSYM issue? You can fix it by uploading dSYM to firebase.

    Official Document

    Build Setting – Build Options

    Check DWARD with dSYM File

    screenshot 2023 12 20 at 6.18.32e280afpm

    Reorder Build Phase in Xcode

    screenshot 2023 12 20 at 5.55.13e280afpm 1

    Edit Run Script

    screenshot 2023 12 20 at 6.00.44e280afpm
    "${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"

    Add above command into Run Script

    Add Input Files

    screenshot 2023 12 20 at 6.00.58e280afpm
    ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}
    ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}
    ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist
    $(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist
    $(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)

    Tap + button at Input Files and Add above paths one by one

    Check dSYM

    screenshot 2023 12 20 at 6.11.38e280afpm

    You can see uploading dsym process in All Messages

    screenshot 2023 12 20 at 6.13.30e280afpm

    Let’s check firebase Crashlytics.

    Crashlytics -> dSYMs

    You can see Uploaded

    • There is a minor issue which is Uploaded dSYM’s version is indicating Unknown

    Conclusion

    screenshot 2023 12 21 at 8.07.39e280afam

    Now I able to see all the crash issue after uploading dSYM (see above screenshot).

  • Xcode Cloud, How to upload ipa to Firebase AppDistribution

    Xcode Cloud, How to upload ipa to Firebase AppDistribution

    1. Download firebase macOS tool

    https://firebase.google.com/docs/cli#mac-linux-standalone-binary

    2. Copy firebase-tools-macos.zip into ci_scripts folder

    3. Create Service Account

    I suggest use Service Account instead of using token. Because firebase no longer support token. (firebase login:ci)

    screenshot 2023 12 20 at 11.59.27e280afam

    Document

    Don’t forget to check Project before creating Service Accounts.

    screenshot 2023 12 20 at 3.00.52e280afam
    screenshot 2023 12 20 at 3.02.16e280afam
    screenshot 2023 12 20 at 3.04.09e280afam

    Once create an account, create private key at KEYS.

    Put json file you download into ci_scripits folder and rename it as credentials.json

    4. Add Environment Variables at Xcode Cloud

    Add FIREBASE_APP_ID

    screenshot 2023 12 20 at 1.20.09e280afpm
    screenshot 2023 12 20 at 4.23.26e280afpm

    [Option] ADD GOOGLE_SERVICE_ACCOUNT_KEY

    You can skip this if you want to add json file into git repo. In this post, I’ll skip this step.

    Open JSON (Step 3, Downloaded JSON Key), Copy all the text in JSON and Paste it.

    # If you added GOOGLE_SERVICE_ACCOUNT_KEY into Xcode Cloud's Environment Variable then use this
    echo "$GOOGLE_SERVICE_ACCOUNT_KEY" | jq -r '.' > credentials.json
            
    export GOOGLE_APPLICATION_CREDENTIALS=./credentials.json

    5. Edit ci_post_clone.sh in ci_scripts folder

    Install any packages you want to use it later.

    #!/bin/sh
    brew install jq

    6. Edit ci_post_xcodebuild.sh in ci_scripts folder

    Document

    #!/bin/zsh
    upload_ipa_to_firebase() {
        unzip firebase-tools-macos.zip
        chmod +x ./firebase-tools-macos
        local file_path="$1/$CI_PRODUCT.ipa"
        echo "🚀 Upload $file_path to Firebase App ID $FIREBASE_APP_ID"
        # https://firebase.google.com/docs/app-distribution/authenticate-service-account?platform=ios
        export GOOGLE_APPLICATION_CREDENTIALS=./credentials.json
        ./firebase-tools-macos appdistribution:distribute "$file_path" --app "$FIREBASE_APP_ID"
        echo "🚀 End uploading dSYMs"
    }
    
    if [[ -n "$CI_ARCHIVE_PATH" && "$CI_XCODEBUILD_EXIT_CODE" == 0 ]]; then
        if [ -d "$CI_APP_STORE_SIGNED_APP_PATH" ]; then
            upload_ipa_to_firebase "$CI_APP_STORE_SIGNED_APP_PATH"
        elif [ -d "$CI_AD_HOC_SIGNED_APP_PATH" ]; then
            upload_ipa_to_firebase "$CI_AD_HOC_SIGNED_APP_PATH"
        fi
    else
        echo "Archive path isn't available. Unable to run dSYMs uploading script."
    fi

    7. Check ipa file

    Check Xcode Cloud Logs

    screenshot 2023 12 20 at 4.24.10e280afpm

    Check Firebase

    screenshot 2023 12 20 at 4.24.45e280afpm

    Conclusion

    I faced a lot of issues to upload ipa file into firebase. Because firebase-tools-macos has updated and token login was deprecated.

    Also Xcode Cloud doesn’t support git-lfs. That’s why I upload firebase-tools-macos.zip file and unzip it at ci_post_xcodebuild.sh. (without zip file I need to set git-lfs because this tool’s size is around 150mb)

    The other option is download firebase-tool at ci_post_xcodebuild.sh.

    curl -sL https://firebase.tools | bash

    All options are up to you. I hope my post helps you. Thanks!

  • What is Dynamic Programming?

    What is Dynamic Programming?

    For me, DP problem is the most challenging problem when I faced it during the interview process. To understand it I summarize basic concepts and patterns.

    Simply It can be defined,

    Dynamic Programming = Complex problems -> smaller subproblem

    img 0378 1

    Dynamic Programming vs Divide and Conquer

    Key difference

    • Dynamic Programming – has overlapping sub problems

    Dynamic Programming Paradigm

    Optimal Substructure

    In computer science, a problem is said to have optimal substructure if an optimal solution can be constructed from optimal solutions of its subproblems. This property is used to determine the usefulness of greedy algorithms for a problem.

    WIKIPEDIA

    It can solve these kinds of problems

    • Greedy approach

    Bellman Equation (Combinatorial optimization)

    Bellman showed that a dynamic optimization problem in discrete time can be stated in a recursive, step-by-step form known as backward induction by writing down the relationship between the value function in one period and the value function in the next period. The relationship between these two value functions is called the “Bellman equation”.

    WIKIPEDIA

    It can solve these kinds of problems

    • 0-1 Knapsack problem

    What is overlapping subproblems?

    img 0379 1

    Let’s see fibonacci problem.

    fib(n) = fib(n-1) + fib(n-2)
    
    //Base case
    fib(0) = 0
    fib(1) = 1

    To get the fib(n), we need to call fib(n-1) and fib(n-2) and then sum it.

    Colored boxes are overlapped sub problems. Because It was called multiple times.

    How to avoid multiple calling overlapped subproblems?

    Answer: Save the result and use it without calling a function or recalculating it.

    Top-Down Approach: Memoization (Recursion)

    Memoization you can implement using Array or Dictionary. In Swift, You have to handle it carefully like out of bounds.

    class Fibonacci {
      func fib(_ n: Int) -> Int {
        var memoization = Array(repeating: 0, count: n + 1)
        func recursiveFib(
          _ n: Int, 
          memoization: inout [Int]
        ) -> Int {
          if memoization[n] != 0 {
            return memoization[n]
          }
          if n < 2 {
            memoization[n] = n
            return n
          }
          let sum = recursiveFib(n-1, memoization: &memoization) + recursiveFib(n-2, memoization: &memoization)
          memoization[n] = sum
          return sum
        }
        return recursiveFib(n, memoization: &memoization)
      }
    }
    
    let fibonacci = Fibonacci()
    let result = fibonacci.fib(7) //Result is 13

    I prefer to use dictionary for memoization. It’s more safe and easy to save the result of subproblems.

    class Fibonacci {
      var memoization = [Int: Int]()
      func fib(_ n: Int) -> Int {
        if let value = memoization[n] {
            return value
        }
        if n < 2 {
          memoization[n] = n
          return n
        }
        let sum = fib(n-1) + fib(n-2)
        memoization[n] = sum
        return sum
      }
    }
    
    let fibonacci = Fibonacci()
    let result = fibonacci.fib(7) //Result is 13
    

    Bottom-Up Approach: Tabulation (avoid recursion)

    This approach solve the bottom subproblems first and save the results. You don’t need to use recursion.

    class Fibonacci {
      func fib(_ n: Int) -> Int {
        var dp = Array(repeating: 0, count: n + 1)
        //base
        dp[0] = 0
        dp[1] = 1
    
        for i in 2...n {
          dp[i] = dp[i-1] + dp[i-2]
        }
        return dp[n]
      }
    }
    
    let fibonacci = Fibonacci()
    let result = fibonacci.fib(7) //Result is 13

    [Working in Progress] Dynamic Programming Problem Patterns

    Step 1. Understand problem

    Step 2. Check Is this problem can be solved by splitting into Sub Problems

    Step 3. Is Sub Problems are overlapped?

    Step 4. If Yes, It’s DP problem.

    Step 5. [Hardest Part] Define a logic when to take an item and do not take an item to reach the solution. -> Don’t memorize the solutions, all the problems we need to define a proper logic by understanding problem.

    Step 6. Give a solution (including all taken items)

    Knapsack Problem

    Divisible Knapsack Problem

    • Greedy approach can be used

    0-1 Knapsack Problem (consider should I take a item or not)

    • 0: Not take an item
    • 1: Take an item

    Rod Cutting Problem

    References

    https://www.udemy.com/share/101AYS3@4sd_kGNnngOfRfF3meBvqpARWBcWTlGxzqIQuKnKpxCDk-bb017OhSQJeotE-YGg/

  • How to implement MasonryLayout (Pinterest Style) using UICollectionViewCompositionalLayout

    How to implement MasonryLayout (Pinterest Style) using UICollectionViewCompositionalLayout

    pinterest

    In this post, I’ll share sample code for implementing MasonryLayout using UICollectionViewCompositionalLayout.

    UICollectionViewCell

    I defined a very simple UICollectionViewCell. It consists of basic UI components such as Photo, Title and Description.

    screenshot 2023 11 26 at 1.55.10e280afpm

    NSCollectionLayoutGroupCustomItem ( >= iOS 13)

    Apple Documents

    This is important to implement masonryLayout. Unfortunately It doesn’t support NSCollectionLayoutSize. (It supports GCRect only)

    It means we need to calculate the cell’s contents size.

    So We need to write 2 main logics.

    Step 1. Calculate Cell’s size

    To calculate size, We need to know cell’s width first.

    We knows collectionView’s width by accessing UICollectionViewCompositionalLayout’s layoutEnvironment.container.contentSize.

    private func masonryLayout(contentInset: NSDirectionalEdgeInsets) -> UICollectionViewCompositionalLayout {
        let layout = UICollectionViewCompositionalLayout { [weak self] sectionIndex, layoutEnvironment in
        var items = [NSCollectionLayoutGroupCustomItem]()
        let snapshotItems = self?.dataSource.snapshot(for: .item).items
        let numberOfItems = snapshotItems?.count ?? 0
        let contentSize = layoutEnvironment.container.contentSize
                ...
    }

    Cell’s width

    We have 2 columns. so Cell’s width is contentSize.width / columns.

    If we set interItemSpacing and NSDirectionalEdgeInsets then logic is below

    let horizontalSpacing = contentInsets.leading + contentInsets.trailing
            let spacing = (columns - 1) * interItemSpacing + horizontalSpacing
            return (contentSize.width - spacing) / columns

    Cell’s Height

    Calculate UIImageView’s size

    In this example, I set image ratio as 4:3. So Image Height is cell’s width * 0.75.

    Calculate UILabel’s size

    To get the correct size, We need to provide the details such as Font, LineBreakMode and NumberOfLines.

    Also to get the correct bounding size we set NSString.DrawingOptions.

    If you want to know more details about drawing options visit Apple documents.

    extension UILabel {
        var textHeight: CGFloat? {
        guard let labelText = text else {
            return nil
        }
        let attributes: [NSAttributedString.Key: UIFont] = [
                .font: font
    ]
        let labelTextSize = (labelText as NSString).boundingRect(
                with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
                options: .usesLineFragmentOrigin,
                attributes: attributes,
                context: nil
            ).size
            return ceil(labelTextSize.height)
        }
    }
    
    func height(text:String, font:UIFont, numberOfLines: CGFloat, lineBreakMode: NSLineBreakMode, width:CGFloat) -> CGFloat{
        let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: UILabel.noIntrinsicMetric))
        label.numberOfLines = numberOfLines
        label.lineBreakMode = lineBreakMode
        label.font = font
        label.text = text
        return label.textHeight ?? 0
    }

    Step 2. Calculate Cell’s Position in a Group

    screenshot 2023 11 26 at 2.05.07e280afpm

    We need to layout cell’s position in a Group. CGRect has origin x, y and width, height. At step 1, We already calculated the cell’s width and height. In step 2, We need to calculate origins.

    Calculate cell’s origin X and Y

    screenshot 2023 11 26 at 3.13.50e280afpm

    Let’s take a look how to layout cells. (row is indicating item’s index)

    First Item (row: 0, column: 0)

    It’s origin is 0, 0

    Second Item (row: 1, column: 1)

    It’s origin y is 0. But x is first item’s width + interItemSpacing.

    let originX = (cellWidth + interItemSpacing) * columnIndex

    Third Item (row: 2, column: 0)

    Its origin x is zero because columnIndex is zero. But origin y is calculated by maxY between First Item and Second Item. Second Item’s maxY is smaller than First Item. So We picked this to layout Third Item.

    UICollectionViewCompositionalLayout for MasonryLayout (Pinterest Style)

    Here is full source code.

    cachedSize used for saving cell’s height. It will be called when you call a reloadData or applySnapshotUsingReloadData. (In this sample, I used dictionary but you can considering NSCache)

    private func masonryLayout(contentInset: NSDirectionalEdgeInsets) -> UICollectionViewCompositionalLayout {
        let layout = UICollectionViewCompositionalLayout { [weak self] sectionIndex, layoutEnvironment in
        var items = [NSCollectionLayoutGroupCustomItem]()
        let snapshotItems = self?.dataSource.snapshot(for: .item).items
        let numberOfItems = snapshotItems?.count ?? 0
        let contentSize = layoutEnvironment.container.contentSize
        let itemProvider = MasonryLayoutProvider(
                    columns: 2,
                    interItemSpacing: 16,
                    collectionWidth: contentSize.width,
                    contentInsets: contentInset) { [weak self] row, cellWidth in
        //Use Cached Height
        if let cachedHeight = self?.cachedSize[row]?.height         {
            return cachedHeight
        }
        let height = snapshotItems?[row].estimatedHeight(width: cellWidth) ?? 0
        self?.cachedSize[row] = .init(width: cellWidth, height: height)
            return height
        }
        for i in 0..<numberOfItems {
            let item = itemProvider.makeLayoutItem(for: i)
            items.append(item)
        }
        let groupLayoutSize = NSCollectionLayoutSize(
                    widthDimension: .fractionalWidth(1),
                    heightDimension: .absolute(itemProvider.maxColumnHeight())
        )
                
        let group = NSCollectionLayoutGroup.custom(layoutSize: groupLayoutSize) { env in
            items
        }
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = contentInset
        section.boundarySupplementaryItems = [
                    .init(layoutSize: .init(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(10)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
        ]
            return section
        }
        return layout
    }

    For more details, You can checkout my repository

    References

    https://www.kodeco.com/4829472-uicollectionview-custom-layout-tutorial-pinterest

    https://github.com/eeshishko/WaterfallTrueCompositionalLayout

  • SwiftUI: Syntax Highlighting JSON using UITextView

    SwiftUI: Syntax Highlighting JSON using UITextView

    It’s very simple. I used this Highlight SPM

    Sample Code

    //
    //  ContentView.swift
    //  Button
    //
    //  Created by Sungwook Baek on 2023/09/26.
    //
    
    import SwiftUI
    import Highlight
    
    struct ContentView: View {
        @State var jsonString = NSMutableAttributedString(string: "")
        var body: some View {
            HighlightTextView(text: $jsonString)
        }
    }
    
    #Preview {
        ContentView()
    }
    
    struct HighlightTextView: UIViewRepresentable {
        @Binding var text: NSMutableAttributedString
        
        func makeUIView(context: Context) -> UITextView {
            let textView = UITextView()
            textView.keyboardType = .asciiCapable
            textView.autocapitalizationType = .none
            textView.delegate = context.coordinator
            textView.attributedText = text
            return textView
        }
    
        func updateUIView(_ uiView: UITextView, context: Context) {
            let selectedRange = uiView.selectedRange
            uiView.attributedText = text
            uiView.selectedRange = selectedRange
        }
        
        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
            
        class Coordinator: NSObject, UITextViewDelegate {
            var parent: HighlightTextView
            
            init(_ parent: HighlightTextView) {
                self.parent = parent
            }
            
            func textViewDidChange(_ textView: UITextView) {
                let highligher = JsonSyntaxHighlightProvider.shared.highlight( textView.attributedText.string, as: .json)
                let selectedRange = textView.selectedRange
                textView.attributedText = highligher
                textView.selectedRange = selectedRange
            }
        }
    }
  • Project Setup for Full Stack Swift Developer – Use Workspace

    Project Setup for Full Stack Swift Developer – Use Workspace

    This post is about project setup for full stack swift developer. It is the same as WWDC 2022 – Use Xcode for server-side development

    I like this setup because I don’t want to switching projects between iOS and Vapor.

    Step 1. Copy Vapor Project folder into iOS App Project folder

    
    89e1f 09baf screenshot2023 09 03at1.09.19am
    

    Step 2. Open iOS App Project (xcodeproj) and drag Vapor Project folder at the top level

    9487c ac91f screenshot2023 09 03at1.10.48am

    Step 3. Close iOS App Project folder and open Xcode Workspace

    a726f c08d4 folder

    Step 4. Resolve git subproject -dirt issue

    If you worked on vapor project with git, you may facing git subproject issue. To resolve it you need to update submodule.

    Go to the vapor project you copied in iOS project.

    Check git status

    Update git status by syncing with remote and commit any changes

    Go back to parent folder (iOS Project folder)
    Sync with remote and commit any changes