第十一章 ViewModify|ZStack|border|clipped|shadow|fixedSize|extension View

25 阅读2分钟

通过 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())
    }
}
image-20211117175336005转存失败,建议直接上传图片文件

通过 background 设置背景 border 设置边框

这个和我们想要的效果差距也太大了吧!主要原因还是我们组件的颜色和下面组件的颜色融合在一起了,为了能够区分出来,我们回到 PopMenuButton 添加背景颜色和边框。

.background(.white)
.border(Color(uiColor: appColor.c_cccccc), width: 0.5)
image-20211118084117357转存失败,建议直接上传图片文件

使用 clipped 切除多余内容

我们发现了一个致命的问题,我们的数据竟然溢出在我们的控件的外面了,我们把溢出的内容切掉。

.clipped()

使用 shadow 添加阴影

溢出的内容切掉了,有了边框还是很难看,我们给控件添加一个 5 的阴影。

.shadow(radius: 5)

这样看起来,就慢慢的好看多了。

image-20211118084605528转存失败,建议直接上传图片文件

我们添加一个默认的 Padding,让两边有间隙。

.padding()
image-20211118085740791转存失败,建议直接上传图片文件

我们将输入更改为下面数据源,观察一下数据少,但是数据很长,会是什么效果。

[
	"这是一条很长很长很长很长很长很长很长很长很长的URL",
	"https://www.baidu.com"
]
image-20211118091054801

糟糕,我们的组件超出屏幕了,主要原因是我们设置宽度为无限宽度,我们内容超出了屏幕宽度但是有没有超出无限宽度,自然是自身宽度。

通过 fixedSize 设置是否自适应

我们设置一下,让关闭横向的自适应宽度,打开纵向的自适应宽度。并且我们需要将放在 .frame 的后面,不然起不到效果。

.fixedSize(horizontal: false, vertical: true)
image-20211118091945640

我们看一下数据很短的情况下

image-20211118092158902

数据量很多的情况下

image-20211118092318784

看起来感觉还能接受。

image-20211119101720523

通过显形的 VStack 去掉 ForEach 的间隙

我们发现我们通过 ForEach创建的列表是有间隙的,通过谷歌,我们知道在 ScrollViewForEach的中间有一个隐形的 VStack,我们创建一个显形的 VStack 设置 Spacing = 0 即可。

ScrollView() {
    VStack(spacing:0) {
        ForEach(items, id:\.self) {item in
            PopMenuButtonItem(title: item,
                              isSelect: currentItem == item)
                .onTapGesture {
                    currentItem = item
                }
        }
    }
}
image-20211119102238526

默认 Item 的间隙已经去掉,我们高度正好显示了第六个一半,是隐约的人告诉用户,还可以滚动查看更多的数据。

我们去看一下我们 Modify 封装组件的预览,发现底部我们的组件已经露出来了。

image-20211119102754476

我们修改 PopMenuButtonPadding,设置为下面参数。

.padding(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20))

我们只保留左侧和右侧有对应间距,这样我们的组件看起来就完美了。

image-20211119103047055

通过 @State 控制显示和隐藏

为了控制 PopMenuButton 的显示和隐藏,我们在 PopMenuButtonModify 中添加一个 @State 的参数控制。

@State private var isShowPopMenuButton:Bool = false
ZStack(alignment:.top) {
    content
    if isShowPopMenuButton {
        PopMenuButton(items: items,
                      currentItem: $currentItem)
    }
}

我们给 ZStack 添加一个点击事件,当点击就修改 isShowPopMenuButton = truePopMenuButton 显示出来。

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 的封装就告一段落了。