diff --git a/Examples/PageViewDemo WatchKit Extension/ContentView.swift b/Examples/PageViewDemo WatchKit Extension/ContentView.swift index f4e7aac..8b9bfa9 100644 --- a/Examples/PageViewDemo WatchKit Extension/ContentView.swift +++ b/Examples/PageViewDemo WatchKit Extension/ContentView.swift @@ -22,6 +22,13 @@ struct ContentView: View { CustomListView(pageIndex: 4) CustomView(pageIndex: 5) }.edgesIgnoringSafeArea(.init(arrayLiteral: .leading, .trailing, .bottom)) + + // ForEach Horizontal +// HPageView(selectedPage: $selectedPage, data: 0..<6) { i in +// CustomButtonView(pageIndex: i) +// } +// .edgesIgnoringSafeArea(.init(arrayLiteral: .leading, .trailing, .bottom)) + // Vertical // VPageView(selectedPage: $selectedPage) { // CustomButtonView(pageIndex: 0) @@ -31,6 +38,12 @@ struct ContentView: View { // CustomView(pageIndex: 4) // CustomView(pageIndex: 5) // }.edgesIgnoringSafeArea(.init(arrayLiteral: .leading, .trailing, .bottom)) + + // ForEach Vertical +// VPageView(selectedPage: $selectedPage, data: 0..<6) { i in +// CustomButtonView(pageIndex: i) +// } +// .edgesIgnoringSafeArea(.init(arrayLiteral: .leading, .trailing, .bottom)) } } diff --git a/Examples/PageViewDemo.xcodeproj/project.pbxproj b/Examples/PageViewDemo.xcodeproj/project.pbxproj index 69523b6..0e5ec34 100644 --- a/Examples/PageViewDemo.xcodeproj/project.pbxproj +++ b/Examples/PageViewDemo.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + E2080C0F2704C9C2001DC9AE /* PageGestureType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2080C0E2704C9C2001DC9AE /* PageGestureType.swift */; }; + E2080C102704C9C2001DC9AE /* PageGestureType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2080C0E2704C9C2001DC9AE /* PageGestureType.swift */; }; E211E06623F219F9000EB7DA /* PageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = E211E06323F219F9000EB7DA /* PageControl.swift */; }; E211E06723F219F9000EB7DA /* PageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E211E06423F219F9000EB7DA /* PageView.swift */; }; E211E06823F219F9000EB7DA /* PageControlTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E211E06523F219F9000EB7DA /* PageControlTheme.swift */; }; @@ -81,6 +83,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + E2080C0E2704C9C2001DC9AE /* PageGestureType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PageGestureType.swift; path = ../Sources/PageGestureType.swift; sourceTree = ""; }; E211E05C23F21695000EB7DA /* PageView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PageView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E211E05F23F2169A000EB7DA /* PageView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PageView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E211E06323F219F9000EB7DA /* PageControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PageControl.swift; path = ../Sources/PageControl.swift; sourceTree = ""; }; @@ -138,6 +141,7 @@ E211E07123F30C49000EB7DA /* PageContent.swift */, E211E07023F30C49000EB7DA /* PageScrollState.swift */, E2B0F9CD242568510065DFBE /* PageViewBuilder.swift */, + E2080C0E2704C9C2001DC9AE /* PageGestureType.swift */, ); name = PageView; sourceTree = ""; @@ -376,6 +380,7 @@ E711489323EF7D14009ABC2D /* SceneDelegate.swift in Sources */, E211E07423F30C49000EB7DA /* PageContent.swift in Sources */, E711489523EF7D14009ABC2D /* ContentView.swift in Sources */, + E2080C0F2704C9C2001DC9AE /* PageGestureType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -393,6 +398,7 @@ E71148BA23EF7D16009ABC2D /* ExtensionDelegate.swift in Sources */, E211E07523F30C49000EB7DA /* PageContent.swift in Sources */, E211E06B23F21A27000EB7DA /* PageView.swift in Sources */, + E2080C102704C9C2001DC9AE /* PageGestureType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -565,7 +571,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 6.1; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Debug; }; @@ -589,7 +595,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 6.1; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Release; }; @@ -608,7 +614,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 6.1; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Debug; }; @@ -627,7 +633,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 6.1; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Release; }; @@ -640,6 +646,7 @@ DEVELOPMENT_TEAM = JE2494MFB5; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = PageViewDemo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -660,6 +667,7 @@ DEVELOPMENT_TEAM = JE2494MFB5; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = PageViewDemo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Examples/PageViewDemo/ContentView.swift b/Examples/PageViewDemo/ContentView.swift index bf02fe1..b33fbbc 100644 --- a/Examples/PageViewDemo/ContentView.swift +++ b/Examples/PageViewDemo/ContentView.swift @@ -13,18 +13,35 @@ struct ContentView: View { @State var selectedPage = 2 var body: some View { - // horizontal + // Horizontal HPageView(selectedPage: $selectedPage) { CustomView(pageIndex: 0) CustomListView(pageIndex: 1) CustomView(pageIndex: 2) } - // vertical + + // ForEach Horizontal +// HPageView(selectedPage: $selectedPage, data: 0..<6) { i in +// CustomView(pageIndex: i) +// } + + // Vertical // VPageView(selectedPage: $selectedPage) { // CustomView(pageIndex: 0) // CustomView(pageIndex: 1) // CustomView(pageIndex: 2) // } + + // ForEach Vertical +// VPageView(selectedPage: $selectedPage, data: 0..<6) { i in +// CustomView(pageIndex: i) +// } + } +} + +extension Int: Identifiable { + public var id: Int { + return self } } diff --git a/Package.swift b/Package.swift index d8ac514..3ddbbf7 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,8 +6,8 @@ import PackageDescription let package = Package( name: "PageView", platforms: [ - .iOS(.v13), - .watchOS(.v6) + .iOS(.v14), + .watchOS(.v7) ], products: [ .library(name: "PageView", targets: ["PageView"]), diff --git a/PageView.xcodeproj/project.pbxproj b/PageView.xcodeproj/project.pbxproj index 3357bbb..4a8f40c 100644 --- a/PageView.xcodeproj/project.pbxproj +++ b/PageView.xcodeproj/project.pbxproj @@ -9,11 +9,11 @@ /* Begin PBXAggregateTarget section */ "PageView::PageViewPackageTests::ProductTarget" /* PageViewPackageTests */ = { isa = PBXAggregateTarget; - buildConfigurationList = OBJ_34 /* Build configuration list for PBXAggregateTarget "PageViewPackageTests" */; + buildConfigurationList = OBJ_46 /* Build configuration list for PBXAggregateTarget "PageViewPackageTests" */; buildPhases = ( ); dependencies = ( - OBJ_37 /* PBXTargetDependency */, + OBJ_49 /* PBXTargetDependency */, ); name = PageViewPackageTests; productName = PageViewPackageTests; @@ -21,27 +21,28 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - E211E06D23F30AD5000EB7DA /* PageContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E211E06C23F30AD5000EB7DA /* PageContent.swift */; }; - E211E06F23F30AFD000EB7DA /* PageScrollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E211E06E23F30AFD000EB7DA /* PageScrollState.swift */; }; - E2B0F9CC242561620065DFBE /* PageViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B0F9CB242561620065DFBE /* PageViewBuilder.swift */; }; - OBJ_23 /* PageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_8 /* PageControl.swift */; }; - OBJ_24 /* PageControlTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* PageControlTheme.swift */; }; - OBJ_25 /* PageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* PageView.swift */; }; - OBJ_32 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; - OBJ_43 /* PageViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* PageViewTests.swift */; }; - OBJ_44 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* XCTestManifests.swift */; }; - OBJ_46 /* PageView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "PageView::PageView::Product" /* PageView.framework */; }; + OBJ_31 /* PageContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_8 /* PageContent.swift */; }; + OBJ_32 /* PageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* PageControl.swift */; }; + OBJ_33 /* PageControlTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* PageControlTheme.swift */; }; + OBJ_34 /* PageGestureType.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* PageGestureType.swift */; }; + OBJ_35 /* PageScrollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* PageScrollState.swift */; }; + OBJ_36 /* PageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* PageView.swift */; }; + OBJ_37 /* PageViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* PageViewBuilder.swift */; }; + OBJ_44 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; + OBJ_55 /* PageViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* PageViewTests.swift */; }; + OBJ_56 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* XCTestManifests.swift */; }; + OBJ_58 /* PageView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "PageView::PageView::Product" /* PageView.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - E711488223EF79FA009ABC2D /* PBXContainerItemProxy */ = { + E2D838522705CDE3008D0EC0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = OBJ_1 /* Project object */; proxyType = 1; remoteGlobalIDString = "PageView::PageView"; remoteInfo = PageView; }; - E711488323EF79FA009ABC2D /* PBXContainerItemProxy */ = { + E2D838552705CDE3008D0EC0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = OBJ_1 /* Project object */; proxyType = 1; @@ -51,57 +52,62 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - E211E06C23F30AD5000EB7DA /* PageContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageContent.swift; sourceTree = ""; }; - E211E06E23F30AFD000EB7DA /* PageScrollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageScrollState.swift; sourceTree = ""; }; - E2B0F9CB242561620065DFBE /* PageViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageViewBuilder.swift; sourceTree = ""; }; - OBJ_10 /* PageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageView.swift; sourceTree = ""; }; - OBJ_13 /* PageViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageViewTests.swift; sourceTree = ""; }; - OBJ_14 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = ""; }; + OBJ_10 /* PageControlTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlTheme.swift; sourceTree = ""; }; + OBJ_11 /* PageGestureType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageGestureType.swift; sourceTree = ""; }; + OBJ_12 /* PageScrollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageScrollState.swift; sourceTree = ""; }; + OBJ_13 /* PageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageView.swift; sourceTree = ""; }; + OBJ_14 /* PageViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageViewBuilder.swift; sourceTree = ""; }; + OBJ_17 /* PageViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageViewTests.swift; sourceTree = ""; }; + OBJ_18 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = ""; }; + OBJ_22 /* Images */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Images; sourceTree = SOURCE_ROOT; }; + OBJ_23 /* Examples */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Examples; sourceTree = SOURCE_ROOT; }; + OBJ_24 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + OBJ_25 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; - OBJ_8 /* PageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControl.swift; sourceTree = ""; }; - OBJ_9 /* PageControlTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlTheme.swift; sourceTree = ""; }; + OBJ_8 /* PageContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageContent.swift; sourceTree = ""; }; + OBJ_9 /* PageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControl.swift; sourceTree = ""; }; "PageView::PageView::Product" /* PageView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PageView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; "PageView::PageViewTests::Product" /* PageViewTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = PageViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - OBJ_26 /* Frameworks */ = { + OBJ_38 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 0; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - OBJ_45 /* Frameworks */ = { + OBJ_57 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 0; files = ( - OBJ_46 /* PageView.framework in Frameworks */, + OBJ_58 /* PageView.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - OBJ_11 /* Tests */ = { + OBJ_15 /* Tests */ = { isa = PBXGroup; children = ( - OBJ_12 /* PageViewTests */, + OBJ_16 /* PageViewTests */, ); name = Tests; sourceTree = SOURCE_ROOT; }; - OBJ_12 /* PageViewTests */ = { + OBJ_16 /* PageViewTests */ = { isa = PBXGroup; children = ( - OBJ_13 /* PageViewTests.swift */, - OBJ_14 /* XCTestManifests.swift */, + OBJ_17 /* PageViewTests.swift */, + OBJ_18 /* XCTestManifests.swift */, ); name = PageViewTests; path = Tests/PageViewTests; sourceTree = SOURCE_ROOT; }; - OBJ_15 /* Products */ = { + OBJ_19 /* Products */ = { isa = PBXGroup; children = ( "PageView::PageViewTests::Product" /* PageViewTests.xctest */, @@ -110,25 +116,31 @@ name = Products; sourceTree = BUILT_PRODUCTS_DIR; }; - OBJ_5 = { + OBJ_5 /* */ = { isa = PBXGroup; children = ( OBJ_6 /* Package.swift */, OBJ_7 /* Sources */, - OBJ_11 /* Tests */, - OBJ_15 /* Products */, + OBJ_15 /* Tests */, + OBJ_19 /* Products */, + OBJ_22 /* Images */, + OBJ_23 /* Examples */, + OBJ_24 /* LICENSE */, + OBJ_25 /* README.md */, ); + name = ""; sourceTree = ""; }; OBJ_7 /* Sources */ = { isa = PBXGroup; children = ( - OBJ_8 /* PageControl.swift */, - OBJ_9 /* PageControlTheme.swift */, - OBJ_10 /* PageView.swift */, - E211E06C23F30AD5000EB7DA /* PageContent.swift */, - E211E06E23F30AFD000EB7DA /* PageScrollState.swift */, - E2B0F9CB242561620065DFBE /* PageViewBuilder.swift */, + OBJ_8 /* PageContent.swift */, + OBJ_9 /* PageControl.swift */, + OBJ_10 /* PageControlTheme.swift */, + OBJ_11 /* PageGestureType.swift */, + OBJ_12 /* PageScrollState.swift */, + OBJ_13 /* PageView.swift */, + OBJ_14 /* PageViewBuilder.swift */, ); path = Sources; sourceTree = SOURCE_ROOT; @@ -138,10 +150,10 @@ /* Begin PBXNativeTarget section */ "PageView::PageView" /* PageView */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_19 /* Build configuration list for PBXNativeTarget "PageView" */; + buildConfigurationList = OBJ_27 /* Build configuration list for PBXNativeTarget "PageView" */; buildPhases = ( - OBJ_22 /* Sources */, - OBJ_26 /* Frameworks */, + OBJ_30 /* Sources */, + OBJ_38 /* Frameworks */, ); buildRules = ( ); @@ -154,15 +166,15 @@ }; "PageView::PageViewTests" /* PageViewTests */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_39 /* Build configuration list for PBXNativeTarget "PageViewTests" */; + buildConfigurationList = OBJ_51 /* Build configuration list for PBXNativeTarget "PageViewTests" */; buildPhases = ( - OBJ_42 /* Sources */, - OBJ_45 /* Frameworks */, + OBJ_54 /* Sources */, + OBJ_57 /* Frameworks */, ); buildRules = ( ); dependencies = ( - OBJ_47 /* PBXTargetDependency */, + OBJ_59 /* PBXTargetDependency */, ); name = PageViewTests; productName = PageViewTests; @@ -171,9 +183,9 @@ }; "PageView::SwiftPMPackageDescription" /* PageViewPackageDescription */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_28 /* Build configuration list for PBXNativeTarget "PageViewPackageDescription" */; + buildConfigurationList = OBJ_40 /* Build configuration list for PBXNativeTarget "PageViewPackageDescription" */; buildPhases = ( - OBJ_31 /* Sources */, + OBJ_43 /* Sources */, ); buildRules = ( ); @@ -199,8 +211,8 @@ knownRegions = ( en, ); - mainGroup = OBJ_5; - productRefGroup = OBJ_15 /* Products */; + mainGroup = OBJ_5 /* */; + productRefGroup = OBJ_19 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( @@ -213,53 +225,54 @@ /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ - OBJ_22 /* Sources */ = { + OBJ_30 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( - E211E06D23F30AD5000EB7DA /* PageContent.swift in Sources */, - OBJ_23 /* PageControl.swift in Sources */, - E211E06F23F30AFD000EB7DA /* PageScrollState.swift in Sources */, - E2B0F9CC242561620065DFBE /* PageViewBuilder.swift in Sources */, - OBJ_24 /* PageControlTheme.swift in Sources */, - OBJ_25 /* PageView.swift in Sources */, + OBJ_31 /* PageContent.swift in Sources */, + OBJ_32 /* PageControl.swift in Sources */, + OBJ_33 /* PageControlTheme.swift in Sources */, + OBJ_34 /* PageGestureType.swift in Sources */, + OBJ_35 /* PageScrollState.swift in Sources */, + OBJ_36 /* PageView.swift in Sources */, + OBJ_37 /* PageViewBuilder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - OBJ_31 /* Sources */ = { + OBJ_43 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( - OBJ_32 /* Package.swift in Sources */, + OBJ_44 /* Package.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - OBJ_42 /* Sources */ = { + OBJ_54 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( - OBJ_43 /* PageViewTests.swift in Sources */, - OBJ_44 /* XCTestManifests.swift in Sources */, + OBJ_55 /* PageViewTests.swift in Sources */, + OBJ_56 /* XCTestManifests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - OBJ_37 /* PBXTargetDependency */ = { + OBJ_49 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = "PageView::PageViewTests" /* PageViewTests */; - targetProxy = E711488323EF79FA009ABC2D /* PBXContainerItemProxy */; + targetProxy = E2D838552705CDE3008D0EC0 /* PBXContainerItemProxy */; }; - OBJ_47 /* PBXTargetDependency */ = { + OBJ_59 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = "PageView::PageView" /* PageView */; - targetProxy = E711488223EF79FA009ABC2D /* PBXContainerItemProxy */; + targetProxy = E2D838522705CDE3008D0EC0 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - OBJ_20 /* Debug */ = { + OBJ_28 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CURRENT_PROJECT_VERSION = 1; @@ -270,10 +283,10 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = PageView.xcodeproj/PageView_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.4.1; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MARKETING_VERSION = 1.5.0; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; @@ -285,11 +298,11 @@ SWIFT_VERSION = 5.0; TARGET_NAME = PageView; TVOS_DEPLOYMENT_TARGET = 9.0; - WATCHOS_DEPLOYMENT_TARGET = 6.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Debug; }; - OBJ_21 /* Release */ = { + OBJ_29 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CURRENT_PROJECT_VERSION = 1; @@ -300,10 +313,10 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = PageView.xcodeproj/PageView_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.4.1; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MARKETING_VERSION = 1.5.0; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; @@ -315,19 +328,10 @@ SWIFT_VERSION = 5.0; TARGET_NAME = PageView; TVOS_DEPLOYMENT_TARGET = 9.0; - WATCHOS_DEPLOYMENT_TARGET = 6.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Release; }; - OBJ_29 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - LD = /usr/bin/true; - OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.1"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; OBJ_3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -355,27 +359,6 @@ }; name = Debug; }; - OBJ_30 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - LD = /usr/bin/true; - OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.1"; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - OBJ_35 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - }; - name = Debug; - }; - OBJ_36 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - }; - name = Release; - }; OBJ_4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -400,7 +383,37 @@ }; name = Release; }; - OBJ_40 /* Debug */ = { + OBJ_41 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD = /usr/bin/true; + OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -package-description-version 5.3.0"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + OBJ_42 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD = /usr/bin/true; + OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -package-description-version 5.3.0"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + OBJ_47 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Debug; + }; + OBJ_48 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Release; + }; + OBJ_52 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; @@ -411,9 +424,9 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = PageView.xcodeproj/PageViewTests_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; @@ -421,11 +434,11 @@ SWIFT_VERSION = 5.0; TARGET_NAME = PageViewTests; TVOS_DEPLOYMENT_TARGET = 9.0; - WATCHOS_DEPLOYMENT_TARGET = 6.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Debug; }; - OBJ_41 /* Release */ = { + OBJ_53 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; @@ -436,9 +449,9 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = PageView.xcodeproj/PageViewTests_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; @@ -446,54 +459,54 @@ SWIFT_VERSION = 5.0; TARGET_NAME = PageViewTests; TVOS_DEPLOYMENT_TARGET = 9.0; - WATCHOS_DEPLOYMENT_TARGET = 6.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - OBJ_19 /* Build configuration list for PBXNativeTarget "PageView" */ = { + OBJ_2 /* Build configuration list for PBXProject "PageView" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_20 /* Debug */, - OBJ_21 /* Release */, + OBJ_3 /* Debug */, + OBJ_4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_2 /* Build configuration list for PBXProject "PageView" */ = { + OBJ_27 /* Build configuration list for PBXNativeTarget "PageView" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_3 /* Debug */, - OBJ_4 /* Release */, + OBJ_28 /* Debug */, + OBJ_29 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_28 /* Build configuration list for PBXNativeTarget "PageViewPackageDescription" */ = { + OBJ_40 /* Build configuration list for PBXNativeTarget "PageViewPackageDescription" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_29 /* Debug */, - OBJ_30 /* Release */, + OBJ_41 /* Debug */, + OBJ_42 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_34 /* Build configuration list for PBXAggregateTarget "PageViewPackageTests" */ = { + OBJ_46 /* Build configuration list for PBXAggregateTarget "PageViewPackageTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_35 /* Debug */, - OBJ_36 /* Release */, + OBJ_47 /* Debug */, + OBJ_48 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_39 /* Build configuration list for PBXNativeTarget "PageViewTests" */ = { + OBJ_51 /* Build configuration list for PBXNativeTarget "PageViewTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_40 /* Debug */, - OBJ_41 /* Release */, + OBJ_52 /* Debug */, + OBJ_53 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/PageView.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PageView.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 919434a..fe1aa71 100644 --- a/PageView.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/PageView.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,4 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index da46d20..aa0222f 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ This package attempts to provide native SwiftUI component for navigation between ## Installation -Package requires iOS 13, watchOS 6 and Xcode 11. +Package requires iOS 14, watchOS 7 and Xcode 12. ### Swift Package Manager For Swift Package Manager add the following package to your Package.swift: ```swift -.package(url: "https://github.com/fredyshox/PageView.git", .upToNextMajor(from: "1.4.1")), +.package(url: "https://github.com/fredyshox/PageView.git", .upToNextMajor(from: "1.5.0")), ``` ### Carthage @@ -26,7 +26,7 @@ For Swift Package Manager add the following package to your Package.swift: Carthage is also supported, add following line to Cartfile: ``` -github "fredyshox/PageView" ~> 1.4.1 +github "fredyshox/PageView" ~> 1.5.0 ``` ## Demo @@ -74,12 +74,47 @@ withAnimation { } ``` +### ForEach-style init + +PageView supports `ForEach`-like initialization, that is: + +```swift +// integer ranges +HPageView(selectedPage: $pageIndex, data: 0..<5) { index in + SomeCustomView(withIndex: index) +} + +// or collection of `Identifiable` +let identifiableArray: [SomeIdentifiableItem] = ... +HPageView(selectedPage: $pageIndex, data: identifiableArray) { item in + SomeCustomView(withItem: item) +} + +// or any other collection, by specifing id by key-path +let nonIdentifiableArray: [SomeNonIdentifiableItem] = ... +HPageView(selectedPage: $pageIndex, data: nonIdentifiableArray, idKeyPath: \.path.to.id) { item in + SomeCustomView(withItem: item) +} +``` + ### 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`. +### Gesture type + +If PageView gestures are intefering with gestures present on individual pages, PageView's gesture type can be changed by passing `PageGestureType`. Possible values: `.standard`, `.simultaneous`, `.highPriority` (default) + +```swift +VPageView(selectedPage: $pageIndex, pageGestureType: .simultaneous) { + // views +} +``` + + + ### Theme Styling of page control component can be customized by passing `PageControlTheme`. Customizable properties: @@ -92,6 +127,7 @@ Styling of page control component can be customized by passing `PageControlTheme * `padding`: padding of page control * `xOffset`: page control x-axis offset, used only in vertical mode * `yOffset`: page control y-axis offset, used only in horizontal mode +* `opacity`: page control opacity in range from 0.0 (invisible) to 1.0 (opaque) * `alignment`: alignment of page control component (default: bottom-center in horizontal mode, center-leading in vertical mode) ```swift @@ -104,6 +140,7 @@ let theme = PageControlTheme( padding: 5.0, xOffset: 8.0, yOffset: -8.0, + opacity: 0.5, alignment: Alignment(horizontal: .trailing, vertical: .top) ) ... @@ -112,7 +149,7 @@ VPageView(theme: theme) { } ``` -There is also a built-in `PageControlTheme.default` style, mimicking `UIPageControl` appearance. +There is also a built-in `PageControlTheme.default` style, mimicking `UIPageControl` appearance. To hide it completely use `PageControlTheme.invisible`. ## API @@ -122,6 +159,7 @@ public struct HPageView: View where Pages: View { public init( selectedPage: Binding, pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, theme: PageControlTheme = .default, @PageViewBuilder builder: () -> PageContainer ) @@ -132,11 +170,16 @@ public struct VPageView: View where Pages: View { public init( selectedPage: Binding, pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, theme: PageControlTheme = .default, @PageViewBuilder builder: () -> PageContainer ) } +public enum PageGestureType { + case standard, simultaneous, highPriority +} + public struct PageControlTheme { public var backgroundColor: Color public var dotActiveColor: Color @@ -146,6 +189,7 @@ public struct PageControlTheme { public var padding: CGFloat public var xOffset: CGFloat public var yOffset: CGFloat + public var opacity: Double public var alignment: Alignment? } ``` diff --git a/Sources/PageContent.swift b/Sources/PageContent.swift index f9eed86..9672f3b 100644 --- a/Sources/PageContent.swift +++ b/Sources/PageContent.swift @@ -42,7 +42,9 @@ struct VerticalPageStack: View where Pages: View { } struct PageContent: View where Stack: View, Control: View { - @ObservedObject var state: PageScrollState + @Binding var selectedPage: Int + @Binding var pageOffset: CGFloat + @Binding var isGestureActive: Bool let compositeView: Stack let childCount: Int let pageControlBuilder: (Int, Binding) -> Control @@ -51,8 +53,20 @@ struct PageContent: View where Stack: View, Control: View { let geometry: GeometryProxy private let baseOffset: CGFloat - init(state: PageScrollState, axis: Axis, alignment: Alignment, geometry: GeometryProxy, childCount: Int, compositeView: Stack, pageControlBuilder: @escaping (Int, Binding) -> Control) { - self.state = state + init( + selectedPage: Binding, + pageOffset: Binding, + isGestureActive: Binding, + axis: Axis, + alignment: Alignment, + geometry: GeometryProxy, + childCount: Int, + compositeView: Stack, + pageControlBuilder: @escaping (Int, Binding) -> Control + ) { + self._selectedPage = selectedPage + self._pageOffset = pageOffset + self._isGestureActive = isGestureActive self.compositeView = compositeView self.childCount = childCount self.pageControlBuilder = pageControlBuilder @@ -67,7 +81,7 @@ struct PageContent: View where Stack: View, Control: View { } var body: some View { - let pageControl = pageControlBuilder(childCount, $state.selectedPage) + let pageControl = pageControlBuilder(childCount, $selectedPage) return ZStack(alignment: .center) { compositeView @@ -91,18 +105,18 @@ struct PageContent: View where Stack: View, Control: View { } private func horizontalOffset(using geometry: GeometryProxy) -> CGFloat { - if state.isGestureActive { - return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.width + state.pageOffset + if isGestureActive { + return baseOffset + -1 * CGFloat(selectedPage) * geometry.size.width + pageOffset } else { - return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.width + return baseOffset + -1 * CGFloat(selectedPage) * geometry.size.width } } private func verticalOffset(using geometry: GeometryProxy) -> CGFloat { - if state.isGestureActive { - return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.height + state.pageOffset + if isGestureActive { + return baseOffset + -1 * CGFloat(selectedPage) * geometry.size.height + pageOffset } else { - return baseOffset + -1 * CGFloat(state.selectedPage) * geometry.size.height + return baseOffset + -1 * CGFloat(selectedPage) * geometry.size.height } } } diff --git a/Sources/PageControl.swift b/Sources/PageControl.swift index b2e9387..7fb3a93 100644 --- a/Sources/PageControl.swift +++ b/Sources/PageControl.swift @@ -38,6 +38,7 @@ public enum PageControl { } .modifier(Background(theme: theme)) .offset(x: 0.0, y: theme.yOffset) + .opacity(theme.opacity) } } @@ -55,6 +56,7 @@ public enum PageControl { } .modifier(Background(theme: theme)) .offset(x: theme.xOffset, y: 0.0) + .opacity(theme.opacity) } } diff --git a/Sources/PageControlTheme.swift b/Sources/PageControlTheme.swift index 4f01d02..1b7bb14 100644 --- a/Sources/PageControlTheme.swift +++ b/Sources/PageControlTheme.swift @@ -16,6 +16,7 @@ public struct PageControlTheme { public var padding: CGFloat public var xOffset: CGFloat public var yOffset: CGFloat + public var opacity: Double public var alignment: Alignment? public init( @@ -27,6 +28,7 @@ public struct PageControlTheme { padding: CGFloat, xOffset: CGFloat, yOffset: CGFloat, + opacity: Double = 1.0, alignment: Alignment? = nil ) { self.backgroundColor = backgroundColor @@ -37,9 +39,25 @@ public struct PageControlTheme { self.padding = padding self.xOffset = xOffset self.yOffset = yOffset + self.opacity = opacity self.alignment = alignment } + public static var invisible: PageControlTheme { + return PageControlTheme( + backgroundColor: .clear, + dotActiveColor: .clear, + dotInactiveColor: .clear, + dotSize: .zero, + spacing: .zero, + padding: .zero, + xOffset: .zero, + yOffset: .zero, + opacity: .zero, + alignment: nil + ) + } + public static var `default`: PageControlTheme { #if os(iOS) return PageControlTheme( @@ -51,6 +69,7 @@ public struct PageControlTheme { padding: 4.0, xOffset: 12.0, yOffset: -12.0, + opacity: 1.0, alignment: nil ) #elseif os(watchOS) @@ -63,6 +82,7 @@ public struct PageControlTheme { padding: 2.0, xOffset: 0.0, yOffset: 0.0, + opacity: 1.0, alignment: nil ) #else @@ -75,6 +95,7 @@ public struct PageControlTheme { padding: 8.0, xOffset: 16.0, yOffset: -16.0, + opacity: 1.0, alignment: nil ) #endif diff --git a/Sources/PageGestureType.swift b/Sources/PageGestureType.swift new file mode 100644 index 0000000..39e3f06 --- /dev/null +++ b/Sources/PageGestureType.swift @@ -0,0 +1,26 @@ +// +// PageGestureType.swift +// PageView +// +// Created by Kacper Rączy on 29/09/2021. +// + +import SwiftUI + +public enum PageGestureType { + case standard, simultaneous, highPriority +} + +extension View { + func gesture(_ gesture: G, type: PageGestureType, including mask: GestureMask = .all) -> some View where G: Gesture { + Group { + if type == .simultaneous { + self.simultaneousGesture(gesture, including: mask) + } else if type == .highPriority { + self.highPriorityGesture(gesture, including: mask) + } else { + self.gesture(gesture, including: mask) + } + } + } +} diff --git a/Sources/PageScrollState.swift b/Sources/PageScrollState.swift index f286e60..a09dc9e 100644 --- a/Sources/PageScrollState.swift +++ b/Sources/PageScrollState.swift @@ -7,7 +7,7 @@ import SwiftUI -class PageScrollState: ObservableObject { +final class PageScrollState: ObservableObject { // MARK: Types @@ -66,7 +66,7 @@ class PageScrollState: ObservableObject { newPage += 1 } - withAnimation(.easeInOut(duration: 0.2)) { + withAnimation(.easeOut(duration: 0.2)) { self.pageOffset = 0.0 self.selectedPage = newPage } diff --git a/Sources/PageView.swift b/Sources/PageView.swift index f949b27..cd8e1dd 100644 --- a/Sources/PageView.swift +++ b/Sources/PageView.swift @@ -8,29 +8,110 @@ import SwiftUI public struct HPageView: View where Pages: View { - let state: PageScrollState public let theme: PageControlTheme public let pages: PageContainer public let pageCount: Int public let pageControlAlignment: Alignment + public let pageGestureType: PageGestureType + + @StateObject var state: PageScrollState @GestureState var stateTransaction: PageScrollState.TransactionInfo public init( selectedPage: Binding, pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, theme: PageControlTheme = .default, @PageViewBuilder builder: () -> PageContainer + ) { + let pages = builder() + self.init( + selectedPage: selectedPage, + pageCount: pages.count, + pageContainer: pages, + pageSwitchThreshold: pageSwitchThreshold, + pageGestureType: pageGestureType, + theme: theme + ) + } + + public init( + selectedPage: Binding, + data: Data, + pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, + theme: PageControlTheme = .default, + builder: @escaping (Data.Element) -> ForEachContent + ) where Data.Element: Identifiable, Pages == ForEach { + let forEachContainer = PageContainer(count: data.count, content: ForEach(data, content: builder)) + self.init( + selectedPage: selectedPage, + pageCount: data.count, + pageContainer: forEachContainer, + pageSwitchThreshold: pageSwitchThreshold, + pageGestureType: pageGestureType, + theme: theme + ) + } + + public init( + selectedPage: Binding, + data: Data, + idKeyPath: KeyPath, + pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, + theme: PageControlTheme = .default, + builder: @escaping (Data.Element) -> ForEachContent + ) where Pages == ForEach { + let forEachContainer = PageContainer(count: data.count, content: ForEach(data, id: idKeyPath, content: builder)) + self.init( + selectedPage: selectedPage, + pageCount: data.count, + pageContainer: forEachContainer, + pageSwitchThreshold: pageSwitchThreshold, + pageGestureType: pageGestureType, + theme: theme + ) + } + + public init( + selectedPage: Binding, + data: Range, + pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, + theme: PageControlTheme = .default, + builder: @escaping (Int) -> ForEachContent + ) where Pages == ForEach, Int, ForEachContent> { + let forEachContainer = PageContainer(count: data.count, content: ForEach(data, content: builder)) + self.init( + selectedPage: selectedPage, + pageCount: data.count, + pageContainer: forEachContainer, + pageSwitchThreshold: pageSwitchThreshold, + pageGestureType: pageGestureType, + theme: theme + ) + } + + private init( + selectedPage: Binding, + pageCount: Int, + pageContainer: PageContainer, + pageSwitchThreshold: CGFloat, + pageGestureType: PageGestureType, + theme: PageControlTheme ) { // prevent values outside of 0...1 let threshold = CGFloat(abs(pageSwitchThreshold) - floor(abs(pageSwitchThreshold))) - self.state = PageScrollState(switchThreshold: threshold, selectedPageBinding: selectedPage) + let wrappedStateObj = PageScrollState(switchThreshold: threshold, selectedPageBinding: selectedPage) + self._state = StateObject(wrappedValue: wrappedStateObj) + self._stateTransaction = wrappedStateObj.horizontalGestureState(pageCount: pageCount) self.theme = theme - let pages = builder() - self.pages = pages - self.pageCount = pages.count + self.pages = pageContainer + self.pageCount = pageCount self.pageControlAlignment = theme.alignment ?? Alignment(horizontal: .center, vertical: .bottom) - self._stateTransaction = state.horizontalGestureState(pageCount: pages.count) + self.pageGestureType = pageGestureType } public var body: some View { @@ -41,7 +122,9 @@ public struct HPageView: View where Pages: View { } return GeometryReader { geometry in - PageContent(state: self.state, + PageContent(selectedPage: state.$selectedPage, + pageOffset: $state.pageOffset, + isGestureActive: $state.isGestureActive, axis: .horizontal, alignment: self.pageControlAlignment, geometry: geometry, @@ -49,60 +132,149 @@ public struct HPageView: View where Pages: View { compositeView: HorizontalPageStack(pages: self.pages, geometry: geometry), pageControlBuilder: pageControlBuilder) .contentShape(Rectangle()) - .highPriorityGesture(DragGesture(minimumDistance: 8.0) - .updating(self.$stateTransaction, body: { value, state, _ in - state.dragValue = value - state.geometryProxy = geometry - }) - .onChanged({ - let width = geometry.size.width - let pageCount = self.pageCount - self.state.horizontalDragChanged($0, viewCount: pageCount, pageWidth: width) - }) - /* - There is a bug, where onEnded is not called, when gesture is cancelled. - So onEnded is handled using reset handler in `GestureState` (look `PageScrollState`) - */ + .gesture( + dragGesture(geometry: geometry), + type: pageGestureType ) } } + + private func dragGesture(geometry: GeometryProxy) -> some Gesture { + /* + There is a bug, where onEnded is not called, when gesture is cancelled. + So onEnded is handled using reset handler in `GestureState` (look `PageScrollState`) + */ + DragGesture(minimumDistance: 8.0) + .updating(self.$stateTransaction, body: { value, state, _ in + state.dragValue = value + state.geometryProxy = geometry + }) + .onChanged({ + let width = geometry.size.width + let pageCount = self.pageCount + self.state.horizontalDragChanged($0, viewCount: pageCount, pageWidth: width) + }) + } } public struct VPageView: View where Pages: View { - let state: PageScrollState public let theme: PageControlTheme public let pages: PageContainer public let pageCount: Int public let pageControlAlignment: Alignment - @GestureState var stateTransaction: PageScrollState.TransactionInfo + public let pageGestureType: PageGestureType + @StateObject var state: PageScrollState + @GestureState var stateTransaction: PageScrollState.TransactionInfo + public init( selectedPage: Binding, pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, theme: PageControlTheme = .default, @PageViewBuilder builder: () -> PageContainer + ) { + let pages = builder() + self.init( + selectedPage: selectedPage, + pageCount: pages.count, + pageContainer: pages, + pageSwitchThreshold: pageSwitchThreshold, + pageGestureType: pageGestureType, + theme: theme + ) + } + + public init( + selectedPage: Binding, + data: Data, + pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, + theme: PageControlTheme = .default, + builder: @escaping (Data.Element) -> ForEachContent + ) where Data.Element: Identifiable, Pages == ForEach { + let forEachContainer = PageContainer(count: data.count, content: ForEach(data, content: builder)) + self.init( + selectedPage: selectedPage, + pageCount: data.count, + pageContainer: forEachContainer, + pageSwitchThreshold: pageSwitchThreshold, + pageGestureType: pageGestureType, + theme: theme + ) + } + + public init( + selectedPage: Binding, + data: Data, + idKeyPath: KeyPath, + pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, + theme: PageControlTheme = .default, + builder: @escaping (Data.Element) -> ForEachContent + ) where Pages == ForEach { + let forEachContainer = PageContainer(count: data.count, content: ForEach(data, id: idKeyPath, content: builder)) + self.init( + selectedPage: selectedPage, + pageCount: data.count, + pageContainer: forEachContainer, + pageSwitchThreshold: pageSwitchThreshold, + pageGestureType: pageGestureType, + theme: theme + ) + } + + public init( + selectedPage: Binding, + data: Range, + pageSwitchThreshold: CGFloat = .defaultSwitchThreshold, + pageGestureType: PageGestureType = .highPriority, + theme: PageControlTheme = .default, + builder: @escaping (Int) -> ForEachContent + ) where Pages == ForEach, Int, ForEachContent> { + let forEachContainer = PageContainer(count: data.count, content: ForEach(data, content: builder)) + self.init( + selectedPage: selectedPage, + pageCount: data.count, + pageContainer: forEachContainer, + pageSwitchThreshold: pageSwitchThreshold, + pageGestureType: pageGestureType, + theme: theme + ) + } + + private init( + selectedPage: Binding, + pageCount: Int, + pageContainer: PageContainer, + pageSwitchThreshold: CGFloat, + pageGestureType: PageGestureType, + theme: PageControlTheme ) { // prevent values outside of 0...1 let threshold = CGFloat(abs(pageSwitchThreshold) - floor(abs(pageSwitchThreshold))) - self.state = PageScrollState(switchThreshold: threshold, selectedPageBinding: selectedPage) + let wrappedStateObj = PageScrollState(switchThreshold: threshold, selectedPageBinding: selectedPage) + self._state = StateObject(wrappedValue: wrappedStateObj) + self._stateTransaction = wrappedStateObj.verticalGestureState(pageCount: pageCount) self.theme = theme - let pages = builder() - self.pages = pages - self.pageCount = pages.count + self.pages = pageContainer + self.pageCount = pageCount self.pageControlAlignment = theme.alignment ?? Alignment(horizontal: .leading, vertical: .center) - self._stateTransaction = state.verticalGestureState(pageCount: pages.count) + self.pageGestureType = pageGestureType } - + public var body: some View { let pageControlBuilder = { (childCount, selectedPageBinding) in return PageControl.DefaultVertical(pageCount: childCount, selectedPage: selectedPageBinding, theme: self.theme) } - + return GeometryReader { geometry in - PageContent(state: self.state, + PageContent(selectedPage: state.$selectedPage, + pageOffset: $state.pageOffset, + isGestureActive: $state.isGestureActive, axis: .vertical, alignment: self.pageControlAlignment, geometry: geometry, @@ -110,23 +282,29 @@ public struct VPageView: View where Pages: View { compositeView: VerticalPageStack(pages: self.pages, geometry: geometry), pageControlBuilder: pageControlBuilder) .contentShape(Rectangle()) - .highPriorityGesture(DragGesture(minimumDistance: 8.0) - .updating(self.$stateTransaction, body: { value, state, _ in - state.dragValue = value - state.geometryProxy = geometry - }) - .onChanged({ - let height = geometry.size.height - let pageCount = self.pageCount - self.state.verticalDragChanged($0, viewCount: pageCount, pageHeight: height) - }) - /* - There is a bug, where onEnded is not called, when gesture is cancelled. - So onEnded is handled using reset handler in `GestureState`. (look `PageScrollState`) - */ + .gesture( + dragGesture(geometry: geometry), + type: pageGestureType ) } } + + private func dragGesture(geometry: GeometryProxy) -> some Gesture { + /* + There is a bug, where onEnded is not called, when gesture is cancelled. + So onEnded is handled using reset handler in `GestureState` (look `PageScrollState`) + */ + DragGesture(minimumDistance: 8.0) + .updating(self.$stateTransaction, body: { value, state, _ in + state.dragValue = value + state.geometryProxy = geometry + }) + .onChanged({ + let height = geometry.size.height + let pageCount = self.pageCount + self.state.verticalDragChanged($0, viewCount: pageCount, pageHeight: height) + }) + } } extension CGFloat {