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

相关推荐
sweet丶4 小时前
iOS内存映射技术:mmap如何用有限内存操控无限数据
ios·操作系统·app
漫天星梦9 小时前
iOS 手机无法播放视频问题排查与解决方案记录
前端·ios
崽崽长肉肉13 小时前
Swift中Package Manager的使用
swift
如此风景14 小时前
IOS UIKit 相关知识
ios
QuantumLeap丶16 小时前
《Flutter全栈开发实战指南:从零到高级》- 22 -插件开发与原生交互
android·flutter·ios
2501_9159214316 小时前
混合开发应用安全方案,在多技术栈融合下构建可持续、可回滚的保护体系
android·安全·ios·小程序·uni-app·iphone·webview
Sheffi6616 小时前
RunLoop Mode 深度剖析:为什么滚动时 Timer 会“失效“?
ios·objective-c
QuantumLeap丶17 小时前
《Flutter全栈开发实战指南:从零到高级》- 21 -响应式设计与适配
android·javascript·flutter·ios·前端框架
2501_9151063217 小时前
Charles抓包怎么用 Charles抓包工具详细教程、网络调试方法、HTTPS配置与手机抓包实战
网络·ios·智能手机·小程序·https·uni-app·webview
00后程序员张18 小时前
Fastlane 结合 开心上架,构建跨平台可发布的 iOS 自动化流水线实践
android·运维·ios·小程序·uni-app·自动化·iphone