SwiftUI 中自定义网格视图的额外不必要渲染问题

Posted

技术标签:

【中文标题】SwiftUI 中自定义网格视图的额外不必要渲染问题【英文标题】:Extra unneeded rendering issue with Custom Grid View in SwiftUI 【发布时间】:2021-12-26 23:00:58 【问题描述】:

我在不使用 Apple Grid 的情况下制作 CustomGridView,我的代码运行良好,但是当我添加新行时它有一个额外的渲染问题!只要我在我的代码中看到,如果我添加新行,Xcode 应该只为新项目渲染!但事实上它渲染了所有的项目!我找不到问题!如果我添加新列,Xcode 会渲染所有项目,我没问题,因为大小会影响每个项目,但是添加行后,对项目没有一般影响!

import SwiftUI

struct GridItemView: View 

    let rowItem: GridItemRowType
    let columnItem: GridItemColumnType
    let string: String
    let size: CGFloat
    @State private var color: Color = Color.randomColor

    init(rowItem: GridItemRowType, columnItem: GridItemColumnType, size: CGFloat) 
        self.rowItem = rowItem
        self.columnItem = columnItem
        self.size = size
        self.string = "(" + String(describing: rowItem.index) + "," + String(describing: columnItem.index) + ")"
    

    var body: some View 
        
        print("rendering for:", string)
        
        return RoundedRectangle(cornerRadius: 10.0)
            .fill(color)
            .padding(5.0)
            .frame(width: size, height: size)
            .overlay(
                
                Text(string)
                    .bold()
                    .foregroundColor(Color.white)
                    .shadow(color: .black, radius: 2, x: 0.0, y: 0.0)
                
            )
            .onTapGesture 
                color = Color.white
            
            .shadow(radius: 20.0)
        
    



extension Color 

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



struct CustomGridView: View 

    let gridItemColumn: [GridItemColumnType]
    let gridItemRow: [GridItemRowType]

    var body: some View 

        GeometryReader  proxy in
            
            let size: CGFloat = (proxy.size.width - 5.0)/CGFloat(gridItemColumn.count)
            
            ScrollView 

                VStack(spacing: .zero) 

                    ForEach(gridItemRow)  rowItem in

                        HStack(spacing: .zero) 

                            ForEach(gridItemColumn)  columnItem in

                                GridItemView(rowItem: rowItem, columnItem: columnItem, size: size)
                                
                            
                            
                        
                        .transition(AnyTransition.asymmetric(insertion: AnyTransition.move(edge: .top), removal: AnyTransition.move(edge: .top)))
                        
                    
                    
                    Spacer()
                    
                
                .transition(AnyTransition.asymmetric(insertion: AnyTransition.move(edge: .trailing), removal: AnyTransition.move(edge: .trailing)))
                
            
            .position(x: proxy.size.width/2.0, y: proxy.size.height/2.0)
        
        .animation(Animation.default, value: [gridItemRow.count, gridItemColumn.count])

        
    




struct GridItemColumnType: Identifiable 
    
    let id: UUID = UUID()
    var index: Int
    
    static let initializingGridItemColumn: [GridItemColumnType] = [GridItemColumnType(index: 0),
                                                                   GridItemColumnType(index: 1),
                                                                   GridItemColumnType(index: 2),
                                                                   GridItemColumnType(index: 3),
                                                                   GridItemColumnType(index: 4)]



struct GridItemRowType: Identifiable 
    
    let id: UUID = UUID()
    var index: Int
    
    static let initializingGridItemRowType: [GridItemRowType] = [GridItemRowType(index: 0),
                                                                 GridItemRowType(index: 1),
                                                                 GridItemRowType(index: 2)]



struct ContentView: View 

    @State private var gridItemColumn: [GridItemColumnType] = GridItemColumnType.initializingGridItemColumn
    @State private var gridItemRow: [GridItemRowType] = GridItemRowType.initializingGridItemRowType

    var body: some View 

        CustomGridView(gridItemColumn: gridItemColumn, gridItemRow: gridItemRow)

        Spacer()
        
        Button("add Column")  gridItemColumn.append(GridItemColumnType(index: gridItemColumn.count)) 
        
        Button("add Row")  gridItemRow.append(GridItemRowType(index: gridItemRow.count)) 
        
    


【问题讨论】:

【参考方案1】:

您需要使 cell 视图可相等,以便 SwiftUI 知道是否应该重新渲染它。

使用 Xcode 13.2 / ios 15.2 测试

struct GridItemView: View, Equatable 
    // The `string` property is used just for demo, in real you 
    // need some unique identifier which represents that view
    // has same content
    static func == (lhs: GridItemView, rhs: GridItemView) -> Bool 
        lhs.string == rhs.string
    

// ... other code

然后在网格内部将其用作

ForEach(gridItemColumn)  columnItem in

    EquatableView(content: GridItemView(rowItem: rowItem, 
                                     columnItem: columnItem, size: size))


【讨论】:

我对我的方法几乎感到失望,但多亏了你,它正在发挥作用。我使用了您的代码并更新为 static func == (lhs: GridItemView, rhs: GridItemView) -> Bool return (lhs.string == rhs.string) && (lhs.size == rhs.size) 现在它也适用于列。

以上是关于SwiftUI 中自定义网格视图的额外不必要渲染问题的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI WidgetKit 占位符中自定义占位符外观?

SwiftUI:如何在 MapKit 中自定义地理区域的位置?

如何在 SwiftUI 中自定义角度变化的动画

如何在 SwiftUI 中自定义 Slider 蓝线?

如何获取 AlertDialog 中自定义列表视图的单击项的值?

在 SwiftUI WebView 中自定义内容(添加页脚和页眉注释)