以下是我们今天要创建的内容。在本教程中,你将能够在你的应用程序中使用这个弹出评论按钮,根据需求对类似服务或质量进行评价。
开始
创建一个新的名为ReviewButton
的SwiftUI文件来生成一个模板,这个视图将把所有东西联系在一起。一旦我们完成了按钮的构建,我们将把它添加到我们的应用程序中。
import SwiftUI
struct ReviewButton: View {
var body: some View {
Text("Hello, World!")
}
}
struct ReviewButton_Previews: PreviewProvider {
static var previews: some View {
ReviewButton()
}
}
视图分解
我们将把这个ReviewButton
分解为两部分。第一个是星Image
和“Rate This”Text
,第二个是弹出的5颗星。
很明显,在这个项目中会有很多Star出现,所以让我们继续创造一个对我们有利的Star按钮。
再创建一个SwiftUI视图命名为
StarIcon
-
为
StarIcon
添加一个属性变量filled
var filled: Bool = false
-
用一个
Image
替换模版中的代码,用于显示一个star。这里的关键是改变使用系统提供的star.fill
或star
图标。我们将使用刚才定义的属性来决定。Image(systemName: filled ? "star.fill" : "star")
-
然后我们设置StarIcon中的Image是否填充颜色
Image(systemName: filled ? "star.fill" : "star") .foregroundColor(filled ? Color.yellow : Color.black.opacity(0.6))
组合一下代码,如下:
import SwiftUI
struct RatingIcon: View {
var filled:Bool = true
var body: some View {
Image(systemName: filled ? "star.fill" : "star")
.foregroundColor(filled ? Color.yellow : Color.black.opacity(0.6))
}
}
struct RatingIcon_Previews: PreviewProvider {
static var previews: some View {
RatingIcon(filled: true)
}
}
使用Star
就像我之前说的,我们要先创建Star星号和Label标签,然后是Popup。
回到ReviewButton
,让我们开始第一部分。
替换body中的代码:
Button(action: {
// Empty for now...
}) {
VStack(alignment: .center, spacing: 8) {
//Star Icon and Label Here...
StarIcon()
Text("Rate This")
.foregroundColor(Color.black)
.font(Font.system(size: 11, weight: .semibold, design: .rounded))
}
}
创建Popup
现在我们要创建一个显示5颗星组的弹出窗口。我们将重用之前创建的StarIcon
。
- 在
RatingButton
视图中,像这样在Button
的顶部添加一个覆盖层。
Button(action: {
// Empty for now...
}) {
VStack(alignment: .center, spacing: 8) {
//Star Icon and Label Here...
StarIcon()
Text("Rate This")
.foregroundColor(Color.black)
.font(Font.system(size: 11, weight: .semibold, design: .rounded))
}
}.overlay( /* Star Icons Here */ )
- 使用
HStack
将五个StarIcons
放在一起。确保对齐方式为center
,并且HStack
使用的spacing
值为4。
.overlay(
HStack(alignment: .center, spacing: 4) {
RatingIcon(filled: false)
RatingIcon(filled: false)
RatingIcon(filled: false)
RatingIcon(filled: false)
RatingIcon(filled: false)
}
)
- 现在让我们给弹出框添加一些样式
padding
background
cornerRadius
shadow
.overlay(
HStack(alignment: .center, spacing: 4) {
RatingIcon(filled: false)
RatingIcon(filled: false)
RatingIcon(filled: false)
RatingIcon(filled: false)
RatingIcon(filled: false)
} // Start styling the popup...
.padding(.all, 12)
.background(Color.white)
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.1), radius: 20, x: 0, y: 0)
)
- 现在将弹出窗口向上偏移,这样它就不会直接位于按钮的顶部。
.overlay(
HStack(alignment: .center, spacing: 4) {
RatingIcon(filled: false)
RatingIcon(filled: false)
RatingIcon(filled: false)
RatingIcon(filled: false)
RatingIcon(filled: false)
}
.padding(.all, 12)
.background(Color.white)
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.1), radius: 20, x: 0, y: 0)
.offset(x: 0, y: -70) // Move the view above the button
)
显示弹出框
接下来,我们需要隐藏弹出窗口,直到点击按钮。为ReviewButton
添加bool
属性,用于控制弹出窗口的状态。
@State var popupOpen:Bool = false
现在回到我们之前声明的Button
。将此代码片段添加到action
参数中。
Button(action: {
withAnimation { self.popupOpen = !self.popupOpen }
})
然后将这些代码添加到overlay
覆盖层内的HStack
中,以调整不透明度opacity
。你应该把它放在我们设置覆盖的偏移量的下面。
.opacity(popupOpen ? 1.0 : 0)
现在让我们尝试一下吧!
添加评论功能
接下来,我们需要根据用户评价开始给星星上色。为了跟踪这一点,添加一个属性来跟踪星级评级。
@State var stars:Int = 0
我们将使用这个属性给按钮和弹出框上的星号改变颜色。让我们修改代码来反映这一点。
- 如果当前星级评分大于
0
,则将按钮内的StarIcon
更改为黄色。
// Inside the Button
VStack(alignment: .center, spacing: 8) {
//Star Icon and Label Here...
StarIcon(filled: stars > 0)
// "Rate This" Label Below
- 也可以修改弹出窗口覆盖层内的
StarIcon
s来改变颜色。
HStack(alignment: .center, spacing: 4) {
RatingIcon(filled: stars > 0)
RatingIcon(filled: stars > 1)
RatingIcon(filled: stars > 2)
RatingIcon(filled: stars > 3)
RatingIcon(filled: stars > 4)
}
既然StarIcon
s将随着评级的变化而改变颜色,我们需要允许用户选择评级。我们会使用DragGesture
来实现这个。
在我们声明popupOpen
和stars
属性的下面,创建一个DragGesture
。将minimumDistance
设置为0
,将coordinateSpace
设置为.local
。
var gesture: some Gesture {
return DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged({ val in
// Update Rating Here
})
.onEnded { val in
// Update Rating Here
}
}
最后,要做的最后一件事是根据用户的点击或拖动的x
位置计算已经选择了多少颗星星。
在上面声明一个闭包,返回DragGesture
,并让它接受x
位置的CGFloat
。然后,我们将快速计算确定用户选择了哪个StarIcon
并更新状态。
let updateRating: (CGFloat,RatingButton)->() = { x,this in
let percent = max((x / 110.0), 0.0)
this.stars = min(Int(percent * 5.0) + 1, 5)
}
然后调用我们之前设置的onChanged
和onEnded
函数。
return DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged({ val in
updateRating(val.location.x,self)
})
.onEnded { val in
updateRating(val.location.x,self)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
withAnimation {
self.popupOpen = false
}
}
}
最后要做的是在弹出框中添加手势!只需在HStack
之后直接添加这段代码。
HStack(alignment: .center, spacing: 4) {
RatingIcon(filled: stars > 0)
RatingIcon(filled: stars > 1)
RatingIcon(filled: stars > 2)
RatingIcon(filled: stars > 3)
RatingIcon(filled: stars > 4)
}
.gesture(gesture)
最终效果
完整代码如下:
struct RatingIcon: View {
var filled: Bool = false
var body: some View {
Image(systemName: filled ? "star.fill" : "star")
.foregroundColor(filled ? Color.yellow : Color.black.opacity(0.6))
}
}
struct ReviewButton: View {
@State var popupOpen:Bool = false
@State var stars:Int = 0
let updateRating: (CGFloat,ReviewButton)->() = { x,this in
let percent = max((x / 110.0), 0.0)
this.stars = min(Int(percent * 5.0) + 1, 5)
}
var gesture: some Gesture {
return DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged({ val in
// Update Rating Here
updateRating(val.location.x,self)
})
.onEnded { val in
// Update Rating Here
updateRating(val.location.x,self)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
withAnimation {
self.popupOpen = false
}
}
}
}
var body: some View {
Button(action: {
withAnimation { self.popupOpen = !self.popupOpen }
}) {
VStack(alignment: .center, spacing: 8) {
//Star Icon and Label Here...
RatingIcon(filled: stars > 0)
.frame(width: 50, height: 50)
Text("Rate This")
.foregroundColor(Color.black)
.font(Font.system(size: 11, weight: .semibold, design: .rounded))
}
}
.overlay(
HStack(alignment: .center, spacing: 4) {
RatingIcon(filled: stars > 0)
RatingIcon(filled: stars > 1)
RatingIcon(filled: stars > 2)
RatingIcon(filled: stars > 3)
RatingIcon(filled: stars > 4)
}
.padding(.all, 12)
.background(Color.white)
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.1), radius: 20, x: 0, y: 0)
.offset(x: 0, y: -70)
.opacity(popupOpen ? 1.0 : 0)
.gesture(gesture)
)
}
}