【问题标题】:SwiftUI Custom PickerStyleSwiftUI 自定义 PickerStyle
【发布时间】:2019-11-15 14:24:39
【问题描述】:

我正在尝试编写一个与SegmentedPickerStyle() 相似的自定义PickerStyle。这是我目前的状态:

import SwiftUI

public struct FilterPickerStyle: PickerStyle {
    public static func _makeView<SelectionValue>(value: _GraphValue<_PickerValue<FilterPickerStyle, SelectionValue>>, inputs: _ViewInputs) -> _ViewOutputs where SelectionValue : Hashable {

    }

    public static func _makeViewList<SelectionValue>(value: _GraphValue<_PickerValue<FilterPickerStyle, SelectionValue>>, inputs: _ViewListInputs) -> _ViewListOutputs where SelectionValue : Hashable {

    }
}

我创建了一个符合 PickerStyle 协议的结构。 Xcode 然后添加了所需的协议方法,但我不知道如何使用它们。有人可以解释如何处理这些方法,例如,如果我想实现类似于SegmentedPickerStyle() 的东西吗?

【问题讨论】:

  • 不是公开的api。但是您可以在不使用这种样式的情况下设计您的所有者选择器
  • 嗯,有道理。所以我必须从头开始构建自己的选择器?
  • 在这里构建一个与使用样式构建相同的东西并不难。你只需要一个模板。真正困难的部分是实现您的设计。

标签: ios swift xcode swiftui


【解决方案1】:

我还没有完成它,因为出现了其他东西,但这是我的(未完成的实现 SegmentedPicker 的尝试):


struct SegmentedPickerElementView<Content>: View where Content : View {
    @Binding var selectedElement: Int
    let content: () -> Content

    @inlinable init(_ selectedElement: Binding<Int>, @ViewBuilder content: @escaping () -> Content) {
        self._selectedElement = selectedElement
        self.content = content
    }

    var body: some View {
        GeometryReader { proxy in
            self.content()
                .fixedSize(horizontal: true, vertical: true)
                .frame(minWidth: proxy.size.width, minHeight: proxy.size.height)
                .contentShape(Rectangle())
        }
    }

}

struct SegmentedPickerView: View {
    @Environment (\.colorScheme) var colorScheme: ColorScheme

    var elements: [(id: Int, view: AnyView)]

    @Binding var selectedElement: Int
    @State var internalSelectedElement: Int = 0

    private var width: CGFloat = 620
    private var height: CGFloat = 200
    private var cornerRadius: CGFloat = 20
    private var factor: CGFloat = 0.95

    private var color = Color(UIColor.systemGray)
    private var selectedColor = Color(UIColor.systemGray2)


    init(_ selectedElement: Binding<Int>) {
        self._selectedElement = selectedElement
        self.elements = [
            (id: 0, view: AnyView(SegmentedPickerElementView(selectedElement) {
                Text("4").font(.system(.title))
            })),
            (id: 1, view: AnyView(SegmentedPickerElementView(selectedElement) {
                Text("5").font(.system(.title))

            })),
            (id: 2, view: AnyView(SegmentedPickerElementView(selectedElement) {
                Text("9").font(.system(.title))

            })),
            (id: 3, view: AnyView(SegmentedPickerElementView(selectedElement) {
                Text("13").font(.system(.title))

            })),
            (id: 4, view: AnyView(SegmentedPickerElementView(selectedElement) {
                Text("13").font(.system(.title))

            })),
            (id: 5, view: AnyView(SegmentedPickerElementView(selectedElement) {
                Text("13").font(.system(.title))

            })),
        ]
        self.internalSelectedElement = selectedElement.wrappedValue
    }

    func calcXPosition() -> CGFloat {
        var pos = CGFloat(-self.width * self.factor / 2.4)
        pos += CGFloat(self.internalSelectedElement) * self.width * self.factor / CGFloat(self.elements.count)
        return pos
    }

    var body: some View {
        ZStack {
            Rectangle()
                .foregroundColor(self.selectedColor)
                .cornerRadius(self.cornerRadius * self.factor)
                .frame(width: self.width * self.factor / CGFloat(self.elements.count), height: self.height - self.width * (1 - self.factor))
                .offset(x: calcXPosition())
                .animation(.easeInOut(duration: 0.2))

            HStack(alignment: .center, spacing: 0) {
                ForEach(self.elements, id: \.id) { item in
                    item.view
                        .gesture(TapGesture().onEnded { _ in
                            print(item.id)
                            self.selectedElement = item.id
                            withAnimation {
                                self.internalSelectedElement = item.id
                            }
                        })
                }
            }
        }
        .frame(width: self.width, height: self.height)
        .background(self.color)
        .cornerRadius(self.cornerRadius)
        .padding()
    }
}

struct SegmentedPickerView_Previews: PreviewProvider {
    static var previews: some View {
        SegmentedPickerView(.constant(1))
    }
}

我还没有弄清楚2.4的值所在的公式......这取决于元素的数量......她是我学到的:

2 个元素 = 4

3 个元素 = 3

4 个元素 = 2.6666

5 个元素 = 大约2.4

如果你弄清楚了这一点并修复了选择器中内容的对齐方式,它基本上是完全可调节的......你也可以通过 widthheight 使用 GeometryReader 的洞东西

祝你好运!

P.S.:我会在它完成后更新它,但目前它不是我的第一要务,所以不要指望我这样做。

【讨论】:

  • 不错的起点。我正在做类似的事情,我认为明天应该有这个实现。滑动背景应调整为控制元素的大小。我认为可以实现。
【解决方案2】:

以下代码简化了SegmentPickerElementView的设计和选择状态的维护。此外,它修复了原始帖子中选择指示器的大小(宽度和高度)计算。请注意,此解决方案中的指标位于前景中,有效地“滑动”在选择(段)HStack 的表面上。最后,这是在 iPad 上使用 Swift Playgrounds 开发的。如果您在 Mac 上使用 XCode,则需要注释掉 PlaygroundSupport 代码,并取消注释 SegmentedPickerView_Previews 结构代码。

import Foundation
import Combine
import SwiftUI
import PlaygroundSupport

struct SegmentedPickerElementView<Content>: Identifiable, View where Content : View {
    var id: Int
    let content: () -> Content

    @inlinable init(id: Int, @ViewBuilder content: @escaping () -> Content) {
        self.id = id
        self.content = content
    }

    var body: some View {
        /*
         By simply wrapping “content” in a GeometryReader
         you get a view which will flexibly take up the available 
         width in the parent container. As "Hacking Swift" put it:
         "GeometryReader has an interesting side effect that might 
         catch you out at first: the view that gets returned has a 
         flexible preferred size, which means it will expand to 
         take up more space as needed."
         (https://www.hackingwithswift.com/books/ios-swiftui/understanding-frames-and-coordinates-inside-geometryreader)
         Interesting side effect, indeed. (Don't know about you, 
         but I don't like side effects, interesting or not.) As 
         suggested in the cited article, uncomment the 
         “background()“ modifiers to see this side effect.
        */
        GeometryReader { proxy in
            self.content()//.background(Color.white)
        }//.background(Color.green)
    }
}

struct SegmentedPickerView: View {
    @Environment (\.colorScheme) var colorScheme: ColorScheme
    @State var selectedIndex: Int = 0
    @State var elementWidth: CGFloat = 0

    // The values for width and height are arbitrary, and this part 
    // of the implementation can be improved (left to the reader).
    private let width: CGFloat = 380
    private let height: CGFloat = 72
    private let cornerRadius: CGFloat = 8
    private let selectorStrokeWidth: CGFloat = 4
    private let selectorInset: CGFloat = 6 
    private let backgroundColor = Color(UIColor.lightGray)

    private let choices: [String]
    private var elements: [SegmentedPickerElementView<Text>] = [SegmentedPickerElementView<Text>]()

    init(choices: [String]) {
        self.choices = choices
        for i in choices.indices {
            self.elements.append(SegmentedPickerElementView(id: i) {
                Text(choices[i]).font(.system(.title))
            })
        }
        self.selectedIndex = 0
    }

    @State var selectionOffset: CGFloat = 0
    func updateSelectionOffset(id: Int) {
        let widthOfElement = self.width/CGFloat(self.elements.count)
        self.selectedIndex = id
        selectionOffset = CGFloat((widthOfElement * CGFloat(id)) + widthOfElement/2.0)
    }

    var body: some View {
        VStack {
            ZStack(alignment: .leading) {
                HStack(alignment: .center, spacing: 0) {
                    ForEach(self.elements) { item in
                        (item as SegmentedPickerElementView )
                            .onTapGesture(perform: { 
                                withAnimation {
                                    self.updateSelectionOffset(id: item.id)
                                }
                            })
                    }
                }
                RoundedRectangle(cornerRadius: cornerRadius)
                    .stroke(Color.gray, lineWidth: selectorStrokeWidth)
                    .foregroundColor(Color.clear) 
                    .frame(
                        width: (width/CGFloat(elements.count)) - 2.0 * selectorInset, 
                        height: height - 2.0 * selectorInset)
                    .position(x: selectionOffset, y: height/2.0)
                    .animation(.easeInOut(duration: 0.2))
            }
            .frame(width: width, height: height)
            .background(backgroundColor)
            .cornerRadius(cornerRadius)
            .padding()

            Text("selected element: \(selectedIndex) -> \(choices[selectedIndex])")
        }.onAppear(perform: { self.updateSelectionOffset(id: 0) })
    }
}

//  struct SegmentedPickerView_Previews: PreviewProvider {
//      static var previews: some View {
//          SegmentedPickerView(choices: ["A", "B", "C", "D", "E", "F" ])
//      }
//  }

PlaygroundPage.current.setLiveView(SegmentedPickerView(choices: ["A", "B", "C", "D", "E", "F" ]))


【讨论】:

    猜你喜欢
    • 2020-05-18
    • 1970-01-01
    • 2023-01-10
    • 2021-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-23
    相关资源
    最近更新 更多