日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

使用 SwiftUI 創(chuàng)建一個靈活的選擇器

來源: 責編: 時間:2023-11-03 17:07:43 400觀看
導讀前言最近,在我正在開發(fā)一個在 Dribbble 上找到的設計的 SwiftUI 實現(xiàn)時,我想到了一個點子,可以通過一些酷炫的篩選器擴展該項目以縮小結(jié)果列表。我決定篩選視圖將由兩個獨立的篩選選項組成,兩者都有一些可選項可供選擇。

fZ928資訊網(wǎng)——每日最新資訊28at.com

前言

最近,在我正在開發(fā)一個在 Dribbble 上找到的設計的 SwiftUI 實現(xiàn)時,我想到了一個點子,可以通過一些酷炫的篩選器擴展該項目以縮小結(jié)果列表。fZ928資訊網(wǎng)——每日最新資訊28at.com

我決定篩選視圖將由兩個獨立的篩選選項組成,兩者都有一些可選項可供選擇。但然后我遇到了一個問題。在使用 UIKit 時,我總是將這種類型的視圖實現(xiàn)為具有特定 UICollectionViewFlowLayout 的 UICollectionView。但在 SwiftUI 中該如何實現(xiàn)呢?fZ928資訊網(wǎng)——每日最新資訊28at.com

讓我們來看看使用 SwiftUI 創(chuàng)建靈活選擇器的實現(xiàn)!fZ928資訊網(wǎng)——每日最新資訊28at.com

可選擇協(xié)議

選擇器的最重要部分是,我們可以通過該視圖組件選擇一些所需的選項。因此,首先創(chuàng)建了一個 Selectable 協(xié)議。fZ928資訊網(wǎng)——每日最新資訊28at.com

所有符合該協(xié)議的對象必須實現(xiàn)兩個屬性:displayedName(在選擇器中顯示的名稱)和 isSelected(一個布爾值,指示特定選項是否已選擇)。fZ928資訊網(wǎng)——每日最新資訊28at.com

此外,為了能夠通過映射字符串值數(shù)組創(chuàng)建 Selectable 對象,實現(xiàn) Selectable 的對象必須提供帶 displayedName 作為參數(shù)的自定義初始化。fZ928資訊網(wǎng)——每日最新資訊28at.com

Identifiable 和 Hashable 協(xié)議確保我們可以輕松創(chuàng)建具有 ForEach 循環(huán)的 SwiftUI 視圖。此外,符合 Selectable 協(xié)議的所有對象都將實現(xiàn)存儲 UUID 值的常量 id。fZ928資訊網(wǎng)——每日最新資訊28at.com

我會故意省略符合 Selectable 協(xié)議的對象的實現(xiàn),因為我認為這是顯而易見的。核心代碼如下:fZ928資訊網(wǎng)——每日最新資訊28at.com

protocol Selectable: Identifiable, Hashable {    var displayedName: String { get }    var isSelected: Bool { get set }        init(displayedName: String)}

自定義化

我的目標不僅是創(chuàng)建靈活的選擇器的實現(xiàn),還要盡量使其可自定義。fZ928資訊網(wǎng)——每日最新資訊28at.com

因此,將使用符合 Selectable 協(xié)議的泛型類型 T 創(chuàng)建 FlexiblePicker。這樣,以后更容易重用該組件,因為它將是獨立于類型的。fZ928資訊網(wǎng)——每日最新資訊28at.com

在實現(xiàn)選擇器本身之前,我列出了所有可自定義屬性。接下來,創(chuàng)建了用于計算特定字符串值的寬度和高度的字符串擴展。由于我的實現(xiàn)允許更改字體大小和權(quán)重,因此先前提到的兩個擴展都以由靈活選擇器使用的 UIFont 作為參數(shù)。fZ928資訊網(wǎng)——每日最新資訊28at.com

extension String {    func getWidth(with font: UIFont) -> CGFloat {        let fontAttributes = [NSAttributedString.Key.font: font]        let size = self.size(withAttributes: fontAttributes)        return size.width    }        func getHeight(with font: UIFont) -> CGFloat {        let fontAttributes = [NSAttributedString.Key.font: font]        let size = self.size(withAttributes: fontAttributes)        return size.height    }}

由于我的字符串擴展用于計算給定字符串的大小,因此需要將所有 UIFont 權(quán)重轉(zhuǎn)換為 SwiftUI 等效項。fZ928資訊網(wǎng)——每日最新資訊28at.com

這就是為什么我引入了一個 FontWeight 枚舉,其中包含以 UIFont 權(quán)重命名的所有可能情況。fZ928資訊網(wǎng)——每日最新資訊28at.com

此外,該枚舉有兩個屬性,一個返回 UIFont 權(quán)重,另一個返回 SwiftUI Font 權(quán)重。通過這種方式,我們只需向 FlexiblePicker 提供 FontWeight 枚舉的特定情況。fZ928資訊網(wǎng)——每日最新資訊28at.com

enum FontWeight {    case light    // the rest of possible cases        var swiftUIFontWeight: Font.Weight {        switch self {        case .light:            return .light        // switching through the rest of possible cases         }    }        var uiFontWeight: UIFont.Weight {        switch self {        case .light:            return .light        // switching through the rest of possible cases         }    }}

FlexiblePicker 邏輯

之后,我終于準備好開始編寫 FlexiblePicker 的實現(xiàn)了。fZ928資訊網(wǎng)——每日最新資訊28at.com

首先,我需要一個函數(shù)來計算并返回輸入數(shù)據(jù)的所有寬度。我通過將所有輸入值映射到元組中,其中包含輸入值和自身的寬度來完成。fZ928資訊網(wǎng)——每日最新資訊28at.com

在映射中,我使用 reduce 函數(shù)來總結(jié)與給定輸入值相關(guān)聯(lián)的所有寬度(文本寬度、邊框?qū)挾取⑽谋咎畛浜烷g距)。fZ928資訊網(wǎng)——每日最新資訊28at.com

private func calculateWidths(for data: [T]) -> [(value: T, width: CGFloat)] {    return data.map { selectableType -> (T, CGFloat) in        let font = UIFont.systemFont(ofSize: fontSize, weight: fontWeight.uiFontWeight)        let textWidth = selectableType.displayedName.getWidth(with: font)        let width = [textPadding, textPadding, borderWidth, borderWidth, spacing]            .reduce(textWidth, +)        return (selectableType, width)    }}

現(xiàn)在,計算寬度的函數(shù)準備好了,我們可以遍歷所有輸入數(shù)據(jù)并將它們分成單獨的數(shù)組。每個數(shù)組包含能夠適應同一 HStack 中的項目的項目。邏輯很簡單。我們有兩個數(shù)組:fZ928資訊網(wǎng)——每日最新資訊28at.com

  • singleLineResult 數(shù)組——負責存儲適合特定行的項目。
  • allLinesResult 數(shù)組——負責存儲所有項目數(shù)組(每個數(shù)組都等同于一行項目)。

首先,我們檢查從 HStack 行寬中減去項寬的結(jié)果是否大于0。fZ928資訊網(wǎng)——每日最新資訊28at.com

如果滿足條件,我們將當前項附加到 singleLineResult 中,更新可用的 HStack 行寬,并繼續(xù)到下一個元素。fZ928資訊網(wǎng)——每日最新資訊28at.com

如果結(jié)果小于 0,這意味著我們無法將下一個元素放入給定行中,因此我們將 singleLineResult 附加到 allLinesResult 中,將 singleLineResult 設置為僅由當前元素組成的數(shù)組(不能適應上一行的元素),并通過減去當前項的寬度來更新 HStack 的行寬。fZ928資訊網(wǎng)——每日最新資訊28at.com

在遍歷所有元素之后,我們必須處理特定的邊緣情況。singleLineResult 可能不會為空,也不會附加到 allLinesResult 中——因為我們只在減去項目寬度的結(jié)果小于 0 時附加 singleLineResult。在這種情況下,我們必須檢查 singleLineResult 是否為空。如果為真,我們返回 allLinesResult,如果不為真,我們必須首先附加 singleLineResult,然后返回 allLinesResult。fZ928資訊網(wǎng)——每日最新資訊28at.com

private func divideDataIntoLines(lineWidth: CGFloat) -> [[T]] {    let data = calculateWidths(for: inputData)    var singleLineWidth = lineWidth    var allLinesResult = [[T]]()    var singleLineResult = [T]()    var partialWidthResult: CGFloat = 0    data.forEach { (selectableType, width) in        partialWidthResult = singleLineWidth - width        if partialWidthResult > 0 {            singleLineResult.append(selectableType)            singleLineWidth -= width        } else {            allLinesResult.append(singleLineResult)            singleLineResult = [selectableType]            singleLineWidth = lineWidth - width        }    }    guard !singleLineResult.isEmpty else { return allLinesResult }    allLinesResult.append(singleLineResult)    return allLinesResult}

最后但并非最不重要的是,我們必須計算 VStack 的高度,以使 SwiftUI 更容易解釋我們的視圖組件。VStack 的高度是根據(jù)兩個值計算的:fZ928資訊網(wǎng)——每日最新資訊28at.com

  • 輸入數(shù)據(jù)中任何項目的高度(類似于寬度的計算,通過使用 reduce 函數(shù),總結(jié)與項目相關(guān)的所有高度)。
  • 將顯示在 VStack 中的行數(shù)。
private func calculateVStackHeight(width: CGFloat) -> CGFloat {    let data = divideDataIntoLines(lineWidth: width)    let font = UIFont.systemFont(ofSize: fontSize, weight: fontWeight.uiFontWeight)    guard let textHeight = data.first?.first?.displayedName            .getHeight(with: font) else { return 16 }    let result = [textPadding, textPadding, borderWidth, borderWidth, spacing]        .reduce(textHeight, +)    return result * CGFloat(data.count)}

將這兩個數(shù)字相乘的結(jié)果將是我們的 VStack 的高度。fZ928資訊網(wǎng)——每日最新資訊28at.com

FlexiblePicker 視圖

最后,當所有邏輯準備好后,我們需要實現(xiàn)一個視圖主體。如我之前所提到的,視圖將使用嵌套的 ForEach 循環(huán)創(chuàng)建。fZ928資訊網(wǎng)——每日最新資訊28at.com

需要記住的是,F(xiàn)orEach 循環(huán)要求迭代的集合中的每個元素必須符合 Identifiable 協(xié)議,或者應該具有唯一的標識符。fZ928資訊網(wǎng)——每日最新資訊28at.com

這就是為什么我將分隔行的結(jié)果映射到元組中,其中包含每行和 UUID 值。fZ928資訊網(wǎng)——每日最新資訊28at.com

由于如此,我可以向 ForEach 循環(huán)提供 id 參數(shù)。另一點需要記住的是,F(xiàn)orEach 循環(huán)期望獲得一些 View 作為返回值。fZ928資訊網(wǎng)——每日最新資訊28at.com

如果我們只插入另一個 ForEach 循環(huán),我們將在視圖的適當功能性方面遇到問題,因為 ForEach 不是一種 View。fZ928資訊網(wǎng)——每日最新資訊28at.com

這就是為什么我首先將整個 ForEach 循環(huán)包裝在 HStack 中,然后再包裝在 Group 中,以確保編譯器可以正確解釋一切。fZ928資訊網(wǎng)——每日最新資訊28at.com

var body: some View {    GeometryReader { geo in        VStack(alignment: alignment, spacing: spacing) {            ForEach(              divideDataIntoLines(lineWidth: geo.size.width)                  .map { (data: $0, id: UUID()) },               id: /.id            ) { dataArray in                Group {                    HStack(spacing: spacing) {                        ForEach(dataArray.data, id: /.id) { data in                            Button(action: { updateSelectedData(with: data)                            }) {                                Text(data.displayedName)                                    .lineLimit(1)                                    .foregroundColor(textColor)                                    .font(.system(                                        size: fontSize,                                         weight: fontWeight.swiftUIFontWeight                                    ))                                    .padding(textPadding)                            }                            .background(                                data.isSelected                                ? selectedColor.opacity(0.5)                                : notSelectedColor.opacity(0.5)                            )                            .cornerRadius(10)                            .disabled(!isSelectable)                            .overlay(RoundedRectangle(cornerRadius: 10)                                        .stroke(borderColor, lineWidth: borderWidth))                        }                    }                }            }        }        .frame(width: geo.size.width, height: calculateVStackHeight(width: geo.size.width))    }  }}

幾乎所有都已經(jīng)完成,我們只需添加一個函數(shù)來處理與按鈕的用戶交互。該函數(shù)只需切換特定數(shù)據(jù)的 isSelected 屬性。fZ928資訊網(wǎng)——每日最新資訊28at.com

private func updateSelectedData(with data: T) {    guard let index = inputData.indices      .first(where: { inputData[$0] == data }) else { return }    inputData[index].isSelected.toggle()}

其余的代碼很簡單,主要是配置所有屬性,如字體、顏色或邊框。此外,在 VStack 的底部,我們設置一個 frame,其中寬度取自 GeometryReader,高度則由先前創(chuàng)建的函數(shù)計算。fZ928資訊網(wǎng)——每日最新資訊28at.com

fZ928資訊網(wǎng)——每日最新資訊28at.com

現(xiàn)在 FlexiblePicker 已經(jīng)完成,可以使用了!fZ928資訊網(wǎng)——每日最新資訊28at.com

總結(jié)

這篇文章介紹了如何使用 SwiftUI 構(gòu)建一個靈活的選擇器(FlexiblePicker),用于選擇多個選項。fZ928資訊網(wǎng)——每日最新資訊28at.com

首先創(chuàng)建了一個 Selectable 協(xié)議,使得選擇的選項對象需要實現(xiàn) displayedName 和 isSelected 屬性。fZ928資訊網(wǎng)——每日最新資訊28at.com

然后,詳細介紹了實現(xiàn)該選擇器的邏輯,包括如何處理選項的布局、寬度和高度,以及如何處理用戶與按鈕的交互。fZ928資訊網(wǎng)——每日最新資訊28at.com

最后,提供了一個簡單的視圖實現(xiàn),可以在 SwiftUI 中使用該選擇器。這個選擇器可用于創(chuàng)建各種交互式選擇界面。fZ928資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-16865-0.html使用 SwiftUI 創(chuàng)建一個靈活的選擇器

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 一文搞懂為什么選擇 Java 虛擬線程?

下一篇: Swift 中 User Defaults 的讀取和寫入

標簽:
  • 熱門焦點
  • MIX Fold3包裝盒泄露 新機本月登場

    小米的全新折疊屏旗艦MIX Fold3將于本月發(fā)布,近日該機的真機包裝盒在網(wǎng)上泄露。從圖上來看,新的MIX Fold3包裝盒在外觀設計方面延續(xù)了之前的方案,變化不大,這也是目前小米旗艦
  • 掘力計劃第 20 期:Flutter 混合開發(fā)的混亂之治

    在掘力計劃系列活動第20場,《Flutter 開發(fā)實戰(zhàn)詳解》作者,掘金優(yōu)秀作者,Github GSY 系列目負責人戀貓的小郭分享了Flutter 混合開發(fā)的混亂之治。Flutter 基于自研的 Skia 引擎
  • 一年經(jīng)驗在二線城市面試后端的經(jīng)驗分享

    忠告這篇文章只適合2年內(nèi)工作經(jīng)驗、甚至沒有工作經(jīng)驗的朋友閱讀。如果你是2年以上工作經(jīng)驗,請果斷劃走,對你沒啥幫助~主人公這篇文章內(nèi)容來自 「升職加薪」星球星友 的投稿,坐
  • 三言兩語說透設計模式的藝術(shù)-單例模式

    寫在前面單例模式是一種常用的軟件設計模式,它所創(chuàng)建的對象只有一個實例,且該實例易于被外界訪問。單例對象由于只有一個實例,所以它可以方便地被系統(tǒng)中的其他對象共享,從而減少
  • 微信語音大揭秘:為什么禁止轉(zhuǎn)發(fā)?

    大家好,我是你們的小米。今天,我要和大家聊一個有趣的話題:為什么微信語音不可以轉(zhuǎn)發(fā)?這是一個我們經(jīng)常在日常使用中遇到的問題,也是一個讓很多人好奇的問題。讓我們一起來揭開這
  • 一文搞定Java NIO,以及各種奇葩流

    大家好,我是哪吒。很多朋友問我,如何才能學好IO流,對各種流的概念,云里霧里的,不求甚解。用到的時候,現(xiàn)百度,功能雖然實現(xiàn)了,但是為什么用這個?不知道。更別說效率問題了~下次再遇到,
  • 自研Exynos回歸!三星Galaxy S24系列將提供Exynos和驍龍雙版本

    年初,全新的三星Galaxy S23系列發(fā)布,包含Galaxy S23、Galaxy S23+和Galaxy S23 Ultra三個版本,全系搭載超頻版驍龍8 Gen 2,雖同樣采用臺積電4nm工藝制
  • onebot M24巧系列一體機采用輕薄機身設計,現(xiàn)已在各平臺開售

    onebot M24 巧系列一體機目前已在線上線下各平臺同步開售。onebot M24 巧系列采用一體化輕薄機身設計,最薄處為 10.15mm,擁有寶石紅、午夜藍、石墨綠、雅致
  • SN570 NVMe SSD固態(tài)硬盤 價格與性能兼具

    SN570 NVMe SSD固態(tài)硬盤是西部數(shù)據(jù)發(fā)布的最新一代WD Blue系列的固態(tài)硬盤,不僅閃存技術(shù)更為精進,性能也得到了進一步的躍升。WD Blue SN570 NVMe SSD的包裝外
Top 主站蜘蛛池模板: 株洲县| 中西区| 洪泽县| 新密市| 嘉兴市| 阿克陶县| 酒泉市| 龙州县| 龙井市| 桦南县| 万源市| 宁化县| 酒泉市| 昔阳县| 东至县| 灵台县| 庄河市| 天长市| 达日县| 关岭| 高安市| 桦南县| 万荣县| 敦煌市| 哈密市| 中江县| 西乡县| 平果县| 仙桃市| 南平市| 遵化市| 房产| 视频| 汶上县| 咸阳市| 井冈山市| 阿合奇县| 宣城市| 栖霞市| 改则县| 尚志市|