【SwiftUI】LinearGradient 与 HierarchicalShapeStyle
分类:
技术
简介:SwiftUI 中的 LinearGradient 与 HierarchicalShapeStyle:语义化样式系统的两种层级一、背景:SwiftUI 的 ShapeStyle 抽象在 SwiftUI 中,颜色并不是唯一的视觉输入。自 iOS 15 起,Apple 将颜色、渐变、材质等统一抽象为一个协议:protocol ShapeStyle只要一个类型符合 ShapeStyle,它就可以被用于:foregroundStyle(_:)background(_:)fill(_:)stroke(_:)LinearGradient 和 HierarchicalShapeStyle 都是 ShapeStyle 的具体实现,但它们代表的设计层级完全不同:类型本质LinearGradient具体视觉描述(物理样式)HierarchicalShapeStyle语义化样式引用(抽象角色)理解这一点,是理解二者区别的关键。二、LinearGradient:显式的、确定性的视觉样式1. 定义与本质LinearGradient 表示 线性渐变填充,它是一个完全确定的视觉结果:明确的颜色明确的方向明确的渲染方式LinearGradient(
colors: [.red, .orange],
startPoint: .top,
endPoint: .bottom
)它直接告诉渲染系统:“请按照我给定的参数绘制这个渐变。”2. 所属层级:Presentation LayerLinearGradient 属于 表现层(Presentation Layer):不关心系统主题不关心控件层级不关心语义角色换言之,它是“画什么就是什么”。3. 典型使用场景(1)品牌视觉Text("AYU")
.font(.largeTitle.bold())
.foregroundStyle(
LinearGradient(
colors: [.purple, .blue],
startPoint: .leading,
endPoint: .trailing
)
)适用于:Logo品牌主视觉Banner(2)强调性装饰卡片背景CTA 按钮Hero 区域4. 优势与限制优势完全可控表现力强可复现性高限制不自适应系统语义暗黑模式需要手动适配可维护性随复杂度下降三、HierarchicalShapeStyle:语义化的系统层级样式1. 定义与本质HierarchicalShapeStyle 并不是一个“颜色”或“渐变”,而是一个:语义占位符(Semantic Placeholder)它代表的是:“请使用当前上下文中,某一层级的系统前景样式。”常见用法:.foregroundStyle(.secondary)实际上 .primary、.secondary、.tertiary、.quaternary 都是 HierarchicalShapeStyle 的不同实例。2. 所属层级:Semantic Layer它属于 语义层(Semantic Layer):不是“长什么样”而是“扮演什么角色”例如:样式语义.primary主要信息.secondary次要信息.tertiary辅助信息.quaternary极弱提示系统会根据以下条件动态决定最终颜色:Light / Dark Mode高对比度设置父视图的 foregroundStyle控件状态(enabled / disabled)3. 动态解析机制VStack {
Text("Title")
.foregroundStyle(.primary)
Text("Subtitle")
.foregroundStyle(.secondary)
}在不同模式下:模式primarysecondaryLight接近黑色中灰Dark接近白色浅灰开发者不需要写任何适配代码。4. 典型使用场景(1)信息层级表达Text("Heart Rate")
.foregroundStyle(.secondary)
Text("72 bpm")
.foregroundStyle(.primary)(2)系统风格一致性设置页表单列表辅助说明文本四、二者的核心差异对比维度LinearGradientHierarchicalShapeStyle类型具体样式语义样式是否固定是否是否响应系统否是是否表达层级否是是否适合品牌是否是否适合信息文本否是一句话总结:**LinearGradient 告诉 SwiftUI“怎么画”,HierarchicalShapeStyle 告诉 SwiftUI“画给谁看”。**五、组合使用:现代 SwiftUI 的推荐模式SwiftUI 并不要求二选一,正确的方式是分层使用。示例:健康类 App(贴合你的项目场景)VStack(alignment: .leading, spacing: 8) {
Text("Morning Weight")
.font(.caption)
.foregroundStyle(.secondary)
Text("68.4 kg")
.font(.title2.bold())
.foregroundStyle(
LinearGradient(
colors: [.green, .mint],
startPoint: .leading,
endPoint: .trailing
)
)
}语义层负责 信息结构,表现层负责 视觉强调。六、设计哲学总结(非常重要)Apple 在 SwiftUI 中引入 HierarchicalShapeStyle,传达了一个明确的设计方向:不要用颜色表达语义,而要用语义生成颜色。这与传统 UIKit 中大量 UIColor.gray.withAlphaComponent(0.6) 的做法形成根本性转变。而 LinearGradient 则依然是你在需要“突破系统样式”的地方,唯一正确的选择。七、结论LinearGradient 是 视觉工具HierarchicalShapeStyle 是 设计语言一个解决“好不好看”一个解决“对不对”
[swift]Image与font
分类:
简介:为什么 SwiftUI 中 Image(icon).font(...) 不起作用一、问题现象在 SwiftUI 中,很多开发者(尤其是初学者)都会写出类似这样的代码:Image(icon)
.resizable()
.scaledToFit()
.font(.system(size: 12))但实际运行后会发现:图片尺寸、样式完全没有变化,font 似乎被忽略了而当我们换成:Image(systemName: "star.fill")
.font(.system(size: 12))却又是完全生效的。这并不是 SwiftUI 的 bug,而是一个非常重要的设计差异。二、关键结论(一句话版)font 只对“字体驱动的视图”有效,而普通 Image 不是字体视图。要真正理解这句话,我们需要拆开 SwiftUI 中 Image 的两种本质类型。三、SwiftUI 中 Image 的两种“身份”1️⃣ 普通图片(位图 Image)Image("icon") // Assets / png / jpg / webp本质:像素位图(Bitmap)尺寸 = 图片本身的像素大小渲染方式 ≈ UIImage / NSImage👉 它是一个 图像容器(Image View)2️⃣ SF Symbols(符号 Image)Image(systemName: "star.fill")本质:字体符号(Symbol Font)由矢量路径组成与 Text 使用同一套字体系统👉 它是一个 “用字体画出来的图形”四、font 在 SwiftUI 中的真实作用对象1️⃣ font 是什么?在 SwiftUI 的语义模型中:.font(_:)并不是一个“万能样式”,而是:环境中用于排版 Glyph(字形)的字体描述它主要影响三类视图:视图类型是否响应 fontText✅ 是Image(systemName:)✅ 是Image("xxx")❌ 否Shape❌ 否2️⃣ 为什么 SF Symbols 能吃 font?因为 SF Symbols:本质是 字体字形Apple 把它们设计成 “Symbol Font”大小 = font.pointSize粗细 = font.weight例如:Image(systemName: "heart.fill")
.font(.system(size: 24, weight: .bold))等价于:Text("♥︎")
.font(.system(size: 24, weight: .bold))五、为什么普通 Image 完全不理 font?1️⃣ 位图没有“字体”概念Image("icon")它的渲染逻辑是:读取像素 → 显示像素 → 按 frame / scale 适配而不是:选择字体 → 计算字形 → 排版 → 渲染👉 font 对它来说没有任何数学意义2️⃣ SwiftUI 没有隐式“位图转字体”机制SwiftUI 的设计原则之一是:修饰符只对“语义上匹配的视图”生效如果 font 能影响普通 Image,会带来严重问题:字体单位(pt) vs 像素单位(px)混乱不同分辨率下行为不可预测破坏 SwiftUI 的 declarative 语义一致性所以 SwiftUI 直接选择忽略。六、为什么代码不报错?这是一个非常“SwiftUI 风格”的设计点。Image("icon")
.font(.system(size: 12)) // ❌ 无效,但不报错原因是:font 是一个 ViewModifierSwiftUI 允许 modifier 挂在任意 View 上是否生效 → 由 View 自己决定👉 这也是 SwiftUI 的声明式哲学:“描述你想要什么,而不是强制它一定生效”七、正确控制普通 Image 尺寸的方式✅ 使用 frameImage("icon")
.resizable()
.scaledToFit()
.frame(width: 12, height: 12)✅ 使用 scaleEffect.scaleEffect(0.8)❌ 不要对位图 Image 使用 font八、一个对比总结表(非常重要)对比项SF Symbol普通 Image来源systemNameAssets / 文件渲染基础字体像素是否支持 font✅❌控制大小方式fontframe / scale是否支持 weight✅❌是否支持 dynamic type✅❌
【swift】让你的 App 轻松支持多语言
分类:
技术
简介:一、什么是本地化(Localization)本地化(Localization) 是指让应用程序能够根据用户的语言和地区自动显示相应的内容。例如:中文用户看到「登录」英文用户看到「Login」日语用户看到「ログイン」在 Swift / iOS 开发中,本地化字符串通常通过 NSLocalizedString 实现。二、NSLocalizedString 的基本用法最基础的语法如下:NSLocalizedString("Login", comment: "登录按钮标题")"Login" 是字符串键(key)"登录按钮标题" 是注释(供翻译人员理解使用,不会显示在界面上)系统会自动根据当前语言环境,在对应的 .strings 文件中查找该 key 的翻译。三、创建 Localizable.strings 文件在 Xcode 中选择:File → New → File → Strings File命名为 Localizable.strings选中文件 → 在右侧的 File Inspector 中点击 Localize...选择要支持的语言,例如:EnglishChinese (Simplified)Japanese这样 Xcode 会自动为每种语言生成一个对应目录:en.lproj/Localizable.strings
zh Hans.lproj/Localizable.strings
ja.lproj/Localizable.strings四、在 .strings 文件中添加内容示例:英文版 (en.lproj/Localizable.strings)"Login" = "Login";
"Email" = "Email";
"Password" = "Password";中文版 (zh Hans.lproj/Localizable.strings)"Login" = "登录";
"Email" = "邮箱";
"Password" = "密码";日文版 (ja.lproj/Localizable.strings)"Login" = "ログイン";
"Email" = "メール";
"Password" = "パスワード";五、在 Swift 中使用本地化字符串直接调用:Text(NSLocalizedString("Login", comment: "登录按钮标题"))或者用在控制器里:title = NSLocalizedString("Settings", comment: "设置页标题")六、优化写法:字符串扩展(推荐 ✅)在项目中频繁使用 NSLocalizedString 会显得冗长,我们可以通过 String 扩展 提供更简洁的写法:extension String {
/// 快速返回本地化后的字符串
var LocalizedStr: String {
return NSLocalizedString(self, comment: "")
}
}使用方式更优雅:Text("Login".LocalizedStr)
TextField("Email".LocalizedStr, text: $email)✨ 优点:代码更简洁可读性更高不需要重复填写注释参数七、带格式的本地化字符串有时我们需要插入变量,例如:let name = "小明"
Text(String(format: NSLocalizedString("Hello, %@!", comment: "问候语"), name)).strings 文件中对应:"Hello, %@!" = "你好,%@!";这样不同语言都能保持正确语法结构。八、支持动态语言切换(可选进阶)如果希望用户能在 App 内切换语言(不依赖系统设置),可以使用第三方库(如 Localize Swift)或自定义本地化管理器。例如:Localize.setCurrentLanguage("zh Hans")然后重新刷新视图即可。九、常见问题总结问题原因解决方法翻译没显示没有在对应 .lproj 文件添加 key检查字符串文件是否同步中文正常,英文不显示没设置英文语言资源在 Localizable.strings(en) 中补充内容注释出现在界面上写错了参数位置确保 comment 不在显示文本位置新增语言没生效没勾选 Localize 文件右键 Localizable.strings → Localize...🔚 十、总结Swift 本地化流程图:代码调用 → NSLocalizedString(key) → 查找系统语言目录 → 对应的 Localizable.strings → 返回翻译结果最佳实践:所有用户可见文字均通过 NSLocalizedString 处理;使用 .LocalizedStr 扩展简化调用;为每个字符串添加清晰注释;测试多语言切换场景,确保排版和长度兼容。
【swift】SwiftUI 动画
分类:
技术
简介:🔹 SwiftUI 动画SwiftUI 提供声明式动画,核心就是 .animation 修饰符和 withAnimation。1. 隐式动画(Implicit Animation)只要状态值发生变化,UI 会自动过渡。struct ImplicitAnimationView: View {
@State private var isScaled = false
var body: some View {
VStack {
Circle()
.fill(Color.blue)
.frame(width: isScaled ? 200 : 100, height: isScaled ? 200 : 100)
.animation(.easeInOut(duration: 1), value: isScaled) // 隐式动画
Button("切换") {
isScaled.toggle()
}
}
}
}2. 显式动画(Explicit Animation)用 withAnimation { ... } 包裹状态变化。struct ExplicitAnimationView: View {
@State private var isRotated = false
var body: some View {
VStack {
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
.rotationEffect(.degrees(isRotated ? 180 : 0))
Button("旋转") {
withAnimation(.spring(response: 0.5, dampingFraction: 0.4)) {
isRotated.toggle()
}
}
}
}
}3. 过渡动画(Transition Animation)针对视图的 插入和删除。struct TransitionAnimationView: View {
@State private var showBox = false
var body: some View {
VStack {
if showBox {
Rectangle()
.fill(Color.green)
.frame(width: 150, height: 150)
.transition(.scale) // 进入/退出动画
}
Button("切换") {
withAnimation(.easeInOut) {
showBox.toggle()
}
}
}
}
}4. 动画修饰器(Animation Modifier)绑定到某个值,值变化时触发动画。struct AnimationModifierView: View {
@State private var offsetX: CGFloat = 0
var body: some View {
Circle()
.frame(width: 80, height: 80)
.offset(x: offsetX)
.onTapGesture {
offsetX = offsetX == 0 ? 200 : 0
}
.animation(.easeInOut(duration: 1), value: offsetX) // 绑定值
}
}🔹 UIKit 动画在 UIKit 里,主要靠 UIView.animate 和 核心动画(Core Animation)。1. UIView.animateUIView.animate(withDuration: 0.5, animations: {
someView.alpha = 0.0
someView.frame.origin.y += 100
}) { finished in
print("动画完成")
}2. 弹簧动画UIView.animate(withDuration: 0.8,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.5,
options: [],
animations: {
someView.transform = CGAffineTransform(scaleX: 2, y: 2)
},
completion: nil)3. 核心动画(CAAnimation)适合更复杂的场景。let animation = CABasicAnimation(keyPath: "position.x")
animation.fromValue = 0
animation.toValue = 200
animation.duration = 1.0
someView.layer.add(animation, forKey: "moveX")总结SwiftUI → 用 .animation、withAnimation、transition。UIKit → 用 UIView.animate、UIViewPropertyAnimator、CAAnimation。
【swift】通用获取当前系统的语言
分类:
技术
简介:LanguageHelper.swift import Foundation
struct LanguageHelper {
/// 获取当前系统语言代码 (ISO 639 1)
static func currentLanguageCode() > String {
return Locale.current.language.languageCode?.identifier ?? "未知"
}
/// 获取当前系统地区 (ISO 3166 1)
static func currentRegionCode() > String {
return Locale.current.region?.identifier ?? "未知"
}
/// 获取 App 首选语言(取决于 Info.plist 里配置的 Localizations)
static func appPreferredLanguage() > String {
return Bundle.main.preferredLocalizations.first ?? "未知"
}
/// 获取系统语言优先级列表
static func systemPreferredLanguages() > [String] {
return Locale.preferredLanguages
}
/// 打印常用信息
static func debugPrintLanguages() {
print("🌐 系统语言代码: \(currentLanguageCode())")
print("📍 系统地区代码: \(currentRegionCode())")
print("📱 App 首选语言: \(appPreferredLanguage())")
print("🗂 系统语言优先级列表: \(systemPreferredLanguages())")
}
}
调用 LanguageHelper.debugPrintLanguages()
// 也可以单独调用
let lang = LanguageHelper.currentLanguageCode()
let region = LanguageHelper.currentRegionCode()