Number: 050, Date: 2024-12-23

2024.12.16 ~ 2024.12.22

💡 Organizing Weekly Retrospective Blogs


As 2024 is coming to an end, I organized the weekly retrospective blog documents.

I fixed discrepancies in document numbers and formats and applied the code for ContentPreview style settings that I contributed to the Ignite repository after my PR was approved.

⏰ Stopping Ongoing Asynchronous Tasks


To implement asynchronous tasks, you can use either a completionHandler or the async & await syntax.

I explored how to cancel a task after it starts but before it completes using each method.

  1. completionHandler
class AsyncWork {
    var item: DispatchWorkItem?
    
    func startWork() {
      if item == nil {
        item = DispatchWorkItem {
        self.item = nil
      }
    
      DispatchQueue.main.asyncAfter(deadline: .now() + 6, execute: item!)
    }
    
    func stopWork() {
        item.cancel()
        itme.nil
    }
}

let work = AsyncWork()
work.startWork()

work.stopWork()
  1. async & await
class AsyncWork {
    var item: Task?
    
    func startWork() {
      if item == nil {
        item = Task { [weak self] in
        guard let self else { return }

        do {
            try await Task.sleep(nanoseconds: 6_000_000_000)
            self.item = nil
        } catch {
            self.item = nil
        }
      }
     }

      func stopWork() {
        item?.cancel()
        itme.nil
      }
}

let work = AsyncWork()
work.startWork()

work.stopWork()

📞 Validating Pasted Phone Numbers


Two weeks ago, I worked on enabling paste functionality in a UITextField. While working, I thought it was impossible to extract only numbers for pasting. However, seeing it work in another service made me take another look.

I found that the override func paste(_ sender: Any?) function can be used to handle processing at the moment of pasting, so I used this function to resolve the issue.

Additionally, I learned that starting from iOS 16, the system can prompt the user before preventing paste operations.

Example: Phone number validation during pasting

//
//  PhoneNumberTextField.swift
//  SookimIosDesignSystem
//
//  Created by sookim on 12/19/24.
//

import UIKit

protocol PhoneNumberTextFieldDelegate: AnyObject {
    func editPhoneNumberText(result: PhoneNumberTextField.ResultDescription)
}

class PhoneNumberTextField: UITextField {

    enum ResultDescription {
        case success                   
        case invalidPhoneNumber        
        case emptyPhoneNumber          
        case countLimit                
    }

    private let maxPhoneNumberLength = 13

    var onValidationResult: ((ResultDescription) -> Void)?
    weak var phoneNumberTextFieldDelegate: PhoneNumberTextFieldDelegate?

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.translatesAutoresizingMaskIntoConstraints = false
        self.keyboardType = .numberPad
        self.delegate = self
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)

        self.translatesAutoresizingMaskIntoConstraints = false
        self.keyboardType = .numberPad
        self.delegate = self
    }

    override func paste(_ sender: Any?) {
        if let pasteboardString = UIPasteboard.general.string {
            let filteredString = filterAndFormatPhoneNumber(pasteboardString)
            self.text = filteredString

            let validationResult = validatePhoneNumber(filteredString)

            onValidationResult?(validationResult)
            phoneNumberTextFieldDelegate?.editPhoneNumberText(result: validationResult)
        }
    }

}

// MARK: - UITextFieldDelegate
extension PhoneNumberTextField: UITextFieldDelegate {

    func textFieldShouldClear(_ textField: UITextField) -> Bool {
        onValidationResult?(.emptyPhoneNumber)
        phoneNumberTextFieldDelegate?.editPhoneNumberText(result: .emptyPhoneNumber)
        return true
    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let currentText = textField.text as NSString? else { return true }
        let newText = currentText.replacingCharacters(in: range, with: string)

        let filteredText = filterAndFormatPhoneNumber(newText)

        if filteredText.count > maxPhoneNumberLength {
            onValidationResult?(.countLimit)
            phoneNumberTextFieldDelegate?.editPhoneNumberText(result: .countLimit)
            return false
        }

        textField.text = filteredText

        let validationResult = validatePhoneNumber(filteredText)
        onValidationResult?(validationResult)
        phoneNumberTextFieldDelegate?.editPhoneNumberText(result: validationResult)

        return false
    }
}

extension PhoneNumberTextField {

    private func filterAndFormatPhoneNumber(_ input: String) -> String {
        let digitsOnly = input.compactMap { $0.isNumber ? String($0) : nil }.joined()

        let formattedNumber = formatAsPhoneNumber(digitsOnly)
        return formattedNumber
    }

    private func formatAsPhoneNumber(_ digits: String) -> String {
        let length = digits.count

        switch length {
        case 0...3:
            return digits
        case 4...6:
            let firstIndex = digits.index(digits.startIndex, offsetBy: 3)
            return "\(digits[.. ResultDescription {
        if text.isEmpty {
            return .emptyPhoneNumber
        }

        if text.count > maxPhoneNumberLength {
            return .countLimit
        }

        if !text.starts(with: "010") || text.count < (maxPhoneNumberLength - 1) {
            return .invalidPhoneNumber
        }

        return .success
    }

}

🙋🏻‍♂️ Other Notes


  1. From 2024-12-10, Bitbucket started displaying whether commits are signed. The display varies depending on the authentication method:
    • If authenticated via HTTPS, a yellow warning is shown with a prompt to sign.
    • If authenticated via SSH, no indication is shown.
    • If signed with GPG, a checkmark is displayed.
  2. Be cautious when logging into Atlassian-related products on mobile, as it may result in unintended paid subscriptions.
  3. Held an initial meeting with a service provider. Experienced how they handle related inquiries, customization, and general processes upon adoption.
  4. Studied up to the third lecture of CodeFactory’s Dart basics course. I plan to complete the course and organize the repository.