Files
pos-system/microservices/.agent/skills/swift-ui-components/SKILL.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

6.4 KiB

name, description, compatibility, metadata
name description compatibility metadata
swift-ui-components Reusable SwiftUI components, Extensions, Validation patterns. Use for String/View extensions, custom UI components, hoặc khi cần reusable code. Swift 5.9+, iOS 17+, SwiftUI
author version
Velik Ho 1.0

Swift UI Components & Extensions

Reusable components và Extensions cho SwiftUI Enterprise.

When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:

  • Creating string validation / Tạo validation chuỗi
  • Building reusable views / Xây dựng views tái sử dụng
  • View modifiers / Thêm view modifiers
  • Localization patterns / Patterns đa ngôn ngữ

String Extensions

// Core/Extensions/String+Extensions.swift

import Foundation

extension String {
    
    // MARK: - Localization
    
    /// Localized string
    var localized: String {
        NSLocalizedString(self, comment: "")
    }
    
    /// Localized with arguments
    func localized(with arguments: CVarArg...) -> String {
        String(format: localized, arguments: arguments)
    }
    
    // MARK: - Validation
    
    /// Valid email check
    var isValidEmail: Bool {
        let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self)
    }
    
    /// Valid Vietnamese phone
    var isValidVietnamesePhone: Bool {
        let regex = "^(0|\\+84)(3|5|7|8|9)[0-9]{8}$"
        return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self)
    }
    
    /// Valid password (8+ chars, upper, lower, digit)
    var isValidPassword: Bool {
        let regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{8,}$"
        return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self)
    }
    
    /// Trimmed string
    var trimmed: String {
        trimmingCharacters(in: .whitespacesAndNewlines)
    }
    
    /// Is blank (empty or whitespace)
    var isBlank: Bool {
        trimmed.isEmpty
    }
    
    // MARK: - Formatting
    
    /// Masked email (j***@example.com)
    var maskedEmail: String {
        guard isValidEmail else { return self }
        let parts = split(separator: "@")
        guard parts.count == 2 else { return self }
        let local = String(parts[0])
        let domain = String(parts[1])
        if local.count <= 2 {
            return "\(local.prefix(1))***@\(domain)"
        }
        return "\(local.prefix(1))***\(local.suffix(1))@\(domain)"
    }
    
    /// Format VND currency
    static func formatVND(_ amount: Double) -> String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.currencyCode = "VND"
        formatter.currencySymbol = "₫"
        formatter.maximumFractionDigits = 0
        return formatter.string(from: NSNumber(value: amount)) ?? "\(Int(amount))₫"
    }
}

View Extensions

// Core/Extensions/View+Extensions.swift

import SwiftUI

extension View {
    
    /// Conditional modifier
    @ViewBuilder
    func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
        if condition {
            transform(self)
        } else {
            self
        }
    }
    
    /// Hide keyboard
    func hideKeyboard() {
        UIApplication.shared.sendAction(
            #selector(UIResponder.resignFirstResponder),
            to: nil, from: nil, for: nil
        )
    }
    
    /// Corner radius with specific corners
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape(RoundedCorner(radius: radius, corners: corners))
    }
}

/// Custom rounded corner shape
struct RoundedCorner: Shape {
    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners
    
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(
            roundedRect: rect,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius)
        )
        return Path(path.cgPath)
    }
}

Custom Components

Primary Button

/// Primary action button
struct PrimaryButton: View {
    let title: String
    let action: () -> Void
    var isLoading: Bool = false
    var isDisabled: Bool = false
    
    var body: some View {
        Button(action: action) {
            HStack(spacing: DesignSystem.spacingSM) {
                if isLoading {
                    ProgressView()
                        .tint(.white)
                }
                Text(title)
                    .fontWeight(.semibold)
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, DesignSystem.spacingMD)
            .background(isDisabled ? Color.gray : Color.accentColor)
            .foregroundColor(.white)
            .cornerRadius(DesignSystem.cornerRadiusMD)
        }
        .disabled(isDisabled || isLoading)
    }
}

Text Field with Validation

/// Validated text field
struct ValidatedTextField: View {
    let placeholder: String
    @Binding var text: String
    var isSecure: Bool = false
    var isValid: Bool = true
    var errorMessage: String?
    
    var body: some View {
        VStack(alignment: .leading, spacing: DesignSystem.spacingXS) {
            Group {
                if isSecure {
                    SecureField(placeholder, text: $text)
                } else {
                    TextField(placeholder, text: $text)
                }
            }
            .padding()
            .background(Color(.systemGray6))
            .cornerRadius(DesignSystem.cornerRadiusSM)
            .overlay(
                RoundedRectangle(cornerRadius: DesignSystem.cornerRadiusSM)
                    .stroke(isValid ? Color.clear : Color.red, lineWidth: 1)
            )
            
            if let error = errorMessage, !isValid {
                Text(error)
                    .font(.caption)
                    .foregroundColor(.red)
            }
        }
    }
}

Quick Reference / Tham Chiếu Nhanh

Pattern Usage
string.localized NSLocalizedString wrapper
string.isValidEmail Email regex validation
string.trimmed Remove whitespace
.if(condition) { } Conditional modifier
PrimaryButton Loading + disabled support

Resources / Tài Nguyên