通过 ViewModify 封装简单调用
我们封装的 PopMenuButton 已经完毕,我们已经迫不及待的想运行尝试一下。为了让 ServerSelectMenuView 试图点击可以打开 PopMenuButton,我们需要新建一个 ViewModify。
我们新建一个 ViewModify 的目录用来存放我们新建 PopMenuButtonModify.swift 的文件。
struct PopMenuButtonModify: ViewModifier {
func body(content: Content) -> some View {
content
}
}
使用 ZStack 垂直布局
ViewModify 可以将我们现有的试图进行扩展,不需要破坏之前的代码结构。PopMenuButton 需要展示在我们控件的最上方,所以我们需要用到ZStack组件。
struct PopMenuButtonModify: ViewModifier {
let items:[String] = ["item1", "item2"]
@State private var currentItem:String = ""
func body(content: Content) -> some View {
ZStack(alignment:.top) {
content
PopMenuButton(items: items,
currentItem: $currentItem)
}
}
}
struct PopMenuButtonModify_Preview: PreviewProvider {
static var previews: some View {
Text("Hello World!")
.modifier(PopMenuButtonModify())
}
}
通过 background 设置背景 border 设置边框
这个和我们想要的效果差距也太大了吧!主要原因还是我们组件的颜色和下面组件的颜色融合在一起了,为了能够区分出来,我们回到 PopMenuButton 添加背景颜色和边框。
.background(.white)
.border(Color(uiColor: appColor.c_cccccc), width: 0.5)
使用 clipped 切除多余内容
我们发现了一个致命的问题,我们的数据竟然溢出在我们的控件的外面了,我们把溢出的内容切掉。
.clipped()
使用 shadow 添加阴影
溢出的内容切掉了,有了边框还是很难看,我们给控件添加一个 5 的阴影。
.shadow(radius: 5)
这样看起来,就慢慢的好看多了。
我们添加一个默认的 Padding,让两边有间隙。
.padding()
我们将输入更改为下面数据源,观察一下数据少,但是数据很长,会是什么效果。
[
"这是一条很长很长很长很长很长很长很长很长很长的URL",
"https://www.baidu.com"
]
糟糕,我们的组件超出屏幕了,主要原因是我们设置宽度为无限宽度,我们内容超出了屏幕宽度但是有没有超出无限宽度,自然是自身宽度。
通过 fixedSize 设置是否自适应
我们设置一下,让关闭横向的自适应宽度,打开纵向的自适应宽度。并且我们需要将放在 .frame 的后面,不然起不到效果。
.fixedSize(horizontal: false, vertical: true)
我们看一下数据很短的情况下
数据量很多的情况下
看起来感觉还能接受。
通过显形的 VStack 去掉 ForEach 的间隙
我们发现我们通过 ForEach创建的列表是有间隙的,通过谷歌,我们知道在 ScrollView 和 ForEach的中间有一个隐形的 VStack,我们创建一个显形的 VStack 设置 Spacing = 0 即可。
ScrollView() {
VStack(spacing:0) {
ForEach(items, id:\.self) {item in
PopMenuButtonItem(title: item,
isSelect: currentItem == item)
.onTapGesture {
currentItem = item
}
}
}
}
默认 Item 的间隙已经去掉,我们高度正好显示了第六个一半,是隐约的人告诉用户,还可以滚动查看更多的数据。
我们去看一下我们 Modify 封装组件的预览,发现底部我们的组件已经露出来了。
我们修改 PopMenuButton的 Padding,设置为下面参数。
.padding(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20))
我们只保留左侧和右侧有对应间距,这样我们的组件看起来就完美了。
通过 @State 控制显示和隐藏
为了控制 PopMenuButton 的显示和隐藏,我们在 PopMenuButtonModify 中添加一个 @State 的参数控制。
@State private var isShowPopMenuButton:Bool = false
ZStack(alignment:.top) {
content
if isShowPopMenuButton {
PopMenuButton(items: items,
currentItem: $currentItem)
}
}
我们给 ZStack 添加一个点击事件,当点击就修改 isShowPopMenuButton = true 让 PopMenuButton 显示出来。
ZStack(alignment:.top) {
content
if isShowPopMenuButton {
PopMenuButton(items: items,
currentItem: $currentItem)
}
}
.onTapGesture {
isShowPopMenuButton = true
}
我们希望 PopMenuButton 里面子元素在点击的时候可以关闭弹窗,那么我们就需要监听点击事件。
通过闭包将当前选中元素代理出去
我们在 PopMenuButton 新增一个闭包用来通知外部,选中发生了变更,去掉 PopMenuButton 自身的逻辑。
typealias ItemValueChanged = (String) -> Void
let itemValueChanged:ItemValueChanged
.onTapGesture {
itemValueChanged(item)
}
我们修改一下我们 PopMenuButtonModify 的代码。
PopMenuButton(items: items,
currentItem: $currentItem,
itemValueChanged: { item in
currentItem = item
isShowPopMenuButton = false
})
修复空白区域无法点击
此时我们可以正常的点击弹出,选中之后消失。但是在体验的过程中,我们发现在空白区域点击没有相应任何的数据。最主要的原因是 PopMenuButtonItem 背景颜色是透明的,所以忽略了空白的区域,我们给 PopMenuButtonItem 添加一个背景颜色,就解决了空白区域不能点击的问题。
PopMenuButtonItem(title: item,
isSelect: currentItem == item)
.background()
.onTapGesture {
itemValueChanged(item)
}
为了让 PopMenuButtonModify 可以被外部使用,我们需要使用者自己传入数据源和当前已经选中的内容。
let items:[String]
@Binding var currentItem:String
扩展 View 方便其他 View 调用 PopMenuButton
我们给 View 添加一个扩展,方便进行设置和调用。
extension View {
func popMenuButton(items:[String], currentItem:Binding<String>) -> some View{
self.modifier(PopMenuButtonModify(items: items, currentItem: currentItem))
}
}
至此,我们的 PopMenuButton 的封装就告一段落了。