我需求完成一个漫画的翻页作用,只在横屏下完成。

交互速览

  1. 页面作用

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

漫画翻页作用编写(一)—— GPT 收拾思路

  1. 窄宽页面并存

当窄宽页面并存时,为了确保阅览的连续性,这儿选择一起显现两张页面,恰当透明化宽图片,当用户滑动时下降窄页面的透明度。

我想这样能够让阅览布局相对安稳,确保阅览行为的联系性。

漫画翻页作用编写(一)—— GPT 收拾思路

  1. 换章节作用

换章节有两个状况。

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

第二种状况,“最终一页”为半页,也便是只要一张正常图片。那么会运用一个长宽比为 2:3 的 View 来代替别的一半的方位进行显现,里面有一行文字“持续阅览下个章节”;假如没有下一个章节,那么直接提示“无更多章节”。若有下个章节,持续翻页后,直接显现下一章节,不进行 Toast 提示。

漫画翻页作用编写(一)—— GPT 收拾思路

开源代码参阅

在 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进行布局。
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

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用作页面内容
struct PageContent: View {
    var text: String
    var body: some View {
        Text(text)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.gray)
    }
}
  1. 其次,创立一个遵从UIViewControllerRepresentable协议的结构体,并承受SwiftUI视图数组作为初始化参数
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视图中
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 项目》持续