网站建设如何收费中国品牌设计50强

当前位置: 首页 > news >正文

网站建设如何收费,中国品牌设计50强,wordpress免刷新插件,网站源码酒类一、前言 在 Dribbble 上找到的设计的 SwiftUI 实现时#xff0c;可以尝试通过一些酷炫的筛选器扩展该项目以缩小结果列表。筛选视图将由两个独立的筛选选项组成#xff0c;两者都有一些可选项可供选择。但是#xff0c;在使用 UIKit 时#xff0c;总是将这种类型的视图实…一、前言 在 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 casesvar 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) inlet 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 lineWidthvar allLinesResult [T]var singleLineResult Tvar partialWidthResult: CGFloat 0data.forEach { (selectableType, width) inpartialWidthResult singleLineWidth - widthif 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 inVStack(alignment: alignment, spacing: spacing) {ForEach(divideDataIntoLines(lineWidth: geo.size.width).map { (data: \(0, id: UUID()) }, id: \.id) { dataArray inGroup {HStack(spacing: spacing) {ForEach(dataArray.data, id: \.id) { data inButton(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 中使用该选择器这个选择器可用于创建各种交互式选择界面。