macOS Catalina 10.15 beta 7, xcode 11.0 beta 6git
var body: some View {
ZStack(alignment: .bottom) {
/// 滑动控制器视图
PageViewController(currentPage: $currentPage, offsetX: $offsetX, home: self.home, controllers: viewControllers)
.background(Color.clear)
.frame(height: 260)
Text("")
.preference(key: PageKeyTypes.PreKey.self, value: [PageKeyTypes.PreData(index: currentPage,offsetX: offsetX)])
/// 新修改页数指示
TMPageView().padding()
}.onPreferenceChange(PageKeyTypes.PreKey.self) { values in
self.home.index = values.first?.index ?? 0
}
}
复制代码
1)这里 PageViewController()是使用的UIPageViewController实现的,使用的UIKit的控制器,因此里面要遵循UIViewControllerRepresentable这个协议。github
2)Text("") 这一行代码主要是为了监听ScrollView的滚动事件,这里咱们使用的是preference来实现。swift
3) TMPageView() 这个是页码指示器。xcode
1.1 PageViewController页面安全
struct PageViewController: UIViewControllerRepresentable {
typealias UIViewControllerType = UIPageViewController
/// 当前页
@Binding var currentPage: Int
/// 当前页偏移量
@Binding var offsetX: CGFloat
/// 传递过来的首页全局数据
var home: HomeGlobal
var controllers: [UIViewController]
func makeUIViewController(context: UIViewControllerRepresentableContext<PageViewController>) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal,options: [:])
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
/// 获取page内的scrollView
let scrol = findScrollView(vc: pageViewController)
scrol.delegate = context.coordinator
return pageViewController
}
func updateUIViewController(_ uiViewController: UIPageViewController, context: UIViewControllerRepresentableContext<PageViewController>) {
uiViewController.setViewControllers([controllers[currentPage]], direction: .forward, animated: true, completion: nil)
}
func findScrollView(vc: UIPageViewController) -> UIScrollView {
for item in vc.view!.subviews {
if item is UIScrollView {
return item as! UIScrollView
}
}
return UIScrollView()
}
class Coordinator: NSObject,UIPageViewControllerDataSource,UIPageViewControllerDelegate,UIScrollViewDelegate {
var parent: PageViewController
var home: HomeGlobal
init(_ pageViewController: PageViewController,home: HomeGlobal) {
self.parent = pageViewController
self.home = home
}
/// 数据源代理
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = parent.controllers.firstIndex(of: viewController) else {
return nil
}
if index == 0 {
return parent.controllers.last
}
return parent.controllers[index - 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = parent.controllers.firstIndex(of: viewController) else {
return nil
}
if index + 1 == parent.controllers.count {
return parent.controllers.first
}
return parent.controllers[index + 1]
}
/// 代理方法
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed,
let visibleViewController = pageViewController.viewControllers?.first,
let index = parent.controllers.firstIndex(of: visibleViewController)
{
parent.currentPage = index
}
}
/// 监听滚动视图距离
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.home.offsetX = scrollView.contentOffset.x
}
}
func makeCoordinator() -> PageViewController.Coordinator {
Coordinator(self, home: self.home)
}
}
复制代码
1)这里面主要实现makeUIViewController 和 updateUIViewController,这里面主要实现makeUIViewController用于建立UIKit框架中的控制器,updateUIViewController更新的时候会调用到。app
2)class Coordinator这个类是一个协调者,用于实现SwiftUI框架和UIKit以前的连接。 咱们使用Coordinator 来实现UIPageViewController的一些代理。框架
3)由于要监听UIPageViewController的页面的滚动,因此这里咱们添加findScrollView()这个方法来获取当前页面的UIScrollView视图,来监听滑动的偏移量。oop
总结:这个页面主要实现了UIPageViewController代理和监听UIScrollview偏移量用来修改背景颜色的渐变效果。布局
1.2 Text("")学习
这里主要看下:
``` swift
// preference类型
struct PageKeyTypes {
// preference 的value 类型
struct PreData: Equatable{
let index: Int
let offsetX: CGFloat
}
// preference 的 key
struct PreKey: PreferenceKey {
static var defaultValue: [PreData] = []
static func reduce(value: inout [PreData], nextValue: () -> [PreData]) {
value.append(contentsOf: nextValue())
}
typealias Value = [PreData]
}
复制代码
}
```
复制代码
1) preference 这里使用它,能够为View设置任何事件,咱们这里使用了PreData这个类型来监听这个View的index和offsetX。这两个值就能够获取到当前的索引和偏移量了。
2) 当发生变化的时候,就会执行这里面onPreferenceChange,获取以后咱们给首页的全局配置对象设置对应的值,这样咱们就能够在其余任何View中获取咱们的这些属性值了。
1.3 TMPageView()
struct TMPageView: View {
@EnvironmentObject var home: HomeGlobal
var body: some View {
ZStack(alignment: .leading) {
Color(red: 200/255.0, green: 200/255.0, blue: 200/255.0)
.frame(width: 150,height: 2)
.cornerRadius(1)
VStack {
Color.white
.frame(width: 15,height: 2)
.cornerRadius(2)
}.offset(x: CGFloat(self.home.index)*15, y: 0 )
}
}
}
复制代码
这个视图只是指示器做用,根据传递进来的全局数据来设置对应的显示位置。
1.4 这里是整个轮播图的预览View
loop.featureImage用来获取当前轮播图的图片Item。设置了图片的高度和圆角,距离顶部有一段的距离是用来设置顶部导航条的间距的。
这个视图咱们是咱们首页的背景图,用来显示一个默认背景图片,根据全局数据设置不一样的颜色。 这里面主要使用Image这一个,其余的代码都是获取背景图片应该设置为何颜色的代码逻辑。
struct TMHomeBackView: View {
@EnvironmentObject var home: HomeGlobal
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Image("loopbg")
.resizable()
.frame(height: 450)
.background(Color.init(getColor()))
}
.offset(x: 0, y: self.home.offsetY <= 0 ? self.home.offsetY : 0)
}
func getColor() -> UIColor{
/// 当前页
let current = self.home.index
/// 获取下一页的索引
var nextIndex: Int = current
/// 滑动比例
let progress: CGFloat = abs((self.home.offsetX - self.home.width)/self.home.width)
/// 滑动方向
if self.home.offsetX - self.home.width >= 0 {
nextIndex += 1
if nextIndex > 9 {
nextIndex = 0
}
if self.home.offsetX - self.home.width == 0 {
nextIndex = 0
}
} else {
nextIndex -= 1
if nextIndex < 0 {
nextIndex = 9
}
if current == 0 {
nextIndex = 0
}
}
/// 当前颜色
let currentColor: (r : CGFloat, g : CGFloat, b : CGFloat)
= getRGBWithColor(getRGB(current))
/// 下一个颜色
let nextColor: (r : CGFloat, g : CGFloat, b : CGFloat)
= getRGBWithColor(getRGB(nextIndex))
print("\(currentColor)==\(nextColor)")
/// 颜色变量
let colorDelta = (currentColor.0 - nextColor.0, currentColor.1 - nextColor.1, currentColor.2 - nextColor.2)
let finalColr: UIColor = UIColor(red: (currentColor.0 - colorDelta.0*progress) / 255.0, green: (currentColor.1 - colorDelta.1*progress) / 255.0, blue: (currentColor.2 - colorDelta.2*progress) / 255.0, alpha: 1)
return finalColr
}
func getRGB(_ index: Int) -> UIColor {
let color = UIColor(red: CGFloat(loopData[index].colors.red)/255.0, green: CGFloat(loopData[index].colors.green)/255.0, blue: CGFloat(loopData[index].colors.blue)/255.0, alpha: 1)
return color
}
}
复制代码
1)咱们使用getColor方法来获取当前和下一页应该显示什么样的颜色。这里的颜色咱们使用的是RGB颜色来进行渐变的。
由于咱们在使用中发现,SwiftUI中的ScrollView不在跟UIKit中的UIScrollView同样有代理方法,能够监听ScrollView的滚动事件。咱们使用ScrollView(.vertical, showsIndicators: false)发现也只有设置横屏竖屏滚动,和是否显示滚动条的参数。这里好像SwiftUI中已经没有像UIKit中代理的一些东西了。在官网例子中也没有找到对应的实现,官网的例子中都是很简单的教你如何使用SwiftUI。在翻看gitHub上的一些文章后,找寻到了如何自定义实现ScrollView的滚动和若是实现下拉刷新等功能。 咱们下面实现的自定义ScrollView是根据老外写的文章编写的:
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
ZStack(alignment: .top) {
/// 用于接收监听的视图
MovingView()
/// 填充传过来的视图
self.content
}
}
.onPreferenceChange(RefreshableKeyTypes.PreKey.self) { values in
/// 更新赋值
self.home.offsetY = values.first?.bounds.origin.y ?? 0.0
self.home.width = values.first?.bounds.size.width ?? 0.0
}
}
}
复制代码
1) RefreshScrollView中的body代码也是很是简单,这里仍是主要是根据preference 和 onPreferenceChange 实现的。在前面监听滚动的时候咱们已经使用过了。 2) 这里新增的也就多了一个 GeometryReader 这个是用来获取设备尺寸的,
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct GeometryProxy {
/// The size of the container view.
public var size: CGSize { get }
/// Resolves the value of `anchor` to the container view.
public subscript<T>(anchor: Anchor<T>) -> T { get }
/// The safe area inset of the container view.
public var safeAreaInsets: EdgeInsets { get }
/// The container view's bounds rectangle converted to a defined
/// coordinate space.
public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
}
复制代码
看这里是GeometryProxy的size就是获取设置宽度和高度的。
3) self.content这里的content就是传递过来的显示的View
struct TMHomeView: View {
@State private var refresh: Bool = true
@EnvironmentObject var home: HomeGlobal
var body: some View {
/// 导航总试图
NavigationView {
/// 总体叠加
ZStack(alignment: .top) {
/// 首页背景视图
TMHomeBackView()
/// 滚动视图
RefreshScrollView(refreshing: $refresh) {
HomeContentView()
}
/// 顶部导航
HomeNaviView()
}
/// 背景颜色
.background(Color(red: 245/255.0, green: 245/255.0, blue: 245/255.0))
/// 延伸到安全区域
.edgesIgnoringSafeArea(.top)
.navigationBarHidden(true)
}
}
}
复制代码
1) 使用的时候就很是简单了,跟其余系统的View使用同样
RefreshScrollView(refreshing: $refresh) {
HomeContentView()
}
复制代码
struct HomeNaviView: View {
@EnvironmentObject var home: HomeGlobal
@State private var name: String = ""
var body: some View {
VStack(alignment: .leading, spacing: 0) {
/// 顶部安全区域
Color.red
.frame(height: 44)
/// 底部导航栏
HStack {
Image("camera_Normal")
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 5))
/// 导航条位置
HStack{
Image("iconfont-search")
.padding(EdgeInsets(top: 7, leading: 5, bottom: 8, trailing: 5))
TextField("智能家居HongMeng", text: $name)
Image("tmas_entry_pop_icon")
.padding(EdgeInsets(top: 7, leading: 5, bottom: 8, trailing: 5))
}
.background(
Color.white
.cornerRadius(4)
)
.frame(height: 50)
Image("detail_button_cart")
.padding(.leading, 10)
.padding(.trailing, 5)
Image("frontpage_message_btn")
.padding(.leading, 5)
.padding(.trailing, 10)
}
.background(Color.red)
}
}
}
复制代码
1)使用了图片、文本、输入框等View的组合。
2) 其余View的实现主要看代码吧,写法都是同样的实现起来很简单。
奉上上面全部的 代码示例,以供参考,共同窗习;