尝试用AI辅助我写一个类抖音的APP(二)

尝试用AI辅助我写一个类抖音的APP(一)中想让AI帮我写一个完整的APP,不现实。那么接下来,还是一步一步来吧。一口吃不成一个胖子,这点在AI里也是成立的。

探索核心部分,上下滑动翻页

最核心的功能就是滑动翻页了,于是,继续探索。上一篇尝试用AI辅助我写一个类抖音的APP(一)中Claude的回答相对靠谱点,接着问Claude:

txt 复制代码
上面的代码漏了这个功能 1.向上滑动翻页,每页一个皮肤

Claude答:

swift 复制代码
您提醒得对,我漏掉了翻页功能。可以通过以下方式实现:

使用@State变量记录当前显示的皮肤索引currentPage
用TabView包住Skin3DView,在TabView里遍历skins数组展示每个皮肤
给TabView添加.tabViewStyle(.page(indexDisplayMode: .never))使其支持滑动翻页
在TabView上添加.gesture(DragGesture().onChanged{当前页码 += delta.translation.height / UIScreen.main.bounds.height})来通过拖拽手势更新currentPage
修改后的代码如下:


struct SkinView: View {
  @State private var skins = [Skin]()
  
  @State var currentPage = 0

  var body: some View {
    TabView(selection: $currentPage) {
      ForEach(skins, id: \.self) { skin in
        Skin3DView(skin: skin)
          .rotationGesture(for: .horizontal)
          .disabled(for: .vertical)
      }
    }
    .tabViewStyle(.page(indexDisplayMode: .never))
    .gesture(
      DragGesture()
        .onChanged { 
          self.currentPage -= Int($0.translation.height / UIScreen.main.bounds.height)
        }  
    )
    
    // 其他代码不变
  }
}
这样就可以通过上下拖拽TabView来实现翻页了。请检查是否符合您的要求,需要我解释或修改的地方请指出,谢谢!

问ChatGPT,给出的翻页实现同样是TabView方法。ChatGPT的上下文见尝试用AI辅助我写一个类抖音的APP(一) 问:

txt 复制代码
这部分实现滑动翻页,不能做到每次翻一整页,请修改完善代码。'''struct ContentView: View { @StateObject var skinViewModel = SkinViewModel() var body: some View { VStack { ScrollView { ForEach(skinViewModel.skins, id: .self) { skin in SkinView(skin: skin) } } AdView() } .onAppear { skinViewModel.fetchSkins() } } }'''

答:

swift 复制代码
对于实现滑动翻页(每次滑动显示一个新的页面),我们可以使用 TabView 代替 ScrollView。每个 SkinView 可以作为一个独立的页面。在 TabView 的 tabViewStyle 参数中,使用 PageTabViewStyle 来实现页面翻页的效果。

以下是修改后的 ContentView:

struct ContentView: View {
    @StateObject var skinViewModel = SkinViewModel()
    var body: some View {
        VStack {
            TabView {
                ForEach(skinViewModel.skins, id: \.self) { skin in
                    SkinView(skin: skin)
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            AdView()
        }
        .onAppear {
            skinViewModel.fetchSkins()
        }
    }
}
这样,每次滑动都会显示一个新的 SkinView 页面,实现了翻页效果。

注意,PageTabViewStyle 默认会显示一个页面指示器(小点),如果你不想显示它,可以设置 indexDisplayMode 为 .never。

请注意,上述代码仅为示例,可能需要根据实际项目需求进行调整。

上面的两个答案,都要用TabView,但TabView只支持水平滑动,如果要垂直滑动,那么就要将页面旋转90度。有没有不用TabView实现的方案呢?问了ChatGPT,它给出了一个方案。 问:

txt 复制代码
swiftUI实现类似抖音的上滑翻页,每次滑动一整页,不用TabView实现,用Scrollview实现可以吗?给出完整代码

ChatGPT答:

swift 复制代码
在SwiftUI中,可以使用ScrollView和手势识别器实现类似抖音的上滑翻页效果,不过需要注意的是,ScrollView并不直接支持分页效果,需要自己去做一些调整。 以下是一个简单的例子:

import SwiftUI

struct ContentView: View {
    @State private var currentPage = 0
    let totalPage = 5

    var body: some View {
        GeometryReader { geometry in
            ScrollView(.vertical, showsIndicators: false) {
                VStack(spacing: 0) {
                    ForEach(0..<self.totalPage) { i in
                        PageView(pageNumber: i)
                            .frame(width: geometry.size.width, height: geometry.size.height)
                    }
                }
            }
            .content.offset(y: -CGFloat(self.currentPage) * geometry.size.height)
            .frame(width: geometry.size.width, height: geometry.size.height, alignment: .leading)
            .gesture(
                DragGesture()
                    .onEnded { value in
                        let offset = value.translation.height / geometry.size.height
                        let newIndex = (CGFloat(self.currentPage) - offset).rounded()
                        self.currentPage = min(max(Int(newIndex), 0), self.totalPage - 1)
                    }
            )
        }
    }
}

struct PageView: View {
    var pageNumber: Int

    var body: some View {
        VStack {
            Text("Page \(pageNumber)")
            Spacer()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.random)
        .edgesIgnoringSafeArea(.all)
    }
}

// 为了让每个页面的颜色不同,我们可以通过扩展Color来生成随机颜色
extension Color {
    static var random: Color {
        return Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1))
    }
}
在这个例子中,一个ScrollView包含了多个PageView,每个PageView占据了整个ScrollView的宽度和高度。当用户滑动ScrollView时,我们会根据滑动的距离来计算当前显示的页面,然后修改ScrollView的offset来显示正确的页面。

注意:这个例子在iOS 13和14上运行良好,但在15上可能会有一些问题,因为在15中,ScrollView的行为有所改变。

选一个,用TabView来实现看看有什么问题

结合上面的代码,快速写了一个上滑翻页的代码

swift 复制代码
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            //写一个抖音一样的滑动视图
            TabView {
                ForEach(0..<10) { _ in
                    // Image("tikmc")
                    Image(systemName: "globe")
                        .resizable()
                        .scaledToFill()
                        .frame(width: 100, height: 100)
                        .clipped()
                        .rotationEffect(.degrees(-90))
                }
            }.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
                .rotationEffect(.degrees(90))

        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

效果如下

可以看到, 这个滑动翻页是可以实现的,但有个问题是,滑动快的时候出现一下滑动好几页的情况。活动太快出现多页的情况试了下在真机上测试,倒是不太明显。

问题2: TabView旋转90度后,显示的区域非常小,TabView的高度被限制手机屏幕宽度,所以看起来是个正方形。见下图

刚好昨天申请下来了Github Copilot Chat,试了一下它给的代码,问题更多。

swift 复制代码
            TabView(selection: $currentPage) {
                ForEach(0..<100) { index in
                    Skin3DView()
                        .frame(width: 1000, height: 1000)
                        .rotationEffect(.degrees(-90))
                        .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .global)
                                    .onChanged({ value in
                                        if value.translation.width > 0 {
                                            if currentPage > 0 {
                                                currentPage -= 1
                                            }
                                        } else {
                                            // if currentPage < skins.count - 1 {
                                                currentPage += 1
                                            // }
                                        }
                                    }))
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            .rotationEffect(.degrees(90))
            .frame(height: 1000)

这段代码滑动用滑动距离>0来翻页,页面切得飞快。不禁感叹,Github Copilot智能程度有限唉。

那么尝试用Scrollview直接实现上下翻页吧

这次用让AI给出答案都不行

txt 复制代码
用ScrollView实现一个模拟抖音的上下滑动翻页的swiftUI代码,要求: (1)不能用TabView,效果不好 (2)必须要用ScrollView(3)每次只能滑动一页

ChatGPT回答

swift 复制代码
在SwiftUI中,我们可以使用ScrollView来实现类似抖音的上下翻页效果。不过,我们需要一些额外的处理来确保每次只滑动一页,并且ScrollView没有内建的页面控制机制,所以我们需要手动处理滑动事件。

下面是使用ScrollView来实现的一个例子:

import SwiftUI

struct ContentView: View {
    @State private var currentPage: Int = 0

    var body: some View {
        GeometryReader { geometry in
            ScrollView(.vertical, showsIndicators: false) {
                VStack(spacing: 0) {
                    ForEach(0..<10) { i in
                        Image("image\(i)")
                            .resizable()
                            .scaledToFit()
                            .frame(width: geometry.size.width, height: geometry.size.height)
                            .overlay(Color.black.opacity(0.4))
                    }
                }
            }
            .content.offset(y: -geometry.size.height * CGFloat(currentPage))
            .frame(width: geometry.size.width, height: nil, alignment: .top)
            .gesture(
                DragGesture().onEnded { value in
                    if value.predictedEndTranslation.height > 50, currentPage > 0 {
                        currentPage -= 1
                    } else if value.predictedEndTranslation.height < -50, currentPage < 9 {
                        currentPage += 1
                    }
                }
            )
        }
    }
}
这个例子中,我们使用了一个ScrollView并且把它的content.offset设为了基于当前页面的偏移量。然后我们添加了一个DragGesture,当用户结束拖动时,我们会检查predictedEndTranslation来决定是否翻页。

这个例子的一个限制是所有的图片都必须在一开始就加载,如果有大量的图片,可能会消耗大量内存。对于这种情况,可能需要使用其他的方法来实现,比如自定义的ScrollView。

另外,这个例子只是一个基本的实现,可能还需要处理其他的细节,比如滑动动画、页面指示器等。

Claude的答案也不行,这个只能自己修理了。

体验了一下抖音,抖音加载视频的时候,是从第一页开始的,在第一页下拉,会触发它的刷新。所以推测它每次是加载一个视频列表,每个视频是一个分页。 这时候可以用LazyVStack来显示每个页面,页面的高度为除了屏幕高度减去下面的tab栏高度。

一开始我自己整了一个基础的代码,是这样

swift 复制代码
struct ContentView: View {
    @State private var currentPage: Int = 0

    var body: some View {
        GeometryReader { geometry in
            ScrollView(.vertical, showsIndicators: false) {
                LazyVStack(spacing: 0) {
                    ForEach(0..<10) { i in
                        Skin3DView()
                            .frame(width: geometry.size.width, height: geometry.size.height)
                    }
                }
            }

        }.edgesIgnoringSafeArea(.all)//这行代码是问了Gpt后它加上去的
    }
}

补充问ChatGPT的问题:

txt 复制代码
修改下面的代码,实现Skin3DView铺满整个屏幕,忽略屏幕安全区域。
struct ContentView: View {
    @State private var currentPage: Int = 0


    var body: some View {
        GeometryReader { geometry in
            ScrollView(.vertical, showsIndicators: false) {
                LazyVStack(spacing: 0) {
                    ForEach(0..<10) { i in
                        Skin3DView()
                    }
                }
            }
        }
    }
}

好了,这时候可以实现页面占满屏幕,并可以滑动了。但不是抖音的那样每次滑动一页后就停止,这个是可以滑很多页。

进一步改进,让它每次只能滑动一页

在做这一步让ChatGPT给出类似抖音的滑动翻页的时候,它给出的答案都不可行。想起提问时设定ChatGPT的身份很重要,于是先对ChatGPT吹捧一番,说它是一名可靠的抖音开发,这样就相对限定了它尽可能利用抖音开发人员发的语料。这一招果然好使,下面给出的这个答案靠谱多了

txt 复制代码
你是一名可靠的抖音开发,先理解抖音的滑动翻页交互,再给上面面这段代码增加如下功能:(1)滑动距离如果超过屏幕高度的20%就滑动完一整页 (2)滑动完一整页就停止滑动
swift 复制代码
这个需求可以通过使用SwiftUI的ScrollView和DragGesture配合使用来实现。以下是一个简单的实现:

struct ContentView: View {
    @State private var currentPage = 0
    @GestureState private var translation: CGFloat = 0

    var body: some View {
        GeometryReader { geometry in
            let screenHeight = geometry.size.height
            let threshold = screenHeight * 0.2
            
            ScrollView(.vertical, showsIndicators: false) {
                LazyVStack(spacing: 0) {
                    ForEach(0..<10) { i in
                        Skin3DView()
                            .frame(height: screenHeight)
                            .rotationEffect(.degrees(Double(self.translation / 5))) // Add rotation effect while dragging
                            .offset(y: self.translation)
                            .animation(.interactiveSpring(), value: self.translation)
                    }
                }.frame(height: screenHeight)
            }
            .content.offset(y: -CGFloat(self.currentPage) * screenHeight)
            .offset(y: self.translation)
            .animation(.interactiveSpring(), value: self.translation)
            .gesture(
                DragGesture()
                    .updating(self.$translation) { value, state, _ in
                        state = value.translation.height
                    }
                    .onEnded { value in
                        let offset = value.translation.height
                        if abs(offset) > threshold {
                            self.currentPage += (offset < 0 ? 1 : -1)
                        }
                    }
            )
            .edgesIgnoringSafeArea(.all)
        }
    }
}
这里我们使用了DragGesture手势,通过更新translation状态,实现了滑动时的交互动画。在手势结束时,我们根据滑动的距离判断是否翻页,并更新currentPage。通过对ScrollView的content.offset进行调整,实现了滑动翻页的效果。

这是一个简单的实现,可能需要根据实际的需求进行调整。比如,你可能需要处理滑动方向,或者添加更复杂的交互动画等。

上面的答案美中不足的就是运行时页面没有停在边界上,两页之间的分界在屏幕大概中间位置。还有个问题就是没有对第一页和最后一页做越界处理。

上面的答案相对靠谱,那么就基于这个上面修改一下,应该可以实现。于是,对代码中有些不太清楚的细节,问了ChatGPT。目的是想了解代码在处理拖拽过程中做了什么。

txt 复制代码
解释一下这个代码 DragGesture() .updating(self.$translation) { value, state, _ in state = value.translation.height }
swift 复制代码
这段代码是SwiftUI中的拖拽手势(DragGesture)的使用。

首先,DragGesture()创建了一个拖拽手势。

.updating(self.$translation) { value, state, _ in state = value.translation.height }这行代码做了以下几件事:

这里用到了SwiftUI的@GestureState属性包装器,它可以存储手势的临时状态。在这个例子中,translation就是存储手势的临时状态。

.updating(:_:_:)方法会在手势发生改变时被调用,它有三个参数:

第一个参数是一个绑定(Binding),在这个例子中是self.$translation。它指向了保存手势状态的变量。
第二个参数是一个闭包,会在手势发生改变时被调用。闭包中有三个参数:
value:表示手势的当前值。在这个例子中,value.translation.height表示手势在垂直方向上的偏移量。
state:表示手势的临时状态。在闭包内部,我们将手势的垂直偏移量赋值给了state,这样translation就被更新了。
最后一个参数是一个Transaction,它包含了手势改变的详细信息,比如时间和动画。在这个例子中,我们不需要这个参数,所以用_忽略了它。
总的来说,这段代码的作用是:在拖拽手势发生变化时,将手势在垂直方向上的偏移量存储到translation中。

ChatGPT这个解释很准确了,就是@GestureState private var translation: CGFloat = 0这个变量是用来获取滑动的位移的,updating中的state变量要用参数value来更新位移,这样才能传给translation。

有了实时的位移信息,便可以通过改变content offset来改变scrollview的位移,见代码 .content.offset(y: -CGFloat(self.currentPage) * screenHeight),其中translation影响了currentPage从而影响了content offset。

因为currentPage不是一个连续的值,那么滑动前后会导致content offset的跳变,要想实时连续地看到滑动的效果,代码中增加了 .offset(y: self.translation)这一行。translation在滑动时有偏移,手指松开滑动结束onEnded处理完后便为0了,所以不影响页面的偏移。在onEnded里更新了currentPage,content offset也有了正确的值。

理解了代码思路后,我们就可以针对性地修改了。核心是 content offset的改变, 外加过程动画。过程动画整个scrollview有一个offset就可以了, 把Skin3DView里的offset和动画删除。通过调试发现.edgesIgnoringSafeArea(.all)要放在GeometryReader这一层才能忽略屏幕安全区域,其实它的上面一次回答的代码是正确的,这次却放错地方了。另外就是LazyVStack不用指定高度,删除它的.frame(height: screenHeight)。 再次运行,非常完美。

附上代码

swift 复制代码
struct ContentView: View {
    @State private var currentPage = 0
    @GestureState private var translation: CGFloat = 0

    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) {
                LazyVStack(spacing: 0) {
                    ForEach(0..<10) { i in
                        Skin3DView()
                            .frame(height: screenHeight)
//                            .rotationEffect(.degrees(Double(self.translation / 5))) // Add rotation effect while dragging
//                            .offset(y: self.translation)
//                            .animation(.interactiveSpring(), value: self.translation)
                    }
                }//.frame(height: screenHeight)
            }
            .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
                        state = value.translation.height
                    }
                    .onEnded { value in
                        let offset = value.translation.height
                        if abs(offset) > threshold {
                            if offset < 0 && self.currentPage < 9 {//上滑,翻下一页
                                self.currentPage += 1
                            }else if offset > 0 && self.currentPage > 0{
                                self.currentPage -= 1
                            }
                        }
                    }
            )
            
        }.edgesIgnoringSafeArea(.all)
    }
}

总结

ChatGPT4在回答问题的准确程度上,的确是比Claude要好。内容的新旧程度上也比Claude要新,例如Claude的答案没有用SwiftUI的SceneView,还是用包装的UIKit中的SceneKit,但ChatGPT4有。 另外,要用好AI,给它一个人设很重要,我理解是可以大概圈定它的语料范围,不会满世界给你找一个似乎相关的答案。

相关推荐
自由生长20241 小时前
使用AI的思考-为什么大模型不会主动提问
aigc·ai编程
Mintopia2 小时前
⚖️ AIGC版权确权技术:Web内容的AI生成标识与法律适配
前端·javascript·aigc
用户51914958484518 小时前
使用Python ConfigParser解析INI配置文件完全指南
人工智能·aigc
小溪彼岸19 小时前
分享一个Claude Code宝藏网站Claude Code Templates
aigc·claude
YFCodeDream20 小时前
MLLM技术报告 核心创新一览
python·gpt·aigc
蛋先生DX1 天前
RAG 切片利器 LumberChunker 是如何智能地把文档切割成 LLM 爱吃的块
llm·aigc·ai编程
土丁爱吃大米饭1 天前
AIGC工具助力2D游戏美术全流程
aigc·小游戏·游戏开发·ai助力
安思派Anspire1 天前
为何你的RAG系统无法处理复杂问题(二)
aigc·openai·agent
木易士心1 天前
Android Handler 机制原理详解
android·app
Mintopia1 天前
🧠 可解释性AIGC:Web场景下模型决策透明化的技术路径
前端·javascript·aigc