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的實現主要看代碼吧,寫法都是同樣的實現起來很簡單。
奉上上面全部的 代碼示例,以供參考,共同窗習;