6.4 KiB
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 |
|
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
- Swift Enterprise Architect
- Swift Security - Validation patterns