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
                            }
                        }

加入渐变

有了以上基础,渐变就很简单了。我们只需要把overlayViewforegroundColor 去掉。换成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

相关推荐
游戏开发爱好者813 分钟前
日常开发与测试的 App 测试方法、查看设备状态、实时日志、应用数据
android·ios·小程序·https·uni-app·iphone·webview
黑码哥34 分钟前
ViewHolder设计模式深度剖析:iOS开发者掌握Android列表性能优化的实战指南
android·ios·性能优化·跨平台开发·viewholder
2501_915106322 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview
2501_915106322 小时前
使用 Sniffmaster TCP 抓包和 Wireshark 网络分析
网络协议·tcp/ip·ios·小程序·uni-app·wireshark·iphone
熊猫钓鱼>_>3 小时前
移动端开发技术选型报告:三足鼎立时代的开发者指南(2026年2月)
android·人工智能·ios·app·鸿蒙·cpu·移动端
徐同保1 天前
通过ip访问nginx的服务时,被第一个server重定向了,通过设置default_server解决这个问题
ios·iphone
2501_915918411 天前
在 iOS 环境下查看 App 详细信息与文件目录
android·ios·小程序·https·uni-app·iphone·webview
2501_916007471 天前
没有 Mac 用户如何上架 App Store,IPA生成、证书与描述文件管理、跨平台上传
android·macos·ios·小程序·uni-app·iphone·webview
夏幻灵2 天前
HTTPS全面解析:原理、加密机制与证书体
ios·iphone
TheNextByte12 天前
如何在iPhone上恢复已删除的笔记的综合指南
笔记·ios·iphone