Skip to content

Commit

Permalink
Version 1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
fredyshox committed Feb 17, 2020
2 parents 959ca32 + 843e3e6 commit 63e00f3
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 52 deletions.
4 changes: 2 additions & 2 deletions Examples/PageViewDemo WatchKit Extension/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ struct ContentView: View {
}
}.edgesIgnoringSafeArea([.leading, .trailing, .bottom])
// Vertical
// PageView(axis: .horizontal, pageCount: 3) { (i) -> AnyView in
// PageView(axis: .vertical(alignment: Alignment(horizontal: .trailing, vertical: .top)), pageCount: 3) { (i) -> AnyView in
// if i == 0 {
// return CustomView().eraseToAnyView()
// } else if i == 1 {
// return CustomListView().eraseToAnyView()
// return CustomView().eraseToAnyView()
// } else {
// return CustomView().eraseToAnyView()
// }
Expand Down
4 changes: 2 additions & 2 deletions PageView.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.1.0;
MARKETING_VERSION = 1.2.0;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
Expand Down Expand Up @@ -299,7 +299,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.1.0;
MARKETING_VERSION = 1.2.0;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,32 @@ PageView(pageCount: 3) { pageIndex in

By default `PageView` fills all the available area, you can constrain it's size using `.frame(width:, height:)` View modifier.

Paging axis can be specified using axis parameter. Axis can be `.vertical` or `.horizontal`. By default `PageView` assumes `.horizontal` axis.
Paging axis can be specified using `axis:` parameter. PageAxis can be `.vertical` or `.horizontal`. By default `PageView` assumes `.horizontal` axis.

```swift
PageView(axis: .vertical, pageCount: 4) { pageIndex in
...
}
```

Alignment of page control can be specified using `alignment:` enum parameter of PageAxis. For example, to achieve top-trailing alignment with vertical axis:
```swift
let axis: PageAxis = .vertical(alignment: .topTrailing)
PageView(axis: axis, pageCount: 5) {
...
}
```

Default alignment is bottom-center for horizontal axis and center-leading for vertical.

You can customize the styling of page control component by passing `PageControlTheme`. Customizable properties:
* background color
* active page dot color
* inactive page dot color
* size of page dot
* spacing between dots
* padding of page control
* y-offset from bottom
* page control offset

```swift
let theme = PageControlTheme(
Expand Down
124 changes: 85 additions & 39 deletions Sources/PageContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,62 +7,108 @@

import SwiftUI

struct PageContent: View {
public enum PageAxis {
case horizontal(alignment: Alignment)
case vertical(alignment: Alignment)

public static var horizontal: PageAxis {
return horizontal(alignment: Alignment(horizontal: .center, vertical: .bottom))
}

public static var vertical: PageAxis {
return vertical(alignment: Alignment(horizontal: .leading, vertical: .center))
}
}

struct PageContent<Page>: View where Page: View {
@ObservedObject var state: PageScrollState
let theme: PageControlTheme
let views: [AnyView]
let axis: Axis
let views: [Page]
let axis: PageAxis
let geometry: GeometryProxy
private let baseOffset: CGFloat

var body: some View {
if axis == .horizontal {
return AnyView(horizontal(using: geometry))
init(state: PageScrollState, theme: PageControlTheme, views: [Page], axis: PageAxis, geometry: GeometryProxy) {
self.state = state
self.theme = theme
self.views = views
self.axis = axis
self.geometry = geometry
if case .horizontal(_) = axis {
self.baseOffset = (geometry.size.width / 2) * CGFloat(views.count - 1)
} else {
return AnyView(vertical(using: geometry))
self.baseOffset = (geometry.size.height / 2) * CGFloat(views.count - 1)
}
}

private func horizontal(using geometry: GeometryProxy) -> some View {
let alignment = Alignment(horizontal: .center, vertical: .bottom)
return
ZStack(alignment: alignment) {
HStack(spacing: 0.0) {
ForEach(0..<self.views.count) { (i) in
self.views[i]
.frame(width: geometry.size.width, height: geometry.size.height)
}
var body: some View {
switch axis {
case .horizontal(alignment: let alignment):
return AnyView(horizontal(using: geometry, alignment: alignment))
case .vertical(alignment: let alignment):
return AnyView(vertical(using: geometry, alignment: alignment))
}
}

private func horizontal(using geometry: GeometryProxy, alignment: Alignment) -> some View {
let pageControl =
PageControl.DefaultHorizontal(pageCount: self.views.count,
selectedPage: self.$state.selectedPage,
theme: self.theme)
.offset(y: -self.theme.offset)

return ZStack(alignment: .center) {
HStack(spacing: 0.0) {
ForEach(0..<self.views.count) { (i) in
self.views[i]
.frame(width: geometry.size.width, height: geometry.size.height)
}
.offset(x: self.horizontalOffset(using: geometry, alignment: .center))
PageControl.DefaultHorizontal(pageCount: self.views.count, selectedPage: self.$state.selectedPage, theme: self.theme)
.offset(y: -self.theme.offset)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.offset(x: self.horizontalOffset(using: geometry))
Rectangle()
.frame(width: geometry.size.width, height: geometry.size.height)
.disabled(true)
.foregroundColor(.clear)
.overlay(pageControl, alignment: alignment)
}.frame(width: geometry.size.width, height: geometry.size.height)
}

private func horizontalOffset(using geometry: GeometryProxy, alignment: HorizontalAlignment) -> CGFloat {
// currently for center only
return (geometry.size.width / 2) * CGFloat(self.views.count - 1) + self.state.contentOffset
private func horizontalOffset(using geometry: GeometryProxy) -> CGFloat {
if state.isGestureActive {
return baseOffset + state.contentOffset
} else {
return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.width
}
}

private func vertical(using geometry: GeometryProxy) -> some View {
let alignment = Alignment(horizontal: .leading, vertical: .center)
return
ZStack(alignment: alignment) {
VStack(spacing: 0.0) {
ForEach(0..<self.views.count) { (i) in
self.views[i]
.frame(width: geometry.size.width, height: geometry.size.height)
}
private func vertical(using geometry: GeometryProxy, alignment: Alignment) -> some View {
let pageControl =
PageControl.DefaultVertical(pageCount: self.views.count,
selectedPage: self.$state.selectedPage,
theme: self.theme)
.offset(x: self.theme.offset)

return ZStack(alignment: .center) {
VStack(spacing: 0.0) {
ForEach(0..<self.views.count) { (i) in
self.views[i]
.frame(width: geometry.size.width, height: geometry.size.height)
}
.offset(y: self.verticalOffset(using: geometry, alignment: .center))
PageControl.DefaultVertical(pageCount: self.views.count, selectedPage: self.$state.selectedPage, theme: self.theme)
.offset(x: self.theme.offset)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.offset(y: self.verticalOffset(using: geometry))
Rectangle()
.frame(width: geometry.size.width, height: geometry.size.height)
.disabled(true)
.foregroundColor(.clear)
.overlay(pageControl, alignment: alignment)
}.frame(width: geometry.size.width, height: geometry.size.height)
}

private func verticalOffset(using geometry: GeometryProxy, alignment: VerticalAlignment) -> CGFloat {
// currently center only
return (geometry.size.height / 2) * CGFloat(self.views.count - 1) + self.state.contentOffset
private func verticalOffset(using geometry: GeometryProxy) -> CGFloat {
if state.isGestureActive {
return baseOffset + state.contentOffset
} else {
return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.height
}
}
}
8 changes: 7 additions & 1 deletion Sources/PageScrollState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import SwiftUI
class PageScrollState: ObservableObject {
@Published var selectedPage: Int = 0
@Published var contentOffset: CGFloat = 0.0
@Published var isGestureActive: Bool = false
var scrollOffset: CGFloat = 0.0

func horizontalDragChanged(_ value: DragGesture.Value, viewCount: Int, pageWidth: CGFloat) {
isGestureActive = true
let delta = value.translation.width
if (delta > 0 && selectedPage == 0) || (delta < 0 && selectedPage == viewCount - 1) {
contentOffset = scrollOffset + delta / 3.0
Expand All @@ -26,8 +28,8 @@ class PageScrollState: ObservableObject {
}

func verticalDragChanged(_ value: DragGesture.Value, viewCount: Int, pageHeight: CGFloat) {
isGestureActive = true
let delta = value.translation.height
contentOffset = scrollOffset + delta
if (delta > 0 && selectedPage == 0) || (delta < 0 && selectedPage == viewCount - 1) {
contentOffset = scrollOffset + delta / 3.0
} else {
Expand Down Expand Up @@ -62,5 +64,9 @@ class PageScrollState: ObservableObject {
self.selectedPage = newPage
self.scrollOffset = self.contentOffset
}

DispatchQueue.main.async {
self.isGestureActive = false
}
}
}
12 changes: 6 additions & 6 deletions Sources/PageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

import SwiftUI

public struct PageView: View {
public struct PageView<Page>: View where Page: View {
let state: PageScrollState
public let theme: PageControlTheme
public let views: [AnyView]
public let axis: Axis
public let views: [Page]
public let axis: PageAxis

public init(axis: Axis = .horizontal, theme: PageControlTheme = .default, pageCount: Int, pageContent: @escaping (Int) -> AnyView) {
public init(axis: PageAxis = .horizontal, theme: PageControlTheme = .default, pageCount: Int, pageContent: @escaping (Int) -> Page) {
self.state = PageScrollState()
self.theme = theme
self.views = (0..<pageCount).map { pageContent($0) }
Expand All @@ -32,15 +32,15 @@ public struct PageView: View {
}

private func onDragChanged(_ value: DragGesture.Value, geometry: GeometryProxy) {
if axis == .horizontal {
if case .horizontal(_) = axis {
state.horizontalDragChanged(value, viewCount: views.count, pageWidth: geometry.size.width)
} else {
state.verticalDragChanged(value, viewCount: views.count, pageHeight: geometry.size.height)
}
}

private func onDragEnded(_ value: DragGesture.Value, geometry: GeometryProxy) {
if axis == .horizontal {
if case .horizontal(_) = axis {
state.horizontalDragEnded(value, viewCount: views.count, pageWidth: geometry.size.width)
} else {
state.verticalDragEnded(value, viewCount: views.count, pageHeight: geometry.size.height)
Expand Down

0 comments on commit 63e00f3

Please sign in to comment.