SwiftUI:做一个好看的评分控件

mask在SwiftUI中是用于遮罩控件的,它可以根据我们提供的形状或者图片来裁剪控件的可见区域。比如,我们可以用圆形的mask来让一个图片控件变成圆形,或者用三角形mask一个按钮,只展示按钮的一个角等等

下面先看一个简单的例子,利用mask来显示图片。

SwiftUI 复制代码
Image("0")
            .resizable()
            .scaledToFit()
            .foregroundColor(.green)

我们使用爱心来显示美女图片,使用 mask很容易到达这个需求。

使用mask显示图形

方法定义如下,需要放回一个View

SwiftUI 复制代码
.mask(<#T##mask: () -> View##() -> View#>)

我们只需要在mask的返回体中加入如下代码:

SwiftUI 复制代码
.mask {
  Image(systemName: "heart.fill")
                    .resizable()
                    .scaledToFit()
}

mask指定的形状是什么,最终的图形就是什么。所以最终的图形形状都有mask设定的图形决定


使用 mask 创建渐变评分控件

在很多场景中,都可以见到评分控件。它是由5个五角星组成,初始状态为灰色,当我们点击五角星就会点亮颜色变成黄色。

首先,来创建五角星。

SwiftUI 复制代码
HStack {
            ForEach(1..<6) { index in
                Image(systemName: "star.fill")
                    .font(.largeTitle)
                    .foregroundColor(.orange)
            }
        }

使用上述代码即可创建五个五角星,但是它不具有动态功能。我们来加上点击功能。

首先,我们需要创建一个变量,来保存当前点击的是第几个星星。

SwiftUI 复制代码
@State var rating: Int = 1

其次,我们需要使用不同前景色来区分已点击和未点击的五角星

SwiftUI 复制代码
.foregroundColor(rating >= index ? .orange : .gray)

最后,我们给五角星加入点击事件。并且在点击事件中把当前的index值赋给rating。

SwiftUI 复制代码
.onTapGesture {
    rating = index
 }

下面是效果

但是,如何给控件加渐变色呢?我们就要使用mask来完成这个任务。

首先,把创建五角星的代码提取成为一个变量starView。保持主body简单整洁,这个很重要。

SwiftUI 复制代码
var starView: some View {
        HStack {
            ForEach(1..<6) { index in
                Image(systemName: "star.fill")
                    .font(.largeTitle)
                    .foregroundColor(rating >= index ? .orange : .gray)
                    .onTapGesture {
                        rating = index
                    }
            }
        }
    }

其次,我们给StartView添加一个overlay,将mask添加到overlay上。

less 复制代码
ZStack {
                    starView
                        .overlay(
                            Rectangle()
                                .foregroundColor(.yellow)
                        )
                }

我们可以看到,因为overlay的rectangle不知道宽度是多少,所以它会默认等于主视图的宽度。

我们需要使用GometryReader来读取大小,从而控制overlay的宽度来展示底部五角星视图。因为overlay是在五角星视图上面的。所以我们只有控制好上层视图的宽度,才可以看到下层视图。

我们还是把Overlay的内容提取成一个变量

SWiftUI 复制代码
var overlayView: some View {
            GeometryReader { geo in
                Rectangle()
                    .foregroundColor(.yellow)
                    .frame(width: CGFloat(rating) / 5 * geo.size.width)
            }
            .allowsHitTesting(false)
        }

frame(width: CGFloat(rating) / 5 * geo.size.width) 用于计算overlay的视图宽度,根据点击的index来决定视图显示的宽度是多少

当我们点击五角星时,会发现视图的宽度和我们预料的一样。但是还需要最终的一个mask。

在前面的例子中,我们明白。mask作为一个用于遮罩控件。当你给定mask什么形状,最终的物体就会是给定的mask的样子。所以我们把五角星的形状再设定给mask,就可以显示五角星了。

SwiftUI 复制代码
ZStack {
      starView
        .overlay(overlayView.mask(starView))                
    }
SwiftUI 复制代码
var overlayView: some View {
            GeometryReader { geo in
                Rectangle()
                    .foregroundColor(.yellow)
                    .frame(width: CGFloat(rating) / 5 * geo.size.width)
            }
    }

此时,就可以显示正常的五角星。

但是,此时你会发现。可以把灰色五角星点亮,但是却无法再变成灰色。

那是因为,我们把灰色变成黄色时,触发的是真实的五角星的点击事件。但是当我们把五个五角星全部点亮,此时就显示的是OverlayView的视图,OverlayView的视图阻止了底部的点击事件。所以导致无法响应事件,从而无法改变值,也就无法改变颜色了。那么我们需要把OverlayView的点击事件禁用掉。

使用以下方法禁用视图点击事件

SwiftUI 复制代码
.allowsHitTesting(false)

allowsHitTesting

在 SwiftUI 中,allowsHitTesting属性主要用于控制视图是否可以接收点击(hit test)事件。

  • 如果allowsHitTesting设置为true,则该视图可以接收点击事件,默认为true。
  • 如果设置为false,则该视图将不再响应任何点击事件,点击会透传给下层的视图。

需要注意的是它只影响点击事件,其他触摸事件如长按、拖拽等不受影响。

好了,设置属性后,变得正常了。

加个动画

在点击事件中加入动画,让事件感觉动起来

SwiftUI 复制代码
.onTapGesture {
                            withAnimation(.easeInOut) {
                                rating = index
                            }
                        }

加入渐变

有了以上基础,渐变就很简单了。我们只需要把overlayView 的foregroundColor 去掉。换成fill ,然后在fill中使用一个渐变来填充就OK了

SwiftUI 复制代码
var overlayView: some View {
            GeometryReader { geo in
                Rectangle()
    //                .foregroundColor(.yellow)
                    .fill(
                        LinearGradient(colors: [Color.blue, Color.green], startPoint: .leading, endPoint: .trailing)
                    )
                    .frame(width: CGFloat(rating) / 5 * geo.size.width)
            }
            .allowsHitTesting(false)
        }

我是用来清爽的颜色来做为渐变色,以上是最终效果。

大家有什么看法呢?欢迎留言讨论。 公众号:RobotPBQ

相关推荐
茶底世界之下3 小时前
类型擦除之后,Metal 滤镜组合为什么不能只执行一个 Filter?
ios·swift
传奇开心果编程5 小时前
【SwiftUI提高练中学】第8课 网络层架构:拦截器、重试、缓存与离线优先
学习·macos·ui·ios·swiftui
传奇开心果编程6 小时前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程12 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
tink14 小时前
告别 Xcode IDE:用 VS Code + SweetPad + XcodeGen 开发 iOS 应用的完整指南
ios·swiftui·xcode
传奇开心果编程15 小时前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
黑科技iOS上架16 小时前
iOS隐私合规扫描工具
ios·审核