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

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

使用 SwiftUI 創建一個靈活的選擇器

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

Fxg28資訊網——每日最新資訊28at.com

前言

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

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

讓我們來看看使用 SwiftUI 創建靈活選擇器的實現!Fxg28資訊網——每日最新資訊28at.com

可選擇協議

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

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

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

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

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

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

自定義化

我的目標不僅是創建靈活的選擇器的實現,還要盡量使其可自定義。Fxg28資訊網——每日最新資訊28at.com

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

在實現選擇器本身之前,我列出了所有可自定義屬性。接下來,創建了用于計算特定字符串值的寬度和高度的字符串擴展。由于我的實現允許更改字體大小和權重,因此先前提到的兩個擴展都以由靈活選擇器使用的 UIFont 作為參數。Fxg28資訊網——每日最新資訊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 權重轉換為 SwiftUI 等效項。Fxg28資訊網——每日最新資訊28at.com

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

此外,該枚舉有兩個屬性,一個返回 UIFont 權重,另一個返回 SwiftUI Font 權重。通過這種方式,我們只需向 FlexiblePicker 提供 FontWeight 枚舉的特定情況。Fxg28資訊網——每日最新資訊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 的實現了。Fxg28資訊網——每日最新資訊28at.com

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

在映射中,我使用 reduce 函數來總結與給定輸入值相關聯的所有寬度(文本寬度、邊框寬度、文本填充和間距)。Fxg28資訊網——每日最新資訊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)    }}

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

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

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

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

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

在遍歷所有元素之后,我們必須處理特定的邊緣情況。singleLineResult 可能不會為空,也不會附加到 allLinesResult 中——因為我們只在減去項目寬度的結果小于 0 時附加 singleLineResult。在這種情況下,我們必須檢查 singleLineResult 是否為空。如果為真,我們返回 allLinesResult,如果不為真,我們必須首先附加 singleLineResult,然后返回 allLinesResult。Fxg28資訊網——每日最新資訊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 的高度是根據兩個值計算的:Fxg28資訊網——每日最新資訊28at.com

  • 輸入數據中任何項目的高度(類似于寬度的計算,通過使用 reduce 函數,總結與項目相關的所有高度)。
  • 將顯示在 VStack 中的行數。
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)}

將這兩個數字相乘的結果將是我們的 VStack 的高度。Fxg28資訊網——每日最新資訊28at.com

FlexiblePicker 視圖

最后,當所有邏輯準備好后,我們需要實現一個視圖主體。如我之前所提到的,視圖將使用嵌套的 ForEach 循環創建。Fxg28資訊網——每日最新資訊28at.com

需要記住的是,ForEach 循環要求迭代的集合中的每個元素必須符合 Identifiable 協議,或者應該具有唯一的標識符。Fxg28資訊網——每日最新資訊28at.com

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

由于如此,我可以向 ForEach 循環提供 id 參數。另一點需要記住的是,ForEach 循環期望獲得一些 View 作為返回值。Fxg28資訊網——每日最新資訊28at.com

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

這就是為什么我首先將整個 ForEach 循環包裝在 HStack 中,然后再包裝在 Group 中,以確保編譯器可以正確解釋一切。Fxg28資訊網——每日最新資訊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))    }  }}

幾乎所有都已經完成,我們只需添加一個函數來處理與按鈕的用戶交互。該函數只需切換特定數據的 isSelected 屬性。Fxg28資訊網——每日最新資訊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,高度則由先前創建的函數計算。Fxg28資訊網——每日最新資訊28at.com

Fxg28資訊網——每日最新資訊28at.com

現在 FlexiblePicker 已經完成,可以使用了!Fxg28資訊網——每日最新資訊28at.com

總結

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

首先創建了一個 Selectable 協議,使得選擇的選項對象需要實現 displayedName 和 isSelected 屬性。Fxg28資訊網——每日最新資訊28at.com

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

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

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

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

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

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

標簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 新宁县| 扶余县| 安国市| 汉寿县| 叙永县| 家居| 长治市| 阳曲县| 石柱| 钟山县| 肇州县| 民乐县| 九龙坡区| 梁山县| 塘沽区| 韶关市| 宁蒗| 垦利县| 犍为县| 汉沽区| 清水县| 高陵县| 襄汾县| 阿拉尔市| 兰溪市| 拉萨市| 灵山县| 龙门县| 方山县| 文水县| 龙州县| 乾安县| 什邡市| 赣榆县| 敖汉旗| 蓬溪县| 石河子市| 顺昌县| 化隆| 浙江省| 教育|