如何在添加或删除项目时保持SwiftUI ScrollView在X轴上的位置?

yeotifhr  于 2023-11-16  发布在  Swift
关注(0)|答案(1)|浏览(132)

我在水平滚动视图中遇到了一个问题。当调用onitemTappedonDelete方法时,添加或删除了一个项目,ScrollView被重置到原始位置。我想实现的是,当删除或添加单个itemView时,X轴上的偏移量不会改变。对于ScrollViewWithOffset我使用了this example。这是我的验证码:

public struct ItemView: View {
    var action: () -> Void
    @ObservedObject var itemData: ItemData
    @State private var isReducedSize: Bool = false

    public init(action: @escaping () -> Void, itemData: itemData) {
        self.action = action
        self.itemData = itemData
    }

    public var body: some View {
        HStack {
            ScrollViewWithOffset(onScroll: handleScroll) {
                ItemCollectionView(itemData: itemData)
            }
        }
        .padding(8)
    }

    private func handleScroll(_ offset: CGPoint) {
        
        withAnimation {
            if offset.x >= -100 {
                self.isReducedSize = false
            } else {
                self.isReducedSize = true
            }
        }
    }
}

struct ItemCollectionView: View {
    @ObservedObject var itemData: itemData

    public var body: some View {
        HStack {
            ForEach(itemData.items, id: \.id) { item in
                itemView(
                    content: itemViewContentProvider.content(
                        item: item,
                        onitemTapped: { item in
                            itemData.add(item) },
                        onDelete: { item in
                            itemData.remove(item)
                        })
                )
            }
        }
    }
}

struct itemView: View {
    let content: itemViewContent

    var body: some View {
        HStack {
            Button(action: {
                content.onitemTapped(content.item)
            }, label: {
                Text(content.itemTitle)
                    .font(.copy)
                    .foregroundColor(.contentLead)
            })
            Button(action: {
                content.onDelete(content.item)
            }, label: {
                Image(uiImage: .icons.close)
                    .resizable()
                    .frame(width: 10, height: 10)
                    .foregroundColor(.contentSecondary)
            })
        }
        .padding(.horizontal, 8)
        .background(Rectangle()
            .foregroundColor(.backgroundContrast)
            .cornerRadius(4)
            .frame(height: 30))
        .frame(height: 30)
    }
}

字符串

pwuypxnk

pwuypxnk1#

我可以编写自己的滚动视图,并在一个状态下保存偏移量,作为与总大小的关系,这将依赖于固定的项目大小。或者保持定义的容量并增加元素,而不是删除它们,我会通过一个简单的if语句来增加。这样滚动视图就不必再次呈现,因此保持它的位置。我使用的解决方案在HStack上作为修改器工作,看起来像这样:

struct ScrollingHStackModifier: ViewModifier {
@Binding var thisindex: Int
@Binding var hoveredIndex: Double
@Binding var opacityUnSelected: Double

@State private var scrollOffset: CGFloat
@State private var dragOffset: CGFloat

var items: Int
var itemWidth: CGFloat

private var isEnabled: () -> Bool
private var setNewIndex: (_ newIndex: Int) -> Void

init(
        index: Binding<Int>,
        hoveredIndex: Binding<Double>,
        items: Int, itemHeight: CGFloat,
        opacityUnSelected: Binding<Double>,
        setNewIndex: @escaping (_ newIndex: Int) -> Void,
        isEnabled: @escaping () -> Bool
) {

    self.setNewIndex = setNewIndex
    self.isEnabled = isEnabled
    self._thisindex = index
    self._hoveredIndex = hoveredIndex
    self._opacityUnSelected = opacityUnSelected
    self.items = items
    self.itemWidth = itemHeight
    self._scrollOffset = State(initialValue: 0)
    self._dragOffset = State(initialValue: 0)

}

func body(content: Content) -> some View {
    content
            .offset(x: scrollOffset + dragOffset , y: 0)
            .highPriorityGesture(DragGesture(minimumDistance: 10.0, coordinateSpace: .local)
                    .onChanged({ event in
                        if (isEnabled()) {
                            withAnimation {
                                opacityUnSelected = 0.5
                            }
                            dragOffset = event.translation.width
                            hoveredIndex = getFloatingIndexFor(offset: Double(scrollOffset + dragOffset))
                        }
                    })
                    .onEnded({ event in
                        // Scroll to where user dragged
                        if (isEnabled()) {
                            scrollOffset += event.translation.width
                            dragOffset = 0
                            // Now calculate which item to snap to
                            thisindex = getIndexFor(offset: scrollOffset)
                            // Protect from scrolling out of bounds
                            thisindex = Int(min(CGFloat(thisindex), CGFloat(items - 1)))
                            thisindex = Int(max(CGFloat(thisindex), 0))
                            setNewIndex(thisindex)
                        }
                        // Animate snapping
                        withAnimation {
                            opacityUnSelected = 0.0
                            scrollOffset = getPositionFor(indexNumber: Double(thisindex))
                            hoveredIndex = Double(thisindex)
                        }
                    })
            )
            .onChange(of: thisindex) { ind in
                //withAnimation {
                opacityUnSelected = 0.0
                scrollOffset = getPositionFor(indexNumber: Double(thisindex))
                //}
            }
            .onAppear {
                scrollOffset = getPositionFor(indexNumber: Double(thisindex))
                hoveredIndex = getFloatingIndexFor(offset: Double(scrollOffset + dragOffset))
            }
            .frame(height: itemWidth)
}

private func getPositionFor(indexNumber: Double) -> Double {
    let offset = indexNumber * itemWidth
    let scrollableWidth = itemWidth * CGFloat(items - 1)
    // Center position of current offset
    let endPosition = offset - scrollableWidth / 2.0
    return endPosition
}

private func getIndexFor(offset: Double) -> Int {
    let floatingOffset = getFloatingIndexFor(offset: offset)
    let remaining =  floatingOffset - Double(Int(floatingOffset))
    if (remaining > 0.5){
        return Int(getFloatingIndexFor(offset: offset)) + 1
    }
    else {
        return Int(getFloatingIndexFor(offset: offset))
    }
}

private func getFloatingIndexFor(offset: Double) -> Double {
    let scrollableWidth = itemWidth * CGFloat(items - 1)
    // Center position of current offset
    let center = offset + (scrollableWidth / 2)
    // Calculate which item we are closest to using the defined size
    let endPosition = (center) / itemWidth
    // Should we stay at current index or are we closer to the next item...
    return endPosition
}

字符串

相关问题