问题描述
我正在 SwiftUI 中构建自定义 SegmentedPicker,其中选择器调整其大小以适合每个选择器项目的框架。我已经按照这篇文章 (Inspecting the View Tree) 的启发使用了 {
"type": "Microsoft.Compute/virtualMachines/extensions","name": "[concat(parameters('vmName'),'/installcustomscript')]","apiVersion": "2019-03-01","location": "[parameters('location')]","properties": {
"publisher": "Microsoft.Azure.Extensions","type": "CustomScript","typeHandlerVersion": "2.1","autoUpgradeMinorVersion": true,"settings": {
"fileUris": ["https://mystorageaccount.blob.core.windows.net/oms/oms_linux.sh"]
},"protectedSettings": {
"commandToExecute": "wget https://raw.githubusercontent.com/Microsoft/OMS-Agent-for-Linux/master/installer/scripts/onboard_agent.sh && sh oms_linux.sh","storageAccountName": "xxx","storageAccountKey": "xxxx"
}
}
}
来制作尺寸统一的项目,如下所示:
我认为我可以通过使用 PreferenceKey
来大大简化我的实现并避免完全使用 PreferencyKey
。我的想法是仅在每个项目被选中时才在每个项目后面显示一个选择器,并使用 .matchedGeometryEffect()
同步转换。除了选择器位于先前选择的项目前面的问题外,几乎所有东西都在工作。我尝试明确设置 .matchedGeometryEffect()
,但它似乎不会影响结果:
代码:
zIndex
在struct MatchedGeometryPicker: View {
@Namespace private var animation
@Binding var selection: Int
let items: [String]
var body: some View {
HStack {
ForEach(items.indices) { index in
ZStack {
if isSelected(index) {
Color.gray.clipShape(Capsule())
.matchedGeometryEffect(id: "selector",in: animation)
.animation(.easeInOut)
.zIndex(0)
}
itemView(for: index)
.padding(7)
.zIndex(1)
}
.fixedSize()
}
}
.padding(7)
}
func itemView(for index: Int) -> some View {
Text(items[index])
.frame(minWidth: 0,maxWidth: .infinity)
.foregroundColor(isSelected(index) ? .black : .gray)
.font(.caption)
.onTapGesture { selection = index }
}
func isSelected(_ index: Int) -> Bool { selection == index }
}
中:
ContentView
任何想法如何解决这个问题?
解决方法
当项目具有不同的帧大小时,我设法解决了我在使用 PreferenceKey
的选择器实现中遇到的所有动画问题。这不能解决我对 zIndex
和 .matchedGeometryEffect()
的问题,因此我不会接受我自己的答案,但我会将其发布为参考,以防将来有人需要它。
代码:
public struct PKPicker: View {
@Binding var selection: Int
@State private var frames: [CGRect] = []
let items: [String]
public init(
selection: Binding<Int>,items: [String])
{
self._selection = selection
self._frames = State(wrappedValue: Array<CGRect>(repeating: CGRect(),count: items.count))
self.items = items
}
public var body: some View {
ZStack(alignment: .topLeading) {
selector
HStack {
ForEach(items.indices) { index in
itemView(for: index)
}
}
}
.onPreferenceChange(PKPickerItemPreferenceKey.self) { preferences in
preferences.forEach { frames[$0.id] = $0.frame }
}
.coordinateSpace(name: "picker2")
}
var selector: some View {
Color.gray.opacity(0.25).clipShape(Capsule())
.frame(width: frames[selection].size.width,height: frames[selection].size.height)
.offset(x: frames[selection].minX,y: frames[selection].minY)
}
func itemView(for index: Int) -> some View {
Text(items[index])
.fixedSize()
.padding(7)
.foregroundColor(isSelected(index) ? .black : .gray)
.font( .caption)
.onTapGesture { selection = index }
.background(PKPickerItemPreferenceSetter(id: index))
}
func isSelected(_ index: Int) -> Bool {
index == selection
}
}
struct PKPickerItemPreferenceData: Equatable {
let id: Int
let frame: CGRect
}
struct PKPickerItemPreferenceKey: PreferenceKey {
typealias Value = [PKPickerItemPreferenceData]
static var defaultValue: [PKPickerItemPreferenceData] = []
static func reduce(
value: inout [PKPickerItemPreferenceData],nextValue: () -> [PKPickerItemPreferenceData])
{
value.append(contentsOf: nextValue())
}
}
struct PKPickerItemPreferenceSetter: View {
let id: Int
let coordinateSpace = CoordinateSpace.named("picker2")
var body: some View {
GeometryReader { geometry in
Color.clear
.preference(key: PKPickerItemPreferenceKey.self,value: [PKPickerItemPreferenceData(
id: id,frame: geometry.frame(in: coordinateSpace))])
}
}
}
在ContentView
结构内容视图:查看{ @State 私有变量选择 = 0
let pickerItems = [ "Item 1","Long item 2","Item 3","Item 4","Long Item 5"]
var body: some View {
PKPicker(selection: $selection.animation(.easeInOut),items: pickerItems)
.padding(7)
.background(Color.gray.opacity(0.10).clipShape(Capsule()))
.padding(5)
}
结果: