EasyApp

实用工具

EasyAppSwiftUI中常用的实用工具、视图修饰符、扩展、组件、公共函数等

实用工具

EasyAppSwiftUI 提供了丰富的实用工具集合,包括视图修饰符、扩展方法和工具函数,帮助您快速构建功能完整的 SwiftUI 应用。

我们会持续更新实用工具集,方便您快速构建功能完整的 SwiftUI 应用。

概览

  • 12个自定义视图修饰符 - 用于身份验证、付费墙、加载状态等
  • 扩展方法 - 增强 SwiftUI 视图和基础类型功能
  • 工具函数 - 验证、触觉反馈、应用跳转等实用功能
  • 跨版本兼容 - 支持不同 iOS 版本的兼容性处理

视图修饰符

1. 身份验证修饰符

自动管理用户身份验证状态,根据登录状态显示不同内容。

ContentView()
    .modifier(AuthViewModifier())

// 或使用扩展方法
ContentView()
    .showAuthView()

功能特性:

  • 自动检查用户登录状态
  • 未登录用户显示登录/注册界面
  • 已登录用户显示程序主页
  • 平滑的状态转换动画

2. 付费墙修饰符

将内容锁定在付费墙后面,管理订阅验证。

// 基础用法
PremiumContentView()
    .modifier(RequirePremiumViewModifier())

// 高级用法
PremiumContentView()
    .requirePremium(
        subscriptionType: .revenueCat,
        sheetOrFullScreenCover: .sheet,
        onCancelPaywall: {
            print("用户取消付费")
        },
        onPaySuccess: {
            print("付费成功")
        }
    )

核心实现:

struct RequirePremiumViewModifier: ViewModifier {
    @EnvironmentObject private var authManager: AuthManager
    @State private var showPaywall = false
    
    let subscriptionType: SubscriptionType
    let sheetOrFullScreenCover: SheetOrFullScreenCover
    let onCancelPaywall: () -> Void
    let onPaySuccess: () -> Void
    
    func body(content: Content) -> some View {
        content
            .onAppear {
                showPaywall = !authManager.subscriptionActive
            }
            .sheet(isPresented: sheetOrFullScreenCover == .sheet ? $showPaywall : .constant(false)) {
                paywallContent()
            }
    }
}

3. 加载状态修饰符

显示全屏加载提示,带有可选的消息覆盖层。

@State private var isLoading = false

ContentView()
    .modifier(FullScreenToastViewModifier(
        isPresenting: $isLoading,
        message: "正在处理您的请求...",
        backgroundColor: Color.black.opacity(0.7)
    ))

// 或使用扩展方法
ContentView()
    .fullScreenLoading(isPresenting: $isLoading)

4. 闪光效果修饰符

为视图添加动画闪光效果,常用于加载状态。

LoadingView()
    .modifier(ShimmerViewModifier(duration: 2.0, autoreverse: true))

// 或使用扩展方法
LoadingView()
    .shimmering(enable: true, duration: 1.5)

5. 键盘管理修饰符

在用户拖拽时自动收起键盘。

FormView()
    .modifier(ResignKeyboardOnDragGesture())

// 或使用扩展方法
FormView()
    .dismissKeyboard()

扩展方法

颜色扩展

支持从十六进制字符串创建颜色。

extension Color {
    init(hex: String) {
        let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
        var int: UInt64 = 0
        Scanner(string: hex).scanHexInt64(&int)
        let a, r, g, b: UInt64
        switch hex.count {
        case 3: // RGB (12-bit)
            (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
        case 6: // RGB (24-bit)
            (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
        case 8: // ARGB (32-bit)
            (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
        default:
            (a, r, g, b) = (1, 1, 1, 0)
        }
        
        self.init(
            .sRGB,
            red: Double(r) / 255,
            green: Double(g) / 255,
            blue: Double(b) / 255,
            opacity: Double(a) / 255
        )
    }
}

使用示例:

let primaryColor = Color(hex: "007AFF")     // 6位RGB
let redColor = Color(hex: "F00")            // 3位RGB
let transparentBlue = Color(hex: "80007AFF") // 8位ARGB

字符串扩展

提供字符串内容验证功能。

extension String {
    var containsLetter: Bool {
        let letterRegex = ".*[A-Za-z]+.*"
        let passwordTest = NSPredicate(format: "SELF MATCHES %@", letterRegex)
        return passwordTest.evaluate(with: self)
    }
    
    var containsNumber: Bool {
        let numberRegex = ".*[0-9]+.*"
        let passwordTest = NSPredicate(format: "SELF MATCHES %@", numberRegex)
        return passwordTest.evaluate(with: self)
    }
}

使用示例:

let password = "password123"
let hasLetters = password.containsLetter  // true
let hasNumbers = password.containsNumber  // true

日期扩展

提供用户友好的日期格式化。

extension Date {
    var friendlyLocalFormat: String {
        return self.formatted(date: .abbreviated, time: .omitted)
    }
}

使用示例:

let now = Date()
let formattedDate = now.friendlyLocalFormat  // "Jan 15, 2024"

视图扩展

提供丰富的视图增强功能。

加载状态

// 按钮加载状态
Button("提交") { }
    .loading($isLoading)

// Toast 通知
someView.showToast($showToast, message: "操作成功", isSuccess: true)

// 全屏加载
someView.fullScreenLoading(isPresenting: $isLoading, message: "加载中...")

样式和外观

// 圆角
someView.roundedCorners(radius: 12)
someView.cardRoundedCorners()    // 预定义卡片圆角
someView.circle()                // 圆形裁剪

// 阴影效果
someView.shadowCompat()
someView.colorSchemeShadow(
    light: Color.gray.opacity(0.3),
    dark: Color.white.opacity(0.1),
    radius: 4
)

// 背景处理
someView.viewBackground()  // 自适应系统背景

交互增强

// 增强点击手势
CustomView()
    .tappable { 
        // 点击操作
    }

// Web 视图展示
someView.webViewSheet(
    isPresented: $showWebView,
    url: URL(string: "https://example.com"),
    title: "网页标题"
)

工具函数

触觉反馈引擎

提供简单的触觉反馈功能。

enum HapticsEngine {
    static func impact(style: UIImpactFeedbackGenerator.FeedbackStyle = .light) {
        UIImpactFeedbackGenerator(style: style).impactOccurred()
    }
}

使用示例:

// 轻微震动
HapticsEngine.impact(style: .light)

// 中等震动
HapticsEngine.impact(style: .medium)

// 强烈震动
HapticsEngine.impact(style: .heavy)

验证工具

提供常用的输入验证功能。

enum Validate {
    // 邮箱验证
    static func validateEmail(_ email: String) -> Bool {
        let emailPattern = #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"#
        let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailPattern)
        return emailPredicate.evaluate(with: email)
    }
    
    // 密码长度验证
    static func validatePasswordLength(_ password: String) -> Bool {
        return password.count >= 6 && password.count <= 12 && !password.isEmpty
    }
    
    // 密码字母验证
    static func validatePasswordLetter(_ password: String) -> Bool {
        return password.containsLetter && !password.isEmpty
    }
    
    // 密码数字验证
    static func validatePasswordNumber(_ password: String) -> Bool {
        return password.containsNumber && !password.isEmpty
    }
    
    // 完整密码验证
    static func validatePassword(_ password: String) -> Bool {
        return validatePasswordLength(password) && 
               validatePasswordLetter(password) && 
               validatePasswordNumber(password)
    }
}

使用示例:

let email = "user@example.com"
let password = "password123"

let isValidEmail = Validate.validateEmail(email)        // true
let isValidPassword = Validate.validatePassword(password) // true

应用跳转工具

提供应用内外 URL 跳转功能。

enum OpenApp {
    // 在外部浏览器打开 URL
    static func openUrlOutsideApp(_ url: String) {
        guard let url = URL(string: url) else { return }
        UIApplication.shared.open(url)
    }
    
    // 在应用内打开 URL
    static func openUrlInApp(_ url: String) {
        guard let url = URL(string: url) else { return }
        let safariViewController = SFSafariViewController(url: url)
        if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
           let rootViewController = windowScene.windows.first?.rootViewController {
            rootViewController.present(safariViewController, animated: true)
        }
    }
}

使用示例:

// 在外部浏览器打开
OpenApp.openUrlOutsideApp("https://www.apple.com")

// 在应用内打开
OpenApp.openUrlInApp("https://developer.apple.com")

兼容性功能

跨版本兼容

// iOS 17+ 兼容的 onChange 修饰符
someView.onChangeCompat(of: value) { oldValue, newValue in
    // 处理值变化,自动适配 iOS 17+ 和早期版本
}

// 触觉反馈兼容
someView.sensoryFeedbackCompat(trigger: triggerValue)

枚举定义

// 按钮样式类型
enum ButtonStyleType {
    case common  // 通用按钮样式
    case text    // 文本按钮样式
}

// 订阅类型
enum SubscriptionType: String, CaseIterable {
    case revenueCat  // RevenueCat 订阅
    case storeKit2   // StoreKit2 订阅
}

// 展示样式
enum SheetOrFullScreenCover: String, CaseIterable {
    case sheet           // Sheet 展示
    case fullScreenCover // 全屏覆盖
}

依赖库

该模块需要以下外部库:

  • JDStatusBarNotification - 状态栏通知
  • WhatsNewKit - "新功能"展示
  • AlertToast - Toast 通知
  • RevenueCat (可选) - 订阅管理
  • SafariServices - 应用内浏览器

最佳实践

1. 模块化使用

// 组合多个修饰符
ContentView()
    .cardRoundedCorners()
    .cardShadow()
    .requirePremium()
    .showToast($showToast, message: "成功!", isSuccess: true)

2. 状态管理

@State private var isLoading = false
@State private var showError = false

Button("提交") {
    isLoading = true
    // 执行操作
}
.loading($isLoading)
.showToast($showError, message: "操作失败", isSuccess: false)

3. 主题适配

// 自动适配深色/浅色模式
CardView()
    .colorSchemeShadow(
        light: .gray.opacity(0.3),
        dark: .white.opacity(0.1)
    )
    .viewBackground()

Last updated on