SwiftUI视图性能分析工具

246 阅读4分钟

Xcode提供了一个新的SwiftUI模板:SwiftUI分析工具,用于跟踪视图body的调用、DynamicView属性随时间的更新以及识别较慢的帧。

1. 打开Profile。

image.png

2. 选择SwiftUI。

image.png

3. 启动模拟器。

image.png

4. 启动后,时间轴面板中会显示body调用的实时分析。

image.png

5. 点击停止按钮,关闭模拟器。

从列表可以看出分析工具对视图body的调用、视图的属性、动画的提交以及时间的开销分析和统计。

image.png 6. 将统计信息从上往下看,在body属性里花费7.34微秒,创建了ScrollView组成。由于动画的原因,属性更新了5次。CPU使用率为51%。 image.png 7. 由于点击了界面顶部的标签,所以显示使用24.93微妙来刷新TopMenuView,相关的视图为 Button,更新了6次的属性,CPU使用率为60%。 image.png 8. 在视图列表中,显示了界面里的各视图的数量和消耗的性能。例如总共使用了24个Button,共花费486.04微秒。点击下拉箭头,显示项目里的自定义视图列表。 image.png 9. 此时显示了您自定义的视图的列表,例如共在界面中使用了8个自定义的CircleView,共花费83.44微秒。 image.png 10. 点击此处的箭头,显示项目里的自定义视图列表。 image.png 11. 从列表可以看出视图创建的顺序、花费的时间。最先创建ContentView,共花费130.28微秒。接着创建CountryListView,花费116.19微秒。 image.png 12. 点击此处的时序摘要命令,返回原来的时序列表。 image.png 13. 点击Slow.body标签,显示body里的渲染相对较慢的视图。 image.png 14. 在时间轴上点击,查看此处的视图信息。 image.png 15. 从弹出的信息摘要可以看出,TopMenuView在此处的时间段里渲染较慢,共花费24.93微秒, CPU使用率为120%。接着点击查看渲染较慢的帧。 image.png 16. 从时间轴里可以识别渲染较慢的帧,点击查看此处的帧信息。 image.png 17. 从弹出的信息摘要可以看出,DemoProject的初始化影响了帧速,共花费59.08毫秒,CPU使用率为182%。 image.png 18. 点击此处的选项,可以查看在播放动画时,视图属性的刷新。 image.png 19. 在时间轴上点击,查看此时段的视图属性的刷新。 image.png 20. 在项目上点击鼠标右键,可以打开上下文菜单。 image.png 21. 选择此处的设置检查范围和缩放命令,可以查看状态变更的顺序和数值。 image.png 22. 在时间轴上点击,查看此处属性的变化。 image.png 23. 从信息摘要可以看出,在此时段的状态变化是:CircleView的一个属性的值由false变为true。在完成项目的众多因素的分析之后,点击此处的关闭按钮。 image.png 24. 最后点击此处的不要保存按钮,退出分析工具界面。 image.png

源码:

import SwiftUI

struct ContentView : View
{
    @State var isAnimating = false
    
    var body: some View
    {
        ZStack
        {
            Rectangle()
                .fill(LinearGradient(gradient: Gradient.init(colors: [Color.init(red: 55/255, green: 67/255, blue: 109/255),Color.init(red: 54/255, green: 98/255, blue: 127/255)]), startPoint: .top, endPoint: .bottom))
                .edgesIgnoringSafeArea(.all)
            
            VStack
            {
                TopBarView()
                    .opacity(isAnimating ? 1 : 0)
                    .animation(Animation.spring().delay(0))
                
                SplitterView()
                    .opacity(isAnimating ? 1 : 0)
                    .animation(Animation.spring().delay(0.2))
                
                TopMenuView()
                    .opacity(isAnimating ? 1 : 0)
                    .animation(Animation.spring().delay(0.4))
                
                PieChartView()
                    .opacity(isAnimating ? 1 : 0)
                    .animation(Animation.spring().delay(0.6))
                
                CountryListView()
                    .opacity(isAnimating ? 1 : 0)
                    .animation(Animation.spring().delay(0.8))
            }
        }
        .onAppear
        {
            self.isAnimating.toggle()
        }
    }
}

struct CircleView: View
{
    var diameter = chartWidth
    var color = activeColor
    var startPoint : CGFloat = 0
    var endPoint : CGFloat = 0.5
    var angle = -45.0
    
    @State var isAnimation = false
    
    var body: some View
    {
        ZStack
        {
            Circle()
                .stroke(lineWidth: 2)
                .fill(inactiveColor)
                .frame(width:diameter, height:diameter)
            
            Circle()
                .trim(from: isAnimation ? startPoint : 0, to: isAnimation ? endPoint : 1)
                .stroke(lineWidth: 4)
                .fill(color)
                .frame(width:diameter, height:diameter)
                .rotationEffect(.degrees(isAnimation ? angle : -720))
                .animation(.easeInOut(duration: 3))
                .onAppear {
                    self.isAnimation.toggle()
                }
        }
    }
}

struct TopBarView: View
{
    var body: some View
    {
        HStack
        {
            Image(systemName: "list.bullet.indent")
            
            Spacer()
            
            Text("STATISTICS")
                .bold()
            
            Spacer()
            
            Image(systemName: "magnifyingglass")
        }
        .padding(.leading,30)
        .padding(.trailing,30)
        .foregroundColor(.white)
    }
}

struct TopMenuView: View
{
    @State private var currentIndex : Int = 1
    
    var body: some View
    {
        VStack
        {
            HStack
            {
                Button(action:
                {
                    self.currentIndex = 1
                }){
                    Text("TODAY")
                        .font(.system(size: regularFontSize))
                        .frame(width: menuWidth, height: 40)
                        .foregroundColor(self.currentIndex == 1 ? activeColor : regularColor)
                }
                Button(action:
                {
                    self.currentIndex = 2
                }){
                    Text("WEEK")
                        .font(.system(size: regularFontSize))
                        .frame(width: menuWidth, height:40)
                        .foregroundColor(self.currentIndex == 2 ? activeColor : regularColor)
                }
                Button(action:
                {
                    self.currentIndex = 3
                }){
                    Text("MONTH")
                        .font(.system(size: regularFontSize))
                        .frame(width: menuWidth, height:40)
                        .foregroundColor(self.currentIndex == 3 ? activeColor : regularColor)
                }
                Button(action:
                {
                    self.currentIndex = 4
                }){
                    Text("ALL TIME")
                        .font(.system(size: regularFontSize))
                        .frame(width: menuWidth, height:40)
                        .foregroundColor(self.currentIndex == 4 ? activeColor : regularColor)
                }
            }
            
            ZStack(alignment: .leading)
            {
                SplitterView()
                Rectangle()
                    .fill(activeColor)
                    .frame(width: UIScreen.main.bounds.width/4, height:1)
                    .offset(x: menuWidth * CGFloat(currentIndex-1), y: 0)
                    .animation(.spring())
            }
        }
    }
}

struct PieChartView: View
{
    var body: some View
    {
        ZStack
        {
            CircleView()
            CircleView(diameter: chartWidth - 40, color: .purple, startPoint: 0, endPoint: 0.4, angle: 70)
            CircleView(diameter: chartWidth - 80, color: .green, startPoint: 0, endPoint: 0.3, angle: 190)
            CircleView(diameter: chartWidth - 120, color: .yellow, startPoint: 0, endPoint: 0.2, angle: 135)
            VStack
            {
                Text("1383")
                    .font(.system(size: 32))
                Text("Visits")
                    .font(.system(size: 14))
                    .foregroundColor(Color.white.opacity(0.5))
                    
            }
            .foregroundColor(.white)
        }
        .padding(.top, 30)
        .padding(.bottom, 30)
    }
}

struct CountryListView: View
{
    @State var isAnimating = false
    
    var body: some View
    {
        ScrollView
        {
            VStack
            {
                ForEach(0 ..< 7)
                { i in
                    VStack
                    {
                        HStack
                        {
                            Circle()
                                .fill(colors[i])
                                .frame(width: 10, height: 10)
                            Text(titles[i])
                            Spacer()
                            Text("\(visits[i])")
                        }
                        .padding(.leading, 30)
                        .padding(.trailing, 30)
                        .padding(.top, 10)
                        .padding(.bottom, 10)
                        .foregroundColor(.white)
                        .opacity(self.isAnimating ? 1 : 0)
                        .animation(Animation.spring().delay(Double(i) * 0.2 + 0.8))
                        
                        Rectangle()
                            .fill(inactiveColor)
                            .frame(width:UIScreen.main.bounds.width - 40, height:1)
                    }
                }
            }
            .onAppear
            {
                self.isAnimating.toggle()
            }
        }
    }
}

struct SplitterView: View
{
    var body: some View
    {
        Rectangle()
            .fill(inactiveColor)
            .frame(width: UIScreen.main.bounds.width, height:1)
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider
{
    static var previews: some View
    {
        ContentView()
    }
}
#endif