Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use custom 'PagerData' protocol for data to avoid unexpected slowness #299

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Example/SwiftUIPagerExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
17D9E0F823D4CF6700C5AE93 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D9E0F723D4CF6700C5AE93 /* ContentView.swift */; };
17D9E0FD23D4CF6900C5AE93 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17D9E0FC23D4CF6900C5AE93 /* Assets.xcassets */; };
17D9E10023D4CF6900C5AE93 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 17D9E0FE23D4CF6900C5AE93 /* LaunchScreen.storyboard */; };
576BF5CC28E0CAAA00E73618 /* PagerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 576BF5CB28E0CAAA00E73618 /* PagerData.swift */; };
6B22DC81247E5C9A00EF95C5 /* NestedExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B22DC80247E5C9A00EF95C5 /* NestedExampleView.swift */; };
6B35B6C125346610000D618F /* PaginationSensitivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B35B6C025346610000D618F /* PaginationSensitivity.swift */; };
6B4EC8A2240D072B001E7490 /* ColorsExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B4EC8A1240D072B001E7490 /* ColorsExampleView.swift */; };
Expand Down Expand Up @@ -63,6 +64,7 @@
17D9E0FC23D4CF6900C5AE93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../Assets.xcassets; sourceTree = "<group>"; };
17D9E0FF23D4CF6900C5AE93 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
17D9E10123D4CF6900C5AE93 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
576BF5CB28E0CAAA00E73618 /* PagerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PagerData.swift; path = ../../Sources/SwiftUIPager/PagerData.swift; sourceTree = "<group>"; };
6B22DC80247E5C9A00EF95C5 /* NestedExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedExampleView.swift; sourceTree = "<group>"; };
6B35B6C025346610000D618F /* PaginationSensitivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaginationSensitivity.swift; path = ../../Sources/SwiftUIPager/PageConfiguration/PaginationSensitivity.swift; sourceTree = "<group>"; };
6B4EC8A1240D072B001E7490 /* ColorsExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorsExampleView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -162,6 +164,7 @@
6BC5EDFB24866D9500E1E78C /* PagerContent+Helper.swift */,
6BEF676F24C98B62008533FE /* PageWrapper.swift */,
1748E8BF26695E220016F534 /* PageTransition.swift */,
576BF5CB28E0CAAA00E73618 /* PagerData.swift */,
);
name = Pagination;
sourceTree = "<group>";
Expand Down Expand Up @@ -294,6 +297,7 @@
6BCF139224B2677B00AADE74 /* ContentLoadingPolicy.swift in Sources */,
6BEA731524ACF8D7007EA8DC /* SwipeInteractionArea.swift in Sources */,
6BC5EE0124866D9500E1E78C /* Pager.swift in Sources */,
576BF5CC28E0CAAA00E73618 /* PagerData.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ struct InfiniteExampleView: View {
self.pageView($0)
}
.singlePagination(ratio: 0.5, sensitivity: .high)
.onPageWillChange({ (page) in
.onPageWillChange({ (page, _) in
print("Page will change to: \(page)")
})
.onPageChanged({ page in
.onPageChanged({ page, _ in
print("Page changed to: \(page)")
if page == 1 {
let newData = (1...5).map { data1.first! - $0 }.reversed()
Expand Down
9 changes: 9 additions & 0 deletions Sources/SwiftUIPager/PageWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@ struct PageWrapper<Element, ID>: Equatable, Identifiable where Element: Equatabl
/// Wrappes Value
var element: Element

var indexInData: Int

/// `Identifiable` _id_
var id: String {
"\(batchId)-\(element[keyPath: keyPath])"
}

init(batchId: UInt, keyPath: KeyPath<Element, ID>, element: Element, indexInData: Int = 0) {
self.batchId = batchId
self.keyPath = keyPath
self.element = element
self.indexInData = indexInData
}
}
4 changes: 2 additions & 2 deletions Sources/SwiftUIPager/Pager+Buildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ extension Pager: Buildable {
/// Adds a callback to react whenever the page changes
///
/// - Parameter callback: block to be called when `page` changes
public func onPageChanged(_ callback: ((Int) -> Void)?) -> Self {
public func onPageChanged(_ callback: ((Int, Element) -> Void)?) -> Self {
mutating(keyPath: \.onPageChanged, value: callback)
}

Expand All @@ -345,7 +345,7 @@ extension Pager: Buildable {
/// Adds a callback to react whenever the page will change
///
/// - Parameter callback: block to be called when `page` will change
public func onPageWillChange(_ callback: ((Int) -> Void)?) -> Self {
public func onPageWillChange(_ callback: ((Int, Element) -> Void)?) -> Self {
mutating(keyPath: \.onPageWillChange, value: callback)
}

Expand Down
14 changes: 7 additions & 7 deletions Sources/SwiftUIPager/Pager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
let id: KeyPath<Element, ID>

/// Array of items that will populate each page
var data: [Element]
var data: any PagerData<Element>

/*** ViewModified properties ***/

Expand Down Expand Up @@ -148,13 +148,13 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
var preferredItemSize: CGSize?

/// Callback invoked when a new page will be set
var onPageWillChange: ((Int) -> Void)?
var onPageWillChange: ((Int, Element) -> Void)?

/// Callback invoked when the user ends dragging and a transition will occur
var onPageWillTransition: ((Result<PageTransition, PageTransitionError>) -> Void)?

/// Callback invoked when a new page is set
var onPageChanged: ((Int) -> Void)?
var onPageChanged: ((Int, Element) -> Void)?

/// Callback for a dragging began event
var onDraggingBegan: (() -> Void)?
Expand All @@ -178,9 +178,9 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
/// - Parameter data: Collection of items to populate the content
/// - Parameter id: KeyPath to identifiable property
/// - Parameter content: Factory method to build new pages
public init<Data: RandomAccessCollection>(page: Page, data: Data, id: KeyPath<Element, ID>, @ViewBuilder content: @escaping (Element) -> PageView) where Data.Index == Int, Data.Element == Element {
public init<Data: PagerData>(page: Page, data: Data, id: KeyPath<Element, ID>, @ViewBuilder content: @escaping (Element) -> PageView) where Data.Element == Element {
self.pagerModel = page
self.data = Array(data)
self.data = data
self.id = id
self.content = content
self.pagerModel.totalPages = data.count
Expand Down Expand Up @@ -261,8 +261,8 @@ extension Pager where ID == Element.ID, Element : Identifiable {
/// - Parameter page: Current page index
/// - Parameter data: Collection of items to populate the content
/// - Parameter content: Factory method to build new pages
public init<Data: RandomAccessCollection>(page: Page, data: Data, @ViewBuilder content: @escaping (Element) -> PageView) where Data.Index == Int, Data.Element == Element {
self.init(page: page, data: Array(data), id: \Element.id, content: content)
public init<Data: PagerData>(page: Page, data: Data, @ViewBuilder content: @escaping (Element) -> PageView) where Data.Element == Element {
self.init(page: page, data: data, id: \Element.id, content: content)
}

}
12 changes: 3 additions & 9 deletions Sources/SwiftUIPager/PagerContent+Buildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ extension Pager.PagerContent: Buildable {
/// pages on both the screen and the sides. If your sequence is not large enough, use `count` to
/// repeat it and pass more elements.
func loopPages(_ value: Bool = true, repeating count: UInt = 1) -> Self {
var newData = data
if let id = newData.first?.keyPath {
let count = max(1, count)
newData = (1...count).map { it in
data.map { PageWrapper(batchId: it, keyPath: id, element: $0.element) }
}.flatMap { $0 }
}
let newData = data.withRepeating(repeating: Int(count))
self.pagerModel.isInfinite = value
self.pagerModel.totalPages = newData.count
return mutating(keyPath: \.isInifinitePager, value: value)
Expand Down Expand Up @@ -295,7 +289,7 @@ extension Pager.PagerContent: Buildable {
/// Adds a callback to react whenever the page will change
///
/// - Parameter callback: block to be called when `page` will change
func onPageWillChange(_ callback: ((Int) -> Void)?) -> Self {
func onPageWillChange(_ callback: ((Int, Element) -> Void)?) -> Self {
mutating(keyPath: \.onPageWillChange, value: callback)
}

Expand All @@ -310,7 +304,7 @@ extension Pager.PagerContent: Buildable {
/// Adds a callback to react whenever the page changes
///
/// - Parameter callback: block to be called when `page` changes
func onPageChanged(_ callback: ((Int) -> Void)?) -> Self {
func onPageChanged(_ callback: ((Int, Element) -> Void)?) -> Self {
mutating(keyPath: \.onPageChanged, value: callback)
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftUIPager/PagerContent+Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ extension Pager.PagerContent {

/// Extra offset to complentate the alignment
var alignmentOffset: CGFloat {
let indexOfPageFocused = dataDisplayed.firstIndex(where: { data.firstIndex(of: $0) == self.page }) ?? 0
let indexOfPageFocused = dataDisplayed.firstIndex(where: { $0.indexInData == self.page }) ?? 0

let offset: CGFloat
switch (alignment, indexOfPageFocused) {
Expand Down Expand Up @@ -249,7 +249,7 @@ extension Pager.PagerContent {

/// Offset applied to `HStack` along the X-Axis. It's limitted by `offsetLowerbound` and `offsetUpperbound`
var xOffset: CGFloat {
let indexOfPageFocused = CGFloat(dataDisplayed.firstIndex(where: { data.firstIndex(of: $0) == self.page }) ?? 0)
let indexOfPageFocused = CGFloat(dataDisplayed.firstIndex(where: { $0.indexInData == self.page }) ?? 0)
let numberOfPages = CGFloat(numberOfPagesDisplayed)
let xIncrement = pageDistance / 2
let offset = (numberOfPages / 2 - indexOfPageFocused) * pageDistance - xIncrement + totalOffset + alignmentOffset
Expand Down
16 changes: 8 additions & 8 deletions Sources/SwiftUIPager/PagerContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extension Pager {
let id: KeyPath<PageWrapper<Element, ID>, String>

/// Array of items that will populate each page
var data: [PageWrapper<Element, ID>]
var data: PagerWrapperData<Element, ID>

/*** ViewModified properties ***/

Expand Down Expand Up @@ -126,13 +126,13 @@ extension Pager {
var preferredItemSize: CGSize?

/// Callback invoked when a new page will be set
var onPageWillChange: ((Int) -> Void)?
var onPageWillChange: ((Int, Element) -> Void)?

/// Callback invoked when the user ends dragging and a transition will occur
var onPageWillTransition: ((Result<PageTransition, PageTransitionError>) -> Void)?

/// Callback invoked when a new page is set
var onPageChanged: ((Int) -> Void)?
var onPageChanged: ((Int, Element) -> Void)?

/// Callback for a dragging began event
var onDraggingBegan: (() -> Void)?
Expand Down Expand Up @@ -170,10 +170,10 @@ extension Pager {
/// - 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, pagerModel: Page, data: [Element], id: KeyPath<Element, ID>, @ViewBuilder content: @escaping (Element) -> PageView) {
init(size: CGSize, pagerModel: Page, data: some PagerData<Element>, id: KeyPath<Element, ID>, @ViewBuilder content: @escaping (Element) -> PageView) {
self.size = size
self.pagerModel = pagerModel
self.data = data.map { PageWrapper(batchId: 1, keyPath: id, element: $0) }
self.data = PagerWrapperData(wrapper: data, id: id)
self.id = \PageWrapper<Element, ID>.id
self.content = content
}
Expand Down Expand Up @@ -229,7 +229,7 @@ extension Pager {

if pagerModel.pageIncrement != 0 {
pagerModel.pageIncrement = 0
onPageChanged?(pagerModel.index)
onPageChanged?(pagerModel.index, data.itemFor(index: pagerModel.index).element)
}
})
.eraseToAny()
Expand Down Expand Up @@ -388,7 +388,7 @@ extension Pager.PagerContent {

let animation = pagingAnimation.animation?.speed(speed)
if page != newPage {
onPageWillChange?(newPage)
onPageWillChange?(newPage, data.itemFor(index: newPage).element)
onPageWillTransition?(.success(.init(currentPage: page, nextPage: newPage, pageIncrement: pageIncrement)))
} else {
onPageWillTransition?(.failure(.draggingStopped))
Expand All @@ -411,7 +411,7 @@ extension Pager.PagerContent {
// Do nothing
} else if page != newPage {
self.pagerModel.pageIncrement = 0
onPageChanged?(newPage)
onPageChanged?(newPage, data.itemFor(index: newPage).element)
}
}

Expand Down
56 changes: 56 additions & 0 deletions Sources/SwiftUIPager/PagerData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// File.swift
//
//
// Created by feichao on 2022/9/25.
//

import Foundation

public protocol PagerData<Element> {
associatedtype Element

func itemFor(index: Int) -> Element

var count: Int { get }
}


struct PagerWrapperData<Element, ID> where Element: Equatable, ID: Hashable {
let wrapper: any PagerData<Element>
let id: KeyPath<Element, ID>
var repeating: Int

init<T: PagerData<Element>>(wrapper: T, id: KeyPath<Element, ID>, repeating: Int = 1) {
self.wrapper = wrapper
self.id = id
self.repeating = repeating
}

func itemFor(index: Int) -> PageWrapper<Element, ID> {
assert(index < count * repeating)
let batch = index / count
let item = wrapper.itemFor(index: index % count)
return PageWrapper(batchId: UInt(batch), keyPath: id, element: item, indexInData: index)
}

var count: Int {
wrapper.count * repeating
}

subscript(index: Int) -> PageWrapper<Element, ID> {
return itemFor(index: index)
}

func withRepeating(repeating: Int) -> Self {
var new = self
new.repeating = repeating
return new
}
}

extension Array: PagerData {
public func itemFor(index: Int) -> Element {
self[index]
}
}
6 changes: 3 additions & 3 deletions Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ final class Pager_Buildable_Tests: XCTestCase {
}

func test_GivenPager_WhenOnPageChanged_ThenCallbackNotNil() {
let pager = givenPager.onPageChanged { _ in }
let pager = givenPager.onPageChanged { _, _ in }
let pagerContent = pager.content(for: CGSize(width: 100, height: 100))
XCTAssertNotNil(pagerContent.onPageChanged)
}
Expand Down Expand Up @@ -563,7 +563,7 @@ final class Pager_Buildable_Tests: XCTestCase {
var pager = givenPager

var newPage: Int? = nil
pager = pager.onPageWillChange({ (page) in
pager = pager.onPageWillChange({ (page, _) in
newPage = page
})

Expand All @@ -579,7 +579,7 @@ final class Pager_Buildable_Tests: XCTestCase {
var pager = givenPager

var newPage: Int? = nil
pager = pager.onPageWillChange({ (page) in
pager = pager.onPageWillChange({ (page, _) in
newPage = page
})

Expand Down