精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

国产在线视频91| 国产一区二区日韩精品欧美精品| 免费在线黄网站| 丝袜视频国产在线播放| 日本aⅴ精品一区二区三区| 久久视频在线观看免费| 喷水视频在线观看| 日本美女久久| 亚洲大型综合色站| 日韩国产伦理| 老牛影视av牛牛影视av| 免费观看成人av| 欧美激情精品久久久久久久变态| 日韩人妻无码一区二区三区| 国产精品xnxxcom| 一本一本大道香蕉久在线精品 | 日韩大片一区二区| 国产夫妻在线| 亚洲欧美在线高清| 鲁丝一区鲁丝二区鲁丝三区| www.蜜臀av.com| 三级一区在线视频先锋| 性色av一区二区三区在线观看| 亚洲色图日韩精品| 神马电影久久| 日韩高清av一区二区三区| 四虎1515hh.com| 国产综合色激情| 一本大道综合伊人精品热热| 国产精品www在线观看| www.久久ai| 中文av一区二区| 欧美亚洲爱爱另类综合| 天天干天天舔天天射| 国产99久久久国产精品| 国产一区二区色| 最近中文字幕免费在线观看| 久久永久免费| 欧美日韩精品一本二本三本 | 欧美亚洲国产视频小说| 欧美黄色一区二区三区| 久久久久久久久丰满| 色综合伊人色综合网站| 在线视频第一页| 国产区精品区| 国产亚洲人成网站在线观看| 日本黄色特级片| 天堂av一区二区三区在线播放| 亚洲精品动漫100p| 欧美做受高潮中文字幕| 成人av动漫| 精品国产三级a在线观看| 91精品国产高清91久久久久久| 久久伦理中文字幕| 日韩视频国产视频| 日韩女优在线视频| 国产精品传媒| 日韩av影片在线观看| 九九九九九九精品| 美女搡bbb又爽又猛又黄www| 美女国产精品久久久| 欧美一级午夜免费电影| 亚洲国产欧美日韩在线| 99久久免费精品国产72精品九九| 欧美tickling挠脚心丨vk| 亚洲精品无码一区二区| 精品三级av| 亚洲欧美综合精品久久成人| 国产一二三四五区| 日韩一区二区中文| 久久福利网址导航| 久久久久久久久97| 国产精品毛片在线| 国产精品十八以下禁看| 国产又大又黄的视频| 国产黄人亚洲片| 精品欧美一区二区久久久伦| yourporn在线观看中文站| 国产精品成人免费精品自在线观看 | 91精品国产乱码久久久久| 国模大尺度一区二区三区| 国产精品久久一区二区三区| 日本在线一二三| 国产精品久久久久影院色老大| 男人天堂网站在线| 欧美gv在线| 欧美日韩国产首页| 国产精品嫩草69影院| 亚洲免费专区| 美女精品视频一区| 天天操中文字幕| 极品美女销魂一区二区三区| 久久99欧美| 日本激情在线观看| 精品国产成人av| 岛国毛片在线播放| 久久超级碰碰| 色婷婷综合久久久久| 精品一区免费观看| 日本不卡免费在线视频| 国产精品一级久久久| 国内在线免费高清视频| 亚洲综合视频在线观看| 四季av一区二区| 露出调教综合另类| 久久成人精品视频| 精品久久久久久久久久久久久久久久久久| 久久超碰97人人做人人爱| 精品国产综合| 影音先锋在线视频| 欧美色爱综合网| 风间由美一二三区av片| 一区二区三区在线观看免费| 日本久久久久久久久久久| 成人av无码一区二区三区| 国产丝袜美腿一区二区三区| 欧美精品一区二区三区三州| 国产美女亚洲精品7777| 一区二区三区回区在观看免费视频| 久久久精品人妻一区二区三区四| 麻豆精品在线播放| 欧美1o一11sex性hdhd| a级大胆欧美人体大胆666| 欧美军同video69gay| 最近中文字幕免费视频| 亚洲大胆在线| 97人人香蕉| 精精国产xxxx视频在线| 欧美午夜一区二区三区| 免费看黄色aaaaaa 片| 欧美日韩免费观看一区=区三区| 国产精品免费久久久| 巨骚激情综合| 欧美日韩综合视频| 捆绑凌虐一区二区三区| 精品电影一区| 成人久久18免费网站漫画| 久操视频在线播放| 欧美三级一区二区| 国产美女免费网站| 丝袜脚交一区二区| 牛人盗摄一区二区三区视频| 亚洲欧美韩国| 国产丝袜一区视频在线观看| aaa人片在线| 99麻豆久久久国产精品免费优播| 三上悠亚久久精品| 国产伦乱精品| 茄子视频成人在线| 激情福利在线| 欧美日韩免费不卡视频一区二区三区| 天天摸日日摸狠狠添| 麻豆国产精品视频| 在线成人性视频| www欧美在线观看| 欧美情侣性视频| 亚洲免费视频网| 午夜国产精品一区| 一区二区不卡免费视频| 久久先锋影音| 一本久道久久综合| 国产欧美日韩电影| 欧美黄色免费网站| 婷婷伊人综合中文字幕| 欧美日韩在线免费| 国产成人精品无码免费看夜聊软件| 丝袜脚交一区二区| 五月天色婷婷综合| 97视频一区| 欧美在线性爱视频| 视频一区二区三区不卡| 精品卡一卡二卡三卡四在线| 日本一级黄色大片| 国产色综合久久| 伊人国产精品视频| 亚洲激情网站| 亚洲高清不卡一区| 欧美经典一区| 欧美性受xxxx黑人猛交| 婷婷成人激情| 亚洲第一天堂无码专区| 日韩熟女一区二区| 亚洲色图.com| 91精品人妻一区二区| 精品在线观看视频| 福利视频一区二区三区四区| 精品美女视频| 成人黄视频免费| 国产精品毛片久久久久久久久久99999999| xxxxx成人.com| 天天操天天操天天干| 欧美日韩一本到| 日本三级视频在线| 国产精品国模大尺度视频| 手机在线成人av| 国内精品写真在线观看| 欧美亚洲国产成人| 亚洲一区二区日韩| 欧美凹凸一区二区三区视频| 精品国产亚洲日本| 国产精品av电影| av福利在线导航| 久久精品国产精品| 色视频在线看| 欧美精品一区二区三区久久久| 老熟妇一区二区三区啪啪| 天天综合色天天综合色h| 日本黄色片免费观看| 久久久久久久免费视频了| 午夜性福利视频| 麻豆一区二区三| 亚洲成熟丰满熟妇高潮xxxxx| 欧美视频网站| 麻豆中文字幕在线观看| 青青一区二区三区| 欧美日韩精品一区| 色爱av综合网| 国产欧美日韩亚洲| 日韩一区二区三区精品视频第3页| 国产精品视频区1| 电影一区二区三区| 亚洲91av视频| jizz一区二区三区| 久久777国产线看观看精品| avav免费在线观看| 亚洲视频综合网| 日韩二区三区| 日韩黄在线观看| 婷婷在线观看视频| 精品国产电影一区二区| 国产视频在线观看免费| 欧美日韩电影在线播放| 中日韩在线观看视频| 在线亚洲一区二区| 五月婷婷六月婷婷| 一本色道久久综合亚洲aⅴ蜜桃| www.av麻豆| 亚洲成人中文在线| 日本在线视频免费观看| 亚洲第一福利一区| 日韩精品成人一区| 欧美日韩国产精品一区二区三区四区 | 国产精品揄拍一区二区| 精品视频一区二区三区四区五区| 日本成人黄色片| 日日av拍夜夜添久久免费| 日本道色综合久久影院| 成人免费看黄| 国产精品流白浆视频| 亚洲精品69| 亚洲最大av网站| 成人h动漫精品一区二区器材| 国产精品久久久久久久免费大片| 韩国女主播一区二区三区| 精品国产一区二区三区四区vr| 欧美日韩夜夜| 日韩欧美三级电影| 亚州av乱码久久精品蜜桃| 波多野结衣激情| 国产精品v欧美精品v日本精品动漫| 黄色一级片在线看| 新狼窝色av性久久久久久| 精品久久久久久中文字幕2017| 蜜桃视频在线观看一区| 亚洲成人激情小说| av不卡在线播放| 亚洲无人区码一码二码三码的含义| 国产精品天干天干在线综合| 91麻豆精品成人一区二区| 亚洲一区二区三区爽爽爽爽爽| 日日夜夜综合网| 欧美性生活一区| 国产极品久久久| 亚洲精品久久久久久久久久久久久| 久久久久久久影视| 久久精品电影网| 福利影院在线看| 国产精品成人免费电影| 国产日韩欧美中文在线| 国产专区一区二区三区| 欧美日韩国产在线观看网站| 性生活免费观看视频| 国产人成精品一区二区三| 超碰超碰在线观看| 成人精品视频一区二区三区 | 亚洲一二区在线观看| eeuss影院一区二区三区| 九九九视频在线观看| 亚洲一二三区视频在线观看| 国产伦精品一区二区三区视频我| 91精品国产色综合久久不卡蜜臀| 日本黄色一区二区三区| 中文日韩在线观看| 日韩伦理在线| 91久色国产| 欧美精品一区二区三区精品| 丰满的少妇愉情hd高清果冻传媒| 日日摸夜夜添夜夜添亚洲女人| 国产av一区二区三区传媒| 中文字幕精品三区| 国产午夜精品无码一区二区| 欧美艳星brazzers| 无套内谢的新婚少妇国语播放| 日韩中文字幕视频在线| 日韩脚交footjobhd| 99re国产视频| 日韩一区二区中文| 国模杨依粉嫩蝴蝶150p| 成人av网站在线| 国产精品 欧美激情| 在线看一区二区| 天堂av在线免费观看| 欧美激情亚洲自拍| 日韩城人网站| 图片区小说区区亚洲五月| 香蕉精品999视频一区二区| 亚洲精品乱码久久久久久9色| 国产精品网站在线观看| 日本中文字幕在线| 精品欧美一区二区三区精品久久| 日本韩国在线视频爽| 日本视频久久久| 天天躁日日躁狠狠躁欧美| 久久av综合网| 成人丝袜视频网| 欧美三根一起进三p| 91精品国产高清一区二区三区蜜臀| 国产福利片在线| 日韩美女写真福利在线观看| 婷婷五月色综合香五月| 九九爱精品视频| 99这里都是精品| 日本熟妇毛茸茸丰满| 精品少妇一区二区三区视频免付费 | 一卡二卡三卡在线| 中文字幕国内精品| 免费观看成人性生生活片 | 裸体xxxx视频在线| 日本精品在线视频| 深爱激情综合网| 手机看片福利盒子久久| 国产欧美精品一区二区三区四区| 五月天婷婷导航| 亚洲午夜性刺激影院| 天堂久久午夜av| 天天人人精品| 久久成人免费网| 精品无码久久久久成人漫画| 欧美一区二区三区日韩视频| caopeng在线| 91精品婷婷国产综合久久蝌蚪| 欧美成人精品| 精品国产乱码久久久久夜深人妻| 亚洲一区二区成人在线观看| 色婷婷av一区二区三区之红樱桃| 91sa在线看| 欧美日韩一二三四| 日韩va在线观看| 一区二区三区四区在线播放 | 无码人妻精品一区二区50| 在线不卡国产精品| 亚洲欧美综合久久久久久v动漫| 一区二区三区四区免费观看| 国产成人av资源| 一区二区三区福利视频| 亚洲图片在区色| 国产精品成人3p一区二区三区| 国产一级爱c视频| 久久精品日产第一区二区三区高清版 | 91视频免费看片| 日韩丝袜情趣美女图片| 9999热视频在线观看| 欧美成人综合一区| 久久99久久久欧美国产| 久久久全国免费视频| 亚洲欧洲xxxx| 高清精品久久| 日本久久久精品视频| 国产精品不卡在线| 色丁香婷婷综合久久| 国产噜噜噜噜噜久久久久久久久| 欧美成人一品| 国产真实乱人偷精品人妻| 欧美一区二区三区在线观看视频 | 中文字幕在线视频区| av一区二区三区免费| 香蕉av777xxx色综合一区| 欧美爱爱免费视频| 亚洲男女自偷自拍图片另类| 亚洲三级在线| 一本大道熟女人妻中文字幕在线| 亚洲欧洲在线观看av| 午夜在线观看视频18| 成人淫片在线看| 三级在线观看一区二区| 欧美激情精品久久| 在线播放精品一区二区三区| 成人av影音| www.日本久久| 欧美亚洲综合久久|