Skip to content

Commit

Permalink
Merge pull request #7 from fredyshox/prcontrol
Browse files Browse the repository at this point in the history
Version 1.4.0
  • Loading branch information
fredyshox authored Jun 18, 2020
2 parents baf9f9d + 9677f74 commit d9ef69e
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 83 deletions.
30 changes: 16 additions & 14 deletions Examples/PageViewDemo WatchKit Extension/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,26 @@ import SwiftUI
//import PageView

struct ContentView: View {
@State var selectedPage: Int = 0

var body: some View {
// Horizontal
HPageView {
CustomButtonView()
CustomButtonView()
CustomButtonView()
CustomView()
CustomListView()
CustomView()
HPageView(selectedPage: $selectedPage) {
CustomButtonView(pageIndex: 0)
CustomButtonView(pageIndex: 1)
CustomButtonView(pageIndex: 2)
CustomView(pageIndex: 3)
CustomListView(pageIndex: 4)
CustomView(pageIndex: 5)
}.edgesIgnoringSafeArea(.init(arrayLiteral: .leading, .trailing, .bottom))
// Vertical
// VPageView {
// CustomButtonView()
// CustomButtonView()
// CustomButtonView()
// CustomView()
// CustomView()
// CustomView()
// VPageView(selectedPage: $selectedPage) {
// CustomButtonView(pageIndex: 0)
// CustomButtonView(pageIndex: 1)
// CustomButtonView(pageIndex: 2)
// CustomView(pageIndex: 3)
// CustomView(pageIndex: 4)
// CustomView(pageIndex: 5)
// }.edgesIgnoringSafeArea(.init(arrayLiteral: .leading, .trailing, .bottom))
}
}
Expand Down
17 changes: 13 additions & 4 deletions Examples/PageViewDemo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ import SwiftUI
//import PageView

struct ContentView: View {
@State var selectedPage = 2

var body: some View {
HPageView {
CustomView()
CustomListView()
CustomView()
// horizontal
HPageView(selectedPage: $selectedPage) {
CustomView(pageIndex: 0)
CustomListView(pageIndex: 1)
CustomView(pageIndex: 2)
}
// vertical
// VPageView(selectedPage: $selectedPage) {
// CustomView(pageIndex: 0)
// CustomView(pageIndex: 1)
// CustomView(pageIndex: 2)
// }
}
}

Expand Down
14 changes: 10 additions & 4 deletions Examples/PageViewDemo/Views.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,49 @@ extension View {
}

struct CustomButtonView: View {
let pageIndex: Int

var body: some View {
VStack {
Button(action: {
print("Button 1 tapped")
}, label: {
Text("Button 1")
Text("Button 1 at \(pageIndex)")
})
Button(action: {
print("Button 2 tapped")
}, label: {
Text("Button 2")
Text("Button 2 at \(pageIndex)")
})
}
}
}

struct CustomView: View {
let pageIndex: Int

var body: some View {
VStack {
Image(systemName: "globe").resizable()
.scaledToFit()
.frame(width: 50, height: 50)
.foregroundColor(.orange)
Text("Hello world")
Text("Hello world: \(pageIndex)")
.font(.system(size: 24))
.fontWeight(.bold)
}
}
}

struct CustomListView: View {
let pageIndex: Int

var body: some View {
List(0..<10) { (i) in
HStack {
Text("Cell \(i)")
Spacer()
Text("Detail")
Text("Page index: \(self.pageIndex)")
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions PageView.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,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.3.1;
MARKETING_VERSION = 1.4.0;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
Expand Down Expand Up @@ -303,7 +303,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.3.1;
MARKETING_VERSION = 1.4.0;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
Expand Down
78 changes: 70 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ Package requires iOS 13, watchOS 6 and Xcode 11.

For Swift Package Manager add the following package to your Package.swift:
```swift
.package(url: "https://github.com/fredyshox/PageView.git", .upToNextMajor(from: "1.3.3")),
.package(url: "https://github.com/fredyshox/PageView.git", .upToNextMajor(from: "1.4.0")),
```

### Carthage


Carthage is also supported, add FormView by adding to Cartfile:
```
github "fredyshox/PageView" ~> 1.3.3
github "fredyshox/PageView" ~> 1.4.0
```

## Demo
Expand All @@ -39,27 +39,51 @@ Demo app for both iOS and watchOS is provided in `Examples/` directory.
import PageView
```

PageView component is available as `HPageView` or `VPageView` depending on scroll direction (horizontal and vertical, respectively).
To add paged view with 3 pages use following code:
PageView component is available as `HPageView` or `VPageView` depending on scroll direction (horizontal and vertical, respectively). To add paged view with 3 pages use following code:

```swift
@State var pageIndex = 0

...

// horizontal axis
HPageView {
HPageView(selectedPage: $pageIndex) {
SomeCustomView()
AnotherCustomView()
AnotherCustomView()
}

// vertical axis
VPageView {
VPageView(selectedPage: $pageIndex) {
SomeCustomView()
AnotherCustomView()
AnotherCustomView()
}
```

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

### Selected page binding

Displayed page can be programmatically controled using `Binding`. For example, you can change it, with animation effect using:

```swift
withAnimation {
// page index is some State property, which binding was passes into PageView
self.pageIndex = 2
}
```

### Page switch threshold

You can also control minimum distance that needs to be scrolled to switch page, expressed in fraction of page dimension (width or height, depending on axis). This parameter is called `pageSwitchThreshold`, and must be in range from 0.0 to 1.0.

For iOS the default value is set to `0.3`, while on watchOS `0.5`.

### Theme

Styling of page control component can be customized by passing `PageControlTheme`. Customizable properties:

You can customize the styling of page control component by passing `PageControlTheme`. Customizable properties:
* `backgroundColor`
* `dotActiveColor`: active page dot color
* `dotInactiveColor`: inactive page dot color
Expand Down Expand Up @@ -90,6 +114,44 @@ VPageView(theme: theme) {

There is also a built-in `PageControlTheme.default` style, mimicking `UIPageControl` appearance.

## API

```swift
// Horizontal page view
public struct HPageView<Pages>: View where Pages: View {
public init(
selectedPage: Binding<Int>,
pageSwitchThreshold: CGFloat = .defaultSwitchThreshold,
theme: PageControlTheme = .default,
@PageViewBuilder builder: () -> PageContainer<Pages>
)
}

// Vertical page view
public struct VPageView<Pages>: View where Pages: View {
public init(
selectedPage: Binding<Int>,
pageSwitchThreshold: CGFloat = .defaultSwitchThreshold,
theme: PageControlTheme = .default,
@PageViewBuilder builder: () -> PageContainer<Pages>
)
}

public struct PageControlTheme {
public var backgroundColor: Color
public var dotActiveColor: Color
public var dotInactiveColor: Color
public var dotSize: CGFloat
public var spacing: CGFloat
public var padding: CGFloat
public var xOffset: CGFloat
public var yOffset: CGFloat
public var alignment: Alignment?
}
```



## Screenshots

![iOS example](./Images/iOS-example.png)
4 changes: 2 additions & 2 deletions Sources/PageContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ struct PageContent<Stack, Control>: View where Stack: View, Control: View {

private func horizontalOffset(using geometry: GeometryProxy) -> CGFloat {
if state.isGestureActive {
return baseOffset + state.contentOffset
return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.width + state.pageOffset
} else {
return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.width
}
}

private func verticalOffset(using geometry: GeometryProxy) -> CGFloat {
if state.isGestureActive {
return baseOffset + state.contentOffset
return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.height + state.pageOffset
} else {
return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.height
}
Expand Down
70 changes: 47 additions & 23 deletions Sources/PageScrollState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,35 @@
import SwiftUI

class PageScrollState: ObservableObject {
@Published var selectedPage: Int = 0
@Published var contentOffset: CGFloat = 0.0

// MARK: Types

struct TransactionInfo {
var dragValue: DragGesture.Value!
var geometryProxy: GeometryProxy!
}

// MARK: Properties

let switchThreshold: CGFloat
@Binding var selectedPage: Int
@Published var pageOffset: CGFloat = 0.0
@Published var isGestureActive: Bool = false
var scrollOffset: CGFloat = 0.0

init(switchThreshold: CGFloat, selectedPageBinding: Binding<Int>) {
self.switchThreshold = switchThreshold
self._selectedPage = selectedPageBinding
}

// MARK: DragGesture callbacks

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
pageOffset = delta / 3.0
} else {
contentOffset = scrollOffset + delta
pageOffset = delta
}
}

Expand All @@ -31,9 +48,9 @@ class PageScrollState: ObservableObject {
isGestureActive = true
let delta = value.translation.height
if (delta > 0 && selectedPage == 0) || (delta < 0 && selectedPage == viewCount - 1) {
contentOffset = scrollOffset + delta / 3.0
pageOffset = delta / 3.0
} else {
contentOffset = scrollOffset + delta
pageOffset = delta
}
}

Expand All @@ -42,31 +59,38 @@ class PageScrollState: ObservableObject {
}

private func dragEnded(_ value: DragGesture.Value, viewCount: Int, dimension: CGFloat) {
var newOffset = contentOffset
var newPage = selectedPage
if contentOffset > 0 {
newOffset = 0
} else if contentOffset < -(dimension * CGFloat(viewCount - 1)) {
newOffset = -(dimension * CGFloat(viewCount - 1))
} else {
let pageOffset = abs(contentOffset) - CGFloat(selectedPage) * dimension
if pageOffset > 0.5*dimension {
newPage += 1
} else if pageOffset < -0.5*dimension {
newPage -= 1
}

newOffset = -CGFloat(newPage) * dimension
if pageOffset > switchThreshold*dimension && selectedPage != 0 {
newPage -= 1
} else if pageOffset < -switchThreshold*dimension && selectedPage != viewCount - 1 {
newPage += 1
}

withAnimation(.easeInOut(duration: 0.2)) {
self.contentOffset = newOffset
self.pageOffset = 0.0
self.selectedPage = newPage
self.scrollOffset = self.contentOffset
}

DispatchQueue.main.async {
self.isGestureActive = false
}
}

// MARK: Gesture States

func horizontalGestureState(pageCount: Int) -> GestureState<TransactionInfo> {
return GestureState(initialValue: TransactionInfo()) { [weak self] (info, _) in
let width = info.geometryProxy.size.width
let dragValue = info.dragValue!
self?.horizontalDragEnded(dragValue, viewCount: pageCount, pageWidth: width)
}
}

func verticalGestureState(pageCount: Int) -> GestureState<TransactionInfo> {
return GestureState(initialValue: TransactionInfo()) { [weak self] (info, _) in
let height = info.geometryProxy.size.height
let dragValue = info.dragValue!
self?.verticalDragEnded(dragValue, viewCount: pageCount, pageHeight: height)
}
}
}
Loading

0 comments on commit d9ef69e

Please sign in to comment.