SwiftUI 可重排序

一个纯 SwiftUI 结构组件,可以轻松实现拖放重新排序操作。它支持基于 DragGesture 的与其元素进行快速交互,而不是像 .onDrag/.draggable 那样基于“长按”的交互。这是它在即将推出的 Vis iOS 应用中的实际应用示例。

An animated recording of the Vis app, where the user selects the "Reorder Blocks" option and then proceeds to drag blocks from the planned workout around, rearranging their order.

此软件包包含 ReorderableVStackReorderableHStack

特性

安装

此框架以 Swift 包 的形式分发。要使用,请将以下 URL 添加到您的软件包列表

https://github.com/visfitness/reorderable

要将此包添加到您的 XCode 项目,请按照这些说明

文档

该软件包的文档可以在这里找到

用法

注意

以下所有示例都使用以下 struct 作为其数据

private struct Sample: Identifiable {
 var color: UIColor
 var id: UUID = UUID()
 var height: CGFloat
 
 init(_ color: UIColor, _ height: CGFloat) {
   self.color = color
   self.height = height
 }
}

简单示例

struct SimpleExample: View {
  @State var data = [
    Sample(UIColor.systemBlue, 200),
    Sample(UIColor.systemGreen, 100),
    Sample(UIColor.systemGray, 300)
  ]
  
  var body: some View {
    ReorderableVStack($data) { $sample in
      RoundedRectangle(cornerRadius: 32, style: .continuous)
        .fill(Color(sample.color))
        .frame(height: sample.height)
        .padding()
    }
    .padding()
  }
}

使用集合和 onMove

struct SimpleExample: View {
  @State var data = [
    Sample(UIColor.systemBlue, 200),
    Sample(UIColor.systemGreen, 100),
    Sample(UIColor.systemGray, 300)
  ]
  
  var body: some View {
    ReorderableVStack(data, onMove: { from, to in
      withAnimation {
        data.move(fromOffsets: IndexSet(integer: from),
                  toOffset: (to > from) ? to + 1 : to)
      }
    }) { sample in
      RoundedRectangle(cornerRadius: 32, style: .continuous)
        .fill(Color(sample.color))
        .frame(height: sample.height)
        .padding()
    }
    .padding()
  }
}

具有自定义拖动手柄和拖动效果

struct SimpleExample: View {
  @State var data = [
    Sample(UIColor.systemBlue, 200),
    Sample(UIColor.systemGreen, 100),
    Sample(UIColor.systemGray, 300)
  ]
  
  var body: some View {
    ReorderableVStack($data) { $sample, isDragged in // <------ Notice the additional `isDragged` parameter
      ZStack(alignment: .leading) {
        RoundedRectangle(cornerRadius: 32, style: .continuous)
          .fill(Color(sample.color))
          .frame(height: sample.height)
        
        Image(systemName: "line.3.horizontal")
          .foregroundStyle(.secondary)
          .padding()
          .offset(x: 16)
          // This will now be the only place users can drag the view from
          .dragHandle() // <------------
      }
      .scaleEffect(isDragged ? 1.1: 1)
      .animation(.easeOut, value: isDragged)
      .padding()
    }.padding()
  }
}

当作为 ScrollView 的一部分时

警告

由于此软件包不依赖于 SwiftUI 的原生 onDrag,因此当用户将元素拖动到父/祖先 ScrollView 的边缘时,它也不会自动触发自动滚动。要启用此行为,需要将 autoScrollOnEdges() 修饰符应用于 ScrollView

struct SimpleExample: View {
  @State var data = [
    Sample(UIColor.systemBlue, 200),
    Sample(UIColor.systemGreen, 200),
    Sample(UIColor.systemGray, 300),
    Sample(UIColor.systemMint, 200),
    Sample(UIColor.systemPurple, 300),
    Sample(UIColor.orange, 200)
  ]
  
  var body: some View {  
    ScrollView {
      ReorderableVStack($data) { $sample in
        RoundedRectangle(cornerRadius: 32, style: .continuous)
          .fill(Color(sample.color))
          .frame(height: sample.height)
          .padding()
      }.padding()
    }.autoScrollOnEdges() // <------- This modifier enables the autoscrolling
  }
}

嵌套的 ReorderableHStackReorderableVStack

private struct Sample2D: Identifiable {
  var id: UUID = UUID()
  var row: [Sample]
}

struct SimpleExample: View {
  @State var data: [Sample2D] = [
    .init(row: [.init(UIColor.systemBlue, 200), .init(UIColor.systemGreen, 100), .init(UIColor.systemGray, 200)]),
    .init(row: [.init(UIColor.systemRed, 200), .init(UIColor.systemMint, 100), .init(UIColor.systemPurple, 200)]),
    .init(row: [.init(UIColor.systemIndigo, 200), .init(UIColor.systemTeal, 100), .init(UIColor.systemYellow, 200)]),
  ]

  var body: some View {
    ReorderableVStack($data) { $sample in
      HStack {
        ZStack {
          RoundedRectangle(cornerRadius: 24, style: .continuous)
            .fill(Color(UIColor.systemOrange))
            .frame(width: 64, height: 64)
            .padding()
         
          Image(systemName: "line.3.horizontal")
            .foregroundStyle(.secondary)
            .padding()
        }
        .dragHandle()
        
        ReorderableHStack($sample.row) { $sample in
          RoundedRectangle(cornerRadius: 24, style: .continuous)
            .fill(Color(sample.color))
            .frame(width: 64, height: 64)
            .padding()
        }
      }
    }
  }
}

版权和许可

版权所有 Vis Fitness Inc。在 MIT 许可证 下获得许可