swiftUI 中按钮拖动可见问题

问题描述 投票:0回答:1

我创建了一个像这里一样的

Sentence creator
(Pure SwiftUI)。按钮拖动和定位工作正常。问题是,

  • 拖动按钮时,它不会显示在句子区域,反之亦然。 (有2个区域,句子区域和按钮区域)
  • 放下按钮后,它的定位没有任何问题。
  • 其他行为按预期工作

谁能帮我解决这个问题?

struct SwiftUIDraggableButtonView: View {
    @State private var buttons: [DraggableButtonModel] = [
        DraggableButtonModel(id: 1, text: "me"),
        DraggableButtonModel(id: 2, text: "foreign friends."),
        DraggableButtonModel(id: 3, text: "to me,"),
        DraggableButtonModel(id: 4, text: "to make"),
        DraggableButtonModel(id: 5, text: "means")
    ]
    @State private var sentenceButtons: [DraggableButtonModel] = []

    var body: some View {
        VStack(spacing: 20) {
            // Unified FlowLayout for sentence and default areas
            FlowLayout(spacing: 5, items: sentenceButtons) { button in
                DraggableButtonNew(
                    model: button,
                    isInSentence: true
                ) { action in
                    handleButtonAction(action, button: button)
                }
                .transition(.move(edge: .bottom))
            }
            .padding()
            .frame(height: 200)  // Set height for sentence area
            .background(Color.gray)
            .zIndex(0)

            // Default button area
            FlowLayout(spacing: 10, items: buttons) { button in
                DraggableButtonNew(
                    model: button,
                    isInSentence: false
                ) { action in
                    handleButtonAction(action, button: button)
                }
                .allowsHitTesting(!button.isDisabled)  // Disable interaction for disabled buttons
                .transition(.move(edge: .top))
            }
            .frame(height: 200)  // Set height for default button area
            .zIndex(1)
            Spacer()
        }
        .padding()
    }

    private func handleButtonAction(_ action: DraggableButtonAction, button: DraggableButtonModel) {
        withAnimation {
            switch action {
            case .tap, .drag:
                // Handle button tap: move button between sentence and default areas
                if let index = sentenceButtons.firstIndex(where: { $0.id == button.id }) {
                    // Button is in the sentence area, move it back to default
                    sentenceButtons.remove(at: index)
                    if let defaultIndex = buttons.firstIndex(where: { $0.id == button.id }) {
                        buttons[defaultIndex].isDisabled = false // Re-enable in default area
                    }
                } else if let defaultIndex = buttons.firstIndex(where: { $0.id == button.id }),
                          !buttons[defaultIndex].isDisabled {
                    // Button is in default area, move it to sentence
                    buttons[defaultIndex].isDisabled = true
                    sentenceButtons.append(button)
                }
            }
        }
    }
}

// FlowLayout for wrapping buttons in multiple lines
struct FlowLayout<Data: RandomAccessCollection, Content: View>: View
where Data.Element: Identifiable {
    let spacing: CGFloat
    let items: Data
    let content: (Data.Element) -> Content

    var body: some View {
        var width: CGFloat = 0
        var height: CGFloat = 0

        return GeometryReader { geometry in
            ZStack(alignment: .topLeading) {
                ForEach(items) { item in
                    content(item)
                        .alignmentGuide(.leading) { d in
                            if abs(width - d.width) > geometry.size.width {
                                width = 0
                                height -= d.height + spacing
                            }
                            let result = width
                            if item.id == items.last?.id {
                                width = 0
                            } else {
                                width -= d.width + spacing
                            }
                            return result
                        }
                        .alignmentGuide(.top) { _ in
                            let result = height
                            if item.id == items.last?.id {
                                height = 0
                            }
                            return result
                        }
                }
            }
        }
        .frame(maxHeight: .infinity, alignment: .topLeading)
    }
}
// Draggable Button
struct DraggableButtonNew: View {
    let model: DraggableButtonModel
    let isInSentence: Bool
    let actionHandler: (DraggableButtonAction) -> Void
    @State private var offset: CGSize = .zero

    var body: some View {
        Text(model.text)
            .padding(8)
            .background(isInSentence ? Color.orange : model.isDisabled ? Color.gray : Color.orange)
            .foregroundColor(Color.white)
            .offset(offset)
            .zIndex(offset == .zero ? 0 : 1)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        offset = value.translation
                    }
                    .onEnded { _ in
                        actionHandler(.drag)
                        offset = .zero
                    }
            )
            .onTapGesture {
                actionHandler(.tap)
            }
    }
}

// Button Model
struct DraggableButtonModel: Identifiable, Equatable {
    let id: Int
    let text: String
    var isDisabled: Bool = false
}
enum DraggableButtonAction {
    case tap
    case drag
}
ios swift swiftui
1个回答
0
投票

如果您使用

.matchedGeometryEffect
来匹配句子中单词和按钮的位置,则拖动手势的变化看起来会更加流畅。重叠的问题也可以这样解决。

使其工作的一种方法如下:

  • 句子区域内的单词和按钮区域内的按钮显示为简单的隐藏占位符。

  • 占位符的id基于按钮(单词)的id,但在句子区域的占位符的id中添加了一个常量。这是为了使所有占位符的 id 都是唯一的。

  • 可拖动的单词显示在父级

    VStack
    上方的叠加层中。这些单词always显示为已启用(橙色),但它们的位置使用
    .matchedGeometryEffect
    与占位符匹配。换句话说,这些词有一个浮动的位置。

  • 被拖动的单词具有更高的

    zIndex
    (和以前一样)。这确保了如果将其拖动到其他单词上,它会显示在其他单词上。这解决了帖子中的原始问题。

  • 当在句子中添加单词时,禁用的表单会显示在按钮位置的背景中。

  • 拖动单词时,我在控制台中看到有关“无效示例 AnimatablePair”的错误。可以通过使用 withAnimation 更新偏移量和持续时间 0 来防止这些问题,请参阅

    this post
    了解更多详细信息。

  • 为了使动画在释放拖动后顺利运行,重要的是还要执行拖动结束操作
  • withAnimation

    
    

  • 其他建议:

  • isInSentence

    中的参数

    DraggableButtonNew
    是多余的,可以去掉。实际上,将模型参数
    isDisabled
    重命名为
    isInSentence
    然后反向使用可能更有意义: false 变为 true。
    
    

  • 修饰符
  • .foregroundColor

    已弃用,请使用

    .foregroundStyle
    代替。
    
    

  • // SwiftUIDraggableButtonView let sentenceWord = 1_000_000 @Namespace private var namespace var body: some View { VStack(spacing: 20) { // Unified FlowLayout for sentence and default areas FlowLayout(spacing: 5, items: sentenceButtons) { button in // Placeholder for word in sentence PlainWord(text: button.text) .hidden() .matchedGeometryEffect(id: sentenceWord + button.id, in: namespace, isSource: true) } .padding() .frame(height: 200) // Set height for sentence area .background(.gray) // Default button area FlowLayout(spacing: 10, items: buttons) { button in // Placeholder for a button PlainWord(text: button.text) .hidden() .matchedGeometryEffect(id: button.id, in: namespace, isSource: true) .background { if button.isDisabled { PlainWord(text: button.text, isDisabled: true) } } } .frame(height: 200) // Set height for default button area Spacer() } .overlay { ForEach(buttons) { button in DraggableButtonNew(model: button) { action in handleButtonAction(action, button: button) } .matchedGeometryEffect( id: button.isDisabled ? sentenceWord + button.id : button.id, in: namespace, isSource: false ) } } .padding() }
struct PlainWord: View {
    let text: String
    var isDisabled = false

    var body: some View {
        Text(text)
            .padding(8)
            .background(isDisabled ? .gray : .orange)
            .foregroundStyle(.white)
    }
}

// Draggable Button
struct DraggableButtonNew: View {
    let model: DraggableButtonModel
    let actionHandler: (DraggableButtonAction) -> Void
    @State private var offset: CGSize = .zero

    var body: some View {
        PlainWord(text: model.text, isDisabled: false)
            .offset(offset)
            .zIndex(offset == .zero ? 0 : 1)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        withAnimation(.easeInOut(duration: 0)) {
                            offset = value.translation
                        }
                    }
                    .onEnded { _ in
                        withAnimation {
                            actionHandler(.drag)
                            offset = .zero
                        }
                    }
            )
            .onTapGesture {
                actionHandler(.tap)
            }
    }
}

Animation

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.