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

探究中心部分,上下滑动翻页

最中心的功用便是滑动翻页了,所以,持续探究。上一篇尝试用AI辅佐我写一个类抖音的APP(一)中Claude的答复相对靠谱点,接着问Claude:

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

Claude答:

您提醒得对,我漏掉了翻页功用能够经过以下办法完成:
运用@State变量记载当时显现的皮肤索引currentPageTabView包住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(一) 问:

这部分完成滑动翻页,不能做到每次翻一整页,请修正完善代码。‘’‘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() } } }’‘’

答:

关于完成滑动翻页(每次滑动显现一个新的页面),咱们能够运用 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,它给出了一个计划。 问:

swiftUI完成相似抖音的上滑翻页,每次滑动一整页,不必TabView完成,用Scrollview完成能够吗?给出完好代码

ChatGPT答:

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 1314上运转杰出,但在15上或许会有一些问题,因为在15中,ScrollView的行为有所改动

选一个,用TabView来完成看看有什么问题

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

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()
    }
}

作用如下

尝试用AI辅助我写一个类抖音的APP(二)
能够看到, 这个滑动翻页是能够完成的,但有个问题是,滑动快的时分出现一下滑动好几页的状况。活动太快出现多页的状况试了下在真机上测试,倒是不太明显。

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

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

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

            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给出答案都不可

用ScrollView完成一个模仿抖音的上下滑动翻页的swiftUI代码,要求: (1)不能用TabView,作用欠好 (2)必须要用ScrollView(3)每次只能滑动一页

ChatGPT答复

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栏高度。

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

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的问题:

修正下面的代码,完成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吹捧一番,说它是一名牢靠的抖音开发,这样就相对限定了它尽或许使用抖音开发人员发的语料。这一招公然好使,下面给出的这个答案靠谱多了

你是一名牢靠的抖音开发,先了解抖音的滑动翻页交互,再给上面面这段代码增加如下功用:(1)滑动间隔假如超越屏幕高度的20%就滑动完一整页 (2)滑动完一整页就中止滑动
这个需求能够经过运用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。目的是想了解代码在处理拖拽过程中做了什么。

解说一下这个代码 DragGesture() .updating(self.$translation) { value, state, _ in state = value.translation.height }
这段代码是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)。 再次运转,十分完美。

附上代码

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,给它一个人设很重要,我了解是能够大约圈定它的语料规模,不会满世界给你找一个好像相关的答案。