在SwiftUI的ScrollView中似乎找不到isPagingEnabled
类似的属性,用于开启分页滑动。
iOS14中用TabView{...}.tabViewStyle(PageTabViewStyle())
也可以达到分页滑动的效果,但不知如何获得currentPage
之类的属性,无法使用自定义的PageControl
。
等官方的ScrollView
或TabView
更新page
相关的功能,便无需考虑此转接方案
那么考虑使用UIViewRepresentable
、UIHostingController
等转接UIkit的方案。
大概思路是定义一个PageView
,其content
传入View
,、且有其Coordinator
实例作为UIScrollViewDelegate
代理监听滑动contentOffset
,并且更改绑定的currentPage
本文总结了一个无需传入PageCount的方案
import SwiftUI
struct PageView<ViewBuilerContent: View>: UIViewRepresentable {
@Binding var currentPage: Int
let contentGetter: () -> [ViewBuilerContent]
init(currentPage: Binding<Int>, @MyViewBuilder content: @escaping (() -> [ViewBuilerContent])) {
_currentPage = currentPage
contentGetter = content
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.contentInsetAdjustmentBehavior = .never
scrollView.isPagingEnabled = true
scrollView.delegate = context.coordinator
scrollView.showsHorizontalScrollIndicator = false
let stackView = UIStackView()
stackView.alignment = .fill
stackView.distribution = .fillEqually
stackView.axis = .horizontal
stackView.spacing = 0
scrollView.addSubview(stackView)
return scrollView
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
let contents = self.contentGetter()
for i in uiView.subviews {
i.removeFromSuperview()
}
let hStack = HStack(alignment: .center, spacing: 0) {
ForEach(0 ..< contents.count) { i in
contents[i]
}
}
let contro = UIHostingController(rootView: hStack)
if let view = contro.view {
view.backgroundColor = .clear
uiView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.topAnchor.constraint(equalTo: uiView.topAnchor).isActive = true
view.leftAnchor.constraint(equalTo: uiView.leftAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: uiView.bottomAnchor).isActive = true
view.rightAnchor.constraint(equalTo: uiView.rightAnchor).isActive = true
view.heightAnchor.constraint(equalTo: uiView.heightAnchor).isActive = true
view.widthAnchor.constraint(equalTo: uiView.widthAnchor, multiplier: CGFloat(contents.count)).isActive = true
}
}
typealias UIViewType = UIScrollView
func makeCoordinator() -> Coor<ViewBuilerContent> {
let coo = Coor<ViewBuilerContent>(self)
return coo
}
class Coor<ViewBuilerContent: View>: NSObject, UIScrollViewDelegate {
var parent: PageView<ViewBuilerContent>
init(_ parent: PageView<ViewBuilerContent>) {
self.parent = parent
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let width = scrollView.frame.size.width
let offsetx = scrollView.contentOffset.x
let page = offsetx / width + 0.5
self.parent.currentPage = Int(page)
}
}
}
通常在PageView内会传入ForEachView、或for in等,因此用自定义的ResultBuilder特殊处理ForEachView得到View数组,这样有了count
@resultBuilder
enum MyViewBuilder{
typealias MyContainerView = AnyView
// MARK: 条件判断
static func buildArray<Content: View>(_ components: [[Content]]) -> [MyContainerView] {
return components.flatMap { $0 }.map { MyContainerView($0) }
}
static func buildEither<Content: View>(first component: [Content]) -> [MyContainerView] {
return component.map { MyContainerView($0) }
}
static func buildEither<Content: View>(second component: [Content]) -> [MyContainerView] {
return component.map { MyContainerView($0) }
}
static func buildIf<Content: View>(_ content: Content?) -> [MyContainerView] {
return []
}
// MARK: 分情况处理不同类型
static func buildOptional<Content: View>(_ component: [Content]?) -> [MyContainerView] {
return []
}
static func buildExpression<Content: View>(_ expression: Content) -> [MyContainerView] {
return [MyContainerView(expression)]
}
// MARK: 把ForEachView拆分
static func buildExpression<C0, C1, Content: View>(_ expression: ForEach<C0, C1, Content>) -> [MyContainerView] {
return expression.data.map { index in
let co = expression.content(index)
return MyContainerView(co)
}
}
// MARK: 单个类型
static func buildBlock<C0: View>(_ c0: [C0]) -> [MyContainerView] {
var res = [MyContainerView]()
for i in c0 {
res.append(MyContainerView(i))
}
return res
}
// MARK: 不同类型集合总体输出
static func buildBlock<C0: View, C1: View>(_ c0: [C0], _ c1: [C1]) -> [MyContainerView] {
var res = [MyContainerView]()
for i in c0 {
res.append(MyContainerView(i))
}
for i in c1 {
res.append(MyContainerView(i))
}
return res
}
}
最终在页面声明使用
PageView(currentPage: $currentPage) {
ForEach(0 ..< 4) { index in
Color(.red).cornerRadius(10).padding(.horizontal, 10)
}
}
.frame(height: 360)
附赠一个简单的PageControl
HStack(alignment: .center, spacing: 6) {
ForEach(0 ..< 4) { index in
let isCurr = index == currentPage
Color(isCurr ? .systemBlue : .gray).cornerRadius(2).frame(width: isCurr ? 10 : 4, height: 4)
}
}
.frame(height: 20).offset(x: 0, y: -10)

网友评论