You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI图片切换卡顿问题的解决方法咨询

SwiftUI图片组切换卡顿问题:CPU占用100%,首次滑动卡顿

问题背景

我实现了一个SwiftUI视图,包含4张本地图片,通过切换选中状态更换图片组,功能运行正常,但切换时严重卡顿/冻结,模拟器CPU直接拉满100%并持续数秒。所有图片已压缩至0.5-1.5MB。

排查过程

  • 最初猜测卡顿由动画导致,删除动画后问题依旧。推测是同时渲染4张图片对CPU负载过高,想在视图创建时预加载图片,但不知道如何结合本地SwiftData实现。
  • 后续更新代码至3组图片,调整动画参数withAnimation(Animation.linear(duration: 0.1).delay(0))后,组间卡顿问题解决,但首次滑动仍存在特殊卡顿;将延迟调整至1秒可修复该问题,但视觉效果较差。

相关代码

第一版(2组图片)

//Same Extension
struct ContentView:View {
    let set1 = ["pic1", "pic2", "pic3","pic4"]
    let set2 = ["pic5", "pic6", "pic7","pic8"]
    @State private var useSet1 = true
    @State private var position: Int?

    
    var body: some View {
        ZStack{
            RoundedRectangle(cornerRadius: 18)
                .foregroundStyle(Color.gray)
                .frame(width: 380,height: 440)
            ScrollView(showsIndicators: false){
                LazyVStack(spacing:0){
                    Text("Text Here for pic Set 1")
                    .padding(.top,10)
                    .padding(.leading,15)
                    .padding(.trailing,20)
                    .frame(width: 380,height: 440, alignment: .topLeading)
                    .id(1)
                    Text("Text Here for pic Set 2")
                    .padding(.top,10)
                    .padding(.leading,15)
                    .padding(.trailing,20)
                    .frame(width: 380,height: 440, alignment: .topLeading)
                    .id(2)
                }
                .scrollTargetLayout()
            }
            .scrollTargetBehavior(.paging)
            .scrollPosition(id: $position)
            .onChange(of: position) {
                withAnimation{
                    useSet1.toggle()
                }
            }
        }
        .frame(width: 380,height: 440)
        
        HStack{
            VStack{
                Image(useSet1 ? set1[0] : set2[0])
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                Image(useSet1 ? set1[1] : set2[1])   
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 200)
                    .clipped()
                    .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                    .padding(.top,10)

            }
            .padding(.trailing,5)
            VStack{
                Image(useSet1 ? set1[2] : set2[2])
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 200)
                    .clipped()
                    .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                    .padding(.bottom,5)
                Image(useSet1 ? set1[3] : set2[3])
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                    .padding(.top,5)
            }
            .padding(.leading,5)
        }
        .padding(15)
        .frame(width: 450, height: 440)
        .background(Color.gray)
        .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
    }
}

更新版(3组图片)

struct ContentView: View {
    let set1 = ["pic1", "pic2", "pic3", "pic4"]
    let set2 = ["pic6", "pic5", "pic7", "pic8"]
    let set3 = ["pic1", "pic2", "pic3", "pic4"]
    @State private var useSet = 1 
    @State private var position: Int?
    @State private var isAnimating = false
    @State private var oldPosition: Int?
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 18)
                .foregroundStyle(Color.gray)
                .frame(width: 380, height: 440)
            ScrollView(showsIndicators: false) {
                LazyVStack(spacing: 0) {
                    ForEach(1...3, id: \.self) { setId in
                        Text("Text Here for pic Set \(setId)")
                            .padding(.top, 10)
                            .padding(.leading, 15)
                            .padding(.trailing, 20)
                            .frame(width: 380, height: 440, alignment: .topLeading)
                            .id(setId)
                    }
                }
                .scrollTargetLayout()
            }
            .scrollTargetBehavior(.paging)
            .scrollPosition(id: $position)
            .customOnChange(value: position) { newValue, oldValue in
                guard let oldPosition else {
                    self.oldPosition = newValue
                    return
                }
                if let oldValue = oldValue {
                    print("iOS 17+")
                    if isAnimating {
                        self.position = oldValue ?? 0
                        return
                    }
                } else {
                    print("iOS 16")
                    if isAnimating {
                        self.position = oldPosition
                        return
                    }
                }
                guard !isAnimating else { return }
                isAnimating = true
                self.oldPosition = newValue ?? 0
                let duration: Double = 0.3
                withAnimation(.linear(duration: duration)) {
                    useSet = newValue ?? 1
                }
                let delay: Double = duration + 0.05
                DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
                    isAnimating = false
                }
            }
        }
        .frame(width: 380, height: 440)
        .padding(.top, 100)
        
        HStack {
            VStack {
                Image(currentSet()[0])
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                Image(currentSet()[1])
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 200)
                    .clipped()
                    .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                    .padding(.top, 10)
            }
            .padding(.trailing, 5)
            VStack {
                Image(currentSet()[2])
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 200)
                    .clipped()
                    .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                    .padding(.bottom, 5)
                Image(currentSet()[3])
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                    .padding(.top, 5)
            }
            .padding(.leading, 5)
        }
        .padding(15)
        .frame(width: 450, height: 440)
        .background(Color.gray)
        .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
    }
    func currentSet() -> [String] {
            switch useSet {
            case 1:
                return set1
            case 2:
                return set2
            case 3:
                return set3
            default:
                return set1
            }
        }
}

需求

求解决首次滑动卡顿的方案,或者结合本地SwiftData的正确图片预加载方式。


内容的提问来源于stack exchange,提问作者Matteo Tripolt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 03:37:05