精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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

開發 前端
這篇文章介紹了如何使用 SwiftUI 構建一個靈活的選擇器(FlexiblePicker),用于選擇多個選項。首先創建了一個 ??Selectable?? 協議,使得選擇的選項對象需要實現 ??DisplayedName?? 和IsSelected?? 屬性。

前言

最近,在我正在開發一個在 Dribbble 上找到的設計的 SwiftUI 實現時,我想到了一個點子,可以通過一些酷炫的篩選器擴展該項目以縮小結果列表。

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

讓我們來看看使用 SwiftUI 創建靈活選擇器的實現!

可選擇協議

選擇器的最重要部分是,我們可以通過該視圖組件選擇一些所需的選項。因此,首先創建了一個 Selectable 協議。

所有符合該協議的對象必須實現兩個屬性:displayedName(在選擇器中顯示的名稱)和 isSelected(一個布爾值,指示特定選項是否已選擇)。

此外,為了能夠通過映射字符串值數組創建 Selectable 對象,實現 Selectable 的對象必須提供帶 displayedName 作為參數的自定義初始化。

Identifiable 和 Hashable 協議確保我們可以輕松創建具有 ForEach 循環的 SwiftUI 視圖。此外,符合 Selectable 協議的所有對象都將實現存儲 UUID 值的常量 id。

我會故意省略符合 Selectable 協議的對象的實現,因為我認為這是顯而易見的。核心代碼如下:

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

自定義化

我的目標不僅是創建靈活的選擇器的實現,還要盡量使其可自定義。

因此,將使用符合 Selectable 協議的泛型類型 T 創建 FlexiblePicker。這樣,以后更容易重用該組件,因為它將是獨立于類型的。

在實現選擇器本身之前,我列出了所有可自定義屬性。接下來,創建了用于計算特定字符串值的寬度和高度的字符串擴展。由于我的實現允許更改字體大小和權重,因此先前提到的兩個擴展都以由靈活選擇器使用的 UIFont 作為參數。

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 等效項。

這就是為什么我引入了一個 FontWeight 枚舉,其中包含以 UIFont 權重命名的所有可能情況。

此外,該枚舉有兩個屬性,一個返回 UIFont 權重,另一個返回 SwiftUI Font 權重。通過這種方式,我們只需向 FlexiblePicker 提供 FontWeight 枚舉的特定情況。

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 的實現了。

首先,我需要一個函數來計算并返回輸入數據的所有寬度。我通過將所有輸入值映射到元組中,其中包含輸入值和自身的寬度來完成。

在映射中,我使用 reduce 函數來總結與給定輸入值相關聯的所有寬度(文本寬度、邊框寬度、文本填充和間距)。

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 中的項目的項目。邏輯很簡單。我們有兩個數組:

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

首先,我們檢查從 HStack 行寬中減去項寬的結果是否大于0。

如果滿足條件,我們將當前項附加到 singleLineResult 中,更新可用的 HStack 行寬,并繼續到下一個元素。

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

在遍歷所有元素之后,我們必須處理特定的邊緣情況。singleLineResult 可能不會為空,也不會附加到 allLinesResult 中——因為我們只在減去項目寬度的結果小于 0 時附加 singleLineResult。在這種情況下,我們必須檢查 singleLineResult 是否為空。如果為真,我們返回 allLinesResult,如果不為真,我們必須首先附加 singleLineResult,然后返回 allLinesResult。

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 的高度是根據兩個值計算的:

  • 輸入數據中任何項目的高度(類似于寬度的計算,通過使用 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 的高度。

FlexiblePicker 視圖

最后,當所有邏輯準備好后,我們需要實現一個視圖主體。如我之前所提到的,視圖將使用嵌套的 ForEach 循環創建。

需要記住的是,ForEach 循環要求迭代的集合中的每個元素必須符合 Identifiable 協議,或者應該具有唯一的標識符。

這就是為什么我將分隔行的結果映射到元組中,其中包含每行和 UUID 值。

由于如此,我可以向 ForEach 循環提供 id 參數。另一點需要記住的是,ForEach 循環期望獲得一些 View 作為返回值。

如果我們只插入另一個 ForEach 循環,我們將在視圖的適當功能性方面遇到問題,因為 ForEach 不是一種 View。

這就是為什么我首先將整個 ForEach 循環包裝在 HStack 中,然后再包裝在 Group 中,以確保編譯器可以正確解釋一切。

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 屬性。

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,高度則由先前創建的函數計算。

現在 FlexiblePicker 已經完成,可以使用了!

總結

這篇文章介紹了如何使用 SwiftUI 構建一個靈活的選擇器(FlexiblePicker),用于選擇多個選項。

首先創建了一個 Selectable 協議,使得選擇的選項對象需要實現 displayedName 和 isSelected 屬性。

然后,詳細介紹了實現該選擇器的邏輯,包括如何處理選項的布局、寬度和高度,以及如何處理用戶與按鈕的交互。

最后,提供了一個簡單的視圖實現,可以在 SwiftUI 中使用該選擇器。這個選擇器可用于創建各種交互式選擇界面。

責任編輯:姜華 來源: Swift社區
相關推薦

2023-03-15 09:00:43

SwiftUISlider

2011-08-08 13:15:35

QWrap

2017-02-09 18:01:22

Android圖片選擇器開發

2010-09-03 09:30:29

CSS選擇器

2022-11-07 08:42:50

iOS 16SwiftUI

2012-04-16 14:32:31

iOS選擇器代碼

2012-04-19 17:42:46

Titanium布局

2011-10-24 10:30:20

CSS

2022-01-17 09:22:42

SwiftUI App Store開源

2024-08-06 09:26:15

Zustand選擇器Action

2013-03-11 10:30:56

CSSWeb

2009-07-16 11:02:33

Swing文件選擇器

2020-12-08 06:23:05

LockSupport線程工具

2025-04-29 10:28:25

2023-12-01 08:31:20

HTML解析庫

2023-08-01 07:25:38

Expresso框架API

2021-08-16 12:13:02

SwiftUIList ArticleList

2023-09-06 18:37:45

CSS選擇器符號

2011-11-28 13:42:55

Sencha Touc組件選擇器

2012-12-27 14:08:39

Android開發顏色選擇器
點贊
收藏

51CTO技術棧公眾號

久久久123| 中文字幕av久久爽| 日韩高清影视在线观看| 在线一区二区观看| 国产一级黄色录像片| 亚洲黄色在线播放| 视频一区二区三区入口| 欧美尺度大的性做爰视频| 少妇精品一区二区三区| 97精品人妻一区二区三区香蕉| 香蕉av一区二区| 日韩电影中文字幕| 久久久久xxxx| av大片在线看| 懂色av一区二区夜夜嗨| 国产suv精品一区二区三区88区| 黄色香蕉视频在线观看| 久久av影视| 日韩女优av电影| 日日噜噜夜夜狠狠| 日韩伦理在线一区| 亚洲综合男人的天堂| 亚洲高清视频一区二区| 日韩毛片一区二区三区| 五月久久久综合一区二区小说| 亚洲国产精品悠悠久久琪琪| √天堂资源在线| 在线日本欧美| 婷婷久久综合九色综合伊人色| 黄色a级在线观看| 国产精品99999| 99麻豆久久久国产精品免费| 久久综合电影一区| 风间由美一二三区av片| 98视频精品全部国产| 欧美日韩精品一二三区| 一区二区三区国| 免费在线黄色电影| jlzzjlzz亚洲日本少妇| 999国内精品视频在线| 国产精品高潮呻吟av| 日本免费新一区视频 | 999国产精品视频免费| 久久欧美肥婆一二区| 91av在线网站| 国产无遮挡免费视频| 老司机精品视频在线播放| 91精品国产综合久久福利| 手机看片日韩国产| 免费黄色网页在线观看| 日本一区二区动态图| 欧美亚洲免费高清在线观看| 色综合久久网女同蕾丝边| caoporn国产一区二区| 国产欧美一区二区三区另类精品 | 天天操天天摸天天干| 亚洲大片在线| 91av福利视频| 亚洲不卡在线视频| 久久久xxx| 国产精品高潮呻吟久久av黑人| 精品日韩在线视频| 精品国内自产拍在线观看视频 | 天堂av网手机版| av在线成人| 日韩一区国产二区欧美三区| 日本少妇xxx| 国产欧美自拍一区| 亚洲九九九在线观看| 91精品人妻一区二区三区蜜桃欧美| 亚洲免费成人av在线| 国产亚洲视频在线观看| 制服丨自拍丨欧美丨动漫丨| 亚洲乱码一区| 精品国产不卡一区二区三区| 天天干天天爽天天射| 日韩一级特黄| 欧美mv和日韩mv的网站| 中文字幕日韩三级片| 经典一区二区| 久久夜色撩人精品| 日本一级黄色录像| 日精品一区二区| 91嫩草在线视频| 日本毛片在线观看| 极品美女销魂一区二区三区| 亚洲最大的av网站| 天天操天天插天天射| 国产清纯在线一区二区www| 中文字幕精品—区二区日日骚| 午夜小视频福利在线观看| 中文字幕一区二区在线播放| 日韩一级片一区二区| 高潮在线视频| 欧美日韩国产在线播放网站| 在线播放av网址| 第一会所亚洲原创| 欧美精品电影在线| 又骚又黄的视频| 成人18视频日本| 亚洲国产欧洲综合997久久| 久草在线新免费首页资源站| 在线精品亚洲一区二区不卡| 中文字幕1区2区| 精品无人区麻豆乱码久久久| 美女国内精品自产拍在线播放| 二区视频在线观看| 国产精品一区二区x88av| 欧洲国产精品| 538在线观看| 在线播放日韩导航| 老司机久久精品| 日韩精选在线| 欧美激情喷水视频| 国产一区二区视频免费观看 | 欧美日韩国产不卡| 欲求不满的岳中文字幕| 综合激情婷婷| 国产美女搞久久| 欧美新色视频| 午夜久久久久久电影| 久久黄色一级视频| 四季av在线一区二区三区| 热99精品里视频精品| 亚洲精品久久久久久动漫器材一区| 亚洲国产经典视频| 国产真实乱子伦| 国产精品男女| 欧美巨乳美女视频| 97超碰人人草| 国产精品毛片高清在线完整版| 麻豆传传媒久久久爱| 国产精品欧美大片| 性色av一区二区三区红粉影视| 2018天天弄| 久久国产综合精品| 亚洲精品第一区二区三区| 欧美xxxx做受欧美护士| 精品无码久久久久久国产| 88久久精品无码一区二区毛片| 女主播福利一区| 91蜜桃网站免费观看| 黄av在线播放| 婷婷国产在线综合| 亚洲精品乱码久久久久久蜜桃图片| 欧美精品18| 日本午夜人人精品| 亚洲av电影一区| 欧美色视频日本高清在线观看| 中文字幕乱码一区| 国产精品亚洲欧美| 欧美日韩免费精品| 欧美影视资讯| 日韩在线视频播放| 国产农村妇女毛片精品| 一区二区三区不卡视频在线观看| 91香蕉国产线在线观看| 大型av综合网站| 欧美激情视频在线免费观看 欧美视频免费一| 国产精品伦一区二区三区| 亚洲免费视频中文字幕| 不许穿内裤随时挨c调教h苏绵 | 久久久精品人体av艺术| 99久久久无码国产精品6| 欧洲美女日日| 91视频8mav| av中文在线资源库| 亚洲成人av片在线观看| 国产精品一区二区6| 国产网站一区二区三区| 午夜精品久久久久久久99热影院| 91tv官网精品成人亚洲| 成人区精品一区二区| 涩涩网在线视频| 中文字幕亚洲二区| www精品国产| 欧美性生交xxxxx久久久| 亚洲高潮女人毛茸茸| 国产美女主播视频一区| 欧美成人三级在线视频| 精品日韩免费| www.成人三级视频| 高清不卡亚洲| 久久亚洲精品一区二区| 日本xxxx人| 欧美日韩综合色| 久久精品国产亚洲av无码娇色| xfplay精品久久| 亚洲免费在线播放视频| 国产精品婷婷| 高清无码视频直接看| 亚洲人成精品久久久| 91久久国产综合久久91精品网站| 91资源在线观看| 日韩在线观看免费全| 五月天久久久久久| 91麻豆精品国产91久久久使用方法| 国产精品 欧美 日韩| 国产精品你懂的在线欣赏| 欧美在线一级片| 极品少妇一区二区三区精品视频| 国产一区二区在线视频播放| 91久久国产| 日本视频一区二区在线观看| 99香蕉久久| 成人黄色免费网站在线观看| 亚洲人体视频| 久久人人97超碰精品888| 日韩av中文| 亚洲三级av在线| 少妇荡乳情欲办公室456视频| 欧美精品v国产精品v日韩精品 | 免费视频一区| av一区二区三区免费观看| 久久激情电影| 女女同性女同一区二区三区91| 视频欧美一区| 国产女人精品视频| 亚洲精品.com| 日本国产一区二区三区| 国产后进白嫩翘臀在线观看视频 | 91www在线| 欧美成人午夜影院| 日本在线免费中文字幕| 国产亚洲免费的视频看| 日韩porn| 亚洲毛片在线观看.| 人人妻人人玩人人澡人人爽| 日韩欧美视频在线| 99产精品成人啪免费网站| 欧美日韩精品是欧美日韩精品| 日韩xxx视频| 色婷婷av一区二区三区之一色屋| 欧美一区二区激情视频| 亚洲国产成人高清精品| 久久国产精品波多野结衣| 亚洲欧美一区二区三区国产精品 | 欧美日韩一级黄色片| 亚洲成av人综合在线观看| 免费一级黄色大片| 一区二区三区欧美视频| wwwav国产| 一区二区三区在线免费视频| 久久精品视频免费在线观看| 亚洲乱码国产乱码精品精可以看 | 欧美日韩亚洲天堂| 久久久久久久极品| 欧美日韩综合视频| 亚洲欧美偷拍一区| 欧洲中文字幕精品| 91精品国产色综合久久不8| 欧美疯狂做受xxxx富婆| 国产视频第二页| 精品国精品自拍自在线| 高h调教冰块play男男双性文| 精品国产亚洲一区二区三区在线观看| www.99视频| 亚洲国产精品va在看黑人| 熟妇人妻一区二区三区四区| 亚洲精品国产精品自产a区红杏吧 亚洲精品国产精品乱码不99按摩 亚洲精品国产精品久久清纯直播 亚洲精品国产精品国自产在线 | 亚洲福利精品在线| 天天综合天天色| 亚洲天堂av图片| 五月婷婷在线观看| 欧美国产日产韩国视频| 理论不卡电影大全神| 国产精品高清免费在线观看| 日韩欧美激情| 肥熟一91porny丨九色丨| 日韩美女毛片| 亚洲综合av一区| 欧美激情在线| 国产二区视频在线播放| 免费不卡在线观看| 超碰人人cao| 91啦中文在线观看| 激情无码人妻又粗又大| 亚洲一二三专区| 久操视频在线免费观看| 欧美一区二区福利视频| 四虎影视在线观看2413| 日韩性xxxx爱| heyzo高清在线| 国产美女久久久| 99久久免费精品国产72精品九九 | 中国极品少妇videossexhd| 久久久久久久综合色一本| www.av成人| 欧美日韩日本国产| 91在线观看喷潮| 亚洲欧美日韩国产成人| 国产原创精品视频| 庆余年2免费日韩剧观看大牛| 欧美男女视频| 久久久精品动漫| 亚洲精品久久久| 国产成人亚洲精品无码h在线| 国产精品888| 黄色片网站免费| 午夜在线成人av| 国产绿帽刺激高潮对白| 亚洲天堂av高清| 91www在线| 亚洲综合自拍一区| 欧美3p在线观看| 99999精品视频| 啊啊啊国产视频| 91视频精品在这里| 精品无码久久久久久久| 亚洲欧美日韩成人高清在线一区| 成人在线免费看视频| 欧美成人官网二区| 婷婷五月在线视频| 国产精品扒开腿做| 欧美日韩导航| 免费高清一区二区三区| 精品综合免费视频观看| 久久久久久亚洲中文字幕无码| 亚洲一区二区三区四区五区黄 | 老色鬼精品视频在线观看播放| 国产草草浮力影院| 亚洲一区电影777| 国产婷婷在线视频| 久久久电影免费观看完整版| 8av国产精品爽爽ⅴa在线观看| 久久99蜜桃综合影院免费观看| 国产精品草草| 国产精品日韩三级| 国产综合色产在线精品| 国产精品suv一区二区88| 色94色欧美sute亚洲线路二| 亚洲日本中文字幕在线| 午夜剧场成人观在线视频免费观看 | 婷婷丁香激情综合| 狠狠躁夜夜躁av无码中文幕| 欧美二区在线播放| 一本色道69色精品综合久久| 一区二区三区四区免费观看| 亚洲天堂成人| 无码人妻丰满熟妇区毛片蜜桃精品| 成人欧美一区二区三区1314| 亚洲一区中文字幕永久在线| 中文字幕亚洲欧美日韩在线不卡| 三级成人在线| 亚洲精品一卡二卡三卡四卡| 青青青伊人色综合久久| 五月激情四射婷婷| 欧美日韩成人综合天天影院| 一区二区高清不卡| 成人午夜黄色影院| 欧美一区高清| 国产女主播在线播放| 精品久久久一区二区| 亚洲人妻一区二区三区| 国产精品va在线播放我和闺蜜| 国产亚洲一区二区三区不卡| 蜜臀av免费观看| 最近日韩中文字幕| www.色婷婷.com| 久久久免费电影| 亚洲系列另类av| 国产一区视频免费观看| 国产精品美女久久久久久| 国产内射老熟女aaaa∵| 午夜精品免费视频| 国产成人一区| 日韩成人av免费| 亚洲一区二区成人在线观看| 三级理论午夜在线观看| 国产精品久久婷婷六月丁香| 国产精品久久久久一区二区三区厕所| 欧美日韩一区二区区别是什么 | 欧美激情一区二区三区成人| 日本三级久久| 亚洲最大成人在线观看| 一区二区三区四区精品在线视频| 天天射天天操天天干| 国产精品视频久| 欧美涩涩视频| 欧洲美一区二区三区亚洲| 一区二区三区日韩精品视频| 日韩在线一区二区三区四区| 国产成人自拍视频在线观看| 福利片一区二区| 妓院一钑片免看黄大片| 亚洲视频在线一区观看| 婷婷开心激情网| 国产主播喷水一区二区| 亚洲精品1234| www.xx日本| 国产视频欧美视频| 国产精品一区二区美女视频免费看 | 欧美黄色视屏| 日本视频精品一区| 成人一区二区三区视频在线观看 | 国产美女高潮在线观看| 亚洲最新在线| 26uuu精品一区二区在线观看| 国产乱叫456在线| 日韩免费不卡av|