我需求完成一个漫画的翻页作用,只在横屏下完成。
交互速览
- 页面作用
页面默认显现两张图片,两张图片组成一个新的 View ,这个 View 等比放大缩小,占满整个屏幕。而当第一张图片宽度大于高度时,页面只显现一张“宽图片”。
- 窄宽页面并存
当窄宽页面并存时,为了确保阅览的连续性,这儿选择一起显现两张页面,恰当透明化宽图片,当用户滑动时下降窄页面的透明度。
我想这样能够让阅览布局相对安稳,确保阅览行为的联系性。
- 换章节作用
换章节有两个状况。
第一种状况是前一个章节的“最终一页”刚好为一整张宽图片或者两张正常图片。那么持续翻页后,直接显现下一章节,并以 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 联接的。
我收拾了我需求了解的工作:
- ViewController 是怎么完成编写一个页面的(不依托 Storyboard)
- ViewController 编写的页面怎么在 SwiftUI 中运用。
- 怎么编写一个 UIPageViewController 视图,并在 SwiftUI 中运用,一起承受 SwiftUI 传递过来的 View 作为 Page 的内容。
- 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中创立一个页面
- 初始化ViewController: 运用UIViewController的子类,掩盖其生命周期办法。
- 设置视图元素: 运用UIKit的组件(如UILabel, UIButton等)。
- 布局: 运用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
创立的页面的根底步骤:
-
创立遵从
UIViewControllerRepresentable
的Swift结构体。 -
完成
makeUIViewController(context:)
办法:这儿创立并回来你的UIViewController
。 -
完成
updateUIViewController(_:context:)
办法:这儿更新UIViewController
的状况。
下面是一个简略的比如,展示了怎么在SwiftUI运用中嵌入一个显现“Hello, World!”的UILabel
的UIViewController
。
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
协议,并完成了必要的办法来创立和更新MyViewController
。ContentView
则用该结构体来显现UIKit视图操控器的内容。
这样,你就能够在SwiftUI运用中无缝地运用由UIViewController
编写的页面了。
Representable 的意思是能够代表的,所以 UIViewControllerRepresentable 应该便是 VC 在 SwiftUI 中的代表。
按理来说应该能够在 UIViewControllerRepresentable 中传入一些参数,通过桥接传入到 VC 中作用。
3. 怎么编写一个 UIPageViewController 视图,并在 SwiftUI 中运用,一起承受 SwiftUI 传递过来的 View 作为 Page 的内容。
GPT答复:
要在SwiftUI中运用UIPageViewController
,而且承受从SwiftUI传递过来的视图作为页面内容,你能够运用UIViewControllerRepresentable
来完成。这儿是一个简略示例:
- 首先,创立一个SwiftUI View用作页面内容。
struct PageContent: View {
var text: String
var body: some View {
Text(text)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
}
}
-
其次,创立一个遵从
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视图。
-
最终,运用
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
- 框架: 归于 UIKit,需求与 SwiftUI 进行桥接。
- 自定义性: 供给高度的自定义才能,如转场动画、数据源等。
- 杂乱性: 相对杂乱,需求完成多个协议办法。
- 功用: 一般用于需求高度动态和交互的分页视图,由于它支持懒加载。
SwiftUI 的 TabView
- 框架: 归于 SwiftUI,与其紧密集成。
- 自定义性: 相对较低,主要用于创立标签式的多页面界面。
- 杂乱性: 运用简略,只需几行代码。
- 功用: 适用于简略的静态页面,不支持页面的懒加载。
SwiftUIX 的 PaginationView
- 框架: 是一个第三方库,专为 SwiftUI 规划。
- 自定义性: 中等,供给了比原生 TabView 更多的自定义选项。
- 杂乱性: 相对简略,但需求安装额定的依靠。
-
功用: 规划更挨近
UIPageViewController
,能够处理更动态的内容。
总结:
- 假如你需求高度自定义的分页视图,而且不介意进行一些额定的设置,
UIPageViewController
是一个好选择。 - 假如你正在构建一个纯 SwiftUI 运用,而且需求简略快速地创立多页面界面,
TabView
会是更好的选项。 - 假如你想在SwiftUI中完成相似
UIPageViewController
的更多功用,而又不想回到UIKit,能够考虑运用PaginationView
。
依据你的具体需求和技能栈(特别是由于你现已有必定的SwiftUI经验),你或许会发现其间一种办法更适合你。
SwiftUI 的 TabView 我现已尝试过,首先是功用较差,更要害的是无法监听到翻页完成事情,而我需求在用户翻到最终一页时预先加载下一页的内容。
假如有想看运用 TabView 完成翻页作用的漫画 APP ,能够去查看这个开源项目:
翻页到下一章节时会有显着的跳动,虽然能够完成功用,但体会并不行完美。
SwiftUIX 的 PaginationView 没有可用监听接口,作用依然不理想的话。
所以还是要回到剖析 Aidoku 项目上,自己写一个控件。
剖析 Aidoku 项目
这个比较杂乱,下一篇文章《漫画翻页作用编写(二)——剖析 Aidoku 项目》持续