首页 > 解决方案 > 如何在 SwiftUI 中使用绑定元素来更新 ForEach 而无需渲染所有 ForEach 数组?

问题描述

我有一个颜色数组,我使用这个数组多次渲染一个视图,这个数组的每个元素都有一个与绑定一起工作的视图的状态角色,所以我们有一个作为状态数组的数组,每个该数组的单个元素将用于提供需要绑定的视图,代码可以正常工作,但是对该数组的每一次小更新都会使 ForEach 渲染所有数组,这是不必要的,我想更正或修改我的代码以停止这种情况不必要的渲染!例如,当我将 1 个元素更改为 Color.black 时,SwiftUI 为该元素呈现一个新视图是可以理解的,但事实上我的代码使 SwiftUI 呈现所有数组!或者当我将新元素添加到数组末尾时,也会发生同样的事情!我怎么解决这个问题?谢谢。

PS:如果你认为indexid:.self会造成这个问题,我不得不说不,因为我必须而且我必须使用 index,因为 Binding 需要一个 State 对象,并且只能使用 index,我不能使用ForEach 的 item 版本,因为 Binding 无法更新它。

var randomColor: Color { return Color(red: Double.random(in: 0...1), green: Double.random(in: 0...1), blue: Double.random(in: 0...1)) }

struct BindingWay: View {
    
    @State private var arrayOfColor: [Color] = [Color]()
    
    var body: some View {
        
        
        VStack(spacing: 0) {
            
            ForEach(arrayOfColor.indices, id:\.self) { index in
                
                CircleViewBindingWay(colorOfCircle: $arrayOfColor[index])
                
            }
            
            Spacer()
            
            Button("append new Color") {
                
                arrayOfColor.append(randomColor)
                
            }
            .padding(.bottom)
            
            Button("update last element color to black") {
                
                
                if arrayOfColor.count > 0 {
                    
                    arrayOfColor[arrayOfColor.count - 1] = Color.black
                    
                }
                
            }
            .padding(.bottom)
            

        }
        .shadow(radius: 10)
        
        
    }
}

struct CircleViewBindingWay: View {
    
    @Binding var colorOfCircle: Color
    
    init(colorOfCircle: Binding<Color>) { print("initializing CircleView"); _colorOfCircle = colorOfCircle }
    
    var body: some View {
        
        print("rendering CircleView")
        
        return Circle()
            .fill(colorOfCircle)
            .frame(width: 50, height: 50, alignment: .center)
            .onTapGesture { colorOfCircle = colorOfCircle.opacity(0.5) }
        
        
    }
}

标签: swiftui

解决方案


以下作品:


struct CircleViewBindingWay: View {
    
    @Binding var colorOfCircle: Color
    
    init(colorOfCircle: Binding<Color>) { print("initializing CircleView"); _colorOfCircle = colorOfCircle }
    
    var body: some View {
        
        print("rendering CircleView")
        
        return Circle()
            .fill(colorOfCircle)
            .frame(width: 50, height: 50, alignment: .center)
            .onTapGesture { colorOfCircle = colorOfCircle.opacity(0.5) }
    }
}

extension CircleViewBindingWay : Equatable { //<-- here
    static func == (lhs: CircleViewBindingWay, rhs: CircleViewBindingWay) -> Bool {
        lhs.colorOfCircle == rhs.colorOfCircle
    }
}

struct ContentView: View {
    
    @State private var arrayOfColor: [Color] = [Color]()
    
    var randomColor: Color { return Color(red: Double.random(in: 0...1), green: Double.random(in: 0...1), blue: Double.random(in: 0...1)) }
    
    var body: some View {
        VStack(spacing: 0) {
            ForEach(arrayOfColor.indices, id: \.self) { index in
                CircleViewBindingWay(colorOfCircle: .init(get: { () -> Color in //<-- here
                    arrayOfColor[index]
                }, set: { (newValue) in
                    arrayOfColor[index] = newValue
                }))
            }
            Spacer()
            Button("append new Color") {
                arrayOfColor.append(randomColor)
            }
            .padding(.bottom)
            Button("update last element color to black") {
                if arrayOfColor.count > 0 {
                    arrayOfColor[arrayOfColor.count - 1] = Color.black
                }
            }
            .padding(.bottom)
        }
        .shadow(radius: 10)
    }
}

必须发生的事情:

  1. CircleViewBindingWay符合Equatable并检查颜色是否相同。ForEachequatable 检查本身,这就是为什么实际上.equatable()不需要附加

  2. Binding声明为内联。必须有另一个相等的检查ForEach/SwiftUI$arrayOfColor失败的检查,但是这个内联检查通过了。


推荐阅读