SwiftUI:制作評(píng)價(jià)按鈕

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

Popup_Review_Button_SwiftUI_Crop.png

開始

創(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顆星。

Decomposition_SwiftUI-1.png

很明顯,在這個(gè)項(xiàng)目中會(huì)有很多Star出現(xiàn),所以讓我們繼續(xù)創(chuàng)造一個(gè)對(duì)我們有利的Star按鈕。

  1. 再創(chuàng)建一個(gè)SwiftUI視圖命名為StarIcon

  2. StarIcon添加一個(gè)屬性變量filled

    var filled: Bool = false
    
  3. 用一個(gè)Image替換模版中的代碼,用于顯示一個(gè)star。這里的關(guān)鍵是改變使用系統(tǒng)提供的star.fillstar圖標(biāo)。我們將使用剛才定義的屬性來決定。

    Image(systemName: filled ? "star.fill" : "star")
    
  4. 然后我們?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)
    }
}
image.png

使用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))
    }
}
ReviewButton_Part1.png

創(chuàng)建Popup

現(xiàn)在我們要?jiǎng)?chuàng)建一個(gè)顯示5顆星組的彈出窗口。我們將重用之前創(chuàng)建的StarIcon。

  1. 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 */ )
  1. 使用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)
    }
)
  1. 現(xiàn)在讓我們給彈出框添加一些樣式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)
)
  1. 現(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
)
ReviewButton_Popup.png

顯示彈出框

接下來,我們需要隱藏彈出窗口,直到點(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)在讓我們嘗試一下吧!

ReviewButton_Popup_SwiftUI.gif

添加評(píng)論功能

接下來,我們需要根據(jù)用戶評(píng)價(jià)開始給星星上色。為了跟蹤這一點(diǎn),添加一個(gè)屬性來跟蹤星級(jí)評(píng)級(jí)。

@State var stars:Int = 0

我們將使用這個(gè)屬性給按鈕和彈出框上的星號(hào)改變顏色。讓我們修改代碼來反映這一點(diǎn)。

  1. 如果當(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
  1. 也可以修改彈出窗口覆蓋層內(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è)。

在我們聲明popupOpenstars屬性的下面,創(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è)置的onChangedonEnded函數(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)

最終效果

ReviewButton_SwiftUI_Final.gif

完整代碼如下:

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容