以下是我們今天要?jiǎng)?chuàng)建的內(nèi)容。在本教程中,你將能夠在你的應(yīng)用程序中使用這個(gè)彈出評(píng)論按鈕,根據(jù)需求對(duì)類似服務(wù)或質(zhì)量進(jìn)行評(píng)價(jià)。

開始
創(chuàng)建一個(gè)新的名為ReviewButton的SwiftUI文件來生成一個(gè)模板,這個(gè)視圖將把所有東西聯(lián)系在一起。一旦我們完成了按鈕的構(gòu)建,我們將把它添加到我們的應(yīng)用程序中。
import SwiftUI
struct ReviewButton: View {
var body: some View {
Text("Hello, World!")
}
}
struct ReviewButton_Previews: PreviewProvider {
static var previews: some View {
ReviewButton()
}
}
視圖分解
我們將把這個(gè)ReviewButton分解為兩部分。第一個(gè)是星Image和“Rate This”Text,第二個(gè)是彈出的5顆星。

很明顯,在這個(gè)項(xiàng)目中會(huì)有很多Star出現(xiàn),所以讓我們繼續(xù)創(chuàng)造一個(gè)對(duì)我們有利的Star按鈕。
再創(chuàng)建一個(gè)SwiftUI視圖命名為
StarIcon-
為
StarIcon添加一個(gè)屬性變量filledvar filled: Bool = false -
用一個(gè)
Image替換模版中的代碼,用于顯示一個(gè)star。這里的關(guān)鍵是改變使用系統(tǒng)提供的star.fill或star圖標(biāo)。我們將使用剛才定義的屬性來決定。Image(systemName: filled ? "star.fill" : "star") -
然后我們?cè)O(shè)置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
就像我之前說的,我們要先創(chuàng)建Star星號(hào)和Label標(biāo)簽,然后是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))
}
}

創(chuàng)建Popup
現(xiàn)在我們要?jiǎng)?chuàng)建一個(gè)顯示5顆星組的彈出窗口。我們將重用之前創(chuàng)建的StarIcon。
- 在
RatingButton視圖中,像這樣在Button的頂部添加一個(gè)覆蓋層。
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將五個(gè)StarIcons放在一起。確保對(duì)齊方式為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)
}
)
- 現(xiàn)在讓我們給彈出框添加一些樣式
paddingbackgroundcornerRadiusshadow
.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)
)
- 現(xiàn)在將彈出窗口向上偏移,這樣它就不會(huì)直接位于按鈕的頂部。
.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
)

顯示彈出框
接下來,我們需要隱藏彈出窗口,直到點(diǎn)擊按鈕。為ReviewButton添加bool屬性,用于控制彈出窗口的狀態(tài)。
@State var popupOpen:Bool = false
現(xiàn)在回到我們之前聲明的Button。將此代碼片段添加到action參數(shù)中。
Button(action: {
withAnimation { self.popupOpen = !self.popupOpen }
})
然后將這些代碼添加到overlay覆蓋層內(nèi)的HStack中,以調(diào)整不透明度opacity。你應(yīng)該把它放在我們?cè)O(shè)置覆蓋的偏移量的下面。
.opacity(popupOpen ? 1.0 : 0)
現(xiàn)在讓我們嘗試一下吧!

添加評(píng)論功能
接下來,我們需要根據(jù)用戶評(píng)價(jià)開始給星星上色。為了跟蹤這一點(diǎn),添加一個(gè)屬性來跟蹤星級(jí)評(píng)級(jí)。
@State var stars:Int = 0
我們將使用這個(gè)屬性給按鈕和彈出框上的星號(hào)改變顏色。讓我們修改代碼來反映這一點(diǎn)。
- 如果當(dāng)前星級(jí)評(píng)分大于
0,則將按鈕內(nèi)的StarIcon更改為黃色。
// Inside the Button
VStack(alignment: .center, spacing: 8) {
//Star Icon and Label Here...
StarIcon(filled: stars > 0)
// "Rate This" Label Below
- 也可以修改彈出窗口覆蓋層內(nèi)的
StarIcons來改變顏色。
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)
}
既然StarIcons將隨著評(píng)級(jí)的變化而改變顏色,我們需要允許用戶選擇評(píng)級(jí)。我們會(huì)使用DragGesture來實(shí)現(xiàn)這個(gè)。
在我們聲明popupOpen和stars屬性的下面,創(chuàng)建一個(gè)DragGesture。將minimumDistance設(shè)置為0,將coordinateSpace設(shè)置為.local。
var gesture: some Gesture {
return DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged({ val in
// Update Rating Here
})
.onEnded { val in
// Update Rating Here
}
}
最后,要做的最后一件事是根據(jù)用戶的點(diǎn)擊或拖動(dòng)的x位置計(jì)算已經(jīng)選擇了多少顆星星。
在上面聲明一個(gè)閉包,返回DragGesture,并讓它接受x位置的CGFloat。然后,我們將快速計(jì)算確定用戶選擇了哪個(gè)StarIcon并更新狀態(tài)。
let updateRating: (CGFloat,RatingButton)->() = { x,this in
let percent = max((x / 110.0), 0.0)
this.stars = min(Int(percent * 5.0) + 1, 5)
}
然后調(diào)用我們之前設(shè)置的onChanged和onEnded函數(shù)。
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
}
}
}
最后要做的是在彈出框中添加手勢(shì)!只需在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)
)
}
}