iOS开发 SwiftUI 11:Form
从桌面转移动端要理解,移动端是以界面为核心的,而且是以系统提供的几种界面模式为核心的。创建窗口、显示、隐藏?不存在的。你提供大致的描述,系统决定如何显示和操作。
Form提供层次结构,适合显示多项数据和提供输入。
目录
基本Form和Section
通常用于Form的组件
Toggle开关
监控变化 修改系统显示模式
Picker
ForEach警告:Non-constant range: argument must be an integer literal
使用复杂视图
Picker样式
.automatic .menu
.inline
.navigationLink
.palette .segmented
.wheel
Stepper
Slider
基本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后面也是一样的。
Picker
picker用来从列表中选择一个,其功能类似桌面的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(i+10) } }初始效果:
注意每个项的索引相当诡异,我们初始设置的选中项索引是0,一共有6个选项,前三个手工编写(其中一个用.tag做了指定),后三个由ForEach生成,具体情况是:
- 默认选项是第一个,如果没有ForEach生成的三个,显示的选中项是第一个
- 如果没有ForEach生成的三个,手工生成的只有第二个可以被选中,因此,手工生成的选项如果不用tag指定的话是没有索引的
- 用ForEach生成的项目没有指定tag也会自动生成,但是是从0开始的,因此和前面手工指定的冲突(显示的选中项为同一个tag的第一个项目)
- 如果启用指定tag的那一句,效果会比较好
ForEach警告:Non-constant range: argument must be an integer literal
现代化编程语言因为动态过程太多而无法用自然的方式跟踪原始数据,因此必须明确指出如何识别数据(如果确信数据不会在运行中更改可以暂时无视这个警告)。
解决方案是明确指出ID:
ForEach(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(i+10) }增加了一个图片,同时希望限制图片的大小(原图很大):
注意顶部的图片就是“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
菜单式是默认方式,就是前面看到的样式。当然,按照说法,默认方式是根据上下文情况来的。
.inline
inline就是桌面的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) }效果: