Skip to content

Commit

Permalink
workaround binding update issue
Browse files Browse the repository at this point in the history
  • Loading branch information
fermoya committed Nov 29, 2020
1 parent 81c2719 commit 58ceb8a
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 15 deletions.
8 changes: 8 additions & 0 deletions Example/SwiftUIPagerExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
1751176A2573D93F00D809CF /* PagerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175117692573D93F00D809CF /* PagerModel.swift */; };
1751176C2573DAC000D809CF /* StateInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751176B2573DAC000D809CF /* StateInstance.swift */; };
17D9E0F423D4CF6700C5AE93 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D9E0F323D4CF6700C5AE93 /* AppDelegate.swift */; };
17D9E0F623D4CF6700C5AE93 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D9E0F523D4CF6700C5AE93 /* SceneDelegate.swift */; };
17D9E0F823D4CF6700C5AE93 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D9E0F723D4CF6700C5AE93 /* ContentView.swift */; };
Expand Down Expand Up @@ -50,6 +52,8 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
175117692573D93F00D809CF /* PagerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PagerModel.swift; path = ../../Sources/SwiftUIPager/PagerModel.swift; sourceTree = "<group>"; };
1751176B2573DAC000D809CF /* StateInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateInstance.swift; sourceTree = "<group>"; };
17D9E0F023D4CF6700C5AE93 /* SwiftUIPagerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIPagerExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
17D9E0F323D4CF6700C5AE93 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
17D9E0F523D4CF6700C5AE93 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -155,6 +159,7 @@
6BB1AAD424C9CA1C0032B5A3 /* PagerContent.swift */,
6BC5EDF424866D9500E1E78C /* PagerContent+Buildable.swift */,
6BC5EDFB24866D9500E1E78C /* PagerContent+Helper.swift */,
175117692573D93F00D809CF /* PagerModel.swift */,
6BEF676F24C98B62008533FE /* PageWrapper.swift */,
);
name = Pagination;
Expand All @@ -166,6 +171,7 @@
6BC5EDF824866D9500E1E78C /* Buildable.swift */,
6BD3828124C97DE3007B1CF6 /* CGPoint+Angle.swift */,
6B9C4A8924B45F66004C06C5 /* OnDeactivateModifier.swift */,
1751176B2573DAC000D809CF /* StateInstance.swift */,
6BC5EDF724866D9500E1E78C /* View+Helper.swift */,
);
name = Helpers;
Expand Down Expand Up @@ -274,13 +280,15 @@
6B9C4A8A24B45F66004C06C5 /* OnDeactivateModifier.swift in Sources */,
6BEA731624ACF8D7007EA8DC /* GesturePriority.swift in Sources */,
6B35B6C125346610000D618F /* PaginationSensitivity.swift in Sources */,
1751176A2573D93F00D809CF /* PagerModel.swift in Sources */,
6BEA731324ACF8D7007EA8DC /* PositionAlignment.swift in Sources */,
6BEA731424ACF8D7007EA8DC /* SwipeDirection.swift in Sources */,
6BC5EE0224866D9500E1E78C /* PagerContent+Helper.swift in Sources */,
6B4EC8A4240D07D5001E7490 /* InfiniteExampleView.swift in Sources */,
6BC5EDFE24866D9500E1E78C /* View+Helper.swift in Sources */,
6B22DC81247E5C9A00EF95C5 /* NestedExampleView.swift in Sources */,
6BB1AAD724C9D0820032B5A3 /* Pager+Buildable.swift in Sources */,
1751176C2573DAC000D809CF /* StateInstance.swift in Sources */,
17D9E0F823D4CF6700C5AE93 /* ContentView.swift in Sources */,
6BC5EDFC24866D9500E1E78C /* PagerContent+Buildable.swift in Sources */,
6BCF139224B2677B00AADE74 /* ContentLoadingPolicy.swift in Sources */,
Expand Down
48 changes: 48 additions & 0 deletions Sources/SwiftUIPager/Helpers/StateInstance.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// StateInstance.swift
// SwiftUIPagerExample
//
// Created by Fernando Moya de Rivas on 29/11/20.
// Copyright © 2020 Fernando Moya de Rivas. All rights reserved.
//

import SwiftUI

/// Workaround to use `StateObject` in _iOS 14_ and `ObservedObject` in _iOS 13_
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper struct StateInstance<ObjectType>: DynamicProperty where ObjectType: ObservableObject {

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@ObservedObject private var observedObject: ObjectType

private var _stateObject: Any?

@available(iOS 14.0, OSX 10.16, tvOS 14.0, watchOS 7.0, *)
private var stateObject: ObjectType? {
_stateObject as? ObjectType
}

var wrappedValue: ObjectType {
get {
if #available(iOS 14.0, OSX 10.16, tvOS 14.0, watchOS 7.0, *) {
return stateObject ?? observedObject
} else {
return observedObject
}
}
set {
if #available(iOS 14.0, OSX 10.16, tvOS 14.0, watchOS 7.0, *) {
self._stateObject = StateObject(wrappedValue: newValue)
}
observedObject = newValue
}
}

init(wrappedValue: ObjectType) {
if #available(iOS 14.0, OSX 10.16, tvOS 14.0, watchOS 7.0, *) {
self._stateObject = StateObject(wrappedValue: wrappedValue)
}
observedObject = wrappedValue
}

}
9 changes: 8 additions & 1 deletion Sources/SwiftUIPager/Pager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
}
}

@StateInstance var pagerModel: PagerModel

/// Initializes a new `Pager`.
///
/// - Parameter page: Binding to the page index
Expand All @@ -182,6 +184,7 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
/// - Parameter content: Factory method to build new pages
public init<Data: RandomAccessCollection>(page: Binding<Int>, data: Data, id: KeyPath<Element, ID>, @ViewBuilder content: @escaping (Element) -> PageView) where Data.Index == Int, Data.Element == Element {
self._page = page
self.pagerModel = PagerModel(page: page.wrappedValue)
self.data = Array(data)
self.id = id
self.content = content
Expand All @@ -190,14 +193,18 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
public var body: some View {
GeometryReader { proxy in
self.content(for: proxy.size)
.environmentObject(pagerModel)
.onReceive(pagerModel.$page) { (page) in
self.page = page
}
}
.clipped()
}

func content(for size: CGSize) -> PagerContent {
var pagerContent =
PagerContent(size: size,
page: $page,
pagerModel: pagerModel,
data: data,
id: id,
content: content)
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftUIPager/PagerContent+Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension Pager.PagerContent {

/// Work around to avoid @State keeps wrong value
var page: Int {
return min(pageIndex, numberOfPages - 1)
return min(pagerModel.page, numberOfPages - 1)
}

/// `true` if `Pager` is vertical
Expand Down
17 changes: 7 additions & 10 deletions Sources/SwiftUIPager/PagerContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,21 +147,18 @@ extension Pager {
@State var pageIncrement = 1

/// Page index
@Binding var pageIndex: Int {
didSet {
onPageChanged?(page)
}
}
@ObservedObject var pagerModel: PagerModel

/// Initializes a new `Pager`.
///
/// - Parameter page: Binding to the page index
/// - Parameter size: Available size
/// - Parameter pagerModel: Wrapper for the current page
/// - Parameter data: Array of items to populate the content
/// - Parameter id: KeyPath to identifiable property
/// - Parameter content: Factory method to build new pages
init(size: CGSize, page: Binding<Int>, data: [Element], id: KeyPath<Element, ID>, @ViewBuilder content: @escaping (Element) -> PageView) {
init(size: CGSize, pagerModel: PagerModel, data: [Element], id: KeyPath<Element, ID>, @ViewBuilder content: @escaping (Element) -> PageView) {
self.size = size
self._pageIndex = page
self.pagerModel = pagerModel
self.data = data.map { PageWrapper(batchId: 1, keyPath: id, element: $0) }
self.id = \PageWrapper<Element, ID>.id
self.content = content
Expand Down Expand Up @@ -283,13 +280,13 @@ extension Pager.PagerContent {
speed = 1 / min(4, Double(pageIncrement))
}

let pagingAnimation = self.pagingAnimation?((pageIndex, newPage, draggingOffset, draggingVelocity)) ?? defaultPagingAnimation
let pagingAnimation = self.pagingAnimation?((page, newPage, draggingOffset, draggingVelocity)) ?? defaultPagingAnimation

let animation = pagingAnimation.animation.speed(speed)
withAnimation(animation) {
self.draggingOffset = 0
self.pageIncrement = pageIncrement
self.pageIndex = newPage
self.pagerModel.page = newPage
self.draggingVelocity = 0
self.lastDraggingValue = nil
}
Expand Down
22 changes: 22 additions & 0 deletions Sources/SwiftUIPager/PagerModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Pager+Helper.swift
// SwiftUIPager
//
// Created by Fernando Moya de Rivas on 19/01/2020.
// Copyright © 2020 Fernando Moya de Rivas. All rights reserved.
//

import SwiftUI

/// Workaround to avoid `Binding` updating after rest of `State` after drag ends
/// More info [here](https://developer.apple.com/forums/thread/667988) and [here](https://developer.apple.com/forums/thread/667720)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
class PagerModel: ObservableObject {

@Published var page: Int

init(page: Int) {
self.page = page
}

}
28 changes: 26 additions & 2 deletions SwiftUIPager.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@
172F4D8423DF8B4400FD2F15 /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDE442223DE12470022A2F7 /* Pager.swift */; };
172F4D8523DF8B4800FD2F15 /* Pager+Buildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDE442323DE12470022A2F7 /* Pager+Buildable.swift */; };
172F4D8623DF8B4B00FD2F15 /* PagerContent+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDE442823DE12480022A2F7 /* PagerContent+Helper.swift */; };
1751176E2573E2AA00D809CF /* StateInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751176D2573E2AA00D809CF /* StateInstance.swift */; };
1751176F2573E2AA00D809CF /* StateInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751176D2573E2AA00D809CF /* StateInstance.swift */; };
175117702573E2AA00D809CF /* StateInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751176D2573E2AA00D809CF /* StateInstance.swift */; };
175117712573E2AA00D809CF /* StateInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751176D2573E2AA00D809CF /* StateInstance.swift */; };
175117722573E2AA00D809CF /* StateInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1751176D2573E2AA00D809CF /* StateInstance.swift */; };
1751177A2573E2D600D809CF /* PagerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175117792573E2D200D809CF /* PagerModel.swift */; };
175117812573E2D700D809CF /* PagerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175117792573E2D200D809CF /* PagerModel.swift */; };
175117822573E2D700D809CF /* PagerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175117792573E2D200D809CF /* PagerModel.swift */; };
1751178A2573E2D900D809CF /* PagerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175117792573E2D200D809CF /* PagerModel.swift */; };
175117912573E2DA00D809CF /* PagerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175117792573E2D200D809CF /* PagerModel.swift */; };
6B0F33DE24B4A50A006031BE /* OnDeactivateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B0F33DD24B4A4DE006031BE /* OnDeactivateModifier.swift */; };
6B0F33DF24B4A50B006031BE /* OnDeactivateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B0F33DD24B4A4DE006031BE /* OnDeactivateModifier.swift */; };
6B0F33E024B4A50B006031BE /* OnDeactivateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B0F33DD24B4A4DE006031BE /* OnDeactivateModifier.swift */; };
Expand Down Expand Up @@ -126,6 +136,8 @@
172F4D6123DF830600FD2F15 /* Info-macOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-macOS.plist"; sourceTree = "<group>"; };
172F4D7823DF8A6400FD2F15 /* SwiftUIPager.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftUIPager.framework; sourceTree = BUILT_PRODUCTS_DIR; };
172F4D7B23DF8A6400FD2F15 /* Info-watchOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-watchOS.plist"; sourceTree = "<group>"; };
1751176D2573E2AA00D809CF /* StateInstance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateInstance.swift; sourceTree = "<group>"; };
175117792573E2D200D809CF /* PagerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PagerModel.swift; path = Sources/SwiftUIPager/PagerModel.swift; sourceTree = SOURCE_ROOT; };
6B0F33DD24B4A4DE006031BE /* OnDeactivateModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnDeactivateModifier.swift; sourceTree = "<group>"; };
6B2C2FDE248D379D00E528F9 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
6B2C2FE2248D37C100E528F9 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS6.2.sdk/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
Expand Down Expand Up @@ -249,9 +261,10 @@
6BEA732F24ACFAB3007EA8DC /* SupportFiles */,
6BDE442223DE12470022A2F7 /* Pager.swift */,
6BDE442323DE12470022A2F7 /* Pager+Buildable.swift */,
6BDE442823DE12480022A2F7 /* PagerContent+Helper.swift */,
6BEF519624C9ECF2000DF66B /* PagerContent.swift */,
6BEF519724C9ECF2000DF66B /* PagerContent+Buildable.swift */,
6BDE442823DE12480022A2F7 /* PagerContent+Helper.swift */,
175117792573E2D200D809CF /* PagerModel.swift */,
6BEF677124C98BED008533FE /* PageWrapper.swift */,
);
path = SwiftUIPager;
Expand All @@ -263,6 +276,7 @@
6BDE442623DE12480022A2F7 /* Buildable.swift */,
6BEF677724C98C12008533FE /* CGPoint+Angle.swift */,
6B0F33DD24B4A4DE006031BE /* OnDeactivateModifier.swift */,
1751176D2573E2AA00D809CF /* StateInstance.swift */,
6BDE442523DE12480022A2F7 /* View+Helper.swift */,
);
name = Helpers;
Expand Down Expand Up @@ -447,7 +461,7 @@
};
6BC5EE4E2487F5E000E1E78C = {
CreatedOnToolsVersion = 11.5;
LastSwiftMigration = 1150;
LastSwiftMigration = 1220;
};
6BDE441623DE10C10022A2F7 = {
CreatedOnToolsVersion = 11.3;
Expand Down Expand Up @@ -553,8 +567,10 @@
172F4D5823DF830600FD2F15 /* Buildable.swift in Sources */,
6B362CBF2534940B008DB2DF /* PaginationSensitivity.swift in Sources */,
172F4D5923DF830600FD2F15 /* Pager.swift in Sources */,
175117702573E2AA00D809CF /* StateInstance.swift in Sources */,
6BEA732724ACFA03007EA8DC /* SwipeDirection.swift in Sources */,
6BEA732224ACF9FF007EA8DC /* PositionAlignment.swift in Sources */,
175117822573E2D700D809CF /* PagerModel.swift in Sources */,
6B6FAA4124D56BAC000D1539 /* PagingAnimation.swift in Sources */,
172F4D5A23DF830600FD2F15 /* PagerContent+Helper.swift in Sources */,
6B0F33E024B4A50B006031BE /* OnDeactivateModifier.swift in Sources */,
Expand All @@ -577,8 +593,10 @@
172F4D8023DF8B3800FD2F15 /* View+Helper.swift in Sources */,
6B362CC02534940B008DB2DF /* PaginationSensitivity.swift in Sources */,
172F4D8523DF8B4800FD2F15 /* Pager+Buildable.swift in Sources */,
175117712573E2AA00D809CF /* StateInstance.swift in Sources */,
6BEA732824ACFA04007EA8DC /* SwipeDirection.swift in Sources */,
6BEA732324ACF9FF007EA8DC /* PositionAlignment.swift in Sources */,
175117812573E2D700D809CF /* PagerModel.swift in Sources */,
6B6FAA4224D56BAC000D1539 /* PagingAnimation.swift in Sources */,
172F4D8623DF8B4B00FD2F15 /* PagerContent+Helper.swift in Sources */,
6B0F33E124B4A50C006031BE /* OnDeactivateModifier.swift in Sources */,
Expand All @@ -601,8 +619,10 @@
6B2C305C248D740800E528F9 /* Buildable.swift in Sources */,
6B362CBE2534940B008DB2DF /* PaginationSensitivity.swift in Sources */,
6B2C305E248D740800E528F9 /* Pager.swift in Sources */,
1751176F2573E2AA00D809CF /* StateInstance.swift in Sources */,
6BEA732624ACFA03007EA8DC /* SwipeDirection.swift in Sources */,
6BEA732124ACF9FE007EA8DC /* PositionAlignment.swift in Sources */,
175117912573E2DA00D809CF /* PagerModel.swift in Sources */,
6B6FAA4024D56BAC000D1539 /* PagingAnimation.swift in Sources */,
6B2C305F248D740800E528F9 /* PagerContent+Helper.swift in Sources */,
6B0F33DF24B4A50B006031BE /* OnDeactivateModifier.swift in Sources */,
Expand All @@ -625,8 +645,10 @@
6BCF139024B2675000AADE74 /* ContentLoadingPolicy.swift in Sources */,
6B362CC12534940B008DB2DF /* PaginationSensitivity.swift in Sources */,
6BBC3D742488DC01004194BD /* View+Helper.swift in Sources */,
175117722573E2AA00D809CF /* StateInstance.swift in Sources */,
6BEA732924ACFA04007EA8DC /* SwipeDirection.swift in Sources */,
6BEA732424ACFA00007EA8DC /* PositionAlignment.swift in Sources */,
1751177A2573E2D600D809CF /* PagerModel.swift in Sources */,
6B6FAA4324D56BAC000D1539 /* PagingAnimation.swift in Sources */,
6BBC3D792488DC11004194BD /* PagerContent+Helper.swift in Sources */,
6B0F33E224B4A50C006031BE /* OnDeactivateModifier.swift in Sources */,
Expand All @@ -649,8 +671,10 @@
6BDE442C23DE12480022A2F7 /* Buildable.swift in Sources */,
6B362CBD2534940B008DB2DF /* PaginationSensitivity.swift in Sources */,
6BDE442923DE12480022A2F7 /* Pager.swift in Sources */,
1751176E2573E2AA00D809CF /* StateInstance.swift in Sources */,
6BEA732524ACFA02007EA8DC /* SwipeDirection.swift in Sources */,
6BEA732024ACF9FE007EA8DC /* PositionAlignment.swift in Sources */,
1751178A2573E2D900D809CF /* PagerModel.swift in Sources */,
6B6FAA3F24D56BAC000D1539 /* PagingAnimation.swift in Sources */,
6BDE442E23DE12480022A2F7 /* PagerContent+Helper.swift in Sources */,
6B0F33DE24B4A50A006031BE /* OnDeactivateModifier.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftUIPagerTests/PagerContent+Helper_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SwiftUI
final class PagerContent_Helper_Tests: XCTestCase {

var givenPager: Pager<Int, Int, Text>.PagerContent {
Pager.PagerContent(size: CGSize(width: 300, height: 300), page: .constant(0), data: Array(0..<20), id: \.self) {
Pager.PagerContent(size: CGSize(width: 300, height: 300), pagerModel: .init(page: 0), data: Array(0..<20), id: \.self) {
Text("\($0)")
}
}
Expand Down

0 comments on commit 58ceb8a

Please sign in to comment.