在SwiftUI DragGesture中更改数组顺序

在SwiftUI DragGesture中更改数组顺序,swiftui,binding,Swiftui,Binding,我目前正在尝试构建一组卡片来表示用户的卡片 这是我的第一个原型,请忽略样式问题。我希望用户能够更改卡的顺序,我尝试通过拖动手势来实现 我的想法是,当拖动手势的平移大于卡的偏移量(或小于卡的负偏移量)时,我只需交换数组中的两个元素,以交换视图中的卡(以及视图模型,因为绑定)。不幸的是,这不起作用,我不知道为什么。我的看法是: struct CardHand: View { @Binding var cards: [Card] @State var activeCardIndex:

我目前正在尝试构建一组卡片来表示用户的卡片

这是我的第一个原型,请忽略样式问题。我希望用户能够更改卡的顺序,我尝试通过拖动手势来实现

我的想法是,当拖动手势的平移大于卡的偏移量(或小于卡的负偏移量)时,我只需交换数组中的两个元素,以交换视图中的卡(以及视图模型,因为绑定)。不幸的是,这不起作用,我不知道为什么。我的看法是:

struct CardHand: View {
    @Binding var cards: [Card]
    @State var activeCardIndex: Int?
    @State var activeCardOffset: CGSize = CGSize.zero
    
    var body: some View {
        ZStack {
            ForEach(cards.indices) { index in
                GameCard(card: cards[index])
                    .offset(x: CGFloat(-30 * index), y: self.activeCardIndex == index ? -20 : 0)
                    .offset(activeCardIndex == index ? activeCardOffset : CGSize.zero)
                    .rotationEffect(Angle.degrees(Double(-2 * Double(index - cards.count))))
                    .zIndex(activeCardIndex == index ? 100 : Double(index))
                    .gesture(getDragGesture(for: index))
            }
            // Move the stack back to the center of the screen
            .offset(x: CGFloat(12 * cards.count), y: 0)
        }
    }
    
    private func getDragGesture(for index: Int) -> some Gesture {
        DragGesture()
            .onChanged { gesture in
                self.activeCardIndex = index
                self.activeCardOffset = gesture.translation
            }
            .onEnded { gesture in
                if gesture.translation.width > 30, index < cards.count {
                    cards.swapAt(index, index + 1)
                }
                self.activeCardIndex = nil
                // DEBUG: Try removing the card after drag gesture ended. Not working either.
                cards.remove(at: index)
            }
    }
}
其背后的模式是:

public struct Card: Identifiable, Hashable {
    public var id: UUID = UUID()
    public var suit: Suit
    public var rank: Rank
}

public enum Type: Identifiable, Hashable {
    case face(String)
    case numeric(rankNumber: Int)
    
    public var id: Type { self }
    
    public func description() -> String{
        switch self {
        case .face(let description):
            return description
        case .numeric(let rankNumber):
            return String(rankNumber)
        }
    }
}

public struct Rank: RankProtocol, Identifiable, Hashable {
    public var id: UUID = UUID()
    public var type: Type
    public var value: Int
    public var ranking: Int
}

public struct Suit: SuitProtocol, Identifiable, Hashable {
    public var id: UUID = UUID()
    public var name: String
    public var value: Int
    
    public init(name: String, value: Int) {
        self.name = name
        self.value = value
    }
}

public protocol RankProtocol {
    var type: Type { get }
    var value: Int { get }
}

public protocol SuitProtocol {
    var name: String { get }
}
有人能告诉我为什么这没有像我预期的那样有效吗?我错过了一些基本的东西吗


谢谢大家!

这里有很多小虫子。下面是一个(粗略的)解决方案,我将在下面详细介绍:


struct CardHand: View {
    @Binding var cards: [Card]
    @State var activeCardIndex: Int?
    @State var activeCardOffset: CGSize = CGSize.zero
    
    func offsetForCardIndex(index: Int) -> CGSize {
        var initialOffset = CGSize(width: CGFloat(-30 * index), height: 0)
        guard index == activeCardIndex else {
            return initialOffset
        }
        initialOffset.width += activeCardOffset.width
        initialOffset.height += activeCardOffset.height
        return initialOffset
    }
    
    var body: some View {
        ZStack {
            ForEach(cards.indices) { index in
                GameCard(card: cards[index])
                    .offset(offsetForCardIndex(index: index))
                    .rotationEffect(Angle.degrees(Double(-2 * Double(index - cards.count))))
                    .zIndex(activeCardIndex == index ? 100 : Double(index))
                    .gesture(getDragGesture(for: index))
            }
            // Move the stack back to the center of the screen
            .offset(x: CGFloat(12 * cards.count), y: 0)
        }
    }
    
    private func getDragGesture(for index: Int) -> some Gesture {
        DragGesture(minimumDistance: 0, coordinateSpace: .local)
            .onChanged { gesture in
                self.activeCardIndex = index
                self.activeCardOffset = gesture.translation
            }
            .onEnded { gesture in
                if gesture.translation.width > 30 {
                    if index - 1 > 0 {
                        cards.swapAt(index, index - 1)
                    }
                }
                if gesture.translation.width < -30 {
                    cards.swapAt(index, index + 1)
                }
                self.activeCardIndex = nil
            }
    }
}

结构卡片:视图{
@绑定var卡:[卡]
@状态变量activeCardIndex:Int?
@状态变量activeCardOffset:CGSize=CGSize.zero
func offsetForCardIndex(索引:Int)->CGSize{
var initialOffset=CGSize(宽度:CGFloat(-30*索引),高度:0)
保护索引==activeCardIndex-else{
返回初始偏移量
}
initialOffset.width+=activeCardOffset.width
initialOffset.height+=activeCardOffset.height
返回初始偏移量
}
var body:一些观点{
ZStack{
ForEach(cards.index){index in
游戏卡(卡:卡[索引])
.offset(offsetForCardIndex(索引:索引))
.rotationEffect(角度度(双精度(-2*Double(索引卡数)))
.zIndex(activeCardIndex==索引?100:双精度(索引))
.手势(getDragGesture(用于:索引))
}
//将堆栈移回屏幕中央
.偏移量(x:CGFloat(12张卡片计数),y:0)
}
}
private func getDragGesture(用于索引:Int)->一些手势{
DragGesture(最小距离:0,坐标空间:。本地)
.onChanged{手势在
self.activeCardIndex=索引
self.activeCardOffset=手势.translation
}
.onEnded{在
如果手势.translation.width>30{
如果索引-1>0{
卡片.swapAt(索引,索引-1)
}
}
如果手势.translation.width<-30{
卡片。swapAt(索引,索引+1)
}
self.activeCardIndex=nil
}
}
}
  • 尝试在一行中执行两个
    offset()
    调用是有问题的。我将这两种方法组合成
    offsetForCardIndex
  • 因为您正在执行
    ZStack
    ,请记住
    数组中的第一项将位于堆栈的底部,朝向屏幕的右侧。这影响了后来的逻辑
  • 您需要确保在
    swapAt
    中检查数组的边界,这样您就不会试图交换索引之外的内容(这是以前发生的事情)

  • 我的“解决方案”仍然相当粗糙——应该有更多的检查来确保数组不会超出边界。另外,在你的原版和我的原版中,从用户体验的角度来看,能够交换多个位置可能更有意义。但是,这是一个更大的问题,超出了这个问题的范围。

    CardHand是子视图还是主视图?什么是卡代码?CardHand是整个游戏场景的子视图@你指的是卡牌还是游戏卡?我很乐意添加它。您的代码不可编译,您需要提供工作代码,因此被否决。我添加了我的模型代码。使用所有这些代码,项目立即编译。
    
    struct CardHand: View {
        @Binding var cards: [Card]
        @State var activeCardIndex: Int?
        @State var activeCardOffset: CGSize = CGSize.zero
        
        func offsetForCardIndex(index: Int) -> CGSize {
            var initialOffset = CGSize(width: CGFloat(-30 * index), height: 0)
            guard index == activeCardIndex else {
                return initialOffset
            }
            initialOffset.width += activeCardOffset.width
            initialOffset.height += activeCardOffset.height
            return initialOffset
        }
        
        var body: some View {
            ZStack {
                ForEach(cards.indices) { index in
                    GameCard(card: cards[index])
                        .offset(offsetForCardIndex(index: index))
                        .rotationEffect(Angle.degrees(Double(-2 * Double(index - cards.count))))
                        .zIndex(activeCardIndex == index ? 100 : Double(index))
                        .gesture(getDragGesture(for: index))
                }
                // Move the stack back to the center of the screen
                .offset(x: CGFloat(12 * cards.count), y: 0)
            }
        }
        
        private func getDragGesture(for index: Int) -> some Gesture {
            DragGesture(minimumDistance: 0, coordinateSpace: .local)
                .onChanged { gesture in
                    self.activeCardIndex = index
                    self.activeCardOffset = gesture.translation
                }
                .onEnded { gesture in
                    if gesture.translation.width > 30 {
                        if index - 1 > 0 {
                            cards.swapAt(index, index - 1)
                        }
                    }
                    if gesture.translation.width < -30 {
                        cards.swapAt(index, index + 1)
                    }
                    self.activeCardIndex = nil
                }
        }
    }