【问题标题】:SwiftUI access a view created in a ForEach loopSwiftUI 访问在 ForEach 循环中创建的视图
【发布时间】:2020-01-08 16:23:07
【问题描述】:

有没有办法访问在ForEach 循环中创建的视图?我在这个循环上用这个结构创建视图(Rectangles)。我想在点击手势时更改矩形的填充颜色。

struct DisplayingRect:Identifiable {

    var id = UUID()
    var width:CGFloat = 0
    var height:CGFloat = 0
    var xAxis:CGFloat = 0
    var yAxis:CGFloat = 0

    init(width:CGFloat, height:CGFloat, xAxis:CGFloat, yAxis:CGFloat) {
        self.width = width
        self.height = height
        self.xAxis = xAxis
        self.yAxis = yAxis
    }
}

ForEach(self.rects) { rect in
    Rectangle()
        .fill(Color.init(.sRGB, red: 1, green: 0, blue: 0, opacity: 0.2))
        .frame(width: rect.width, height: rect.height)
        .offset(x: rect.xAxis, y: rect.yAxis)
        .id(rect.id)
        .onTapGesture {
            print("Clicked")
            self.rectTapped = rect.width
            print(rect.width)
            print(rect.id)
            if !self.didTap {
                self.didTap = true
            } else {
                self.didTap = false
            }
         }

我可以为每个视图分配一个 id 设置其 id 属性,但我不知道它们存储在哪里或如何在单击时修改它们。我可以创建返回视图(矩形)的函数并将它们存储在一个数组中,然后在屏幕上显示它们,但是我不知道如何访问它们并修改我想要的。

【问题讨论】:

    标签: ios swift macos swiftui


    【解决方案1】:

    保留@State 以跟踪突出显示的索引,然后使您的颜色成为该状态的函数。这是一个动画示例:

    struct ContentView: View {
      @State private var selectedIndices = Set<Int>()
    
      var body: some View {
        ForEach (0..<3) { index in
          Color(self.selectedIndices.contains(index) ? .yellow : .blue)
            .frame(width: 200, height: 200)
            .animation(.easeInOut(duration: 0.25))
            .onTapGesture {
              if self.selectedIndices.contains(index) {
                self.selectedIndices.remove(index)
              } else {
                self.selectedIndices.insert(index)
              }
          }
        }
      }
    }
    

    【讨论】:

      【解决方案2】:

      你可以这样做:

      struct DisplayingRect:Identifiable, Hashable {
      
          static var counter = 0
      
          var id : Int = DisplayingRect.counter
      
          var width:CGFloat = 0
          var height:CGFloat = 0
          var xAxis:CGFloat = 0
          var yAxis:CGFloat = 0
          var color: Color = Color.red
      
          init(width:CGFloat, height:CGFloat, xAxis:CGFloat, yAxis:CGFloat) {
              self.width = width
              self.height = height
              self.xAxis = xAxis
              self.yAxis = yAxis
              DisplayingRect.counter = DisplayingRect.counter + 1
          }
      }
      
      struct ContentView : View {
      
          @State var rects  : [DisplayingRect] = [
          DisplayingRect(width: 30, height: 30, xAxis: 0, yAxis: 0),
          DisplayingRect(width: 50, height: 50, xAxis: 50, yAxis: 50)
          ]
      
          func setColorToID(_ id: Int) {
              rects[id].color = Color.blue
          }
      
          var body: some View {
      
              ForEach(self.rects, id: \.self) { rect in
                  Rectangle()
                      .fill(rect.color)
                      .frame(width: rect.width, height: rect.height)
                      .offset(x: rect.xAxis, y: rect.yAxis)
                      .id(rect.id)
                      .onTapGesture {
                          print(rect.id)
                          self.setColorToID(rect.id)
                  }
              }
          }
      }
      

      【讨论】:

        【解决方案3】:

        SwiftUI 鼓励声明式方法——您不应该(实际上也不能)直接访问任何视图来存储对它的引用。可以为您的视图提供数据,并且只要该数据发生更改,它们就会更新。

        在这种情况下,您可以让您的DisplayingRect 存储一个颜色属性,然后让每个Rectangle 上的点击手势按ID 在您的rects 数组中查找正确的结构,然后修改颜色属性。

        要从您的视图中分离出逻辑并使更多的单元可测试,您可能希望创建某种包含此的视图模型类,但将其全部放在您的视图中会没有这些好处。

        这种方法可能看起来像这样(在本地测试和工作):

        struct DisplayingRect: Identifiable {
            let id = UUID()
            var color = Color.red
            var width: CGFloat
            var height: CGFloat
            var xAxis: CGFloat
            var yAxis: CGFloat
        
            init(
                width: CGFloat,
                height: CGFloat,
                xAxis: CGFloat = 0,
                yAxis: CGFloat = 0)
            {
                self.width = width
                self.height = height
                self.xAxis = xAxis
                self.yAxis = yAxis
            }
        }
        
        final class ContentViewModel: ObservableObject {
            @Published
            private(set) var rects: [DisplayingRect] = [
                .init(width: 100, height: 100),
                .init(width: 100, height: 100),
                .init(width: 100, height: 100)
            ]
        
            func didTapRectangle(id: UUID) {
                guard let rectangleIndex = rects.firstIndex(where: { $0.id == id }) else {
                    return
                }
        
                rects[rectangleIndex].color = .blue
            }
        }
        
        struct ContentView: View {
            @ObservedObject
            var viewModel = ContentViewModel()
        
            var body: some View {
                VStack {
                    ForEach(viewModel.rects) { rect in
                        Rectangle()
                            .fill(rect.color)
                            .frame(width: rect.width, height: rect.height)
                            .offset(x: rect.xAxis, y: rect.yAxis)
                            .onTapGesture {
                                self.viewModel.didTapRectangle(id: rect.id)
                            }
                    }
                }
            }
        }
        

        在这种情况下,@ObservedObject 属性包装器和ObservableObject 协议允许视图在其使用来自viewModel 的数据发生更改时自行更新。为了自动通知应该导致视图刷新的属性,使用了@Published 属性包装器。

        https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-observedobject-to-manage-state-from-external-objects

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-02-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多