普通视图

发现新文章,点击刷新页面。
昨天以前首页

远离 dismiss,拥抱状态驱动

作者 Fatbobman
2025年4月2日 22:12

在 SwiftUI 开发中,环境值 dismiss 因其灵活、自适应的特性备受开发者青睐。它能够根据当前视图的上下文智能执行关闭操作:在模态视图中关闭窗口、在导航堆栈中弹出视图,甚至在多列导航容器中自动关闭边栏。正是这种看似“万能”的便捷性,让许多开发者将它作为首选工具。然而,便捷的背后往往隐藏着风险。频繁使用 dismiss 可能在应用程序中埋下隐患,引发测试难题乃至难以追踪的稳定性问题。本文将分析我们为何应谨慎对待 dismiss,并介绍更加健壮可靠的状态管理方案。通过重新审视视图呈现与消失的逻辑,我们能够打造出更稳定、易维护且可预测的 SwiftUI 应用。

MCP 崛起与苹果的 AI 框架设想 - 肘子的 Swift 周报 #77

作者 Fatbobman
2025年3月31日 22:00

在最近一段时间,在社交网络上,越来越多的 Model Context Protocol(MCP)使用者展示了各种丰富多彩的应用场景,从操控 Blender 创建精美场景,到利用最新的 GPT-4o 图片构建完整的漫画故事。MCP 巧妙地打开了以文本为主要互动手段的大模型,与现实世界之间的大门。

SwiftUI-国际化

作者 YungFan
2025年3月30日 20:13

介绍

  • 如果 App 需要提供给不同国家的用户使用,则需要进行国际化处理。
  • SwiftUI 项目的国际化主要包括:Info.plist 文件国际化、文本国际化等。

配置国际化语言

在进行国际化之前,必须要添加需要的国际化语言,选中国际化的项目 —> PROJECT —> Info —> Localizations,点击+添加需要的国际化语言(默认已经存在英文)。

Info.plist文件国际化

  1. 新建一个Strings File,必须命名为InfoPlist.strings
  2. 选中InfoPlist.strings,在 Xcode 的右侧文件检查器中找到Localization,点击Localize...,然后勾选配置的国际化语言。
  3. InfoPlist.strings左侧多了一个箭头,点击箭头展开后可以看见不同语言的Strings File,里面存放的是形如Key = Value的键值对。
  4. 在不同语言的Strings File中设置需要国际化的内容,如 App 名称等。
// 英文App名
"CFBundleName" = "I18N";
// 中文App名
"CFBundleName" = "国际化";

文本国际化

  1. 新建一个Strings File,必须命名为Localizable.strings
  2. 选中InfoPlist.strings,在 Xcode 的右侧文件检查器中找到Localization,点击Localize...,然后勾选配置的国际化语言。
  3. Localizable.strings左侧多了一个箭头,点击箭头展开后可以看见不同语言的Strings File
  4. 在不同语言的Strings File中设置需要国际化的文本键值对。
// 英文
"title" = "Reminder";
"message" = "Weather Information";
// 插值
"Weather is %@" = "Today is %@";
"Temperature is %lld" = "The temperature is %lld";
// 中文
"title" = "提示";
"message" = "今日天气";
// 插值
"Weather is %@" = "今天 %@";
"Temperature is %lld" = "气温 %lld 度";
  1. SwiftUI 文本国际化非常简单,开箱即用,因为大多数 View 与 Modifier 的构造方法中都将LocalizedStringKey作为参数类型,该参数的值为文本键值对中的键。
import SwiftUI

struct ContentView: View {
    let weather = "Sunny"
    let temperature = 10

    var body: some View {
        VStack {
            // 纯文本,有3种方式
            Text(title)
            
            Text(LocalizedStringKey("title"))

            Text("title", comment: "The title of the dialog.")
            
            // 自定义View
            MessageView("message")
            
            // 插值
            Text("Weather is \(weather)")
            
            Text("Temperature is \(temperature)")   
        }
    }
}

struct MessageView: View {
    var messaege: LocalizedStringKey

    init(_ messaege: LocalizedStringKey) {
        self.messaege = messaege
    }

    var body: some View {
        Text(messaege)
    }
}

注意:插值格式参考 String Format Specifiers

测试

默认情况下,App 的语言随着系统语言的变化而变化。但在开发阶段,如果才能快速测试 App 的国际化效果?主要有以下几种方式。

  1. 运行 App 之后在设备/模拟器通过设置(Settings)—> 通用(General)—> 语言与地区(Languages & Region) 切换系统语言以查看 App 的国际化效果。
  2. 通过 Xcode 菜单 —> Product —> Scheme —> Edit Scheme... —> Run —> Options —> App Language,选择需要测试的国际化语言之后再运行 App。
  3. 通过 Xcode 菜单 —> Product —> Scheme —> Manage Scheme... —> 选择需要复制的 Scheme —> 点击下方的圆形...图标 —> Duplicate —> 重命名 Scheme,然后将复制的 Scheme 按照方式 2 将 App Language 设置为需要测试国际化语言,最后运行时选择对应国际化语言的 Scheme。

效果

  • 英文。

英文.png

  • 中文。

中文.png

用「属性包装器」给你的Swift代码请个“管家” —— 告别重复代码的魔法指南

作者 JQShan
2025年3月27日 20:03

大家好!今天咱们来聊一个 Swift 中的宝藏特性: @propertyWrapper(属性包装器)。它就像给你的代码请了个聪明的管家,能帮你自动处理那些繁琐的属性管理逻辑。举个栗子🌰:数据范围限制、自动保存到本地、线程安全检查……这些重复劳动都能被它轻松承包。

一、为什么需要这个“管家”?

想象你在开发一个调色板应用,每个颜色通道的值都要限制在0-255之间。传统写法可能是这样的:

struct Color {
    private var _red: Int = 0
    var red: Int {
        get { _red }
        set { _red = min(max(newValue, 0), 255) }
    }
    // 绿色和蓝色还要再写两遍同样的逻辑 😫
}

每次写这种代码都像是复制粘贴的流水线工人,不仅容易出错,修改需求时更是灾难。这时候就该祭出 @propertyWrapper 了!

二、打造你的第一个“管家”

咱们来造一个自动限制数值范围的管家,就叫它 Clamped

@propertyWrapper
struct Clamped {
    private var value: Int
    let min: Int
    let max: Int
    
    // 重点:初始化时传入 wrappedValue(被包装的默认值)
    init(wrappedValue: Int, min: Int, max: Int) {
        self.min = min
        self.max = max
        self.value = Swift.min(Swift.max(wrappedValue, min), max)
    }
    
    var wrappedValue: Int {
        get { value }
        set { value = Swift.min(Swift.max(newValue, min), max) }
    }
}

用法简单到像写诗 ✨:

struct Color {
    @Clamped(min: 0, max: 255) var red = 0
    @Clamped(min: 0, max: 255) var green = 0
    @Clamped(min: 0, max: 255) var blue = 0
}

var myColor = Color()
myColor.red = 300
print(myColor.red) // 自动变成255,深藏功与名

三、管家的隐藏技能:投影值(Projected Value)

有时候除了管理属性值,咱们还想让管家“多嘴”说点别的。比如记录每次数值变化的日志:

@propertyWrapper
struct Logged<T> {
    private var value: T
    
    var wrappedValue: T {
        get { value }
        set {
            print("[日志] 值从 (value) → (newValue)")
            value = newValue
        }
    }
    
    // 用 $ 访问投影值
    var projectedValue: String { "当前值: (value)" }
    
    init(wrappedValue: T) { self.value = wrappedValue }
}

struct Test {
    @Logged var score = 60
}

let test = Test()
test.score = 90 // 控制台输出:[日志] 值从 60 → 90
print(test.$score) // 输出:当前值: 90

这个 $ 符号就像管家的对讲机,随时获取额外信息!

四、实战:自动保存用户设置

每次用 UserDefaults 保存设置都要写一堆代码?管家来搞定:

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T
    
    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
        // 首次注册默认值
        UserDefaults.standard.register(defaults: [key: defaultValue])
    }
    
    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}

// 用法优雅如呼吸
enum Settings {
    @UserDefault("HAS_SEEN_TUTORIAL", defaultValue: false) 
    static var hasSeenTutorial: Bool
}

Settings.hasSeenTutorial = true // 自动保存到本地

五、高级管家:线程安全守护者

在多线程环境下保护数据安全?加个锁就完事:

@propertyWrapper
struct ThreadSafe<T> {
    private var value: T
    private let queue = DispatchQueue(label: "com.threadsafe.queue")
    
    init(wrappedValue: T) { self.value = wrappedValue }
    
    var wrappedValue: T {
        get { queue.sync { value } }
        set { queue.sync { value = newValue } }
    }
}

class Counter {
    @ThreadSafe var count = 0
    
    func increment() {
        count += 1 // 多线程操作也稳如老狗
    }
}

六、注意事项:别让管家“翻车”

  1. 初始化顺序:第一个参数必须是 wrappedValue
  2. 别用在计算属性上:管家只管存储属性
  3. 性能敏感处慎用:比如高频调用的属性别加复杂逻辑
  4. 避免套娃:别在包装器里调用自身导致死循环

七、总结:让代码自己“长脑子”

通过 @propertyWrapper,咱们把重复的逻辑封装成一个个“智能管家”,让属性自己学会管理自己。这种写法不仅让代码更简洁,还能让团队协作时减少低级错误——毕竟,谁不喜欢一个会主动干活的管家呢?

下次写代码时,不妨想想:“这段逻辑能不能交给属性包装器?” 也许你会发现,代码世界突然清爽了许多 🌟。

动手时间到! 试着给你的项目写一个属性包装器,评论区等你分享奇思妙想~ 🚀

HelloGitHub 第 108 期

2025年3月28日 07:52
本期共有 39 个项目,包含 C 项目 (3),C# 项目 (1),C++ 项目 (3),CSS 项目 (1),Go 项目 (3),Java 项目 (1),JavaScript 项目 (5),Kotlin 项目 (1),Python 项目 (5),Rust 项目 (2),Swift 项目 (1),人工智能 (5),其它 (5),开源书籍 (3)

SwiftUI Environment:理念与实践

作者 Fatbobman
2025年3月26日 22:12

SwiftUI 的 Environment 是一个优雅且功能强大的依赖注入机制,几乎每个 SwiftUI 开发者都会在日常开发中接触和应用。这一机制不仅简化了视图间的数据传递,也为应用架构设计提供了更多的可能性。本文将暂且搁置具体的实现细节,转而聚焦于 Environment 在架构中的角色与边界,探讨那些常被忽视却至关重要的设计理念与实践经验。

❌
❌