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
相关产品推荐
相关产品推荐

