重新捡起这个项目。
系列文章
在尝试用AI辅助我写一个类抖音的APP(一)中探索了AI不能一下把APP的所有功能实现出来。
在尝试用AI辅助我写一个类抖音的APP(二)中探索和实现了核心的滑动翻页部分。
尝试用AI辅助我写一个类抖音的APP(三) 记录了在用LazyVStack下大量View会全部调用它们的init和对body求值的问题
这一期记录一个实现翻页情况下,滑动的过程是一个颜色,滑动完后页面又重新布局了,换了另外一个颜色。
原始代码
Content页面
import SwiftUI
var isLanguageChinese = false
struct ContentView: View {
@State private var currentPage = 0
@GestureState private var translation: CGFloat = 0
@StateObject var skins : SuitListModel = SuitListModel(sex: .boy)
@State var frozen: Bool = false
@State var refreshState: MtRefreshState = .initial
var body: some View {
GeometryReader { geometry in
let screenHeight = geometry.size.height
let threshold = screenHeight * 0.1 //这里把0.2改成0.1更顺畅了
ScrollView(.vertical, showsIndicators: false) {
// ZStack(alignment: .top ){
// RefreshHeader(height: 70,
// loading: true,
// frozen: true,
// rotation: Angle(degrees: 0),
// updateTime: Date.now,
// freshState: .pulling)
LazyVStack(spacing: 0) {
ForEach(0..<100){ i in
//如果index和currentPage相差大于2,就不显示showSceneView
Skin3DView(index: i, currentPage: $currentPage)
.frame(height: screenHeight)
}
// ForEach(skins.list, id: \.uuid) { s in
// SuitDetailView(suit: s)//.environment(\.colorScheme, .dark)
// .environmentObject(ImageModel(img: s.img, armpx: s.armpx))
// .frame(height: screenHeight)
// }
// }
// RefreshHeader(height: 70,
// loading: true,
// frozen: true,
// rotation: Angle(degrees: 0),
// updateTime: Date.now,
// freshState: .pulling)
// .alignmentGuide(.top, computeValue: { viewDimensions in
//
// return viewDimensions[.bottom] - 140
// })
}
}
.content.offset(y: -CGFloat(self.currentPage) * screenHeight)//定位到整页位置,实现翻页
.offset(y: self.translation) //在没有翻页前的跟随手指的滑动效果
// .animation(.interactiveSpring(), value: self.translation)
.animation(.interactiveSpring(), value: self.currentPage)
.gesture(
DragGesture()
.updating(self.$translation) { value, state, _ in
//判断第一页下拉刷新,
//如果进入刷新状态, 且下拉超过 70的距离, 那么进入固定frozen状态
//如果这是松开手,那么开启刷新,triger状态
//刷新完毕,回到初始状态
//判断是否第一页下拉(触发刷新)
// refreshState = stateMachineRefresh(currentState: refreshState, gesture: "pull", isFirstPage: currentPage == 0, translation: value.translation.height)
// if refreshState == .full{
// state = 70
// }else{
// state = value.translation.height
// }
state = value.translation.height
}
.onEnded { value in
let offset = value.translation.height
//状态机判断
refreshState = stateMachineRefresh(currentState: refreshState, gesture: "end", isFirstPage: currentPage == 0, translation: offset)
//如果滑动超过
if abs(offset) > threshold {
if offset < 0 && self.currentPage < 999 {//上滑,翻下一页
self.currentPage += 1
}else if offset > 0 && self.currentPage > 0{
self.currentPage -= 1
}
}
}
)
//接收到json皮肤更新
// .onReceive(NotificationCenter.default.publisher(for: Notification.Name("jsonupdated"))){
// data in
// mtlog.info("receive jsonupdated")
//// skins.reload()
// }
}.edgesIgnoringSafeArea(.all)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Skin3DView
import SwiftUI
import SceneKit
struct Skin3DView: View {
var index: Int//属于第几个索引页
@Binding var currentPage: Int //受限于同时只能显示32个SceneView,设置这个变量来控制同时显示的SceneView数量
init(index: Int, currentPage: Binding<Int>){
self.index = index
// mtlog.info("Skin3DView init() \(index)")
//初始化showSceneView
self._currentPage = currentPage
}
var body: some View {
// ZStack{
// let _ = mtlog.info("Skin3DView body \(index)")
// Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
//添加背景,颜色随机
Rectangle()
.foregroundColor(Color.randomColor())
.edgesIgnoringSafeArea(.all)
// if abs(currentPage - index) < 2{
// SceneView(scene: SCNScene(named: "3dmodel.scnassets/alex20220426.dae"), options: .autoenablesDefaultLighting)
// }
// }
}
}
extension Color {
static func randomColor() -> Color {
return Color(red: Double.random(in: 0...1),
green: Double.random(in: 0...1),
blue: Double.random(in: 0...1))
}
}
struct Skin3DView_Previews: PreviewProvider {
static var previews: some View {
Skin3DView(index: 1, currentPage: .constant(1))
}
}
寻找原因
为什么滑动完一整页后,页面会被重新渲染呢
问ChatGPT
再问
分页滚动过程中和滚动完成后,为什么页面的颜色变了
ChatGPT回答
页面的颜色在分页滚动过程中和滚动完成后发生变化,通常是由以下原因之一导致的:
1. **页面背景色的条件渲染**:代码中可能存在基于当前页面状态来改变背景颜色的逻辑。例如,在不同页面或者根据页面滚动的方向、位置来设置不同的背景颜色。
2. **视图的初始化或状态重置**:在视图重新绘制时(例如滚动到新的页面),可能会重新初始化或者更新某些属性,导致颜色改变。比如,使用 `@State`、`@Binding` 或 `@Environment` 时,视图的属性可能根据状态变化自动更新。
3. **动画或过渡效果**:某些 SwiftUI 组件(比如 `ScrollView`)在滚动时可能带有动画或过渡效果,可能通过过渡效果将颜色从一种状态变为另一种。
4. **3D渲染的影响**:如果你在3D视图中使用了特殊的渲染效果、着色器或光照,它们可能会在视角或场景改变时引起颜色的变化。
你可以检查 `Skin3DView` 和 `ContentView` 中的代码,看看是否存在上述任何逻辑。
它给出了通用的回答,没有结合我的代码来说,所以我感觉没有回答到点子上。 继续问
swiftui中为什么body这个也会不停的执行呢
ChatGPT回答
在 SwiftUI 中,视图的 `body` 属性会频繁重新计算,这是 SwiftUI 响应式更新机制的结果。这个机制基于 SwiftUI 声明式编程范式和状态驱动的设计。
`body` 属性频繁执行的原因:
1. **状态改变**:当使用 `@State`、`@Binding`、`@ObservedObject` 或 `@EnvironmentObject` 等修饰符的属性发生改变时,SwiftUI 会重新计算依赖这些属性的视图 `body`,从而触发视图更新。
2. **父视图的重绘**:如果某个视图的父视图发生了重绘,那么 SwiftUI 会重新计算子视图的 `body`。这种重绘可能由父视图的状态改变或者外部环境的变化导致。
3. **动画**:动画会引起视图的不断重绘,因为 SwiftUI 需要根据动画的进度来逐帧更新视图。
如何优化:
虽然 `body` 频繁执行是 SwiftUI 的设计初衷,但可以通过以下方式进行优化:
1. **避免不必要的状态更新**:尽量将状态和逻辑封装在特定视图中,减少全局状态的使用。
2. **优化子视图**:将 `body` 方法复杂度高的视图拆分成更小的组件,并将状态最小化。
3. **`@ViewBuilder` 和 `@View` 优化**:通过使用 `@ViewBuilder` 和 `@View` 减少子视图的重绘。
总之,虽然 SwiftUI 中 `body` 方法频繁执行是正常现象,但可以通过优化状态管理和视图结构来减少不必要的性能开销。
这个也是通用的回答,没有结合代码来说,可能也是我的问法不对。
直到我看了这篇文章,避免 SwiftUI 视图的重复计算
通过查看代码, 颜色背景页Skin3DView的参数里就有binding类型的currentPage这个参数。
struct Skin3DView: View {
var index: Int//属于第几个索引页
@Binding var currentPage: Int //受限于同时只能显示32个SceneView,设置这个变量来控制同时显示的SceneView数量
init(index: Int, currentPage: Binding<Int>){
self.index = index
// mtlog.info("Skin3DView init() \(index)")
//初始化showSceneView
self._currentPage = currentPage
}
currentPage这个参数在Content里,滑动一页结束的时候,被修改了
.onEnded { value in
let offset = value.translation.height
//状态机判断
refreshState = stateMachineRefresh(currentState: refreshState, gesture: "end", isFirstPage: currentPage == 0, translation: offset)
//如果滑动超过
if abs(offset) > threshold {
if offset < 0 && self.currentPage < 999 {//上滑,翻下一页
self.currentPage += 1 //****在这里被修改了
}else if offset > 0 && self.currentPage > 0{
self.currentPage -= 1 //****在这里被修改了
}
}
}
也就是像这种参数被修改后,会导致Skin3DView的重绘,不管实际页面是否用到currentPage,都会重新求值Skin3DView的body属性,所以颜色就变了。
解决
知道原因后,就可以针对性地找解决方案了。把Skin3DView的binding参数去掉
struct Skin3DView: View {
var index: Int//属于第几个索引页
// @Binding var currentPage: Int //受限于同时只能显示32个SceneView,设置这个变量来控制同时显示的SceneView数量
init(index: Int){
self.index = index
// mtlog.info("Skin3DView init() \(index)")
//初始化showSceneView
// self._currentPage = currentPage
}
var body: some View {
ZStack{
// let _ = mtlog.info("Skin3DView body \(index)")
// Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
//添加背景,颜色随机
Rectangle()
.foregroundColor(Color.randomColor())
.edgesIgnoringSafeArea(.all)
// if abs(currentPage - index) < 2{
// SceneView(scene: SCNScene(named: "3dmodel.scnassets/alex20220426.dae"), options: .autoenablesDefaultLighting)
// }
}
}
}
看看效果 可以看到,这时候滑动到完整一页。它都不会临时变色了。
后记
其实这个问题问ChatGPT的时候,它的回答也没有错:的确是binding类型的变量导致了View重绘,但有个细节没说到就是不论是否View是否用到这个binding变量,这个View都会重绘。只是我潜意识地怀疑它会答非所问,给不出正确答案,忽略了它的回答。当然它的回答没有结合代码来说也是很大的问题。
这个背景色会变只是开发过程中的一个疑问, 至于最终是否要引入currentPage 变量,还是要看实际情况。不引入currentPage变量,目前还没想到怎么解决尝试用AI辅助我写一个类抖音的APP(三)中的LazyVStack里大量3d View的性能问题。