漫画翻页效果编写(一)—— GPT 整理思路

我需要实现一个漫画的翻页效果,只在横屏下实现。

交互速览

  1. 页面效果

页面默认显示两张图片,两张图片组成一个新的 View ,这个 View 等比放大缩小,占满整个屏幕。而当第一张图片宽度大于高度时,页面只显示一张"宽图片"。

  1. 窄宽页面并存

当窄宽页面并存时,为了保证阅读的连续性,这里选择同时显示两张页面,适当透明化宽图片,当用户滑动时降低窄页面的透明度。

我想这样可以让阅读布局相对稳定,保证阅读行为的联系性。

  1. 换章节效果

换章节有两个情况。

第一种情况是前一个章节的"最后一页"刚好为一整张宽图片或者两张正常图片。那么继续翻页后,直接显示下一章节,并以 Toast 提示"Start Reading"+章节名。如果没有下个章节,则显示全黑界面+一行问题"无更多章节"

第二种情况,"最后一页"为半页,也就是只有一张正常图片。那么会使用一个长宽比为 2:3 的 View 来替代另外一半的位置进行显示,里面有一行文字"继续阅读下个章节";如果没有下一个章节,那么直接提示"无更多章节"。若有下个章节,继续翻页后,直接显示下一章节,不进行 Toast 提示。

开源代码参考

在 github 上搜索类似的代码,发现一个开源的项目 Aidoku,其中多漫画章节切换的部分和本需求很像。

Aidoku/Aidoku: Free and open source manga reader for iOS and iPadOS (github.com)

但是还存在一个问题,即该项目使用的是 ViewController 这种传统的方式实现的页面(也就是 SwiftUI 推出之前使用的页面布局编写方式),而且没有依赖 xib 布局文件,是纯代码实现的界面。所以在阅读理解它的源代码之前,需要先了解 ViewController 是如何和 SwiftUI 衔接的。

我整理了我需要了解的事情:

  1. ViewController 是如何实现编写一个页面的(不依靠 Storyboard)
  2. ViewController 编写的页面如何在 SwiftUI 中使用。
  3. 如何编写一个 UIPageViewController 视图,并在 SwiftUI 中使用,同时接受 SwiftUI 传递过来的 View 作为 Page 的内容。
  4. UIPageViewController 和 SwiftUI 中的 TabView ,SwiftUIX 中的 PaginationView 有什么区别

暂时就这些问题,可以先向 GPT 寻求答案了。

需要注意的是,OPENAI 官方是建议我们将内容分布地向 GPT 询问,"Give it time to 'think.'",所以在这里我分开向 GPT 询问。

1. ViewController 是如何实现编写一个页面的(不依靠 Storyboard)

GPT的回答:

在UIKit中,页面通常由UIViewController类来管理,而在SwiftUI中,这种任务由视图(Views)来执行。由于你对SwiftUI更为熟悉,我将简要比较两者的不同,并以代码示例展示如何在UIKit中纯代码实现一个页面。

在UIKit中创建一个页面

  1. 初始化ViewController: 使用UIViewController的子类,覆盖其生命周期方法。
  2. 设置视图元素: 使用UIKit的组件(如UILabel, UIButton等)。
  3. 布局: 使用AutoLayout或手动设置frame进行布局。
swift 复制代码
import UIKit

class MyViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // 设置背景色
        view.backgroundColor = .white

        // 创建一个标签
        let myLabel = UILabel()
        myLabel.text = "Hello, World!"
        myLabel.translatesAutoresizingMaskIntoConstraints = false

        // 添加标签到视图
        view.addSubview(myLabel)

        // 布局
        NSLayoutConstraint.activate([
            myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

可以看到,如果不依赖 Storyboard 进行布局,VC 需要先将子元素使用view.addSubview(...)添加到父视图中,然后使用"Constraint"定义元素的位置关系。

2. ViewController 编写的页面如何在 SwiftUI 中使用。

GPT的回答:

在SwiftUI中,如果你想使用由UIViewController编写的页面,你可以使用UIViewControllerRepresentable协议。这个协议允许SwiftUI与UIKit之间进行桥接。以下是使用UIViewControllerRepresentable在SwiftUI中嵌入一个由UIViewController创建的页面的基础步骤:

  1. 创建遵循UIViewControllerRepresentable的Swift结构体
  2. 实现makeUIViewController(context:)方法 :这里创建并返回你的UIViewController
  3. 实现updateUIViewController(_:context:)方法 :这里更新UIViewController的状态。

下面是一个简单的例子,展示了如何在SwiftUI应用中嵌入一个显示"Hello, World!"的UILabelUIViewController

swift 复制代码
import SwiftUI
import UIKit

struct MyUIViewControllerRepresentable: UIViewControllerRepresentable {
    
    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = MyViewController()
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        // 更新UIViewController(如果需要)
    }
}

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        let label = UILabel()
        label.text = "Hello, World!"
        
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

struct ContentView: View {
    var body: some View {
        MyUIViewControllerRepresentable()
    }
}

在这个例子中,MyUIViewControllerRepresentable结构体遵循了UIViewControllerRepresentable协议,并实现了必要的方法来创建和更新MyViewControllerContentView则用该结构体来显示UIKit视图控制器的内容。

这样,你就可以在SwiftUI应用中无缝地使用由UIViewController编写的页面了。


Representable 的意思是可以代表的,所以 UIViewControllerRepresentable 应该就是 VC 在 SwiftUI 中的代表。

按理来说应该可以在 UIViewControllerRepresentable 中传入一些参数,通过桥接传入到 VC 中作用。

3. 如何编写一个 UIPageViewController 视图,并在 SwiftUI 中使用,同时接受 SwiftUI 传递过来的 View 作为 Page 的内容。

GPT回答:

要在SwiftUI中使用UIPageViewController,并且接受从SwiftUI传递过来的视图作为页面内容,你可以使用UIViewControllerRepresentable来实现。这里是一个简单示例:

  1. 首先,创建一个SwiftUI View用作页面内容
swift 复制代码
struct PageContent: View {
    var text: String
    var body: some View {
        Text(text)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.gray)
    }
}
  1. 其次,创建一个遵循UIViewControllerRepresentable协议的结构体,并接受SwiftUI视图数组作为初始化参数
swift 复制代码
import SwiftUI
import UIKit

struct PageViewController<Content: View>: UIViewControllerRepresentable {
    var controllers: [UIHostingController<Content>]

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal
        )
        
        return pageViewController
    }

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        pageViewController.setViewControllers(
            [controllers[0]], direction: .forward, animated: true, completion: nil
        )
    }
}

在这里,controllers是一个UIHostingController<Content>数组,用于包装传入的SwiftUI视图。

  1. 最后,使用PageViewController在你的SwiftUI视图中
swift 复制代码
struct ContentView: View {
    var body: some View {
        let view1 = PageContent(text: "Page 1")
        let view2 = PageContent(text: "Page 2")
        let view3 = PageContent(text: "Page 3")

        let hostingControllers = [view1, view2, view3].map { UIHostingController(rootView: $0) }

        return PageViewController(controllers: hostingControllers)
    }
}

这样,你就可以在SwiftUI中使用一个基于UIPageViewController的分页视图,并能从SwiftUI传递视图作为页面内容。注意,这个例子非常基础,只是为了展示基础的桥接过程。在实际应用中,你可能还需要添加更多的逻辑,比如分页指示器、滑动控制等。


这个回答错误其实很严重,也很明显,例如 UIPageViewController 没有实现 datasource 和 delegate 协议,使用 setViewControllers 只添加了一个 controller(不知道这样是否正确但实际效果表现为无法滑动页面)。

应该是逻辑的复杂程度已经探到了 GPT 的上限,多次生成后答案也没有改善。不过已经为我指明了大概得思路,即使用 UIHostingController 将 SwiftUI 中的 View 传入。

4. UIPageViewController 和 SwiftUI 中的 TabView ,SwiftUIX 中的 PaginationView 有什么区别

GPT回答:

这三者都可以用于创建多页面的用户界面,但它们之间有一些关键区别:

UIPageViewController

  1. 框架: 属于 UIKit,需要与 SwiftUI 进行桥接。
  2. 自定义性: 提供高度的自定义能力,如转场动画、数据源等。
  3. 复杂性: 相对复杂,需要实现多个协议方法。
  4. 性能: 通常用于需要高度动态和交互的分页视图,因为它支持懒加载。

SwiftUI 的 TabView

  1. 框架: 属于 SwiftUI,与其紧密集成。
  2. 自定义性: 相对较低,主要用于创建标签式的多页面界面。
  3. 复杂性: 使用简单,只需几行代码。
  4. 性能: 适用于简单的静态页面,不支持页面的懒加载。

SwiftUIX 的 PaginationView

  1. 框架: 是一个第三方库,专为 SwiftUI 设计。
  2. 自定义性: 中等,提供了比原生 TabView 更多的自定义选项。
  3. 复杂性: 相对简单,但需要安装额外的依赖。
  4. 性能 : 设计更接近UIPageViewController,可以处理更动态的内容。

总结:

  • 如果你需要高度自定义的分页视图,并且不介意进行一些额外的设置,UIPageViewController是一个好选择。
  • 如果你正在构建一个纯 SwiftUI 应用,并且需要简单快速地创建多页面界面,TabView会是更好的选项。
  • 如果你想在SwiftUI中实现类似UIPageViewController的更多功能,而又不想回到UIKit,可以考虑使用PaginationView

根据你的具体需求和技术栈(特别是因为你已经有一定的SwiftUI经验),你可能会发现其中一种方法更适合你。


SwiftUI 的 TabView 我已经尝试过,首先是性能较差,更关键的是无法监听到翻页完成事件,而我需要在用户翻到最后一页时预先加载下一页的内容。

如果有想看使用 TabView 实现翻页效果的漫画 APP ,可以去查看这个开源项目:

oolxg/Hanami: Manga reading app for iOS/iPadOS written with SwiftUI and Composable Architecture (github.com)

翻页到下一章节时会有明显的跳动,虽然可以实现功能,但体验并不够完美。

SwiftUIX 的 PaginationView 没有可用监听接口,效果依然不理想的话。

所以还是要回到分析 Aidoku 项目上,自己写一个控件。

分析 Aidoku 项目

这个比较复杂,下一篇文章《漫画翻页效果编写(二)------分析 Aidoku 项目》继续

相关推荐
app开发工程师V帅2 小时前
iOS 苹果开发者账号: 查看和添加设备UUID 及设备数量
ios
CodeCreator18182 小时前
iOS AccentColor 和 Color Set
ios
iOS民工4 小时前
iOS keychain
ios
m0_748238927 小时前
webgis入门实战案例——智慧校园
开发语言·ios·swift
Legendary_00811 小时前
LDR6020在iPad一体式键盘的创新应用
ios·计算机外设·ipad
/**书香门第*/20 小时前
Laya ios接入goole广告,搭建环境 1
ios
wakangda1 天前
React Native 集成 iOS 原生功能
react native·ios·cocoa
crasowas2 天前
iOS - 超好用的隐私清单修复脚本(持续更新)
ios·app store
ii_best2 天前
ios按键精灵脚本开发:ios悬浮窗命令
ios
Code&Ocean2 天前
iOS从Matter的设备认证证书中获取VID和PID
ios·matter·chip