swiftui-expert-skill - focus-patterns
工试云启 考证服务中心整理

SwiftUI 焦点模式参考目录FocusState让视图可聚焦命令与菜单的聚焦值默认焦点焦点作用域与分区焦点效果搜索焦点常见陷阱FocusState始终将FocusState标记为private。单个字段使用Bool多个字段使用可选的Hashable枚举。单个字段FocusStateprivatevarisFocused:BoolTextField(Email,text:$email).focused($isFocused)多个字段enumField:Hashable{casename,email,password}FocusStateprivatevarfocusedField:Field?TextField(Name,text:$name).focused($focusedField,equals:.name)TextField(Email,text:$email).focused($focusedField,equals:.email)设置focusedField .email以编程方式移动焦点设置nil以关闭键盘。嵌套视图中的focused(_:)与focused(_:equals:)当被修改的视图或其任何可聚焦后代获得焦点时.focused($bool)报告true。.focused($enum, equals:)仅当该特定视图获得焦点时才报告其值。enumFocus:Hashable{casecontainer,field}FocusStateprivatevarfocus:Focus?VStack{TextField(Name,text:$name).focused($focus,equals:.field)}.focusable().focused($focus,equals:.container)使用focused(_:equals:)和单个FocusStateSwiftUI 可以区分接收焦点的容器与仅包含焦点的容器。isFocused环境值只读环境值当最近的可聚焦祖先获得焦点时返回true。用于为非可聚焦的子视图设置样式。structHighlightWrapper:View{Environment(\.isFocused)privatevarisFocusedvarbody:someView{content.background(isFocused?Color.accentColor.opacity(0.1):.clear)}}让视图可聚焦.focusable(_:)让非文本输入视图参与焦点系统。可聚焦视图可以通过onKeyPress响应键盘事件通过onDeleteCommand响应编辑 删除等菜单命令。structSelectableCard:View{FocusStateprivatevarisFocused:Boolvarbody:someView{CardContent().focusable().focused($isFocused).border(isFocused?Color.accentColor:.clear).onDeleteCommand{deleteCard()}}}.focusable(_:interactions:)iOS 17通过FocusInteractions控制视图支持哪些焦点驱动的交互.activate—— 类似按钮仅当系统级键盘导航开启时可聚焦macOS/iOS.edit—— 捕获键盘/Digital Crown 输入.automatic—— 平台默认activate 和 edit 都有MyTapGestureView(...).focusable(interactions:.activate)对自定义按钮类视图使用.activate以便与系统键盘导航行为匹配。命令与菜单的聚焦值聚焦值让父视图App、Scene、Commands读取当前拥有焦点的任何视图的状态。用于根据聚焦的文档或选择启用/禁用菜单命令。用Entry声明extensionFocusedValues{EntryvarselectedDocument:BindingDocument?}聚焦值通常是可选的当没有视图发布它们时默认为nil但当你有合理的默认值时也可以使用非可选条目。从视图发布// 视图作用域当此视图或后代获得焦点时可用.focusedValue(\.selectedDocument,$document)// 场景作用域当此场景获得焦点时可用.focusedSceneValue(\.selectedDocument,$document)在命令中消费FocusedValue读取值FocusedBinding自动解包Binding。mainstructMyApp:App{FocusedBinding(\.selectedDocument)vardocumentvarbody:someScene{WindowGroup{ContentView()}.commands{CommandGroup(after:.pasteboard){Button(Duplicate){document?.duplicate()}.disabled(documentnil)}}}}FocusedObjectiOS 16用于ObservableObject类型。当聚焦对象变化时视图会失效并重新求值。// 发布.focusedObject(myObservableModel)// 消费FocusedObjectvarmodel:MyModel?场景作用域变体.focusedSceneObject(_:)。默认焦点.defaultFocus(_:_:priority:)iOS 17、macOS 13、tvOS 16对于初始焦点位置优先使用.defaultFocus而不是在onAppear中设置FocusState。FocusStateprivatevarfocusedField:Field?VStack{TextField(Name,text:$name).focused($focusedField,equals:.name)TextField(Email,text:$email).focused($focusedField,equals:.email)}.defaultFocus($focusedField,.email)优先级.automatic默认在窗口出现和编程式焦点变化时应用。.userInitiated也会在用户驱动的焦点导航期间应用。prefersDefaultFocus(_:in:)macOS/tvOS/watchOS与.focusScope(_:)一起使用在作用域区域内标记首选的默认焦点目标。resetFocus环境动作macOS/tvOS/watchOS在命名空间内重新评估默认焦点。NamespacevarscopeIDEnvironment(\.resetFocus)privatevarresetFocusButton(Reset){resetFocus(in:scopeID)}焦点作用域与分区.focusScope(_:)macOS/tvOS/watchOS将默认焦点偏好限制在某个命名空间内。与prefersDefaultFocus和resetFocus一起使用。.focusSection()macOS 13、tvOS 15引导方向性和顺序性的焦点移动穿过一组可聚焦后代。当可聚焦视图在空间上分离、方向性导航会跳过它们时很有用。HStack{VStack{Button(1){};Button(2){};Spacer()}Spacer()VStack{Spacer();Button(A){};Button(B){}}.focusSection()}没有.focusSection()时从按钮 1/2 向右滑动会找不到任何东西。有了它VStack 会接收方向性焦点并将其传递给它的第一个可聚焦子视图。焦点效果.focusEffectDisabled(_:)抑制系统的焦点环macOS或悬停效果。在提供自定义焦点视觉效果时使用。MyCustomCard().focusable().focusEffectDisabled().overlay{customFocusRing}isFocusEffectEnabled环境值读取当前状态。搜索焦点.searchFocused(_:)/.searchFocused(_:equals:)将焦点状态绑定到与最近的.searchable修饰符关联的搜索字段。工作方式类似.focused但目标是搜索栏。FocusStateprivatevarisSearchFocused:BoolNavigationStack{ContentView().searchable(text:$query).searchFocused($isSearchFocused)}// 以编程方式聚焦搜索栏Button(Search){isSearchFocusedtrue}常见陷阱冗余的FocusState写入会撤销焦点.focusable().focused()原生处理点击聚焦。添加一个也写入FocusState的点击手势会触发冗余的状态写入导致第二次 body 求值从而撤销焦点。结果是焦点短暂出现然后消失onDeleteCommand等按键命令停止工作。// 错误 —— 点击手势冗余地设置焦点导致双重求值CardView().focusable().focused($isFocused).onTapGesture{isFocusedtrue}// 删除这行// 正确 —— 让 .focusable() .focused() 处理它CardView().focusable().focused($isFocused)模糊的焦点绑定将同一个枚举 case 绑定到多个视图是模糊的。SwiftUI 会选择第一个候选者并发出运行时警告。// 错误 —— .name 绑定到两个视图TextField(Name,text:$name).focused($focusedField,equals:.name)TextField(Full Name,text:$fullName).focused($focusedField,equals:.name)// 模糊始终为每个可聚焦视图使用不同的枚举 case。.onAppear焦点时机如果视图树尚未稳定在.onAppear中设置FocusState可能会失败。优先使用.defaultFocusiOS 17以获得可靠的初始焦点。如果必须使用.onAppear作为最后手段将其包裹在DispatchQueue.main.async中。非文本视图缺少.focusable()TextField和SecureField是隐式可聚焦的。自定义视图堆栈、形状、图像则不是。忘记.focusable()意味着.focused()绑定无效按键事件处理器永远不会触发。