MichaelDevStudio 的 Swift/macOS 公共工具包,用来沉淀多个 App 中重复使用的工具类、基础 UI 组件和业务无关的通用能力。
这个包目前面向 macOS App,最低平台为 macOS 14,使用 Swift 6。
在 Xcode 中添加 Swift Package:
File > Add Package Dependencies...
开发联调时可以使用本地路径:
/Users/yangxuehui/Documents/dev/MySwiftAppTools
正式项目建议使用 GitHub 仓库 + tag 版本,例如:
Up to Next Major Version: 0.2.0
使用时在 App 代码中导入:
import MySwiftAppTools不同 App 可以在启动时集中配置这些工具:
@main
struct YourApp: App {
init() {
DefaultsTools.configure(appGroupID: "group.com.yourcompany.yourapp")
KeychainTools.configure(defaultService: "YourApp")
Log.configure(subsystem: "com.yourcompany.yourapp")
ToastManager.shared.configure(
maxVisibleToasts: 5,
toastWidth: 420,
copyOnTap: true
)
}
var body: some Scene {
WindowGroup {
ContentView()
.overlay(ToastView())
}
}
}如果某个 App 不需要 App Group,可以不调用 DefaultsTools.configure(...)。
UserDefaults 统一读写工具,支持 App Group。
常用能力:
DefaultsTools.configure(appGroupID:)DefaultsTools.sharedset/value/remove/existsbool/int/double/stringDefaultsTools.Key(rawValue:)- 直接使用 string key 的便捷方法
示例:
DefaultsTools.configure(appGroupID: "group.com.yourcompany.yourapp")
let key = DefaultsTools.Key(rawValue: "launchCount")
let count = DefaultsTools.shared.int(key) ?? 0
DefaultsTools.shared.set(count + 1, for: key)Keychain 通用封装。工具包不内置业务 account,调用 App 自己定义。
示例:
enum AppKeychainAccount: String {
case openAIApiKey = "openai.apiKey"
case azureApiKey = "AZURE_SPEECH_KEY"
}
KeychainTools.configure(defaultService: "YourApp")
KeychainTools.save("sk-xxx", account: AppKeychainAccount.openAIApiKey)
let key = KeychainTools.load(account: AppKeychainAccount.openAIApiKey)基于 OSLog 的轻量日志工具。
常用能力:
Log.configure(subsystem:isEnabled:)Log.debug/info/warn/errorLog.log(category, level, message)
示例:
Log.configure(subsystem: "com.yourcompany.yourapp", isEnabled: true)
Log.info("App started")
Log.log("Export", .error, "Export failed")文件系统工具,提供常见路径、文件判断、目录创建、复制、移动、删除、Finder 打开等能力。
常用能力:
documentsDirectorycachesDirectoryapplicationSupportDirectorytemporaryDirectoryexists/isDirectory/isWritableensureDirectory/createFile/remove/copy/moveopenInFindercopyTextsToPasteboard
示例:
let folder = FileTools.documentsPath("Exports")
try FileTools.ensureDirectory(folder)
FileTools.openInFinder(folder)日期格式化、日期差、月份计算、日期字符串转换等工具。
常用能力:
getStringByCurrentDategetDateTimeStringByCurrentDategetStringByDategetDateByStringgetDateDiffgetDateAfterDaysDateFormatter.zipName
基于 AVAudioPlayer 的简单音频播放工具。
常用能力:
AudioPlayer.sharedplayAudio(forResource:ofType:rate:)play(url:rate:)loadAudiopause/stopplaySystemSound
示例:
AudioPlayer.shared.playSystemSound("Glass")
AudioPlayer.shared.play(url: audioURL)SwiftUI 全局 toast 提示工具。
使用方式:
ContentView()
.overlay(ToastView())然后在 MainActor 上调用:
ShowToast("普通提示")
ShowToastSuccess("保存成功")
ShowToastWarn("请先选择文件")
ShowToastError("操作失败")
ShowToast("处理中...", type: .loading)
ShowToastHide()需要确认按钮:
ShowToast(
"首次使用需要确认",
customIcon: Image(systemName: "sparkles"),
requireConfirm: true,
onConfirm: {
// 用户点击 OK 后执行
}
)StoreKit 购买状态管理工具。产品 ID 由调用 App 配置,工具包不写死任何业务产品。
示例:
enum AppProductID {
static let pro = "com.yourcompany.yourapp.pro"
}
@State private var storeManager = StoreManager(
productIDs: [AppProductID.pro],
proProductID: AppProductID.pro
)
ContentView()
.environment(storeManager)购买页中:
ForEach(storeManager.products) { product in
Button(product.displayPrice) {
Task {
await storeManager.purchase(product)
}
}
}判断 Pro:
if storeManager.hasPurchasedPro {
// 解锁 Pro 功能
}通用 Pro 功能与免费额度控制工具。具体 feature 由调用 App 自己定义。
示例:
enum AppProFeature: String {
case privacyOCR
case privacyAIRepair
case batchExport
}
ProGatekeeper.shared.configure(
freeLimits: [
AppProFeature.privacyOCR: 10,
AppProFeature.privacyAIRepair: 5,
AppProFeature.batchExport: 0
],
keyPrefix: "YourApp.ProGatekeeper",
hasPurchasedPro: {
storeManager.hasPurchasedPro
},
presentPurchase: {
// 打开购买页
}
)
if await ProGatekeeper.shared.check(AppProFeature.privacyOCR) {
// 执行功能
}说明:
freeLimits中没有声明的 feature,会被视为 Pro-only。- limit 为
0表示免费用户完全不可用。 - Pro 用户直接放行,不消耗免费次数。
macOS 沙盒目录授权和 security-scoped bookmark 管理工具。
相关类型:
PermissionPurposePermissionUrlGroupPermissionManagerDirectoryManagerMyDirectoryMyDirectoryType
示例:
if let group = await PermissionManager.shared.ensureAccess(
for: targetURL,
purpose: .write
) {
let accessURL = group.matchUrl
if accessURL.startAccessingSecurityScopedResource() {
defer { accessURL.stopAccessingSecurityScopedResource() }
// 访问文件或目录
}
}用于管理一个页面中多个组件的联动关系,例如:
- 同组组件互斥
- 某组完成后解锁另一组
- 某个组件 busy 时禁用目标组件
相关类型:
ComponentsFlowManagerComponentStateComponentNodeInteractionContextInteractionRuleAnyInteractionRuleGroupMutualExclusionRuleGroupDependencyRuleBusySourceDisablesTargetsRule
示例:
enum ComponentID: String {
case importFile
case parse
case export
}
enum GroupID: String {
case input
case output
}
let flow = ComponentsFlowManager<ComponentID, GroupID>(
rules: [
AnyInteractionRule(
GroupDependencyRule(dependencies: [
.output: [.input]
])
)
]
)
flow.register(.importFile, groupID: .input)
flow.register(.export, groupID: .output)
let canExport = flow.isEnabled(.export)位于 Sources/MySwiftAppTools/DesignSystem,提供一套基础 SwiftUI 组件。
主要内容:
RCMThemeRCMDesignTokensRCMPresetThemeRCMColorTokensRCMSpacingTokensRCMRadiusTokensRCMStrokeTokensRCMShadowTokensRCMTypographyTokensRCMControlSizeTokensRCMButtonRCMPrimaryButtonStyleRCMSecondaryButtonStyleRCMSoftButtonStyleRCMDangerButtonStyleRCMSidebarIconRCMBadgeRCMToggleRCMPageTitleRCMSectionTitleRCMLabelTextRCMCaptionTextRCMMonoTextRCMSettingRowRCMValueRowRCMInlineFieldRCMCardRCMPageSectionRCMHeroPanelMultilineSubtitleRowCollapsibleSection
示例:
RCMPageTitle("Settings", subtitle: "Manage app preferences")
RCMCard {
RCMSettingRow("Enable Feature", subtitle: "Optional helper text") {
RCMToggle(isOn: $enabled, label: "Enabled")
}
}
RCMButton("Save", role: .primary, systemImage: "checkmark") {
save()
}历史项目中沉淀的 UI 辅助组件集合,包含:
ThemeManagerThemeColor(hex:)CustomGroupBoxStyleCustomButtonStylePlaceholderTextEditorReadOnlyTextViewCustomTextViewEscCloseModifierToobarStatusLightMySettingsCardActionBarActionItemActionShortcutActionBarButtonDisplayStyleAppInfo
示例:
ActionBar(items: [
.save(shortcut: .commandS) {
save()
},
.delete {
delete()
}
])沙漏动画视图,可用于等待、处理中或计时场景。
示例:
HourglassView()工具包自带基础国际化资源:
Sources/MySwiftAppTools/Resources/en.lproj/Localizable.strings
Sources/MySwiftAppTools/Resources/zh-Hans.lproj/Localizable.strings
包内文案通过 Bundle.module 读取,不依赖调用 App 的 Localizable.strings。
外部调用常用入口:
L("Some.Key")
"Some.Key".toNSLocalizedStringApp 自己的业务文案建议仍放在 App 自己的本地化文件中。
建议每次给其他 App 使用前打 tag。
cd /Users/yangxuehui/Documents/dev/MySwiftAppTools
git status
git add .
git commit -m "Update reusable app tools"
git tag 0.2.0
git push origin main
git push origin 0.2.0其他 App 更新包时:
File > Packages > Update to Latest Package Versions
如果使用精确版本,需要在 Xcode 的 Package Dependencies 中手动改到新 tag。
- 工具包不写死具体 App 的业务 ID、产品 ID、URL Scheme 或功能枚举。
- 具体业务信息通过
configure(...)、闭包或调用 App 自己定义的 enum 注入。 - 对外使用的类型、初始化器、方法必须是
public。 - 内部 helper、preview、demo 可以保持
internal或private。 - 修改工具包后,至少运行:
swift build
swift test如果改了对外 API,建议再建一个外部 consumer 临时验证 import MySwiftAppTools 后能否正常调用。