iOS开发 SwiftUI 11:Form 从桌面转移动端要理解移动端是以界面为核心的而且是以系统提供的几种界面模式为核心的。创建窗口、显示、隐藏不存在的。你提供大致的描述系统决定如何显示和操作。Form提供层次结构适合显示多项数据和提供输入。目录基本Form和Section通常用于Form的组件Toggle开关监控变化 修改系统显示模式PickerForEach警告Non-constant range: argument must be an integer literal使用复杂视图Picker样式.automatic .menu.inline.navigationLink.palette .segmented.wheelStepperSlider基本Form和Section示例代码Form{ Text(Hello, World! 1) Text(Hello, World! 2) Section(header: Text(段落), footer: Text(这是一个段落)){ Text(Hello, World! 3) Text(Hello, World! 4) Text(Hello, World! 5) } //.background(.black) }代码在一个Form里放了5个文本前两个独立后三个组合在段落里Section并且给段落设置了头尾效果看得出来Form下默认是一个段落每个段落格式类似List。代码中屏蔽了给段落设置背景的代码如果启用效果是这样的晕倒……移动端的行为非常不确定缺乏内在逻辑。通常用于Form的组件表单内通常使用Toggle、Picker、Stepper、Slider等组建来展示数据不过Form只是一种布局形式这些组件大都可以独立使用当然需要嵌套在合适的结构里获得合理的布局所以还是套在Form里比较简单。Toggle开关toggle是非常常用的关联到一个开关变量State var b false var body: some View { Form{ Text(b ? true : false) Section(header: Text(段落), footer: Text(这是一个段落)){ Toggle(isOn:$b){Text(b)} } //.background(.black) } }效果点一下开关监控变化 修改系统显示模式当开关切换的时候我们通常希望立即发生点什么一般我们这么写State var b false State var str info var body: some View { Form { Text(b ? true : false) Text(str) Section(header: Text(段落), footer: Text(这是一个段落)) { Toggle(isOn: $b) { Text(b) } .preferredColorScheme(b ? .dark : .light) .onChange(of: b) {if b { str TRUE } else { str FALSE }} } //.background(.black) } //.preferredColorScheme(b ? .dark : .light) //.onChange(of: b) { if b { str TRUE } else { str FALSE } } }我们增加了一个Text用来在onChange里面做动作同时还用了preferredColorScheme来修饰系统显示模式为亮色或暗色。看得出来这两个代码都直接传入了变量b为参数合理猜测这两个代码其实和Toggle没有任何依赖性将这两句移到Form后面也是一样工作的。下面就是放在Form后的效果点击一次再点击一次这两句放在Section后面也是一样的。Pickerpicker用来从列表中选择一个其功能类似桌面的List或ComboBox。使用方法有点不够高大上因为它只能返回关联选中的项目的索引号。示例代码var items [item1,item2,item3] State var selectedNumber 0 //Form里面 Text(\(selectedNumber)) Picker(选择,selection: $selectedNumber) { Text(1) Text(2).tag(2) Text(3) ForEach(0 .. items.count) { i in Text(items[i]) //.tag(i10) } }初始效果注意每个项的索引相当诡异我们初始设置的选中项索引是0一共有6个选项前三个手工编写其中一个用.tag做了指定后三个由ForEach生成具体情况是默认选项是第一个如果没有ForEach生成的三个显示的选中项是第一个如果没有ForEach生成的三个手工生成的只有第二个可以被选中因此手工生成的选项如果不用tag指定的话是没有索引的用ForEach生成的项目没有指定tag也会自动生成但是是从0开始的因此和前面手工指定的冲突显示的选中项为同一个tag的第一个项目如果启用指定tag的那一句效果会比较好ForEach警告Non-constant range: argument must be an integer literal现代化编程语言因为动态过程太多而无法用自然的方式跟踪原始数据因此必须明确指出如何识别数据如果确信数据不会在运行中更改可以暂时无视这个警告。解决方案是明确指出IDForEach(0 .. items.count, id:\.self)其实.self就是指针。一个谎言需要用十个谎言来弥补。使用复杂视图原则上Picker的条目可以非常复杂改写一下ForEach循环部分ForEach(0 .. items.count, id:\.self) { i in HStack{ Image(pic2).resizable().frame(height:40) Text(items[i]) }.tag(i10) }增加了一个图片同时希望限制图片的大小原图很大注意顶部的图片就是“Image(pic2).resizable().frame(height:40)”的显示效果Picker显示的时候图片已经缩小但是当我们选中之后主界面却无法直视加上.scaledToFit()也没什么用。实际上Image的修饰无效压根不应该在Picker里面使用复杂图片而是使用图标比如换成系统图标Image(systemName: square.and.arrow.up)无需修饰效果Picker样式样式一共有这么多//.pickerStyle(.automatic) //.pickerStyle(.inline) //.pickerStyle(.menu) //.pickerStyle(.navigationLink) //.pickerStyle(.palette) //.pickerStyle(.segmented) .pickerStyle(.wheel)以上代码修饰Picker如果同时写了多个起作用的是第一个。.automatic .menu菜单式是默认方式就是前面看到的样式。当然按照说法默认方式是根据上下文情况来的。.inlineinline就是桌面的ListBox如下图选中项屁股后面会打勾。.navigationLink这个暂时不可用因为内容不合规A picker style represented by a navigation link that presents the options by pushing a List-style picker view..palette .segmented效果.wheel这个轮子很流行但是其实交互上不理想没法子谁让现在是美工主导世界呢Stepper步进器也挺简单基本代码State var selectedNumber 0 //Form内 Stepper(selectedNumber \(selectedNumber),value:$selectedNumber)效果就是这个样还可以复杂一点设定范围和步长Stepper(selectedNumber \(selectedNumber), value: $selectedNumber,in:0 ... 12,step:1)但是注意设置的范围in仅仅用来控制界面并不影响直接设置关联的变量比如再增加一个不限制范围的控件Stepper(selectedNumber \(selectedNumber), value: $selectedNumber,in:0 ... 12,step:1) Stepper(selectedNumber \(selectedNumber), value: $selectedNumber)上面一个到达12的时候加号无效了不可以继续点击。但下面的仍然可以上面的控件已经突破了范围限制而且加号又可用了我们再点一下上面的控件的加号又自动跳回到上限了。Slider滑块也很简单State var floatValue : Float 0 //Form内 Slider(value: $floatValue,in: 1 ... 10,step: 2)效果为了显示数值要自己加工一下HStack{ Text(Slider \(floatValue)) Slider(value: $floatValue,in: 1 ... 10,step: 2) }效果绑定值预设是0初始显示0动过以后就不能再突破限制这和Stepper的行为是一样的。如果需要竖向滑块按照视图的常规做法HStack{ Text(Slider \(floatValue)) Slider(value: $floatValue,in: 1 ... 10,step: 2) .rotationEffect(.degrees(-90)) .frame(height: 200) }效果